PageSourceSearch

https://solid-stone.com/jquery.livetwitter.js

js solid-stone.com collected 2026-10-05 01:10:03 UTC 15,431 bytes, 397 lines download raw bytes

1/*
2 * jQuery LiveTwitter 1.7.3
3 * - Live updating Twitter plugin for jQuery
4 *
5 * Copyright (c) 2009-2011 Inge Jørgensen (elektronaut.no)
6 * Licensed under the MIT license (MIT-LICENSE.txt)
7 *
8 * $Date: 2011/10/28$
9 */
10
11/*jslint browser: true, devel: true, onevar: false, immed: false, regexp: false, indent: 2 */
12/*global window: false, jQuery: false */
13
14/*
15 * Usage example:
16 * $("#twitterSearch").liveTwitter('bacon', {limit: 10, rate: 15000});
17 */
18
19
20(function ($) {
21
22  // Extend jQuery with a reverse function if it isn't already defined
23  if (!$.fn.reverse) {
24    $.fn.reverse = function () {
25      return this.pushStack(this.get().reverse(), arguments);
26    };
27  }
28  
29  $.fn.liveTwitter = function (query, options, callback) {
30    var domNode = this;
31    $(this).each(function () {
32      var settings = {};
33
34      // Does this.twitter already exist? Let's just change the settings.
35      if (this.twitter) {
36        settings = $.extend(this.twitter.settings, options);
37        this.twitter.settings = settings;
38        if (query) {
39          this.twitter.query = query;
40        }
41        if (this.twitter.interval) {
42          this.twitter.refresh();
43        }
44        if (callback) {
45          this.twitter.callback = callback;
46        }
47
48      // ..if not, let's initialize.
49      } else {
50
51        // These are the default settings.
52        settings = $.extend({
53          mode:      'search', // Mode, valid options are: 'search', 'user_timeline', 'list', 'home_timeline'
54          rate:      15000,    // Refresh rate in ms
55          limit:     10,       // Limit number of results
56          imageSize: 24,       // Size of image in pixels
57          refresh:   true,
58          timeLinks: true,
59          replies:   true,
60          retweets:  false,
61          service:   false,
62          localization: {
63            seconds: 'seconds ago',
64            minute:  'a minute ago',
65            minutes: 'minutes ago',
66            hour:    'an hour ago',
67            hours:   'hours ago',
68            day:     'a day ago',
69            days:    'days ago'
70          }
71        }, options);
72        // showAuthor should default to true unless mode is 'user_timeline'.
73        if (typeof settings.showAuthor === "undefined") {
74          settings.showAuthor = (settings.mode === 'user_timeline') ? false : true;
75        }
76
77        // Set up a dummy function for the Twitter API callback.
78        if (!window.twitter_callback) {
79          window.twitter_callback = function () {
80            return true;
81          };
82        }
83
84        this.twitter = {
85          settings:      settings,
86          query:         query,
87          interval:      false,
88          container:     this,
89          lastTimeStamp: 0,
90          callback:      callback,
91
92          // Convert the time stamp to a more human readable format
93          relativeTime: function (timeString) {
94            var parsedDate = Date.parse(timeString);
95            var delta = (Date.parse(Date()) - parsedDate) / 1000;
96            var r = '';
97            if  (delta < 60) {
98              r = delta + " " + settings.localization.seconds;
99            } else if (delta < 120) {
100              r = settings.localization.minute;
101            } else if (delta < (45 * 60)) {
102              r = (parseInt(delta / 60, 10)).toString() + " " + settings.localization.minutes;
103            } else if (delta < (90 * 60)) {
104              r = settings.localization.hour;
105            } else if (delta < (24 * 60 * 60)) {
106              r = '' + (parseInt(delta / 3600, 10)).toString() + " " + settings.localization.hours;
107            } else if (delta < (48 * 60 * 60)) {
108              r = settings.localization.day;
109            } else {
110              r = (parseInt(delta / 86400, 10)).toString() + " " + settings.localization.days;
111            }
112            return r;
113          },
114
115          // Update the relative timestamps
116          updateTimestamps: function () {
117            var twitter = this;
118            $(twitter.container).find('span.time').each(function () {
119              var time_element = twitter.settings.timeLinks ? $(this).find('a') : $(this);
120              time_element.html(twitter.relativeTime(this.timeStamp));
121            });
122          },
123          
124          apiURL: function () {
125            var params = {};
126
127            var protocol = (window.location.protocol === 'https:') ? 'https:' : 'http:';
128            var baseURL  = 'api.twitter.com/1/';
129            var endpoint = '';
130            
131            // Override for Twitter-compatible APIs like Status.net
132            if (this.settings.service) {
133              baseURL = this.settings.service + '/api/';
134            }
135            
136            // Search mode
137            if (this.settings.mode === 'search') {
138              baseURL  = (this.settings.service) ? this.settings.service + '/api/' : 'search.twitter.com/';
139              endpoint = 'search';
140              params   = {
141                q:        (this.query && this.query !== '') ? this.query : null,
142                geocode:  this.settings.geocode,
143                lang:     this.settings.lang,
144                rpp:      (this.settings.rpp) ? this.settings.rpp : this.settings.limit
145              };
146              
147            // User/home timeline mode
148            } else if (this.settings.mode === 'user_timeline' || this.settings.mode === 'home_timeline') {
149              endpoint = 'statuses/' + this.settings.mode + '/' + encodeURIComponent(this.query);
150              params = {
151                count:           this.settings.limit,
152                include_rts:     (this.settings.mode === 'user_timeline' && this.settings.retweets) ? '1' : null,
153                exclude_replies: (!this.settings.replies) ? '1' : null
154              };
155
156            // Favorites mode
157            } else if (this.settings.mode === 'favorites') {
158              endpoint = 'favorites';
159              params = {
160                id:       encodeURIComponent(this.query)
161              };
162
163            // List mode
164            } else if (this.settings.mode === 'list') {
165              endpoint = encodeURIComponent(this.query.user) + 
166                         '/lists/' + 
167                         encodeURIComponent(this.query.list) + 
168                         '/statuses';
169              params = {
170                per_page: this.settings.limit
171              };
172            }
173
174            // Construct the query string
175            var queryString = [];
176            for (var param in params) {
177              if (params.hasOwnProperty(param) && typeof params[param] !== 'undefined' && params[param] !== null) {
178                queryString[queryString.length] = param + '=' + encodeURIComponent(params[param]);
179              }
180            }
181            queryString = queryString.join("&");
182
183            // Return the full URL
184            return protocol + '//' + baseURL + endpoint + '.json?' + queryString + '&callback=?';
185          },
186          
187          // The different APIs will format the results slightly different,
188          // so this method normalizes the tweet object.
189          parseTweet: function (json) {
190            var tweet = {
191              id:         (json.id_str) ? json.id_str : json.id,
192              text:       json.text,
193              created_at: json.created_at
194            };
195
196            // Search/regular API differences
197            if (this.settings.mode === 'search') {
198              tweet = $.extend(tweet, {
199                screen_name:       json.from_user,
200                profile_image_url: json.profile_image_url
201              });
202            } else {
203              tweet = $.extend(tweet, {
204                screen_name:       json.user.screen_name,
205                profile_image_url: json.user.profile_image_url,
206                created_at:        json.created_at.replace(/^(\w+)\s(\w+)\s(\d+)(.*)(\s\d+)$/, "$1, $3 $2$5$4") // Fix for IE
207              });
208            }
209            
210            // Twitter/Status.net
211            if (this.settings.service) {
212              tweet = $.extend(tweet, {
213                url:         'http://' + this.settings.service + '/notice/' + tweet.id,
214                profile_url: 'http://' + this.settings.service + '/' + json.from_user
215              });
216              if (window.location.protocol === 'https:') {
217                tweet.profile_image_url = tweet.profile_image_url.replace('http:', 'https:');
218              }
219
220            } else {
221              tweet = $.extend(tweet, {
222                url:         'http://twitter.com/#!/' + tweet.screen_name + '/status/' + tweet.id,
223                profile_url: 'http://twitter.com/#!/' + tweet.screen_name
224              });
225              // Someday, Twitter will add HTTPS support to twimg.com, but until then
226              // we have to rewrite the profile image urls to the old Amazon S3 urls.
227              if (window.location.protocol === 'https:') {
228                var matches = tweet.profile_image_url.match(/http[s]?:\/\/a[0-9]\.twimg\.com\/(\w+)\/(\w+)\/(.*?)\.(\w+)/i);
229                if (matches) {
230                  tweet.profile_image_url = "https://s3.amazonaws.com/twitter_production/" + matches[1] + "/" + matches[2] + "/" + matches[3] + "." + matches[4];
231                } else {
232                  // Failsafe, if profile image url does not match the pattern above
233                  // then, at least, change the protocol to HTTPS.
234                  // The image may not load, but at least the page stays secure.
235                  tweet.profile_image_url = tweet.profile_image_url.replace('http:', 'https:');
236                }
237              }
238            }
239            
240            return tweet;
241          },
242          
243          // Parses the tweet body, linking URLs, #hashtags and @usernames.
244          parseText: function (text) {
245            // URLs
246            text = text.replace(/[A-Za-z]+:\/\/[A-Za-z0-9-_]+\.[A-Za-z0-9-_:%&\?\/.=]+/g, function (m) {
247              return '<a href="' + m + '" rel="external">' + m + '</a>';
248            });
249
250            // Twitter
251            if (!this.settings.service) {
252              // @usernames
253              text = text.replace(/@[A-Za-z0-9_]+/g, function (u) {
254                return '<a href="http://twitter.com/#!/' + u.replace(/^@/, '') + '" rel="external">' + u + '</a>';
255              });
256              // #hashtags
257              text = text.replace(/#[A-Za-z0-9_\-]+/g, function (u) {
258                return '<a href="http://twitter.com/#!/search?q=' + u.replace(/^#/, '%23') + '" rel="external">' + u + '</a>';
259              });
260              
261            // Other APIs
262            } else {
263              text = text.replace(/@[A-Za-z0-9_]+/g, function (u) {
264                return '<a href="http://' + settings.service + '/' + u.replace(/^@/, '') + '" rel="external">' + u + '</a>';
265              });
266              text = text.replace(/#[A-Za-z0-9_\-]+/g, function (u) {
267                return '<a href="http://' + settings.service + '/search/notice?q?' + u.replace(/^#/, '%23') + '" rel="external">' + u + '</a>';
268              });
269            }
270            
271            return text;
272          },
273          
274          // Renders a tweet to HTML
275          renderTweet: function (tweet) {
276            var html = '<div class="tweet tweet-' + tweet.id + '">';
277
278            if (this.settings.showAuthor) {
279              html += '<img width="' + this.settings.imageSize + '" height="' + this.settings.imageSize + '" src="' + tweet.profile_image_url + '" />';
280              html += '<p class="text"><span class="username"><a href="' + tweet.profile_url + '" rel="external">' + tweet.screen_name + '</a>:</span> ';
281            } else {
282              html += '<p class="text"> ';
283            }
284
285            html += this.parseText(tweet.text);
286            
287            if (this.settings.timeLinks) {
288              html += ' <span class="time">';
289              html += '<a href="' + tweet.url + '" rel="external">';
290              html += this.relativeTime(tweet.created_at);
291              html += '</a></span>';
292            } else {
293              html += ' <span class="time">' + this.relativeTime(tweet.created_at) + '</span>';
294            }
295
296            html += '</p></div>';
297
298            return html;
299          },
300
301          // Handle reloading
302          refresh: function (initialize) {
303            var twitter = this;
304            if (twitter.settings.refresh || initialize) {
305
306              $.getJSON(twitter.apiURL(), function (json) {
307                var newTweets = 0;
308
309                // The search and regular APIs differ somewhat
310                var results = (twitter.settings.mode === 'search') ? json.results : json;
311
312                $(results).reverse().each(function () {
313                  var tweet = twitter.parseTweet(this);
314                  
315                  // Check if tweets should be filtered
316                  if (!twitter.settings.filter || twitter.settings.filter(this)) {
317                    // Check if this is actually a new tweet
318                    if (Date.parse(tweet.created_at) > twitter.lastTimeStamp) {
319
320                      // Insert the HTML
321                      $(twitter.container).prepend(twitter.renderTweet(tweet));
322
323                      // Make a note of the timestamp on the first span
324                      // so we can update it later.
325                      $(twitter.container).find('span.time:first').each(function () {
326                        this.timeStamp = tweet.created_at;
327                      });
328
329                      // Fade in new tweets unless this is the first load.
330                      if (!initialize) {
331                        $(twitter.container).find('.tweet-' + tweet.id).hide().fadeIn();
332                      }
333
334                      // Remember the last timestamp for the next refresh.
335                      twitter.lastTimeStamp = Date.parse(tweet.created_at);
336
337                      newTweets += 1;
338                    }
339                  }
340                });
341
342                // Did we get any new tweets?
343                if (newTweets > 0) {
344                  // Remove old entries exceeding the limit
345                  $(twitter.container).find('div.tweet:gt(' + (twitter.settings.limit - 1) + ')').remove();
346
347                  // Run callback
348                  if (twitter.callback) {
349                    twitter.callback(domNode, newTweets);
350                  }
351
352                  // Trigger event
353                  $(domNode).trigger('tweets');
354                }
355              });
356            }  
357          },
358
359          // Start refreshing
360          start: function () {
361            var twitter = this;
362            if (!this.interval) {
363              this.interval = setInterval(function () {
364                twitter.refresh();
365              }, twitter.settings.rate);
366              this.refresh(true);
367            }
368          },
369
370          // Stop refreshing
371          stop: function () {
372            if (this.interval) {
373              clearInterval(this.interval);
374              this.interval = false;
375            }
376          },
377
378          // Clear all tweets
379          clear: function () {
380            $(this.container).find('div.tweet').remove();
381            this.lastTimeStamp = null;
382          }
383        };
384
385        var twitter = this.twitter;
386
387        // Update the timestamps in realtime
388        this.timeInterval = setInterval(function () {
389          twitter.updateTimestamps();
390        }, 5000);
391
392        this.twitter.start();
393      }
394    });
395    return this;
396  };
397})(jQuery);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.