]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/Meteor/MeteorPlugin.php
Some changes for debugging
[quix0rs-gnu-social.git] / plugins / Meteor / MeteorPlugin.php
1 <?php
2 /**
3  * Laconica, the distributed open-source microblogging tool
4  *
5  * Plugin to do "real time" updates using Comet/Bayeux
6  *
7  * PHP version 5
8  *
9  * LICENCE: This program is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU Affero General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU Affero General Public License for more details.
18  *
19  * You should have received a copy of the GNU Affero General Public License
20  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21  *
22  * @category  Plugin
23  * @package   Laconica
24  * @author    Evan Prodromou <evan@controlyourself.ca>
25  * @copyright 2009 Control Yourself, Inc.
26  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
27  * @link      http://laconi.ca/
28  */
29
30 if (!defined('LACONICA')) {
31     exit(1);
32 }
33
34 /**
35  * Plugin to do realtime updates using Comet
36  *
37  * @category Plugin
38  * @package  Laconica
39  * @author   Evan Prodromou <evan@controlyourself.ca>
40  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
41  * @link     http://laconi.ca/
42  */
43
44 class MeteorPlugin extends Plugin
45 {
46     public $webserver     = null;
47     public $webport       = null;
48     public $controlport   = null;
49     public $controlserver = null;
50     public $channelbase   = null;
51     protected $_socket    = null;
52
53     function __construct($webserver=null, $webport=4670, $controlport=4671, $controlserver=null, $channelbase='')
54     {
55         global $config;
56
57         $this->webserver     = (empty($webserver)) ? $config['site']['server'] : $webserver;
58         $this->webport       = $webport;
59         $this->controlport   = $controlport;
60         $this->controlserver = (empty($controlserver)) ? $webserver : $controlserver;
61         $this->channelbase   = $channelbase;
62
63         parent::__construct();
64     }
65
66     function onEndShowScripts($action)
67     {
68         $timeline = null;
69
70         switch ($action->trimmed('action')) {
71          case 'public':
72             $timeline = 'timelines-public';
73             break;
74          case 'tag':
75             $tag = $action->trimmed('tag');
76             if (!empty($tag)) {
77                 $timeline = 'timelines-tag-'.$tag;
78             } else {
79                 return true;
80             }
81             break;
82          default:
83             return true;
84         }
85
86         $action->element('script', array('type' => 'text/javascript',
87                                          'src' => 'http://'.$this->webserver.(($this->webport == 80) ? '':':'.$this->webport).'/meteor.js'),
88                          ' ');
89
90         foreach (array('meteorupdater.js', 'json2.js') as $script) {
91             $action->element('script', array('type' => 'text/javascript',
92                                              'src' => common_path('plugins/Meteor/'.$script)),
93                          ' ');
94         }
95
96         $user = common_current_user();
97
98         if (!empty($user->id)) {
99             $user_id = $user->id;
100         } else {
101             $user_id = 0;
102         }
103
104         $replyurl = common_local_url('newnotice');
105         $favorurl = common_local_url('favor');
106         // FIXME: need to find a better way to pass this pattern in
107         $deleteurl = common_local_url('deletenotice',
108                                       array('notice' => '0000000000'));
109
110         $action->elementStart('script', array('type' => 'text/javascript'));
111         $action->raw("$(document).ready(function() { MeteorUpdater.init(\"$this->webserver\", $this->webport, \"{$this->channelbase}{$timeline}\", $user_id, \"$replyurl\", \"$favorurl\", \"$deleteurl\"); });");
112         $action->elementEnd('script');
113
114         return true;
115     }
116
117     function onEndNoticeSave($notice)
118     {
119         $timelines = array();
120
121         // XXX: Add other timelines; this is just for the public one
122
123         if ($notice->is_local ||
124             ($notice->is_local == 0 && !common_config('public', 'localonly'))) {
125             $timelines[] = 'timelines-public';
126         }
127
128         $tags = $this->getNoticeTags($notice);
129
130         if (!empty($tags)) {
131             foreach ($tags as $tag) {
132                 $timelines[] = 'timelines-tag-' . $tag;
133             }
134         }
135
136         if (count($timelines) > 0) {
137
138             $json = json_encode($this->noticeAsJson($notice));
139
140             $this->_connect();
141
142             foreach ($timelines as $timeline) {
143                 $this->_addMessage($timeline, $json);
144             }
145
146             $this->_disconnect();
147         }
148
149         return true;
150     }
151
152     protected function _connect()
153     {
154         $controlserver = (empty($this->controlserver)) ? $this->webserver : $this->controlserver;
155         // May throw an exception.
156         $this->_socket = stream_socket_client("tcp://{$controlserver}:{$this->controlport}");
157         if (!$this->_socket) {
158             throw new Exception("Couldn't connect to {$controlserver} on {$this->controlport}");
159         }
160     }
161
162     protected function _addMessage($channel, $message)
163     {
164         $cmd = "ADDMESSAGE {$this->channelbase}{$channel} $message\n";
165         $cnt = fwrite($this->_socket, $cmd);
166         $result = fgets($this->_socket);
167         if (preg_match('/^ERR (.*)$/', $result, $matches)) {
168             throw new Exception('Error adding meteor message "'.$matches[1].'"');
169         }
170         // TODO: parse and deal with result
171     }
172
173     protected function _disconnect()
174     {
175         $cnt = fwrite($this->_socket, "QUIT\n");
176         @fclose($this->_socket);
177     }
178
179     function noticeAsJson($notice)
180     {
181         // FIXME: this code should be abstracted to a neutral third
182         // party, like Notice::asJson(). I'm not sure of the ethics
183         // of refactoring from within a plugin, so I'm just abusing
184         // the TwitterApiAction method. Don't do this unless you're me!
185
186         require_once(INSTALLDIR.'/lib/twitterapi.php');
187
188         $act = new TwitterApiAction('/dev/null');
189
190         $arr = $act->twitter_status_array($notice, true);
191         $arr['url'] = $notice->bestUrl();
192         $arr['html'] = htmlspecialchars($notice->rendered);
193         $arr['source'] = htmlspecialchars($arr['source']);
194
195         if (!empty($notice->reply_to)) {
196             $reply_to = Notice::staticGet('id', $notice->reply_to);
197             if (!empty($reply_to)) {
198                 $arr['in_reply_to_status_url'] = $reply_to->bestUrl();
199             }
200             $reply_to = null;
201         }
202
203         $profile = $notice->getProfile();
204         $arr['user']['profile_url'] = $profile->profileurl;
205
206         return $arr;
207     }
208
209     function getNoticeTags($notice)
210     {
211         $tags = null;
212
213         $nt = new Notice_tag();
214         $nt->notice_id = $notice->id;
215
216         if ($nt->find()) {
217             $tags = array();
218             while ($nt->fetch()) {
219                 $tags[] = $nt->tag;
220             }
221         }
222
223         $nt->free();
224         $nt = null;
225
226         return $tags;
227     }
228
229     // Push this up to Plugin
230
231     function log($level, $msg)
232     {
233         common_log($level, get_class($this) . ': '.$msg);
234     }
235 }