forked from GNUsocial/gnu-social
		
	copy Comet plugin to Orbited
This commit is contained in:
		
				
					committed by
					
						 Evan Prodromou
						Evan Prodromou
					
				
			
			
				
	
			
			
			
						parent
						
							9c460d591e
						
					
				
				
					commit
					22b4a66de3
				
			
							
								
								
									
										205
									
								
								plugins/Orbited/CometPlugin.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										205
									
								
								plugins/Orbited/CometPlugin.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,205 @@ | ||||
| <?php | ||||
| /** | ||||
|  * Laconica, the distributed open-source microblogging tool | ||||
|  * | ||||
|  * Plugin to do "real time" updates using Comet/Bayeux | ||||
|  * | ||||
|  * PHP version 5 | ||||
|  * | ||||
|  * LICENCE: This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as published by | ||||
|  * the Free Software Foundation, either version 3 of the License, or | ||||
|  * (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Affero General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Affero General Public License | ||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  * | ||||
|  * @category  Plugin | ||||
|  * @package   Laconica | ||||
|  * @author    Evan Prodromou <evan@controlyourself.ca> | ||||
|  * @copyright 2009 Control Yourself, Inc. | ||||
|  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 | ||||
|  * @link      http://laconi.ca/ | ||||
|  */ | ||||
|  | ||||
| if (!defined('LACONICA')) { | ||||
|     exit(1); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Plugin to do realtime updates using Comet | ||||
|  * | ||||
|  * @category Plugin | ||||
|  * @package  Laconica | ||||
|  * @author   Evan Prodromou <evan@controlyourself.ca> | ||||
|  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 | ||||
|  * @link     http://laconi.ca/ | ||||
|  */ | ||||
|  | ||||
| class CometPlugin extends Plugin | ||||
| { | ||||
|     var $server = null; | ||||
|  | ||||
|     function __construct($server=null, $username=null, $password=null) | ||||
|     { | ||||
|         $this->server   = $server; | ||||
|         $this->username = $username; | ||||
|         $this->password = $password; | ||||
|  | ||||
|         parent::__construct(); | ||||
|     } | ||||
|  | ||||
|     function onEndShowScripts($action) | ||||
|     { | ||||
|         $timeline = null; | ||||
|  | ||||
|         $this->log(LOG_DEBUG, 'got action ' . $action->trimmed('action')); | ||||
|  | ||||
|         switch ($action->trimmed('action')) { | ||||
|          case 'public': | ||||
|             $timeline = '/timelines/public'; | ||||
|             break; | ||||
|          case 'tag': | ||||
|             $tag = $action->trimmed('tag'); | ||||
|             if (!empty($tag)) { | ||||
|                 $timeline = '/timelines/tag/'.$tag; | ||||
|             } else { | ||||
|                 return true; | ||||
|             } | ||||
|             break; | ||||
|          default: | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         $scripts = array('jquery.comet.js', 'json2.js', 'updatetimeline.js'); | ||||
|  | ||||
|         foreach ($scripts as $script) { | ||||
|             $action->element('script', array('type' => 'text/javascript', | ||||
|                                              'src' => common_path('plugins/Comet/'.$script)), | ||||
|                          ' '); | ||||
|         } | ||||
|  | ||||
|         $user = common_current_user(); | ||||
|  | ||||
|         if (!empty($user->id)) { | ||||
|             $user_id = $user->id; | ||||
|         } else { | ||||
|             $user_id = 0; | ||||
|         } | ||||
|  | ||||
|         $replyurl = common_local_url('newnotice'); | ||||
|         $favorurl = common_local_url('favor'); | ||||
|         // FIXME: need to find a better way to pass this pattern in | ||||
|         $deleteurl = common_local_url('deletenotice', | ||||
|                                       array('notice' => '0000000000')); | ||||
|  | ||||
|         $action->elementStart('script', array('type' => 'text/javascript')); | ||||
|         $action->raw("$(document).ready(function() { updater.init(\"$this->server\", \"$timeline\", $user_id, \"$replyurl\", \"$favorurl\", \"$deleteurl\"); });"); | ||||
|         $action->elementEnd('script'); | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     function onEndNoticeSave($notice) | ||||
|     { | ||||
|         $this->log(LOG_INFO, "Called for save notice."); | ||||
|  | ||||
|         $timelines = array(); | ||||
|  | ||||
|         // XXX: Add other timelines; this is just for the public one | ||||
|  | ||||
|         if ($notice->is_local || | ||||
|             ($notice->is_local == 0 && !common_config('public', 'localonly'))) { | ||||
|             $timelines[] = '/timelines/public'; | ||||
|         } | ||||
|  | ||||
|         $tags = $this->getNoticeTags($notice); | ||||
|  | ||||
|         if (!empty($tags)) { | ||||
|             foreach ($tags as $tag) { | ||||
|                 $timelines[] = '/timelines/tag/' . $tag; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (count($timelines) > 0) { | ||||
|             // Require this, since we need it | ||||
|             require_once(INSTALLDIR.'/plugins/Comet/bayeux.class.inc.php'); | ||||
|  | ||||
|             $json = $this->noticeAsJson($notice); | ||||
|  | ||||
|             // Bayeux? Comet? Huh? These terms confuse me | ||||
|             $bay = new Bayeux($this->server, $this->user, $this->password); | ||||
|  | ||||
|             foreach ($timelines as $timeline) { | ||||
|                 $this->log(LOG_INFO, "Posting notice $notice->id to '$timeline'."); | ||||
|                 $bay->publish($timeline, $json); | ||||
|             } | ||||
|  | ||||
|             $bay = NULL; | ||||
|         } | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     function noticeAsJson($notice) | ||||
|     { | ||||
|         // FIXME: this code should be abstracted to a neutral third | ||||
|         // party, like Notice::asJson(). I'm not sure of the ethics | ||||
|         // of refactoring from within a plugin, so I'm just abusing | ||||
|         // the TwitterApiAction method. Don't do this unless you're me! | ||||
|  | ||||
|         require_once(INSTALLDIR.'/lib/twitterapi.php'); | ||||
|  | ||||
|         $act = new TwitterApiAction('/dev/null'); | ||||
|  | ||||
|         $arr = $act->twitter_status_array($notice, true); | ||||
|         $arr['url'] = $notice->bestUrl(); | ||||
|         $arr['html'] = htmlspecialchars($notice->rendered); | ||||
|         $arr['source'] = htmlspecialchars($arr['source']); | ||||
|  | ||||
|         if (!empty($notice->reply_to)) { | ||||
|             $reply_to = Notice::staticGet('id', $notice->reply_to); | ||||
|             if (!empty($reply_to)) { | ||||
|                 $arr['in_reply_to_status_url'] = $reply_to->bestUrl(); | ||||
|             } | ||||
|             $reply_to = null; | ||||
|         } | ||||
|  | ||||
|         $profile = $notice->getProfile(); | ||||
|         $arr['user']['profile_url'] = $profile->profileurl; | ||||
|  | ||||
|         return $arr; | ||||
|     } | ||||
|  | ||||
|     function getNoticeTags($notice) | ||||
|     { | ||||
|         $tags = null; | ||||
|  | ||||
|         $nt = new Notice_tag(); | ||||
|         $nt->notice_id = $notice->id; | ||||
|  | ||||
|         if ($nt->find()) { | ||||
|             $tags = array(); | ||||
|             while ($nt->fetch()) { | ||||
|                 $tags[] = $nt->tag; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         $nt->free(); | ||||
|         $nt = null; | ||||
|  | ||||
|         return $tags; | ||||
|     } | ||||
|  | ||||
|     // Push this up to Plugin | ||||
|  | ||||
|     function log($level, $msg) | ||||
|     { | ||||
|         common_log($level, get_class($this) . ': '.$msg); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										26
									
								
								plugins/Orbited/README
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								plugins/Orbited/README
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| This is a plugin to automatically load notices in the browser no | ||||
| matter who creates them -- the kind of thing we see with | ||||
| search.twitter.com, rejaw.com, or FriendFeed's "real time" news. | ||||
|  | ||||
| NOTE: this is an insecure version; don't roll it out on a production | ||||
| server. | ||||
|  | ||||
| It requires a cometd server. I've only had the cometd-java server work | ||||
| correctly; something's wiggy with the Twisted-based server. | ||||
|  | ||||
| After you have a cometd server installed, just add this code to your | ||||
| config.php: | ||||
|  | ||||
|     require_once(INSTALLDIR.'/plugins/Comet/CometPlugin.php'); | ||||
|     $cp = new CometPlugin('http://example.com:8080/cometd/'); | ||||
|  | ||||
| Change 'example.com:8080' to the name and port of the server you | ||||
| installed cometd on. | ||||
|  | ||||
| TODO: | ||||
|  | ||||
| * Needs to be tested with Ajax submission. Probably messes everything | ||||
|   up. | ||||
| * Add more timelines: personal inbox and tags would be great. | ||||
| * Add security. In particular, only let the PHP code publish notices | ||||
|   to the cometd server. Currently, it doesn't try to authenticate. | ||||
							
								
								
									
										134
									
								
								plugins/Orbited/bayeux.class.inc.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								plugins/Orbited/bayeux.class.inc.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,134 @@ | ||||
| <?php | ||||
| /* | ||||
|  * | ||||
|  * Phomet: a php comet client | ||||
|  * | ||||
|  * Copyright (C) 2008 Morgan 'ARR!' Allen <morganrallen@gmail.com> http://morglog.alleycatracing.com | ||||
|  * | ||||
|  * This library is free software; you can redistribute it and/or | ||||
|  * modify it under the terms of the GNU Lesser General Public | ||||
|  * License as published by the Free Software Foundation; either | ||||
|  * version 2.1 of the License, or (at your option) any later version. | ||||
|  * | ||||
|  * This library is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|  * Lesser General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Lesser General Public | ||||
|  * License along with this library; if not, write to the Free Software | ||||
|  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
|  * | ||||
|  */ | ||||
|  | ||||
| class Bayeux | ||||
| { | ||||
|     private $oCurl = ''; | ||||
|     private $nNextId = 0; | ||||
|  | ||||
|     private $sUser = ''; | ||||
|     private $sPassword = ''; | ||||
|  | ||||
|     public $sUrl = ''; | ||||
|  | ||||
|     function __construct($sUrl, $sUser='', $sPassword='') | ||||
|     { | ||||
|         $this->sUrl = $sUrl; | ||||
|  | ||||
|         $this->oCurl = curl_init(); | ||||
|  | ||||
|         $aHeaders = array(); | ||||
|         $aHeaders[] = 'Connection: Keep-Alive'; | ||||
|  | ||||
|         curl_setopt($this->oCurl, CURLOPT_URL, $sUrl); | ||||
|         curl_setopt($this->oCurl, CURLOPT_HTTPHEADER, $aHeaders); | ||||
|         curl_setopt($this->oCurl, CURLOPT_HEADER, 0); | ||||
|         curl_setopt($this->oCurl, CURLOPT_POST, 1); | ||||
|         curl_setopt($this->oCurl, CURLOPT_RETURNTRANSFER,1); | ||||
|  | ||||
|         if (!is_null($sUser) && mb_strlen($sUser) > 0) { | ||||
|             curl_setopt($this->oCurl, CURLOPT_USERPWD,"$sUser:$sPassword"); | ||||
|         } | ||||
|  | ||||
|         $this->handShake(); | ||||
|     } | ||||
|  | ||||
|     function __destruct() | ||||
|     { | ||||
|         $this->disconnect(); | ||||
|     } | ||||
|  | ||||
|     function handShake() | ||||
|     { | ||||
|         $msgHandshake = array(); | ||||
|         $msgHandshake['channel'] = '/meta/handshake'; | ||||
|         $msgHandshake['version'] = "1.0"; | ||||
|         $msgHandshake['minimumVersion'] = "0.9"; | ||||
|         $msgHandshake['supportedConnectionTypes'] = array('long-polling'); | ||||
|         $msgHandshake['id'] = $this->nNextId++; | ||||
|  | ||||
|         curl_setopt($this->oCurl, CURLOPT_POSTFIELDS, "message=".urlencode(str_replace('\\', '', json_encode(array($msgHandshake))))); | ||||
|  | ||||
|         $data = curl_exec($this->oCurl); | ||||
|  | ||||
|         if(curl_errno($this->oCurl)) | ||||
|           die("Error: " . curl_error($this->oCurl)); | ||||
|  | ||||
|         $oReturn = json_decode($data); | ||||
|  | ||||
|         if (is_array($oReturn)) { | ||||
|             $oReturn = $oReturn[0]; | ||||
|         } | ||||
|  | ||||
|         $bSuccessful = ($oReturn->successful) ? true : false; | ||||
|  | ||||
|         if($bSuccessful) | ||||
|         { | ||||
|             $this->clientId = $oReturn->clientId; | ||||
|  | ||||
|             $this->connect(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public function connect() | ||||
|     { | ||||
|         $aMsg['channel'] = '/meta/connect'; | ||||
|         $aMsg['id'] = $this->nNextId++; | ||||
|         $aMsg['clientId'] = $this->clientId; | ||||
|         $aMsg['connectionType'] = 'long-polling'; | ||||
|  | ||||
|         curl_setopt($this->oCurl, CURLOPT_POSTFIELDS, "message=".urlencode(str_replace('\\', '', json_encode(array($aMsg))))); | ||||
|  | ||||
|         $data = curl_exec($this->oCurl); | ||||
|     } | ||||
|  | ||||
|     function disconnect() | ||||
|     { | ||||
|         $msgHandshake = array(); | ||||
|         $msgHandshake['channel'] = '/meta/disconnect'; | ||||
|         $msgHandshake['id'] = $this->nNextId++; | ||||
|         $msgHandshake['clientId'] = $this->clientId; | ||||
|  | ||||
|         curl_setopt($this->oCurl, CURLOPT_POSTFIELDS, "message=".urlencode(str_replace('\\', '', json_encode(array($msgHandshake))))); | ||||
|  | ||||
|         curl_exec($this->oCurl); | ||||
|     } | ||||
|  | ||||
|     public function publish($sChannel, $oData) | ||||
|     { | ||||
|         if(!$sChannel || !$oData) | ||||
|           return; | ||||
|  | ||||
|         $aMsg = array(); | ||||
|  | ||||
|         $aMsg['channel'] = $sChannel; | ||||
|         $aMsg['id'] = $this->nNextId++; | ||||
|         $aMsg['data'] = $oData; | ||||
|         $aMsg['clientId'] = $this->clientId; | ||||
|  | ||||
|         curl_setopt($this->oCurl, CURLOPT_POSTFIELDS, "message=".urlencode(str_replace('\\', '', json_encode(array($aMsg))))); | ||||
|  | ||||
|         $data = curl_exec($this->oCurl); | ||||
| //        var_dump($data); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										1451
									
								
								plugins/Orbited/jquery.comet.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1451
									
								
								plugins/Orbited/jquery.comet.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										478
									
								
								plugins/Orbited/json2.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										478
									
								
								plugins/Orbited/json2.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,478 @@ | ||||
| /* | ||||
|     http://www.JSON.org/json2.js | ||||
|     2009-04-16 | ||||
|  | ||||
|     Public Domain. | ||||
|  | ||||
|     NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. | ||||
|  | ||||
|     See http://www.JSON.org/js.html | ||||
|  | ||||
|     This file creates a global JSON object containing two methods: stringify | ||||
|     and parse. | ||||
|  | ||||
|         JSON.stringify(value, replacer, space) | ||||
|             value       any JavaScript value, usually an object or array. | ||||
|  | ||||
|             replacer    an optional parameter that determines how object | ||||
|                         values are stringified for objects. It can be a | ||||
|                         function or an array of strings. | ||||
|  | ||||
|             space       an optional parameter that specifies the indentation | ||||
|                         of nested structures. If it is omitted, the text will | ||||
|                         be packed without extra whitespace. If it is a number, | ||||
|                         it will specify the number of spaces to indent at each | ||||
|                         level. If it is a string (such as '\t' or ' '), | ||||
|                         it contains the characters used to indent at each level. | ||||
|  | ||||
|             This method produces a JSON text from a JavaScript value. | ||||
|  | ||||
|             When an object value is found, if the object contains a toJSON | ||||
|             method, its toJSON method will be called and the result will be | ||||
|             stringified. A toJSON method does not serialize: it returns the | ||||
|             value represented by the name/value pair that should be serialized, | ||||
|             or undefined if nothing should be serialized. The toJSON method | ||||
|             will be passed the key associated with the value, and this will be | ||||
|             bound to the object holding the key. | ||||
|  | ||||
|             For example, this would serialize Dates as ISO strings. | ||||
|  | ||||
|                 Date.prototype.toJSON = function (key) { | ||||
|                     function f(n) { | ||||
|                         // Format integers to have at least two digits. | ||||
|                         return n < 10 ? '0' + n : n; | ||||
|                     } | ||||
|  | ||||
|                     return this.getUTCFullYear()   + '-' + | ||||
|                          f(this.getUTCMonth() + 1) + '-' + | ||||
|                          f(this.getUTCDate())      + 'T' + | ||||
|                          f(this.getUTCHours())     + ':' + | ||||
|                          f(this.getUTCMinutes())   + ':' + | ||||
|                          f(this.getUTCSeconds())   + 'Z'; | ||||
|                 }; | ||||
|  | ||||
|             You can provide an optional replacer method. It will be passed the | ||||
|             key and value of each member, with this bound to the containing | ||||
|             object. The value that is returned from your method will be | ||||
|             serialized. If your method returns undefined, then the member will | ||||
|             be excluded from the serialization. | ||||
|  | ||||
|             If the replacer parameter is an array of strings, then it will be | ||||
|             used to select the members to be serialized. It filters the results | ||||
|             such that only members with keys listed in the replacer array are | ||||
|             stringified. | ||||
|  | ||||
|             Values that do not have JSON representations, such as undefined or | ||||
|             functions, will not be serialized. Such values in objects will be | ||||
|             dropped; in arrays they will be replaced with null. You can use | ||||
|             a replacer function to replace those with JSON values. | ||||
|             JSON.stringify(undefined) returns undefined. | ||||
|  | ||||
|             The optional space parameter produces a stringification of the | ||||
|             value that is filled with line breaks and indentation to make it | ||||
|             easier to read. | ||||
|  | ||||
|             If the space parameter is a non-empty string, then that string will | ||||
|             be used for indentation. If the space parameter is a number, then | ||||
|             the indentation will be that many spaces. | ||||
|  | ||||
|             Example: | ||||
|  | ||||
|             text = JSON.stringify(['e', {pluribus: 'unum'}]); | ||||
|             // text is '["e",{"pluribus":"unum"}]' | ||||
|  | ||||
|  | ||||
|             text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); | ||||
|             // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' | ||||
|  | ||||
|             text = JSON.stringify([new Date()], function (key, value) { | ||||
|                 return this[key] instanceof Date ? | ||||
|                     'Date(' + this[key] + ')' : value; | ||||
|             }); | ||||
|             // text is '["Date(---current time---)"]' | ||||
|  | ||||
|  | ||||
|         JSON.parse(text, reviver) | ||||
|             This method parses a JSON text to produce an object or array. | ||||
|             It can throw a SyntaxError exception. | ||||
|  | ||||
|             The optional reviver parameter is a function that can filter and | ||||
|             transform the results. It receives each of the keys and values, | ||||
|             and its return value is used instead of the original value. | ||||
|             If it returns what it received, then the structure is not modified. | ||||
|             If it returns undefined then the member is deleted. | ||||
|  | ||||
|             Example: | ||||
|  | ||||
|             // Parse the text. Values that look like ISO date strings will | ||||
|             // be converted to Date objects. | ||||
|  | ||||
|             myData = JSON.parse(text, function (key, value) { | ||||
|                 var a; | ||||
|                 if (typeof value === 'string') { | ||||
|                     a = | ||||
| /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); | ||||
|                     if (a) { | ||||
|                         return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], | ||||
|                             +a[5], +a[6])); | ||||
|                     } | ||||
|                 } | ||||
|                 return value; | ||||
|             }); | ||||
|  | ||||
|             myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { | ||||
|                 var d; | ||||
|                 if (typeof value === 'string' && | ||||
|                         value.slice(0, 5) === 'Date(' && | ||||
|                         value.slice(-1) === ')') { | ||||
|                     d = new Date(value.slice(5, -1)); | ||||
|                     if (d) { | ||||
|                         return d; | ||||
|                     } | ||||
|                 } | ||||
|                 return value; | ||||
|             }); | ||||
|  | ||||
|  | ||||
|     This is a reference implementation. You are free to copy, modify, or | ||||
|     redistribute. | ||||
|  | ||||
|     This code should be minified before deployment. | ||||
|     See http://javascript.crockford.com/jsmin.html | ||||
|  | ||||
|     USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO | ||||
|     NOT CONTROL. | ||||
| */ | ||||
|  | ||||
| /*jslint evil: true */ | ||||
|  | ||||
| /*global JSON */ | ||||
|  | ||||
| /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, | ||||
|     call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, | ||||
|     getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, | ||||
|     lastIndex, length, parse, prototype, push, replace, slice, stringify, | ||||
|     test, toJSON, toString, valueOf | ||||
| */ | ||||
|  | ||||
| // Create a JSON object only if one does not already exist. We create the | ||||
| // methods in a closure to avoid creating global variables. | ||||
|  | ||||
| if (!this.JSON) { | ||||
|     JSON = {}; | ||||
| } | ||||
| (function () { | ||||
|  | ||||
|     function f(n) { | ||||
|         // Format integers to have at least two digits. | ||||
|         return n < 10 ? '0' + n : n; | ||||
|     } | ||||
|  | ||||
|     if (typeof Date.prototype.toJSON !== 'function') { | ||||
|  | ||||
|         Date.prototype.toJSON = function (key) { | ||||
|  | ||||
|             return this.getUTCFullYear()   + '-' + | ||||
|                  f(this.getUTCMonth() + 1) + '-' + | ||||
|                  f(this.getUTCDate())      + 'T' + | ||||
|                  f(this.getUTCHours())     + ':' + | ||||
|                  f(this.getUTCMinutes())   + ':' + | ||||
|                  f(this.getUTCSeconds())   + 'Z'; | ||||
|         }; | ||||
|  | ||||
|         String.prototype.toJSON = | ||||
|         Number.prototype.toJSON = | ||||
|         Boolean.prototype.toJSON = function (key) { | ||||
|             return this.valueOf(); | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, | ||||
|         escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, | ||||
|         gap, | ||||
|         indent, | ||||
|         meta = {    // table of character substitutions | ||||
|             '\b': '\\b', | ||||
|             '\t': '\\t', | ||||
|             '\n': '\\n', | ||||
|             '\f': '\\f', | ||||
|             '\r': '\\r', | ||||
|             '"' : '\\"', | ||||
|             '\\': '\\\\' | ||||
|         }, | ||||
|         rep; | ||||
|  | ||||
|  | ||||
|     function quote(string) { | ||||
|  | ||||
| // If the string contains no control characters, no quote characters, and no | ||||
| // backslash characters, then we can safely slap some quotes around it. | ||||
| // Otherwise we must also replace the offending characters with safe escape | ||||
| // sequences. | ||||
|  | ||||
|         escapable.lastIndex = 0; | ||||
|         return escapable.test(string) ? | ||||
|             '"' + string.replace(escapable, function (a) { | ||||
|                 var c = meta[a]; | ||||
|                 return typeof c === 'string' ? c : | ||||
|                     '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); | ||||
|             }) + '"' : | ||||
|             '"' + string + '"'; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     function str(key, holder) { | ||||
|  | ||||
| // Produce a string from holder[key]. | ||||
|  | ||||
|         var i,          // The loop counter. | ||||
|             k,          // The member key. | ||||
|             v,          // The member value. | ||||
|             length, | ||||
|             mind = gap, | ||||
|             partial, | ||||
|             value = holder[key]; | ||||
|  | ||||
| // If the value has a toJSON method, call it to obtain a replacement value. | ||||
|  | ||||
|         if (value && typeof value === 'object' && | ||||
|                 typeof value.toJSON === 'function') { | ||||
|             value = value.toJSON(key); | ||||
|         } | ||||
|  | ||||
| // If we were called with a replacer function, then call the replacer to | ||||
| // obtain a replacement value. | ||||
|  | ||||
|         if (typeof rep === 'function') { | ||||
|             value = rep.call(holder, key, value); | ||||
|         } | ||||
|  | ||||
| // What happens next depends on the value's type. | ||||
|  | ||||
|         switch (typeof value) { | ||||
|         case 'string': | ||||
|             return quote(value); | ||||
|  | ||||
|         case 'number': | ||||
|  | ||||
| // JSON numbers must be finite. Encode non-finite numbers as null. | ||||
|  | ||||
|             return isFinite(value) ? String(value) : 'null'; | ||||
|  | ||||
|         case 'boolean': | ||||
|         case 'null': | ||||
|  | ||||
| // If the value is a boolean or null, convert it to a string. Note: | ||||
| // typeof null does not produce 'null'. The case is included here in | ||||
| // the remote chance that this gets fixed someday. | ||||
|  | ||||
|             return String(value); | ||||
|  | ||||
| // If the type is 'object', we might be dealing with an object or an array or | ||||
| // null. | ||||
|  | ||||
|         case 'object': | ||||
|  | ||||
| // Due to a specification blunder in ECMAScript, typeof null is 'object', | ||||
| // so watch out for that case. | ||||
|  | ||||
|             if (!value) { | ||||
|                 return 'null'; | ||||
|             } | ||||
|  | ||||
| // Make an array to hold the partial results of stringifying this object value. | ||||
|  | ||||
|             gap += indent; | ||||
|             partial = []; | ||||
|  | ||||
| // Is the value an array? | ||||
|  | ||||
|             if (Object.prototype.toString.apply(value) === '[object Array]') { | ||||
|  | ||||
| // The value is an array. Stringify every element. Use null as a placeholder | ||||
| // for non-JSON values. | ||||
|  | ||||
|                 length = value.length; | ||||
|                 for (i = 0; i < length; i += 1) { | ||||
|                     partial[i] = str(i, value) || 'null'; | ||||
|                 } | ||||
|  | ||||
| // Join all of the elements together, separated with commas, and wrap them in | ||||
| // brackets. | ||||
|  | ||||
|                 v = partial.length === 0 ? '[]' : | ||||
|                     gap ? '[\n' + gap + | ||||
|                             partial.join(',\n' + gap) + '\n' + | ||||
|                                 mind + ']' : | ||||
|                           '[' + partial.join(',') + ']'; | ||||
|                 gap = mind; | ||||
|                 return v; | ||||
|             } | ||||
|  | ||||
| // If the replacer is an array, use it to select the members to be stringified. | ||||
|  | ||||
|             if (rep && typeof rep === 'object') { | ||||
|                 length = rep.length; | ||||
|                 for (i = 0; i < length; i += 1) { | ||||
|                     k = rep[i]; | ||||
|                     if (typeof k === 'string') { | ||||
|                         v = str(k, value); | ||||
|                         if (v) { | ||||
|                             partial.push(quote(k) + (gap ? ': ' : ':') + v); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } else { | ||||
|  | ||||
| // Otherwise, iterate through all of the keys in the object. | ||||
|  | ||||
|                 for (k in value) { | ||||
|                     if (Object.hasOwnProperty.call(value, k)) { | ||||
|                         v = str(k, value); | ||||
|                         if (v) { | ||||
|                             partial.push(quote(k) + (gap ? ': ' : ':') + v); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
| // Join all of the member texts together, separated with commas, | ||||
| // and wrap them in braces. | ||||
|  | ||||
|             v = partial.length === 0 ? '{}' : | ||||
|                 gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + | ||||
|                         mind + '}' : '{' + partial.join(',') + '}'; | ||||
|             gap = mind; | ||||
|             return v; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| // If the JSON object does not yet have a stringify method, give it one. | ||||
|  | ||||
|     if (typeof JSON.stringify !== 'function') { | ||||
|         JSON.stringify = function (value, replacer, space) { | ||||
|  | ||||
| // The stringify method takes a value and an optional replacer, and an optional | ||||
| // space parameter, and returns a JSON text. The replacer can be a function | ||||
| // that can replace values, or an array of strings that will select the keys. | ||||
| // A default replacer method can be provided. Use of the space parameter can | ||||
| // produce text that is more easily readable. | ||||
|  | ||||
|             var i; | ||||
|             gap = ''; | ||||
|             indent = ''; | ||||
|  | ||||
| // If the space parameter is a number, make an indent string containing that | ||||
| // many spaces. | ||||
|  | ||||
|             if (typeof space === 'number') { | ||||
|                 for (i = 0; i < space; i += 1) { | ||||
|                     indent += ' '; | ||||
|                 } | ||||
|  | ||||
| // If the space parameter is a string, it will be used as the indent string. | ||||
|  | ||||
|             } else if (typeof space === 'string') { | ||||
|                 indent = space; | ||||
|             } | ||||
|  | ||||
| // If there is a replacer, it must be a function or an array. | ||||
| // Otherwise, throw an error. | ||||
|  | ||||
|             rep = replacer; | ||||
|             if (replacer && typeof replacer !== 'function' && | ||||
|                     (typeof replacer !== 'object' || | ||||
|                      typeof replacer.length !== 'number')) { | ||||
|                 throw new Error('JSON.stringify'); | ||||
|             } | ||||
|  | ||||
| // Make a fake root object containing our value under the key of ''. | ||||
| // Return the result of stringifying the value. | ||||
|  | ||||
|             return str('', {'': value}); | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|  | ||||
| // If the JSON object does not yet have a parse method, give it one. | ||||
|  | ||||
|     if (typeof JSON.parse !== 'function') { | ||||
|         JSON.parse = function (text, reviver) { | ||||
|  | ||||
| // The parse method takes a text and an optional reviver function, and returns | ||||
| // a JavaScript value if the text is a valid JSON text. | ||||
|  | ||||
|             var j; | ||||
|  | ||||
|             function walk(holder, key) { | ||||
|  | ||||
| // The walk method is used to recursively walk the resulting structure so | ||||
| // that modifications can be made. | ||||
|  | ||||
|                 var k, v, value = holder[key]; | ||||
|                 if (value && typeof value === 'object') { | ||||
|                     for (k in value) { | ||||
|                         if (Object.hasOwnProperty.call(value, k)) { | ||||
|                             v = walk(value, k); | ||||
|                             if (v !== undefined) { | ||||
|                                 value[k] = v; | ||||
|                             } else { | ||||
|                                 delete value[k]; | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 return reviver.call(holder, key, value); | ||||
|             } | ||||
|  | ||||
|  | ||||
| // Parsing happens in four stages. In the first stage, we replace certain | ||||
| // Unicode characters with escape sequences. JavaScript handles many characters | ||||
| // incorrectly, either silently deleting them, or treating them as line endings. | ||||
|  | ||||
|             cx.lastIndex = 0; | ||||
|             if (cx.test(text)) { | ||||
|                 text = text.replace(cx, function (a) { | ||||
|                     return '\\u' + | ||||
|                         ('0000' + a.charCodeAt(0).toString(16)).slice(-4); | ||||
|                 }); | ||||
|             } | ||||
|  | ||||
| // In the second stage, we run the text against regular expressions that look | ||||
| // for non-JSON patterns. We are especially concerned with '()' and 'new' | ||||
| // because they can cause invocation, and '=' because it can cause mutation. | ||||
| // But just to be safe, we want to reject all unexpected forms. | ||||
|  | ||||
| // We split the second stage into 4 regexp operations in order to work around | ||||
| // crippling inefficiencies in IE's and Safari's regexp engines. First we | ||||
| // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we | ||||
| // replace all simple value tokens with ']' characters. Third, we delete all | ||||
| // open brackets that follow a colon or comma or that begin the text. Finally, | ||||
| // we look to see that the remaining characters are only whitespace or ']' or | ||||
| // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. | ||||
|  | ||||
|             if (/^[\],:{}\s]*$/. | ||||
| test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'). | ||||
| replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'). | ||||
| replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { | ||||
|  | ||||
| // In the third stage we use the eval function to compile the text into a | ||||
| // JavaScript structure. The '{' operator is subject to a syntactic ambiguity | ||||
| // in JavaScript: it can begin a block or an object literal. We wrap the text | ||||
| // in parens to eliminate the ambiguity. | ||||
|  | ||||
|                 j = eval('(' + text + ')'); | ||||
|  | ||||
| // In the optional fourth stage, we recursively walk the new structure, passing | ||||
| // each name/value pair to a reviver function for possible transformation. | ||||
|  | ||||
|                 return typeof reviver === 'function' ? | ||||
|                     walk({'': j}, '') : j; | ||||
|             } | ||||
|  | ||||
| // If the text is not JSON parseable, then a SyntaxError is thrown. | ||||
|  | ||||
|             throw new SyntaxError('JSON.parse'); | ||||
|         }; | ||||
|     } | ||||
| }()); | ||||
							
								
								
									
										154
									
								
								plugins/Orbited/updatetimeline.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								plugins/Orbited/updatetimeline.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,154 @@ | ||||
| // update the local timeline from a Comet server | ||||
| // | ||||
|  | ||||
| var updater = function() | ||||
| { | ||||
|      var _server; | ||||
|      var _timeline; | ||||
|      var _userid; | ||||
|      var _replyurl; | ||||
|      var _favorurl; | ||||
|      var _deleteurl; | ||||
|      var _cometd; | ||||
|  | ||||
|      return { | ||||
|           init: function(server, timeline, userid, replyurl, favorurl, deleteurl) | ||||
|           { | ||||
|                _cometd = $.cometd; // Uses the default Comet object | ||||
|                _cometd.setLogLevel('debug'); | ||||
|                _cometd.init(server); | ||||
|                _server = server; | ||||
|                _timeline = timeline; | ||||
|                _userid = userid; | ||||
|                _favorurl = favorurl; | ||||
|                _replyurl = replyurl; | ||||
|                _deleteurl = deleteurl; | ||||
|                _cometd.subscribe(timeline, receive); | ||||
|                $(window).unload(leave); | ||||
|           } | ||||
|      } | ||||
|  | ||||
|      function leave() | ||||
|      { | ||||
|           _cometd.disconnect(); | ||||
|      } | ||||
|  | ||||
|      function receive(message) | ||||
|      { | ||||
|           id = message.data.id; | ||||
|  | ||||
|           // Don't add it if it already exists | ||||
|  | ||||
|           if ($("#notice-"+id).length > 0) { | ||||
|                return; | ||||
|           } | ||||
|  | ||||
|           var noticeItem = makeNoticeItem(message.data); | ||||
|           $("#notices_primary .notices").prepend(noticeItem, true); | ||||
|           $("#notices_primary .notice:first").css({display:"none"}); | ||||
|           $("#notices_primary .notice:first").fadeIn(1000); | ||||
|           NoticeHover(); | ||||
|           NoticeReply(); | ||||
|      } | ||||
|  | ||||
|      function makeNoticeItem(data) | ||||
|      { | ||||
|           user = data['user']; | ||||
|           html = data['html'].replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); | ||||
|           source = data['source'].replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); | ||||
|  | ||||
|           ni = "<li class=\"hentry notice\" id=\"notice-"+data['id']+"\">"+ | ||||
|                "<div class=\"entry-title\">"+ | ||||
|                "<span class=\"vcard author\">"+ | ||||
|                "<a href=\""+user['profile_url']+"\" class=\"url\">"+ | ||||
|                "<img src=\""+user['profile_image_url']+"\" class=\"avatar photo\" width=\"48\" height=\"48\" alt=\""+user['screen_name']+"\"/>"+ | ||||
|                "<span class=\"nickname fn\">"+user['screen_name']+"</span>"+ | ||||
|                "</a>"+ | ||||
|                "</span>"+ | ||||
|                "<p class=\"entry-content\">"+html+"</p>"+ | ||||
|                "</div>"+ | ||||
|                "<div class=\"entry-content\">"+ | ||||
|                "<dl class=\"timestamp\">"+ | ||||
|                "<dt>Published</dt>"+ | ||||
|                "<dd>"+ | ||||
|                "<a rel=\"bookmark\" href=\""+data['url']+"\" >"+ | ||||
|                "<abbr class=\"published\" title=\""+data['created_at']+"\">a few seconds ago</abbr>"+ | ||||
|                "</a> "+ | ||||
|                "</dd>"+ | ||||
|                "</dl>"+ | ||||
|                "<dl class=\"device\">"+ | ||||
|                "<dt>From</dt> "+ | ||||
|                "<dd>"+source+"</dd>"+ // may have a link, I think | ||||
|                "</dl>"; | ||||
|  | ||||
|           if (data['in_reply_to_status_id']) { | ||||
|                ni = ni+" <dl class=\"response\">"+ | ||||
|                     "<dt>To</dt>"+ | ||||
|                     "<dd>"+ | ||||
|                     "<a href=\""+data['in_reply_to_status_url']+"\" rel=\"in-reply-to\">in reply to</a>"+ | ||||
|                     "</dd>"+ | ||||
|                     "</dl>"; | ||||
|           } | ||||
|  | ||||
|           ni = ni+"</div>"+ | ||||
|                "<div class=\"notice-options\">"; | ||||
|  | ||||
|           if (_userid != 0) { | ||||
|                var input = $("form#form_notice fieldset input#token"); | ||||
|                var session_key = input.val(); | ||||
|                ni = ni+makeFavoriteForm(data['id'], session_key); | ||||
|                ni = ni+makeReplyLink(data['id'], data['user']['screen_name']); | ||||
|                if (_userid == data['user']['id']) { | ||||
|                     ni = ni+makeDeleteLink(data['id']); | ||||
|                } | ||||
|           } | ||||
|  | ||||
|           ni = ni+"</div>"+ | ||||
|                "</li>"; | ||||
|           return ni; | ||||
|      } | ||||
|  | ||||
|      function makeFavoriteForm(id, session_key) | ||||
|      { | ||||
|           var ff; | ||||
|  | ||||
|           ff = "<form id=\"favor-"+id+"\" class=\"form_favor\" method=\"post\" action=\""+_favorurl+"\">"+ | ||||
|                "<fieldset>"+ | ||||
|                "<legend>Favor this notice</legend>"+ // XXX: i18n | ||||
|                "<input name=\"token-"+id+"\" type=\"hidden\" id=\"token-"+id+"\" value=\""+session_key+"\"/>"+ | ||||
|                "<input name=\"notice\" type=\"hidden\" id=\"notice-n"+id+"\" value=\""+id+"\"/>"+ | ||||
|                "<input type=\"submit\" id=\"favor-submit-"+id+"\" name=\"favor-submit-"+id+"\" class=\"submit\" value=\"Favor\" title=\"Favor this notice\"/>"+ | ||||
|                "</fieldset>"+ | ||||
|                "</form>"; | ||||
|           return ff; | ||||
|      } | ||||
|  | ||||
|      function makeReplyLink(id, nickname) | ||||
|      { | ||||
|           var rl; | ||||
|           rl = "<dl class=\"notice_reply\">"+ | ||||
|                "<dt>Reply to this notice</dt>"+ | ||||
|                "<dd>"+ | ||||
|                "<a href=\""+_replyurl+"?replyto="+nickname+"\" title=\"Reply to this notice\">Reply <span class=\"notice_id\">"+id+"</span>"+ | ||||
|                "</a>"+ | ||||
|                "</dd>"+ | ||||
|                "</dl>"; | ||||
|           return rl; | ||||
|      } | ||||
|  | ||||
|      function makeDeleteLink(id) | ||||
|      { | ||||
|           var dl, delurl; | ||||
|           delurl = _deleteurl.replace("0000000000", id); | ||||
|  | ||||
|           dl = "<dl class=\"notice_delete\">"+ | ||||
|                "<dt>Delete this notice</dt>"+ | ||||
|                "<dd>"+ | ||||
|                "<a href=\""+delurl+"\" title=\"Delete this notice\">Delete</a>"+ | ||||
|                "</dd>"+ | ||||
|                "</dl>"; | ||||
|  | ||||
|           return dl; | ||||
|      } | ||||
| }(); | ||||
|  | ||||
		Reference in New Issue
	
	Block a user