Friendica Communications Platform (please note that this is a clone of the repository at github, issues are handled there) https://friendi.ca
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

303 lines
9.0 KiB

  1. <?php
  2. /**
  3. * @file src/Worker/DiscoverPoCo.php
  4. */
  5. namespace Friendica\Worker;
  6. use Friendica\Core\Cache;
  7. use Friendica\Core\Config;
  8. use Friendica\Core\Worker;
  9. use Friendica\Database\DBM;
  10. use Friendica\Model\GContact;
  11. use Friendica\Network\Probe;
  12. use Friendica\Protocol\PortableContact;
  13. use Friendica\Util\DateTimeFormat;
  14. use Friendica\Util\Network;
  15. class DiscoverPoCo {
  16. /// @todo Clean up this mess of a parameter hell and split it in several classes
  17. public static function execute($command = '', $param1 = '', $param2 = '', $param3 = '', $param4 = '')
  18. {
  19. /*
  20. This function can be called in these ways:
  21. - dirsearch <search pattern>: Searches for "search pattern" in the directory. "search pattern" is url encoded.
  22. - checkcontact: Updates gcontact entries
  23. - suggestions: Discover other servers for their contacts.
  24. - server <poco url>: Searches for the poco server list. "poco url" is base64 encoded.
  25. - update_server: Frequently check the first 250 servers for vitality.
  26. - update_server_directory: Discover the given server id for their contacts
  27. - PortableContact::load: Load POCO data from a given POCO address
  28. - check_profile: Update remote profile data
  29. */
  30. $search = "";
  31. $mode = 0;
  32. if ($command == "dirsearch") {
  33. $search = urldecode($param1);
  34. $mode = 1;
  35. } elseif ($command == "checkcontact") {
  36. $mode = 2;
  37. } elseif ($command == "suggestions") {
  38. $mode = 3;
  39. } elseif ($command == "server") {
  40. $mode = 4;
  41. } elseif ($command == "update_server") {
  42. $mode = 5;
  43. } elseif ($command == "update_server_directory") {
  44. $mode = 6;
  45. } elseif ($command == "load") {
  46. $mode = 7;
  47. } elseif ($command == "check_profile") {
  48. $mode = 8;
  49. } elseif ($command !== "") {
  50. logger("Unknown or missing parameter ".$command."\n");
  51. return;
  52. }
  53. logger('start '.$search);
  54. if ($mode == 8) {
  55. if ($param1 != "") {
  56. PortableContact::lastUpdated($param1, true);
  57. }
  58. } elseif ($mode == 7) {
  59. if (!empty($param4)) {
  60. $url = $param4;
  61. } else {
  62. $url = '';
  63. }
  64. PortableContact::load(intval($param1), intval($param2), intval($param3), $url);
  65. } elseif ($mode == 6) {
  66. PortableContact::discoverSingleServer(intval($param1));
  67. } elseif ($mode == 5) {
  68. self::updateServer();
  69. } elseif ($mode == 4) {
  70. $server_url = $param1;
  71. if ($server_url == "") {
  72. return;
  73. }
  74. $server_url = filter_var($server_url, FILTER_SANITIZE_URL);
  75. if (substr(normalise_link($server_url), 0, 7) != "http://") {
  76. return;
  77. }
  78. $result = "Checking server ".$server_url." - ";
  79. $ret = PortableContact::checkServer($server_url);
  80. if ($ret) {
  81. $result .= "success";
  82. } else {
  83. $result .= "failed";
  84. }
  85. logger($result, LOGGER_DEBUG);
  86. } elseif ($mode == 3) {
  87. GContact::updateSuggestions();
  88. } elseif (($mode == 2) && Config::get('system', 'poco_completion')) {
  89. self::discoverUsers();
  90. } elseif (($mode == 1) && ($search != "") && Config::get('system', 'poco_local_search')) {
  91. self::discoverDirectory($search);
  92. self::gsSearchUser($search);
  93. } elseif (($mode == 0) && ($search == "") && (Config::get('system', 'poco_discovery') > 0)) {
  94. // Query Friendica and Hubzilla servers for their users
  95. PortableContact::discover();
  96. // Query GNU Social servers for their users ("statistics" addon has to be enabled on the GS server)
  97. if (!Config::get('system', 'ostatus_disabled')) {
  98. GContact::discoverGsUsers();
  99. }
  100. }
  101. logger('end '.$search);
  102. return;
  103. }
  104. /**
  105. * @brief Updates the first 250 servers
  106. *
  107. */
  108. private static function updateServer() {
  109. $r = q("SELECT `url`, `created`, `last_failure`, `last_contact` FROM `gserver` ORDER BY rand()");
  110. if (!DBM::is_result($r)) {
  111. return;
  112. }
  113. $updated = 0;
  114. foreach ($r AS $server) {
  115. if (!PortableContact::updateNeeded($server["created"], "", $server["last_failure"], $server["last_contact"])) {
  116. continue;
  117. }
  118. logger('Update server status for server '.$server["url"], LOGGER_DEBUG);
  119. Worker::add(PRIORITY_LOW, "DiscoverPoCo", "server", $server["url"]);
  120. if (++$updated > 250) {
  121. return;
  122. }
  123. }
  124. }
  125. private static function discoverUsers() {
  126. logger("Discover users", LOGGER_DEBUG);
  127. $starttime = time();
  128. $users = q("SELECT `url`, `created`, `updated`, `last_failure`, `last_contact`, `server_url`, `network` FROM `gcontact`
  129. WHERE `last_contact` < UTC_TIMESTAMP - INTERVAL 1 MONTH AND
  130. `last_failure` < UTC_TIMESTAMP - INTERVAL 1 MONTH AND
  131. `network` IN ('%s', '%s', '%s', '%s', '') ORDER BY rand()",
  132. dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA),
  133. dbesc(NETWORK_OSTATUS), dbesc(NETWORK_FEED));
  134. if (!$users) {
  135. return;
  136. }
  137. $checked = 0;
  138. foreach ($users AS $user) {
  139. $urlparts = parse_url($user["url"]);
  140. if (!isset($urlparts["scheme"])) {
  141. q("UPDATE `gcontact` SET `network` = '%s' WHERE `nurl` = '%s'",
  142. dbesc(NETWORK_PHANTOM), dbesc(normalise_link($user["url"])));
  143. continue;
  144. }
  145. if (in_array($urlparts["host"], ["www.facebook.com", "facebook.com", "twitter.com",
  146. "identi.ca", "alpha.app.net"])) {
  147. $networks = ["www.facebook.com" => NETWORK_FACEBOOK,
  148. "facebook.com" => NETWORK_FACEBOOK,
  149. "twitter.com" => NETWORK_TWITTER,
  150. "identi.ca" => NETWORK_PUMPIO,
  151. "alpha.app.net" => NETWORK_APPNET];
  152. q("UPDATE `gcontact` SET `network` = '%s' WHERE `nurl` = '%s'",
  153. dbesc($networks[$urlparts["host"]]), dbesc(normalise_link($user["url"])));
  154. continue;
  155. }
  156. $server_url = PortableContact::detectServer($user["url"]);
  157. $force_update = false;
  158. if ($user["server_url"] != "") {
  159. $force_update = (normalise_link($user["server_url"]) != normalise_link($server_url));
  160. $server_url = $user["server_url"];
  161. }
  162. if ((($server_url == "") && ($user["network"] == NETWORK_FEED)) || $force_update || PortableContact::checkServer($server_url, $user["network"])) {
  163. logger('Check profile '.$user["url"]);
  164. Worker::add(PRIORITY_LOW, "DiscoverPoCo", "check_profile", $user["url"]);
  165. if (++$checked > 100) {
  166. return;
  167. }
  168. } else {
  169. q("UPDATE `gcontact` SET `last_failure` = '%s' WHERE `nurl` = '%s'",
  170. dbesc(DateTimeFormat::utcNow()), dbesc(normalise_link($user["url"])));
  171. }
  172. // Quit the loop after 3 minutes
  173. if (time() > ($starttime + 180)) {
  174. return;
  175. }
  176. }
  177. }
  178. private static function discoverDirectory($search) {
  179. $data = Cache::get("dirsearch:".$search);
  180. if (!is_null($data)) {
  181. // Only search for the same item every 24 hours
  182. if (time() < $data + (60 * 60 * 24)) {
  183. logger("Already searched for ".$search." in the last 24 hours", LOGGER_DEBUG);
  184. return;
  185. }
  186. }
  187. $x = Network::fetchUrl(get_server()."/lsearch?p=1&n=500&search=".urlencode($search));
  188. $j = json_decode($x);
  189. if (count($j->results)) {
  190. foreach ($j->results as $jj) {
  191. // Check if the contact already exists
  192. $exists = q("SELECT `id`, `last_contact`, `last_failure`, `updated` FROM `gcontact` WHERE `nurl` = '%s'", normalise_link($jj->url));
  193. if (DBM::is_result($exists)) {
  194. logger("Profile ".$jj->url." already exists (".$search.")", LOGGER_DEBUG);
  195. if (($exists[0]["last_contact"] < $exists[0]["last_failure"]) &&
  196. ($exists[0]["updated"] < $exists[0]["last_failure"])) {
  197. continue;
  198. }
  199. // Update the contact
  200. PortableContact::lastUpdated($jj->url);
  201. continue;
  202. }
  203. $server_url = PortableContact::detectServer($jj->url);
  204. if ($server_url != '') {
  205. if (!PortableContact::checkServer($server_url)) {
  206. logger("Friendica server ".$server_url." doesn't answer.", LOGGER_DEBUG);
  207. continue;
  208. }
  209. logger("Friendica server ".$server_url." seems to be okay.", LOGGER_DEBUG);
  210. }
  211. $data = Probe::uri($jj->url);
  212. if ($data["network"] == NETWORK_DFRN) {
  213. logger("Profile ".$jj->url." is reachable (".$search.")", LOGGER_DEBUG);
  214. logger("Add profile ".$jj->url." to local directory (".$search.")", LOGGER_DEBUG);
  215. if ($jj->tags != "") {
  216. $data["keywords"] = $jj->tags;
  217. }
  218. $data["server_url"] = $data["baseurl"];
  219. GContact::update($data);
  220. } else {
  221. logger("Profile ".$jj->url." is not responding or no Friendica contact - but network ".$data["network"], LOGGER_DEBUG);
  222. }
  223. }
  224. }
  225. Cache::set("dirsearch:".$search, time(), CACHE_DAY);
  226. }
  227. /**
  228. * @brief Search for GNU Social user with gstools.org
  229. *
  230. * @param string $search User name
  231. */
  232. private static function gsSearchUser($search) {
  233. // Currently disabled, since the service isn't available anymore.
  234. // It is not removed since I hope that there will be a successor.
  235. return false;
  236. $a = get_app();
  237. $url = "http://gstools.org/api/users_search/".urlencode($search);
  238. $result = Network::curl($url);
  239. if (!$result["success"]) {
  240. return false;
  241. }
  242. $contacts = json_decode($result["body"]);
  243. if ($contacts->status == 'ERROR') {
  244. return false;
  245. }
  246. /// @TODO AS is considered as a notation for constants (as they usually being written all upper-case)
  247. /// @TODO find all those and convert to all lower-case which is a keyword then
  248. foreach ($contacts->data AS $user) {
  249. $contact = Probe::uri($user->site_address."/".$user->name);
  250. if ($contact["network"] != NETWORK_PHANTOM) {
  251. $contact["about"] = $user->description;
  252. GContact::update($contact);
  253. }
  254. }
  255. }
  256. }