nodeinfo_2_0.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. <?php
  2. // This file is part of GNU social - https://www.gnu.org/software/social
  3. //
  4. // GNU social is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Affero General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // GNU social is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Affero General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Affero General Public License
  15. // along with GNU social. If not, see <http://www.gnu.org/licenses/>.
  16. /**
  17. * The information is presented at the "api/nodeinfo/2.0.json" endpoint.
  18. *
  19. * @package NodeInfo
  20. * @author Stéphane Bérubé <chimo@chromic.org>
  21. * @author Diogo Cordeiro <diogo@fc.up.pt>
  22. * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org
  23. * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
  24. */
  25. defined('GNUSOCIAL') || die();
  26. /**
  27. * NodeInfo 2.0
  28. *
  29. * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org
  30. * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
  31. */
  32. class Nodeinfo_2_0Action extends Action
  33. {
  34. private $plugins;
  35. protected function handle(): void
  36. {
  37. parent::handle();
  38. header('Access-Control-Allow-Origin: *');
  39. $this->plugins = $this->getActivePluginList();
  40. $this->showNodeInfo();
  41. }
  42. /**
  43. * Most functionality depends on the active plugins, this gives us enough information concerning that
  44. *
  45. * @return array
  46. * @author Stéphane Bérubé <chimo@chromic.org>
  47. * @author Diogo Cordeiro <diogo@fc.up.pt>
  48. */
  49. public function getActivePluginList(): array
  50. {
  51. $plugin_version = [];
  52. $plugins = [];
  53. Event::handle('PluginVersion', [&$plugin_version]);
  54. foreach ($plugin_version as $plugin) {
  55. $plugins[str_replace(' ', '', strtolower($plugin['name']))] = true;
  56. }
  57. return $plugins;
  58. }
  59. /**
  60. * The NodeInfo page
  61. *
  62. * @return void
  63. * @author Stéphane Bérubé <chimo@chromic.org>
  64. * @author Diogo Cordeiro <diogo@fc.up.pt>
  65. */
  66. public function showNodeInfo(): void
  67. {
  68. $openRegistrations = $this->getRegistrationsStatus();
  69. $userCount = $this->getUserCount();
  70. $postCount = $this->getPostCount();
  71. $commentCount = $this->getCommentCount();
  72. $usersActiveHalfyear = $this->getActiveUsers(180);
  73. $usersActiveMonth = $this->getActiveUsers(30);
  74. $protocols = $this->getProtocols();
  75. $inboundServices = $this->getInboundServices();
  76. $outboundServices = $this->getOutboundServices();
  77. $metadata = $this->getMetadata();
  78. /* Required NodeInfo fields
  79. "version",
  80. "software",
  81. "protocols",
  82. "services",
  83. "openRegistrations",
  84. "usage",
  85. "metadata"
  86. */
  87. $json = json_encode([
  88. // The schema version, must be 2.0.
  89. 'version' => '2.0',
  90. // [Mandatory] Metadata about server software in use.
  91. 'software' => [
  92. 'name' => 'gnusocial', // The canonical name of this server software.
  93. 'version' => GNUSOCIAL_VERSION // The version of this server software.
  94. ],
  95. // The protocols supported on this server.
  96. // The spec requires an array containing at least 1 item but we can't ensure that.
  97. 'protocols' => $protocols,
  98. // The third party sites this server can connect to via their application API.
  99. 'services' => [
  100. // The third party sites this server can retrieve messages from for combined display with regular traffic.
  101. 'inbound' => $inboundServices,
  102. // The third party sites this server can publish messages to on the behalf of a user.
  103. 'outbound' => $outboundServices
  104. ],
  105. // Whether this server allows open self-registration.
  106. 'openRegistrations' => $openRegistrations,
  107. // Usage statistics for this server.
  108. 'usage' => [
  109. 'users' => [
  110. // The total amount of on this server registered users.
  111. 'total' => $userCount,
  112. // The amount of users that signed in at least once in the last 180 days.
  113. 'activeHalfyear' => $usersActiveHalfyear,
  114. // The amount of users that signed in at least once in the last 30 days.
  115. 'activeMonth' => $usersActiveMonth
  116. ],
  117. // The amount of posts that were made by users that are registered on this server.
  118. 'localPosts' => $postCount,
  119. // The amount of comments that were made by users that are registered on this server.
  120. 'localComments' => $commentCount
  121. ],
  122. // Free form key value pairs for software specific values. Clients should not rely on any specific key present.
  123. 'metadata' => $metadata
  124. ]);
  125. header('Content-Type: application/json; profile=http://nodeinfo.diaspora.software/ns/schema/2.0#; charset=utf-8');
  126. print $json;
  127. }
  128. /**
  129. * The protocols supported on this server.
  130. * The spec requires an array containing at least 1 item but we can't ensure that
  131. *
  132. * These can only be one of:
  133. * - activitypub,
  134. * - buddycloud,
  135. * - dfrn,
  136. * - diaspora,
  137. * - libertree,
  138. * - ostatus,
  139. * - pumpio,
  140. * - tent,
  141. * - xmpp,
  142. * - zot
  143. *
  144. * @return array
  145. * @author Diogo Cordeiro <diogo@fc.up.pt>
  146. */
  147. public function getProtocols(): array
  148. {
  149. $protocols = [];
  150. Event::handle('NodeInfoProtocols', [&$protocols]);
  151. return $protocols;
  152. }
  153. /**
  154. * The third party sites this server can retrieve messages from for combined display with regular traffic.
  155. *
  156. * These can only be one of:
  157. * - atom1.0,
  158. * - gnusocial,
  159. * - imap,
  160. * - pnut,
  161. * - pop3,
  162. * - pumpio,
  163. * - rss2.0,
  164. * - twitter
  165. *
  166. * @return array
  167. * @author Diogo Cordeiro <diogo@fc.up.pt>
  168. * @author Stéphane Bérubé <chimo@chromic.org>
  169. */
  170. public function getInboundServices(): array
  171. {
  172. $inboundServices = [];
  173. $ostatusEnabled = array_key_exists('ostatus', $this->plugins);
  174. // We need those two to read feeds (despite WebSub).
  175. if ($ostatusEnabled && array_key_exists('feedpoller', $this->plugins)) {
  176. $inboundServices[] = 'atom1.0';
  177. $inboundServices[] = 'rss2.0';
  178. }
  179. if (array_key_exists('twitterbridge', $this->plugins) && common_config('twitterimport', 'enabled')) {
  180. $inboundServices[] = 'twitter';
  181. }
  182. if (array_key_exists('imap', $this->plugins)) {
  183. $inboundServices[] = 'imap';
  184. }
  185. // We can receive messages from another GNU social instance if we have at least one of those enabled.
  186. // And the same happens in the other instance
  187. if ($ostatusEnabled || array_key_exists('activitypub', $this->plugins)) {
  188. $inboundServices[] = 'gnusocial';
  189. }
  190. return $inboundServices;
  191. }
  192. /**
  193. * The third party sites this server can publish messages to on the behalf of a user.
  194. *
  195. * These can only be one of:
  196. * - atom1.0,
  197. * - blogger,
  198. * - buddycloud,
  199. * - diaspora,
  200. * - dreamwidth,
  201. * - drupal,
  202. * - facebook,
  203. * - friendica,
  204. * - gnusocial,
  205. * - google,
  206. * - insanejournal,
  207. * - libertree,
  208. * - linkedin,
  209. * - livejournal,
  210. * - mediagoblin,
  211. * - myspace,
  212. * - pinterest,
  213. * - pnut,
  214. * - posterous,
  215. * - pumpio,
  216. * - redmatrix,
  217. * - rss2.0,
  218. * - smtp,
  219. * - tent,
  220. * - tumblr,
  221. * - twitter,
  222. * - wordpress,
  223. * - xmpp
  224. *
  225. * @return array
  226. * @author Diogo Cordeiro <diogo@fc.up.pt>
  227. * @author Stéphane Bérubé <chimo@chromic.org>
  228. */
  229. public function getOutboundServices(): array
  230. {
  231. // Those two are always available
  232. $outboundServices = ['atom1.0', 'rss2.0'];
  233. if (array_key_exists('twitterbridge', $this->plugins)) {
  234. $outboundServices[] = 'twitter';
  235. }
  236. // We can send messages to another GNU social instance if we have at least one of those enabled.
  237. // And the same happens in the other instance
  238. if (array_key_exists('ostatus', $this->plugins) ||
  239. array_key_exists('activitypub', $this->plugins)) {
  240. $outboundServices[] = 'gnusocial';
  241. }
  242. $xmppEnabled = (array_key_exists('xmpp', $this->plugins) && common_config('xmpp', 'enabled')) ? true : false;
  243. if ($xmppEnabled) {
  244. $outboundServices[] = 'xmpp';
  245. }
  246. return $outboundServices;
  247. }
  248. /**
  249. * Whether this server allows open self-registration.
  250. *
  251. * @return bool
  252. * @author Stéphane Bérubé <chimo@chromic.org>
  253. */
  254. public function getRegistrationsStatus(): bool
  255. {
  256. $areRegistrationsClosed = (common_config('site', 'closed')) ? true : false;
  257. $isSiteInviteOnly = (common_config('site', 'inviteonly')) ? true : false;
  258. return !($areRegistrationsClosed || $isSiteInviteOnly);
  259. }
  260. /**
  261. * The total amount of on this server registered users.
  262. *
  263. * @return int
  264. * @author Stéphane Bérubé <chimo@chromic.org>
  265. */
  266. public function getUserCount(): int
  267. {
  268. $users = new Usage_stats();
  269. $userCount = $users->getUserCount();
  270. return $userCount;
  271. }
  272. /**
  273. * The amount of users that were active at least once in the last $days days.
  274. *
  275. * Technically, the NodeInfo spec defines 'active' as 'signed in at least once in the
  276. * last {180, 30} days depending on request', but GNU social doesn't keep track of when
  277. * users last logged in.
  278. *
  279. * Therefore, we use Favourites, Notices and Date of account creation to underestimate a
  280. * value. Underestimate because a user that only logs in to see his feed is too an active
  281. * user.
  282. *
  283. * @param int $days
  284. * @return int
  285. * @author Diogo Cordeiro <diogo@fc.up.pt>
  286. */
  287. public function getActiveUsers(int $days): int
  288. {
  289. $userTable = common_database_tablename('user');
  290. $query = <<<END
  291. SELECT COUNT(DISTINCT profile_id) AS active_users_count
  292. FROM (
  293. SELECT profile_id FROM notice
  294. WHERE notice.created >= CURRENT_DATE - INTERVAL '{$days}' DAY AND notice.is_local = 1
  295. UNION ALL
  296. SELECT user_id FROM fave INNER JOIN {$userTable} ON fave.user_id = {$userTable}.id
  297. WHERE fave.created >= CURRENT_DATE - INTERVAL '{$days}' DAY
  298. UNION ALL
  299. SELECT id FROM {$userTable} WHERE {$userTable}.created >= CURRENT_DATE - INTERVAL '{$days}' DAY
  300. ) AS source
  301. LEFT JOIN profile_role USING (profile_id)
  302. WHERE profile_role.profile_id IS NULL OR profile_role.role <> 'silenced';
  303. END;
  304. $activeUsersCount = new DB_DataObject();
  305. $activeUsersCount->query($query);
  306. $activeUsersCount->fetch();
  307. return $activeUsersCount->active_users_count;
  308. }
  309. /**
  310. * The amount of posts that were made by users that are registered on this server.
  311. *
  312. * @return int
  313. * @author Stéphane Bérubé <chimo@chromic.org>
  314. */
  315. public function getPostCount(): int
  316. {
  317. $posts = new Usage_stats();
  318. $postCount = $posts->getPostCount();
  319. return $postCount;
  320. }
  321. /**
  322. * The amount of comments that were made by users that are registered on this server.
  323. *
  324. * @return int
  325. * @author Stéphane Bérubé <chimo@chromic.org>
  326. */
  327. public function getCommentCount(): int
  328. {
  329. $comments = new Usage_stats();
  330. $commentCount = $comments->getCommentCount();
  331. return $commentCount;
  332. }
  333. /**
  334. * Some additional information related to this GNU social instance
  335. *
  336. * @return array
  337. * @author Diogo Cordeiro <diogo@fc.up.pt>
  338. */
  339. public function getMetadata(): array
  340. {
  341. $metadata = [
  342. 'nodeName' => common_config('site', 'name'),
  343. 'software' => [
  344. 'homepage' => GNUSOCIAL_ENGINE_URL,
  345. 'repository' => GNUSOCIAL_ENGINE_REPO_URL,
  346. ],
  347. 'uploadLimit' => common_get_preferred_php_upload_limit(),
  348. 'postFormats' => [
  349. 'text/plain',
  350. 'text/html'
  351. ],
  352. 'features' => []
  353. ];
  354. if (array_key_exists('poll', $this->plugins)) {
  355. $metadata['features'][] = 'polls';
  356. }
  357. return $metadata;
  358. }
  359. }