apidirectmessage.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516
  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. * Direct messaging implementation for GNU social
  18. *
  19. * @package GNUsocial
  20. * @author Adrian Lang <mail@adrianlang.de>
  21. * @author Evan Prodromou <evan@status.net>
  22. * @author Robin Millette <robin@millette.info>
  23. * @author Zach Copley <zach@status.net>
  24. * @copyright 2009, 2020 Free Software Foundation, Inc http://www.fsf.org
  25. * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
  26. */
  27. defined('GNUSOCIAL') || die();
  28. /**
  29. * Show a list of direct messages from or to the authenticating user
  30. *
  31. * @category Plugin
  32. * @package GNUsocial
  33. * @author Adrian Lang <mail@adrianlang.de>
  34. * @author Evan Prodromou <evan@status.net>
  35. * @author Robin Millette <robin@millette.info>
  36. * @author Zach Copley <zach@status.net>
  37. * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
  38. */
  39. class ApiDirectMessageAction extends ApiAuthAction
  40. {
  41. public $messages = null;
  42. public $title = null;
  43. public $subtitle = null;
  44. public $link = null;
  45. public $selfuri_base = null;
  46. public $id = null;
  47. /**
  48. * Take arguments for running
  49. *
  50. * @param array $args $_REQUEST args
  51. *
  52. * @return boolean success flag
  53. */
  54. protected function prepare(array $args = [])
  55. {
  56. parent::prepare($args);
  57. if (!$this->scoped instanceof Profile) {
  58. // TRANS: Client error given when a user was not found (404).
  59. $this->clientError(_('No such user.'), 404);
  60. }
  61. $server = common_root_url();
  62. $taguribase = TagURI::base();
  63. if ($this->arg('sent')) {
  64. // Action was called by /api/direct_messages/sent.format
  65. $this->title = sprintf(
  66. // TRANS: Title. %s is a user nickname.
  67. _("Direct messages from %s"),
  68. $this->scoped->getNickname()
  69. );
  70. $this->subtitle = sprintf(
  71. // TRANS: Subtitle. %s is a user nickname.
  72. _("All the direct messages sent from %s"),
  73. $this->scoped->getNickname()
  74. );
  75. $this->link = $server . $this->scoped->getNickname() . '/outbox';
  76. $this->selfuri_base = common_root_url() . 'api/direct_messages/sent';
  77. $this->id = "tag:$taguribase:SentDirectMessages:" . $this->scoped->getID();
  78. } else {
  79. $this->title = sprintf(
  80. // TRANS: Title. %s is a user nickname.
  81. _("Direct messages to %s"),
  82. $this->scoped->getNickname()
  83. );
  84. $this->subtitle = sprintf(
  85. // TRANS: Subtitle. %s is a user nickname.
  86. _("All the direct messages sent to %s"),
  87. $this->scoped->getNickname()
  88. );
  89. $this->link = $server . $this->scoped->getNickname() . '/inbox';
  90. $this->selfuri_base = common_root_url() . 'api/direct_messages';
  91. $this->id = "tag:$taguribase:DirectMessages:" . $this->scoped->getID();
  92. }
  93. $this->messages = $this->getMessages();
  94. return true;
  95. }
  96. protected function handle()
  97. {
  98. parent::handle();
  99. $this->showMessages();
  100. }
  101. /**
  102. * Show the messages
  103. *
  104. * @return void
  105. */
  106. public function showMessages()
  107. {
  108. switch ($this->format) {
  109. case 'xml':
  110. $this->showXmlDirectMessages();
  111. break;
  112. case 'rss':
  113. $this->showRssDirectMessages();
  114. break;
  115. case 'atom':
  116. $this->showAtomDirectMessages();
  117. break;
  118. case 'json':
  119. $this->showJsonDirectMessages();
  120. break;
  121. default:
  122. // TRANS: Client error displayed when coming across a non-supported API method.
  123. $this->clientError(_('API method not found.'), $code = 404);
  124. break;
  125. }
  126. }
  127. /**
  128. * Get messages
  129. *
  130. * @return array
  131. */
  132. public function getMessages(): array
  133. {
  134. $message = $this->arg('sent')
  135. ? $this->getOutboxMessages()
  136. : $this->getInboxMessages();
  137. $ret = [];
  138. if (!is_null($message)) {
  139. while ($message->fetch()) {
  140. $ret[] = clone $message;
  141. }
  142. }
  143. return $ret;
  144. }
  145. /**
  146. * Return data object of the messages received by some user.
  147. *
  148. * @return Notice data object
  149. */
  150. private function getInboxMessages()
  151. {
  152. // fetch all notice IDs related to the user
  153. $attention = new Attention();
  154. $attention->selectAdd('notice_id');
  155. $attention->whereAdd('profile_id = ' . $this->scoped->getID());
  156. $ids = $attention->find() ? $attention->fetchAll('notice_id') : [];
  157. // get the messages
  158. $message = new Notice();
  159. $message->whereAdd('scope = ' . NOTICE::MESSAGE_SCOPE);
  160. if (!empty($this->max_id)) {
  161. $message->whereAdd('id <= ' . $this->max_id);
  162. }
  163. if (!empty($this->since_id)) {
  164. $message->whereAdd('id > ' . $this->since_id);
  165. }
  166. $message->whereAddIn('id', $ids, 'int');
  167. $message->orderBy('created DESC, id DESC');
  168. $message->limit((($this->page - 1) * $this->count), $this->count);
  169. return $message->find() ? $message : null;
  170. }
  171. /**
  172. * Return data object of the messages sent by some user.
  173. *
  174. * @return Notice data object
  175. */
  176. private function getOutboxMessages()
  177. {
  178. $message = new Notice();
  179. $message->profile_id = $this->scoped->getID();
  180. $message->whereAdd('scope = ' . NOTICE::MESSAGE_SCOPE);
  181. if (!empty($this->max_id)) {
  182. $message->whereAdd('id <= ' . $this->max_id);
  183. }
  184. if (!empty($this->since_id)) {
  185. $message->whereAdd('id > ' . $this->since_id);
  186. }
  187. $message->orderBy('created DESC, id DESC');
  188. $message->limit((($this->page - 1) * $this->count), $this->count);
  189. return $message->find() ? $message : null;
  190. }
  191. /**
  192. * Is this action read only?
  193. *
  194. * @param array $args other arguments
  195. *
  196. * @return boolean true
  197. */
  198. public function isReadOnly($args)
  199. {
  200. return true;
  201. }
  202. /**
  203. * When was this notice last modified?
  204. *
  205. * @return string datestamp of the latest notice in the stream
  206. */
  207. public function lastModified()
  208. {
  209. if (!empty($this->messages)) {
  210. return strtotime($this->messages[0]->created);
  211. }
  212. return null;
  213. }
  214. // BEGIN import from lib/apiaction.php
  215. public function showSingleXmlDirectMessage($message)
  216. {
  217. $this->initDocument('xml');
  218. $dmsg = $this->directMessageArray($message);
  219. $this->showXmlDirectMessage($dmsg, true);
  220. $this->endDocument('xml');
  221. }
  222. public function showSingleJsonDirectMessage($message)
  223. {
  224. $this->initDocument('json');
  225. $dmsg = $this->directMessageArray($message);
  226. $this->showJsonObjects($dmsg);
  227. $this->endDocument('json');
  228. }
  229. public function showXmlDirectMessage($dm, $namespaces = false)
  230. {
  231. $attrs = [];
  232. if ($namespaces) {
  233. $attrs['xmlns:statusnet'] = 'http://status.net/schema/api/1/';
  234. }
  235. $this->elementStart('direct_message', $attrs);
  236. foreach ($dm as $element => $value) {
  237. if ($element === 'text') {
  238. $this->element($element, null, common_xml_safe_str($value));
  239. } elseif (
  240. $element === 'sender'
  241. || preg_match('/recipient$|recipient_[0-9]+/', $element) == 1
  242. ) {
  243. $this->showTwitterXmlUser($value, $element);
  244. } else {
  245. $this->element($element, null, $value);
  246. }
  247. }
  248. $this->elementEnd('direct_message');
  249. }
  250. public function directMessageArray($message)
  251. {
  252. $dmsg = [];
  253. $from = $message->getProfile();
  254. $to = $message->getAttentionProfiles();
  255. $dmsg['id'] = intval($message->id);
  256. $dmsg['sender_id'] = (int) $from->id;
  257. $dmsg['text'] = trim($message->content);
  258. $dmsg['total_recipients'] = (int) count($to);
  259. $dmsg['recipient_id'] = (int) $to[0]->id;
  260. for ($i = 1; $i < count($to); ++$i) {
  261. $dmsg['recipient_id_' . $i] = (int) $to[$i]->id;
  262. }
  263. $dmsg['created_at'] = $this->dateTwitter($message->created);
  264. $dmsg['sender_screen_name'] = $from->nickname;
  265. $dmsg['recipient_screen_name'] = $to[0]->nickname;
  266. for ($i = 1; $i < count($to); ++$i) {
  267. $dmsg['recipient_screen_name_' . $i] = $to[$i]->nickname;
  268. }
  269. $dmsg['sender'] = $this->twitterUserArray($from);
  270. $dmsg['recipient'] = $this->twitterUserArray($to[0]);
  271. for ($i = 1; $i < count($to); ++$i) {
  272. $dmsg['recipient_' . $i] = $this->twitterUserArray($to[$i]);
  273. }
  274. return $dmsg;
  275. }
  276. public function rssDirectMessageArray($message)
  277. {
  278. $entry = [];
  279. $from = $message->getProfile();
  280. $to = $message->getAttentionProfiles();
  281. $entry['title'] = 'Message from ' . $from->nickname . ' to ';
  282. $entry['title'] .= (count($to) == 1) ? $to[0]->nickname : 'many';
  283. $entry['content'] = common_xml_safe_str($message->rendered);
  284. $entry['link'] = common_local_url(
  285. 'showmessage',
  286. ['message' => $message->id]
  287. );
  288. $entry['published'] = common_date_iso8601($message->created);
  289. $taguribase = TagURI::base();
  290. $entry['id'] = "tag:$taguribase:$entry[link]";
  291. $entry['updated'] = $entry['published'];
  292. $entry['author-name'] = $from->getBestName();
  293. $entry['author-uri'] = $from->homepage;
  294. $entry['avatar'] = $from->avatarUrl(AVATAR_STREAM_SIZE);
  295. try {
  296. $avatar = $from->getAvatar(AVATAR_STREAM_SIZE);
  297. $entry['avatar-type'] = $avatar->mediatype;
  298. } catch (Exception $e) {
  299. $entry['avatar-type'] = 'image/png';
  300. }
  301. // RSS item specific
  302. $entry['description'] = $entry['content'];
  303. $entry['pubDate'] = common_date_rfc2822($message->created);
  304. $entry['guid'] = $entry['link'];
  305. return $entry;
  306. }
  307. // END import from lib/apiaction.php
  308. /**
  309. * Shows a list of direct messages as Twitter-style XML array
  310. *
  311. * @return void
  312. */
  313. public function showXmlDirectMessages()
  314. {
  315. $this->initDocument('xml');
  316. $this->elementStart('direct-messages', [
  317. 'type' => 'array',
  318. 'xmlns:statusnet' => 'http://status.net/schema/api/1/',
  319. ]);
  320. foreach ($this->messages as $m) {
  321. $dm_array = $this->directMessageArray($m);
  322. $this->showXmlDirectMessage($dm_array);
  323. }
  324. $this->elementEnd('direct-messages');
  325. $this->endDocument('xml');
  326. }
  327. /**
  328. * Shows a list of direct messages as a JSON encoded array
  329. *
  330. * @return void
  331. */
  332. public function showJsonDirectMessages()
  333. {
  334. $this->initDocument('json');
  335. $dmsgs = [];
  336. foreach ($this->messages as $m) {
  337. $dm_array = $this->directMessageArray($m);
  338. array_push($dmsgs, $dm_array);
  339. }
  340. $this->showJsonObjects($dmsgs);
  341. $this->endDocument('json');
  342. }
  343. /**
  344. * Shows a list of direct messages as RSS items
  345. *
  346. * @return void
  347. */
  348. public function showRssDirectMessages()
  349. {
  350. $this->initDocument('rss');
  351. $this->element('title', null, $this->title);
  352. $this->element('link', null, $this->link);
  353. $this->element('description', null, $this->subtitle);
  354. $this->element('language', null, 'en-us');
  355. $this->element(
  356. 'atom:link',
  357. [
  358. 'type' => 'application/rss+xml',
  359. 'href' => $this->selfuri_base . '.rss',
  360. 'rel' => self,
  361. ],
  362. null
  363. );
  364. $this->element('ttl', null, '40');
  365. foreach ($this->messages as $m) {
  366. $entry = $this->rssDirectMessageArray($m);
  367. $this->showTwitterRssItem($entry);
  368. }
  369. $this->endTwitterRss();
  370. }
  371. /**
  372. * Shows a list of direct messages as Atom entries
  373. *
  374. * @return void
  375. */
  376. public function showAtomDirectMessages()
  377. {
  378. $this->initDocument('atom');
  379. $this->element('title', null, $this->title);
  380. $this->element('id', null, $this->id);
  381. $selfuri = common_root_url() . 'api/direct_messages.atom';
  382. $this->element(
  383. 'link',
  384. [
  385. 'href' => $this->link,
  386. 'rel' => 'alternate',
  387. 'type' => 'text/html',
  388. ],
  389. null
  390. );
  391. $this->element(
  392. 'link',
  393. [
  394. 'href' => $this->selfuri_base . '.atom',
  395. 'rel' => 'self',
  396. 'type' => 'application/atom+xml',
  397. ],
  398. null
  399. );
  400. $this->element('updated', null, common_date_iso8601('now'));
  401. $this->element('subtitle', null, $this->subtitle);
  402. foreach ($this->messages as $m) {
  403. $entry = $this->rssDirectMessageArray($m);
  404. $this->showTwitterAtomEntry($entry);
  405. }
  406. $this->endDocument('atom');
  407. }
  408. /**
  409. * An entity tag for this notice
  410. *
  411. * Returns an Etag based on the action name, language, and
  412. * timestamps of the notice
  413. *
  414. * @return string etag
  415. */
  416. public function etag()
  417. {
  418. if (!empty($this->messages)) {
  419. $last = count($this->messages) - 1;
  420. return '"' . implode(
  421. ':',
  422. [
  423. $this->arg('action'),
  424. common_user_cache_hash($this->auth_user),
  425. common_language(),
  426. strtotime($this->messages[0]->created),
  427. strtotime($this->messages[$last]->created),
  428. ]
  429. )
  430. . '"';
  431. }
  432. return null;
  433. }
  434. }