]> git.mxchange.org Git - friendica-addons.git/blob - discourse/discourse.php
6a203950a728a242d8a97dc0709aed4288b13a2d
[friendica-addons.git] / discourse / discourse.php
1 <?php
2
3 /**
4  * Name: Discourse Mail Connector
5  * Description: Improves mails from Discourse in mailing list mode
6  * Version: 0.1
7  * Author: Michael Vogel <http://pirati.ca/profile/heluecht>
8  *
9  */
10 //use DOMDocument;
11 //use DOMXPath;
12 use Friendica\App;
13 use Friendica\Core\Hook;
14 use Friendica\Core\L10n;
15 use Friendica\Core\Logger;
16 use Friendica\Core\PConfig;
17 use Friendica\Core\Protocol;
18 use Friendica\Database\DBA;
19 use Friendica\Model\Contact;
20 use Friendica\Util\XML;
21 use Friendica\Content\Text\Markdown;
22 use Friendica\Util\Network;
23 use Friendica\Util\Strings;
24 Use Friendica\Util\DateTimeFormat;
25
26 function discourse_install()
27 {
28         Hook::register('email_getmessage',     __FILE__, 'discourse_email_getmessage');
29         Hook::register('email_getmessage_end', __FILE__, 'discourse_email_getmessage_end');
30         Hook::register('addon_settings',       __FILE__, 'discourse_addon_settings');
31         Hook::register('addon_settings_post',  __FILE__, 'discourse_addon_settings_post');
32 }
33
34 function discourse_uninstall()
35 {
36         Hook::unregister('email_getmessage',     __FILE__, 'discourse_email_getmessage');
37         Hook::unregister('email_getmessage_end', __FILE__, 'discourse_email_getmessage_end');
38         Hook::unregister('addon_settings',       __FILE__, 'discourse_addon_settings');
39         Hook::unregister('addon_settings_post',  __FILE__, 'discourse_addon_settings_post');
40 }
41
42 function discourse_addon_settings(App $a, &$s)
43 {
44 }
45
46 function discourse_addon_settings_post(App $a)
47 {
48 }
49
50 function discourse_email_getmessage(App $a, &$message)
51 {
52 //      Logger::info('Got raw message', $message);
53
54 /*      if (preg_match('=topic/(.*)/(.*)@(.*)=', $message['item']['uri'], $matches)) {
55                 Logger::info('Got post data', ['topic' => $matches[1], 'post' => $matches[2], 'host' => $matches[3]]);
56                 if (discourse_fetch_post_from_api($message, $matches[2], $matches[3])) {
57                         return;
58                 }
59         }
60 */
61         // Search in the text part for the link to the discourse entry and the text body
62         // The text body is used as alternative, if the fetched HTML isn't working
63         if (!empty($message['text'])) {
64                 $message = discourse_get_text($message);
65         }
66
67         if (!empty($message['item']['plink'])) {
68                 if (preg_match('=(http.*)/t/.*/(.*\d)/(.*\d)=', $message['item']['plink'], $matches)) {
69                         if (discourse_fetch_topic_from_api($message, $matches[1], $matches[2], $matches[3])) {
70                                 return;
71                         }
72                 }
73         }
74         Logger::info('Stop');
75 die('Test');
76         // Search in the HTML part for the discourse entry and the author profile
77         if (!empty($message['html'])) {
78                 $message = discourse_get_html($message);
79         }
80
81         // Remove the title on comments, they don't serve any purpose there
82         if ($message['item']['parent-uri'] != $message['item']['uri']) {
83                 unset($message['item']['title']);
84         }
85 }
86
87 function discourse_fetch_post($host, $topic, $pid)
88 {
89         $url = $host . '/t/' . $topic . '/' . $pid . '.json';
90         $curlResult = Network::curl($url);
91         if (!$curlResult->isSuccess()) {
92                 Logger::info('No success', ['url' => $url]);
93                 return false;
94         }
95
96         $raw = $curlResult->getBody();
97         $data = json_decode($raw, true);
98         $posts = $data['post_stream']['posts'];
99         foreach($posts as $post) {
100                 if ($post['post_number'] != $pid) {
101                         // Test
102                         discourse_get_user($post, $host);
103                         continue;
104                 }
105                 Logger::info('Got post data from topic', $post);
106                 return $post;
107         }
108
109         Logger::info('Post not found', ['host' => $host, 'topic' => $topic, 'pid' => $pid]);
110         return false;
111 }
112
113 function discourse_fetch_topic_from_api(&$message, $host, $topic, $pid)
114 {
115         $post = discourse_fetch_post($host, $topic, $pid);
116         if (empty($post)) {
117                 return false;
118         }
119
120         $message = discourse_process_post($message, $post, $host);
121         return true;
122 }
123
124 function discourse_fetch_post_from_api(&$message, $post, $host)
125 {
126         $hostaddr = 'https://' . $host;
127         $url = $hostaddr . '/posts/' . $post . '.json';
128         $curlResult = Network::curl($url);
129         if (!$curlResult->isSuccess()) {
130                 return false;
131         }
132
133         $raw = $curlResult->getBody();
134         $data = json_decode($raw, true);
135         if (empty($data)) {
136                 return false;
137         }
138
139         $message = discourse_process_post($message, $data, $hostaddr);
140
141         Logger::info('Got API data', $message);
142         return true;
143 }
144
145 function discourse_get_user($post, $hostaddr)
146 {
147         $host = parse_url($hostaddr, PHP_URL_HOST);
148
149         $contact = [];
150         // display_username
151         // user_id
152         $contact['uid'] = 0;
153         $contact['network'] = Protocol::DISCOURSE;
154         $contact['name'] = $contact['nick'] = $post['username'];
155         if (!empty($post['name'])) {
156                 $contact['name'] = $post['name'];
157         }
158
159         $contact['about'] = $post['user_title'];
160
161         if (parse_url($post['avatar_template'], PHP_URL_SCHEME)) {
162                 $contact['photo'] = str_replace('{size}', '300', $post['avatar_template']);
163         } else {
164                 $contact['photo'] = $hostaddr . str_replace('{size}', '300', $post['avatar_template']);
165         }
166
167         $contact['addr'] = $contact['nick'] . '@' . $host;
168         $contact['contact-type'] = Contact::TYPE_PERSON;
169         $contact['url'] = $hostaddr . '/u/' . $contact['nick'];
170         $contact['nurl'] = Strings::normaliseLink($contact['url']);
171         $contact['baseurl'] = $hostaddr;
172         Logger::info('Contact', $contact);
173         $contact['id'] = Contact::getIdForURL($contact['url'], 0, true, $contact);
174         if (!empty($contact['id'])) {
175                 $avatar = $contact['photo'];
176                 unset($contact['photo']);
177                 DBA::update('contact', $contact, ['id' => $contact['id']]);
178                 Contact::updateAvatar($avatar, 0, $contact['id']);
179                 $contact['photo'] = $avatar;
180         }
181
182         return $contact;
183 }
184
185 function discourse_process_post($message, $post, $hostaddr)
186 {
187         $host = parse_url($hostaddr, PHP_URL_HOST);
188
189         $message['html'] = $post['cooked'];
190
191         $contact = discourse_get_user($post, $hostaddr);
192         $message['item']['author-id'] = $contact['id'];
193         $message['item']['author-link'] = $contact['url'];
194         $message['item']['author-name'] = $contact['name'];
195         $message['item']['author-avatar'] = $contact['photo'];
196         $message['item']['created'] = DateTimeFormat::utc($post['created_at']);
197         $message['item']['plink'] = $hostaddr . '/t/' . $post['topic_slug'] . '/' . $post['topic_id'] . '/' . $post['post_number'];
198
199         if ($post['post_number'] == 1) {
200                 $message['item']['parent-uri'] = $message['item']['uri'] = 'topic/' . $post['topic_id'] . '@' . $host;
201                 // To-Do: Thread information
202         } else {
203                 $message['item']['uri'] = 'topic/' . $post['topic_id'] . '/' . $post['id'] . '@' . $host;
204                 unset($message['item']['title']);
205                 if (empty($post['reply_to_post_number']) || $post['reply_to_post_number'] == 1) {
206                         $message['item']['parent-uri'] = 'topic/' . $post['topic_id'] . '@' . $host;
207                 } else {
208                         $reply = discourse_fetch_post($hostaddr, $post['topic_id'], $post['reply_to_post_number']);
209                         $message['item']['parent-uri'] = 'topic/' . $post['topic_id'] . '/' . $reply['id'] . '@' . $host;
210                 }
211         }
212
213         return $message;
214 }
215
216 function discourse_get_html($message)
217 {
218         $doc = new DOMDocument();
219         $doc2 = new DOMDocument();
220         $doc->preserveWhiteSpace = false;
221
222         $html = mb_convert_encoding($message['html'], 'HTML-ENTITIES', "UTF-8");
223         @$doc->loadHTML($html, LIBXML_HTML_NODEFDTD);
224
225         $xpath = new DomXPath($doc);
226
227         // Fetch the first 'div' before the 'hr' -hopefully this fits for all systems
228         $result = $xpath->query("//hr//preceding::div[1]");
229         $div = $doc2->importNode($result->item(0), true);
230         $doc2->appendChild($div);
231         $message['html'] = $doc2->saveHTML();
232         Logger::info('Found html body', ['html' => $message['html']]);
233
234         $profile = discourse_get_profile($xpath);
235         if (!empty($profile)) {
236                 Logger::info('Found profile', $profile);
237                 $message['item']['author-id'] = Contact::getIdForURL($profile['url'], 0, true, $profile);
238                 $message['item']['author-link'] = $profile['url'];
239                 $message['item']['author-name'] = $profile['name'];
240                 $message['item']['author-avatar'] = $profile['photo'];
241         }
242
243         return $message;
244 }
245
246 function discourse_get_text($message)
247 {
248         $text = $message['text'];
249         $text = str_replace("\r", '', $text);
250         $pos = strpos($text, "\n---\n");
251         if ($pos == 0) {
252                 Logger::info('No separator found', ['text' => $text]);
253                 return $message;
254         }
255
256         $message['text'] = trim(substr($text, 0, $pos));
257
258         Logger::info('Found text body', ['text' => $message['text']]);
259
260         $message['text'] = Markdown::toBBCode($message['text']);
261
262         $text = substr($text, $pos);
263         Logger::info('Found footer', ['text' => $text]);
264         if (preg_match('=\((http.*/t/.*/.*\d/.*\d)\)=', $text, $link)) {
265                 $message['item']['plink'] = $link[1];
266                 Logger::info('Found plink', ['plink' => $message['item']['plink']]);
267         }
268         return $message;
269 }
270
271 function discourse_get_profile($xpath)
272 {
273         $profile = [];
274         $list = $xpath->query("//td//following::img");
275         foreach ($list as $node) {
276                 $attr = [];
277                 foreach ($node->attributes as $attribute) {
278                         $attr[$attribute->name] = $attribute->value;
279                 }
280
281                 if (!empty($attr['src']) && !empty($attr['title'])
282                         && !empty($attr['width']) && !empty($attr['height'])
283                         && ($attr['width'] == $attr['height'])) {
284                         $profile = ['photo' => $attr['src'], 'name' => $attr['title']];
285                         break;
286                 }
287         }
288
289         $list = $xpath->query("//td//following::a");
290         foreach ($list as $node) {
291                 if (!empty(trim($node->textContent)) && $node->attributes->length) {
292                         $attr = [];
293                         foreach ($node->attributes as $attribute) {
294                                 $attr[$attribute->name] = $attribute->value;
295                         }
296                         if (!empty($attr['href']) && (strpos($attr['href'], '/' . $profile['name']))) {
297                                 $profile['url'] = $attr['href'];
298                                 break;
299                         }
300                 }
301         }
302         return $profile;
303 }
304
305 function discourse_email_getmessage_end(App $a, &$message)
306 {
307 //      Logger::info('Got converted message', $message);
308 }