├── twitsocket
├── __init__.py
├── management
│ ├── __init__.py
│ └── commands
│ │ ├── __init__.py
│ │ ├── top_tweets.py
│ │ ├── lister.py
│ │ └── websockets.py
├── templatetags
│ ├── __init__.py
│ └── twitsocket_tags.py
├── templates
│ └── twitsocket
│ │ ├── count.html
│ │ ├── switch.html
│ │ ├── top_users.html
│ │ ├── flash_hack.html
│ │ ├── top_tweets.html
│ │ ├── tweets.html
│ │ └── websocket.html
├── static
│ └── twitsocket
│ │ ├── WebSocketMain.swf
│ │ ├── swfobject.js
│ │ ├── web_socket.js
│ │ └── FABridge.js
└── models.py
├── MANIFEST.in
├── setup.py
├── LICENCE
└── README.rst
/twitsocket/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/twitsocket/management/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/twitsocket/templatetags/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/twitsocket/management/commands/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/twitsocket/templates/twitsocket/count.html:
--------------------------------------------------------------------------------
1 | tweet{{ count|pluralize }}
2 |
--------------------------------------------------------------------------------
/twitsocket/static/twitsocket/WebSocketMain.swf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brutasse-archive/django-twitsocket/HEAD/twitsocket/static/twitsocket/WebSocketMain.swf
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include LICENCE README.rst
2 | include bin/websockets.py
3 | recursive-include twitsocket/media *
4 | recursive-include twitsocket/bin *
5 | recursive-include twitsocket/templates *
6 |
--------------------------------------------------------------------------------
/twitsocket/templates/twitsocket/switch.html:
--------------------------------------------------------------------------------
1 | showhide retweets
2 |
3 |
14 |
--------------------------------------------------------------------------------
/twitsocket/templates/twitsocket/top_users.html:
--------------------------------------------------------------------------------
1 |
{% for t in top_users %}
2 |
3 | {{ t.username }}
4 |
5 | {{ t.total_count }} tweet{{ t.total_count|pluralize }}
6 |
7 | {% endfor %}
8 |
--------------------------------------------------------------------------------
/twitsocket/templates/twitsocket/flash_hack.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
--------------------------------------------------------------------------------
/twitsocket/templates/twitsocket/top_tweets.html:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import os
3 | from distutils.core import setup
4 | from setuptools import find_packages
5 |
6 | def read(fname):
7 | return open(os.path.join(os.path.dirname(__file__), fname)).read()
8 |
9 | setup(
10 | name='django-twitsocket',
11 | version='0.1',
12 | author=u'Bruno Renie, Gautier Hayoun',
13 | author_email='bruno@renie.fr',
14 | packages=find_packages(),
15 | include_package_data=True,
16 | url='http://github.com/brutasse/django-twitsocket',
17 | license='BSD',
18 | description='A twitter wall / live stream for your conference / event / topic of interest, as a Django reusable app.',
19 | long_description=read('README.rst'),
20 | zip_safe=False,
21 | )
22 |
23 |
--------------------------------------------------------------------------------
/twitsocket/templatetags/twitsocket_tags.py:
--------------------------------------------------------------------------------
1 | from django import template
2 | from django.conf import settings
3 |
4 | from twitsocket.models import Tweet, Top, Flooder
5 |
6 | register = template.Library()
7 |
8 |
9 | @register.inclusion_tag('twitsocket/websocket.html')
10 | def websocket_client():
11 | return {'websocket_server': settings.WEBSOCKET_SERVER}
12 |
13 |
14 | @register.inclusion_tag('twitsocket/tweets.html')
15 | def render_tweets(count):
16 | count = int(count)
17 | return {'tweets': Tweet.objects.all()[:count]}
18 |
19 |
20 | @register.inclusion_tag('twitsocket/flash_hack.html')
21 | def flash_hack():
22 | return {'STATIC_URL': settings.STATIC_URL}
23 |
24 |
25 | @register.inclusion_tag('twitsocket/top_tweets.html')
26 | def top_tweets(count):
27 | count = int(count)
28 | return {'top_tweets': Top.objects.all()[:count]}
29 |
30 |
31 | @register.inclusion_tag('twitsocket/top_users.html')
32 | def top_users(count):
33 | count = int(count)
34 | return {'top_users': Flooder.objects.all()[:count]}
35 |
36 |
37 | @register.inclusion_tag('twitsocket/count.html')
38 | def count():
39 | return {'count': Tweet.objects.count()}
40 |
41 |
42 | @register.inclusion_tag('twitsocket/switch.html')
43 | def retweet_switch():
44 | return {}
45 |
--------------------------------------------------------------------------------
/twitsocket/templates/twitsocket/tweets.html:
--------------------------------------------------------------------------------
1 |
2 | Your browser doesn't seem to support WebSockets. Install Chromium if you want real-time updates, otherwise use the plain old refresh button.
3 |
4 |
5 | Failed to establish a WebSocket connection. Try to refresh the page...
6 |
7 |
8 | Error with the WebSocket connection. Try to refresh the page...
9 |
10 |
26 |
--------------------------------------------------------------------------------
/twitsocket/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from django.utils import simplejson as json
3 |
4 |
5 | class Tweet(models.Model):
6 | status_id = models.BigIntegerField()
7 | content = models.TextField(blank=True)
8 |
9 | class Meta:
10 | ordering = ('-status_id',)
11 |
12 | def get_content(self):
13 | return json.loads(self.content)
14 |
15 |
16 | class Top(models.Model):
17 | """Top tweets: tweets that are retweeted the most"""
18 | status_id = models.BigIntegerField()
19 | content = models.TextField(null=True)
20 | rt_count = models.IntegerField(default=0, db_index=True)
21 |
22 | def __unicode__(self):
23 | return u'%s, %s rts' % (self.status_id, self.rt_count)
24 |
25 | class Meta:
26 | ordering = ['-rt_count']
27 |
28 | def get_content(self):
29 | return json.loads(self.content)
30 |
31 |
32 | class Flooder(models.Model):
33 | """People who tweet a lot (too much?) about the topic"""
34 | username = models.CharField(max_length=255)
35 | profile_picture = models.CharField(max_length=1023, null=True)
36 | tweet_count = models.IntegerField(default=0)
37 | rt_count = models.IntegerField(default=0)
38 | total_count = models.IntegerField(default=0, db_index=True)
39 |
40 | def __unicode__(self):
41 | return u'%s, %s, %s, %s' % (self.username, self.tweet_count,
42 | self.rt_count, self.total_count)
43 |
44 | class Meta:
45 | ordering = ['-total_count']
46 |
--------------------------------------------------------------------------------
/LICENCE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2010 Bruno Renié and Gautier Hayoun
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification,
5 | are permitted provided that the following conditions are met:
6 |
7 | 1. Redistributions of source code must retain the above copyright notice,
8 | this list of conditions and the following disclaimer.
9 |
10 | 2. Redistributions in binary form must reproduce the above copyright
11 | notice, this list of conditions and the following disclaimer in the
12 | documentation and/or other materials provided with the distribution.
13 |
14 | 3. Neither the name of this project nor the names of its contributors may be
15 | used to endorse or promote products derived from this software without
16 | specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/twitsocket/management/commands/top_tweets.py:
--------------------------------------------------------------------------------
1 | from django.core.management.base import NoArgsCommand
2 |
3 | from twitsocket.models import Tweet, Top, Flooder
4 |
5 |
6 | class Command(NoArgsCommand):
7 |
8 | def handle_noargs(self, **options):
9 | top_tw = {}
10 | top_users = {}
11 |
12 | for tw in Tweet.objects.order_by('id'):
13 | content = tw.get_content()
14 | screen_name = content['user']['screen_name']
15 |
16 | if not screen_name in top_users:
17 | top_users[screen_name], created = Flooder.objects.get_or_create(username=screen_name)
18 | top_users[screen_name].tweet_count = 0
19 | top_users[screen_name].rt_count = 0
20 | top_users[screen_name].profile_picture = content['user']['profile_image_url']
21 |
22 | if 'retweeted_status' in content:
23 | top_users[screen_name].rt_count += 1
24 |
25 | # Top tweets
26 | rt_id = int(content['retweeted_status']['id'])
27 | if not rt_id in top_tw:
28 | top_tw[rt_id], created = Top.objects.get_or_create(status_id=rt_id)
29 | top_tw[rt_id].rt_count = 0
30 | top_tw[rt_id].rt_count = top_tw[rt_id].rt_count + 1
31 | if not top_tw[rt_id].content:
32 | top_tw[rt_id].content = tw.content
33 |
34 | else:
35 | top_users[screen_name].tweet_count += 1
36 |
37 | for u in top_users.values():
38 | u.total_count = u.tweet_count + u.rt_count
39 | u.save()
40 |
41 | for u in top_tw.values():
42 | u.save()
43 |
--------------------------------------------------------------------------------
/twitsocket/management/commands/lister.py:
--------------------------------------------------------------------------------
1 | import urllib2
2 | import urllib
3 | import time
4 |
5 | from django.conf import settings
6 | from django.core.management.base import NoArgsCommand
7 | from django.utils import simplejson as json
8 |
9 | import oauth2 as oauth
10 |
11 | from twitsocket.models import Tweet
12 |
13 | LIST_MEMBERS = 'https://api.twitter.com/1/%s/members.json' % settings.TWITTER_LIST
14 |
15 | def oauth_request(url, method='GET', params={}, data=None):
16 | qs = ''
17 | if method == 'GET':
18 | qs = '&'.join(['%s=%s' % (key, value) for key, value in params.items()])
19 | if qs:
20 | url += '?%s' % qs
21 | consumer = oauth.Consumer(secret=settings.CONSUMER_SECRET,
22 | key=settings.CONSUMER_KEY)
23 | token = oauth.Token(secret=settings.TOKEN_SECRET,
24 | key=settings.TOKEN_KEY)
25 | oparams = {
26 | 'oauth_version': '1.0',
27 | 'oauth_nonce': oauth.generate_nonce(),
28 | 'oauth_timestamp': int(time.time()),
29 | 'oauth_token': token.key,
30 | 'oauth_consumer_key': consumer.key,
31 | }
32 | if method == 'POST':
33 | oparams.update(params)
34 | req = oauth.Request(method=method, url=url, parameters=oparams)
35 | signature_method = oauth.SignatureMethod_HMAC_SHA1()
36 | req.sign_request(signature_method, consumer, token)
37 | if method == 'POST':
38 | return urllib2.Request(url, data, headers=req.to_header())
39 | return urllib2.Request(url, headers=req.to_header())
40 |
41 |
42 | class Command(NoArgsCommand):
43 | """Adds all the twitter users to the list"""
44 |
45 | def handle_noargs(self, **options):
46 | members = self.get_list_members()
47 | users = self.get_users(Tweet.objects.all())
48 | for u in users:
49 | if u not in members:
50 | print "Adding %s to list" % u
51 | self.add_to_list(u)
52 | time.sleep(1)
53 |
54 | def get_list_members(self):
55 | more_pages = True
56 | members = []
57 | cursor = -1
58 | while more_pages:
59 | request = oauth_request(LIST_MEMBERS, params={'cursor': cursor})
60 | data = urllib2.urlopen(request).read()
61 |
62 | payload = json.loads(data)
63 | cursor = payload['next_cursor']
64 | for user in payload['users']:
65 | members.append(user['id'])
66 | more_pages = len(payload['users']) == 20
67 | return members
68 |
69 | def get_users(self, queryset):
70 | users = []
71 | for tweet in queryset:
72 | content = tweet.get_content()
73 | if 'retweeted_status' in content:
74 | continue
75 | user_id = content['user']['id']
76 | if user_id not in users:
77 | users.append(user_id)
78 | return users
79 |
80 | def add_to_list(self, user_id):
81 | data = urllib.urlencode({'id': user_id})
82 | request = oauth_request(LIST_MEMBERS, method='POST',
83 | params={'id': user_id}, data=data)
84 | response = urllib2.urlopen(request).read()
85 |
--------------------------------------------------------------------------------
/twitsocket/templates/twitsocket/websocket.html:
--------------------------------------------------------------------------------
1 |
93 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | Twitsocket, twitter + WebSockets = ♥
2 | ====================================
3 |
4 | A twitter wall / live stream for your conference / event / topic of interest,
5 | integrated in your Django website.
6 |
7 | How it works
8 | ------------
9 |
10 | Twitsocket has 3 components:
11 |
12 | * A client for twitter's streaming API which you can use to track keywords and
13 | specific accounts.
14 |
15 | * A websocket server to broadcast new tweets to every connected client.
16 |
17 | * A tiny Django app to store the tweets in a DB and render them.
18 |
19 | Installation
20 | ------------
21 |
22 | ::
23 |
24 | pip install https://github.com/brutasse/django-twitsocket/tarball/master
25 |
26 | **Requirements**:
27 |
28 | * Python >=2.4
29 | * Django >= 1.0
30 | * python-oauth2
31 | * Jquery >= 1.4 enabled in your templates.
32 |
33 | Configuration
34 | -------------
35 |
36 | You need to register an OAuth consumer on twitter.com (read AND write if you
37 | want to maintain the Twitter list) and get a token for it (using ``tweepy``
38 | and `this script`_ for instance).
39 |
40 | .. _this script: https://gist.github.com/545143
41 |
42 | Add ``twitsocket`` to your ``INSTALLED_APPS``, run ``syncdb`` and add a few
43 | settings::
44 |
45 | CONSUMER_SECRET = 'your consumer secret'
46 | CONSUMER_KEY = 'your consumer key'
47 |
48 | TOKEN_SECRET = 'your token secret'
49 | TOKEN_KEY = 'your token key'
50 |
51 | # Optional, in case of spam
52 | BANNED_USERS = ('list', 'of', 'banned', 'users')
53 |
54 | TRACK_KEYWORDS = ('europython', 'python', 'java')
55 | TRACK_USERS = (15324940, 15389140) # Only IDs here
56 |
57 | # The WebSocket port is mandatory, no trailing slash.
58 | WEBSOCKET_SERVER = 'ws://ws.example.com:8888'
59 | WEB_SERVER = 'http://example.com'
60 |
61 | # The log file tells you when new clients and new tweets arrive.
62 | LOGFILE = '/path/to/logfile.log'
63 |
64 | Create a view somewhere that renders a template and add this to the content::
65 |
66 | {% load twitsocket_tags %}
67 |
68 | {% render_tweets 30 %}
69 |
70 | {% websocket_client %}
71 |
72 | Extra template tags
73 | -------------------
74 |
75 | The following template tags are available. They are not required but may be
76 | used to display extra information about the stream.
77 |
78 | * **count**: ``{% count %}`` will output the number of tweets saved in the
79 | database. The counter is automatically incremented when new tweets are
80 | received.
81 |
82 | * **top_users**: ``{% top_users %}`` will display the top ````
83 | users, people who are tweeting the most about the topic.
84 |
85 | * **top_tweets**: ``{% top_tweets %}`` will display the top ````
86 | tweets, ordered by number of retweets.
87 |
88 | * **retweet_switch**: ``{% retweet_switch %}`` will display a "show retweets" /
89 | "hide retweets" button if users want to see only original tweets. The user's
90 | preference is stored in the browser's localStorage and restored when the
91 | page is reloaded.
92 |
93 | .. note::
94 |
95 | For the ``top_users`` and ``top_tweets`` tags, you need to run the
96 | ``top_tweets`` management command every once a while to refreshed the
97 | cached values. It's up to you to decide on the frequency since the
98 | operation can be rather intensive with a large number of tweets.
99 |
100 | Running the websocket server
101 | ----------------------------
102 |
103 | ::
104 |
105 | ./manage.py websockets
106 |
107 | Ideally, you should put this line in a bash script and run it with supervisord
108 | to restart it automatically when Twitter cuts the connection.
109 |
110 | Bonus
111 | -----
112 |
113 | There is a ``lister`` management command which you can use to maintain a
114 | Twitter list of people tweeting about the subject you're tracking.
115 |
116 | If you want to use the command, create the list on twitter and add it to your
117 | settings (prepend you twitter username)::
118 |
119 | TWITTER_LIST = 'brutasse/europython2010'
120 |
121 | Running ``manage.py lister`` will add everyone to the list. Run it every once
122 | a while, Twitter has a rate-limit (and a max. of 500 ppl on a list). Note that
123 | only people who send an 'original' tweet (not a retweet) will appear on the
124 | list.
125 |
126 | Styling
127 | -------
128 |
129 | This example CSS code will add a nice look to your tweets (WTFPL, modify it to
130 | fit your needs)::
131 |
132 | #tweets {
133 | font: normal 18px Georgia, Times, serif; width: 600px;
134 | margin: 10px auto; padding: 10px;
135 | border: 1px solid #ccc; background-color: #eee;
136 | }
137 | #tweets a { text-decoration: none; color: #4096ee; }
138 | #tweets a.username { color: #73880a; font-weight: bold; }
139 | #tweets a:hover { text-decoration: underline; }
140 | #tweets .tweet { color: #444; }
141 | #tweets .tweet img {
142 | display: block; float: left;
143 | background-color: #fff; border: 1px solid #bbb;
144 | padding: 3px; margin-right: 10px;
145 | }
146 | #tweets .tweet p { margin: 0; padding: 0; float: left; width: 500px; }
147 | #tweets .clear {
148 | clear: both; border-bottom: 1px solid #ccc;
149 | margin-bottom: 10px; padding-bottom: 10px;
150 | font-size: 0.8em; color: #aaa;
151 | text-align: right; text-shadow: 0 1px 0 #fff;
152 | }
153 | #tweets .rt { color: #d01f3c; font-weight: bold; padding-right: 15px; }
154 | .notice {
155 | width: 610px; text-shadow: 0 1px 0 #fff;
156 | margin: 10px auto; background-color: #FFFFaa;
157 | padding: 5px; border: 1px solid #eecc55;
158 | color: #555; font-size: 0.8em;
159 | }
160 |
161 | The flash hack
162 | --------------
163 |
164 | As you may know, not all browsers support WebSockets. They are implemented in
165 | Safari, Chrome and Firefox 4. There is a clever hack involving Flash that
166 | implements WebSockets for older browsers. To enable it, simply use
167 | contrib.staticfiles (or django-staticfiles with django 1.2). Add it to your
168 | ``INSTALLED_APPS``, configure ``STATIC_ROOT`` and ``STATIC_URL`` and run
169 | ``manage.py collectstatic``.
170 |
171 | Then add to your ```` block (assuming you've loaded
172 | ``twitsocket_tags``)::
173 |
174 |
175 | ... whatever you have
176 | ...
177 | {% flash_hack %}
178 |
179 |
180 | Note that because of some cross-domain security concerns, the flash hack will
181 | only if the media files are served on the same domain name as the website
182 | itself. No media.example.com for serving static files.
183 |
184 | TODO
185 | ----
186 |
187 | * i18n for websocket error messages.
188 |
189 | * Try to decouple the Twitter consumer and the WebSocket server. Maybe with
190 | Redis and its Pub/Sub mechanism.
191 |
--------------------------------------------------------------------------------
/twitsocket/static/twitsocket/swfobject.js:
--------------------------------------------------------------------------------
1 | /* SWFObject v2.2
2 | is released under the MIT License
3 | */
4 | var swfobject=function(){var D="undefined",r="object",S="Shockwave Flash",W="ShockwaveFlash.ShockwaveFlash",q="application/x-shockwave-flash",R="SWFObjectExprInst",x="onreadystatechange",O=window,j=document,t=navigator,T=false,U=[h],o=[],N=[],I=[],l,Q,E,B,J=false,a=false,n,G,m=true,M=function(){var aa=typeof j.getElementById!=D&&typeof j.getElementsByTagName!=D&&typeof j.createElement!=D,ah=t.userAgent.toLowerCase(),Y=t.platform.toLowerCase(),ae=Y?/win/.test(Y):/win/.test(ah),ac=Y?/mac/.test(Y):/mac/.test(ah),af=/webkit/.test(ah)?parseFloat(ah.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,X=!+"\v1",ag=[0,0,0],ab=null;if(typeof t.plugins!=D&&typeof t.plugins[S]==r){ab=t.plugins[S].description;if(ab&&!(typeof t.mimeTypes!=D&&t.mimeTypes[q]&&!t.mimeTypes[q].enabledPlugin)){T=true;X=false;ab=ab.replace(/^.*\s+(\S+\s+\S+$)/,"$1");ag[0]=parseInt(ab.replace(/^(.*)\..*$/,"$1"),10);ag[1]=parseInt(ab.replace(/^.*\.(.*)\s.*$/,"$1"),10);ag[2]=/[a-zA-Z]/.test(ab)?parseInt(ab.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}}else{if(typeof O.ActiveXObject!=D){try{var ad=new ActiveXObject(W);if(ad){ab=ad.GetVariable("$version");if(ab){X=true;ab=ab.split(" ")[1].split(",");ag=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}}catch(Z){}}}return{w3:aa,pv:ag,wk:af,ie:X,win:ae,mac:ac}}(),k=function(){if(!M.w3){return}if((typeof j.readyState!=D&&j.readyState=="complete")||(typeof j.readyState==D&&(j.getElementsByTagName("body")[0]||j.body))){f()}if(!J){if(typeof j.addEventListener!=D){j.addEventListener("DOMContentLoaded",f,false)}if(M.ie&&M.win){j.attachEvent(x,function(){if(j.readyState=="complete"){j.detachEvent(x,arguments.callee);f()}});if(O==top){(function(){if(J){return}try{j.documentElement.doScroll("left")}catch(X){setTimeout(arguments.callee,0);return}f()})()}}if(M.wk){(function(){if(J){return}if(!/loaded|complete/.test(j.readyState)){setTimeout(arguments.callee,0);return}f()})()}s(f)}}();function f(){if(J){return}try{var Z=j.getElementsByTagName("body")[0].appendChild(C("span"));Z.parentNode.removeChild(Z)}catch(aa){return}J=true;var X=U.length;for(var Y=0;Y0){for(var af=0;af0){var ae=c(Y);if(ae){if(F(o[af].swfVersion)&&!(M.wk&&M.wk<312)){w(Y,true);if(ab){aa.success=true;aa.ref=z(Y);ab(aa)}}else{if(o[af].expressInstall&&A()){var ai={};ai.data=o[af].expressInstall;ai.width=ae.getAttribute("width")||"0";ai.height=ae.getAttribute("height")||"0";if(ae.getAttribute("class")){ai.styleclass=ae.getAttribute("class")}if(ae.getAttribute("align")){ai.align=ae.getAttribute("align")}var ah={};var X=ae.getElementsByTagName("param");var ac=X.length;for(var ad=0;ad'}}aa.outerHTML='";N[N.length]=ai.id;X=c(ai.id)}else{var Z=C(r);Z.setAttribute("type",q);for(var ac in ai){if(ai[ac]!=Object.prototype[ac]){if(ac.toLowerCase()=="styleclass"){Z.setAttribute("class",ai[ac])}else{if(ac.toLowerCase()!="classid"){Z.setAttribute(ac,ai[ac])}}}}for(var ab in ag){if(ag[ab]!=Object.prototype[ab]&&ab.toLowerCase()!="movie"){e(Z,ab,ag[ab])}}aa.parentNode.replaceChild(Z,aa);X=Z}}return X}function e(Z,X,Y){var aa=C("param");aa.setAttribute("name",X);aa.setAttribute("value",Y);Z.appendChild(aa)}function y(Y){var X=c(Y);if(X&&X.nodeName=="OBJECT"){if(M.ie&&M.win){X.style.display="none";(function(){if(X.readyState==4){b(Y)}else{setTimeout(arguments.callee,10)}})()}else{X.parentNode.removeChild(X)}}}function b(Z){var Y=c(Z);if(Y){for(var X in Y){if(typeof Y[X]=="function"){Y[X]=null}}Y.parentNode.removeChild(Y)}}function c(Z){var X=null;try{X=j.getElementById(Z)}catch(Y){}return X}function C(X){return j.createElement(X)}function i(Z,X,Y){Z.attachEvent(X,Y);I[I.length]=[Z,X,Y]}function F(Z){var Y=M.pv,X=Z.split(".");X[0]=parseInt(X[0],10);X[1]=parseInt(X[1],10)||0;X[2]=parseInt(X[2],10)||0;return(Y[0]>X[0]||(Y[0]==X[0]&&Y[1]>X[1])||(Y[0]==X[0]&&Y[1]==X[1]&&Y[2]>=X[2]))?true:false}function v(ac,Y,ad,ab){if(M.ie&&M.mac){return}var aa=j.getElementsByTagName("head")[0];if(!aa){return}var X=(ad&&typeof ad=="string")?ad:"screen";if(ab){n=null;G=null}if(!n||G!=X){var Z=C("style");Z.setAttribute("type","text/css");Z.setAttribute("media",X);n=aa.appendChild(Z);if(M.ie&&M.win&&typeof j.styleSheets!=D&&j.styleSheets.length>0){n=j.styleSheets[j.styleSheets.length-1]}G=X}if(M.ie&&M.win){if(n&&typeof n.addRule==r){n.addRule(ac,Y)}}else{if(n&&typeof j.createTextNode!=D){n.appendChild(j.createTextNode(ac+" {"+Y+"}"))}}}function w(Z,X){if(!m){return}var Y=X?"visible":"hidden";if(J&&c(Z)){c(Z).style.visibility=Y}else{v("#"+Z,"visibility:"+Y)}}function L(Y){var Z=/[\\\"<>\.;]/;var X=Z.exec(Y)!=null;return X&&typeof encodeURIComponent!=D?encodeURIComponent(Y):Y}var d=function(){if(M.ie&&M.win){window.attachEvent("onunload",function(){var ac=I.length;for(var ab=0;ab.?) #(?P[A-Za-z0-9_]+)(?P.?)")
19 | HASH_RE2 = re.compile(r"^#(?P[A-Za-z0-9_]+)(?P.?)")
20 | USERNAME_RE = re.compile(r"(?P.?)@(?P[A-Za-z0-9_]+)(?P.?)")
21 |
22 | logger = logging.getLogger('twitstream')
23 | handler = logging.FileHandler(settings.LOGFILE)
24 | formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
25 | handler.setFormatter(formatter)
26 | logger.addHandler(handler)
27 | logger.setLevel(logging.INFO)
28 |
29 | BANNED_USERS = getattr(settings, 'BANNED_USERS', ())
30 |
31 | POLICY_FILE = """
32 |
33 |
34 | """
35 |
36 |
37 | def get_oauth_request(url, consumer, token, extra_params):
38 | oparams = {
39 | 'oauth_version': '1.0',
40 | 'oauth_nonce': oauth.generate_nonce(),
41 | 'oauth_timestamp': int(time.time()),
42 | 'oauth_token': token.key,
43 | 'oauth_consumer_key': consumer.key,
44 | }
45 | oparams.update(extra_params)
46 |
47 | req = oauth.Request(method="POST", url=url, parameters=oparams)
48 | signature_method = oauth.SignatureMethod_HMAC_SHA1()
49 | req.sign_request(signature_method, consumer, token)
50 | return req
51 |
52 |
53 | class StreamClient(asyncore.dispatcher):
54 |
55 | def __init__(self, host, headers, body, server):
56 | asyncore.dispatcher.__init__(self)
57 | self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
58 | self.connect((host, 80))
59 | self.buffer = '\r\n'.join(headers) + '\r\n\r\n' + body + '\r\n\r\n'
60 | self.data = ''
61 | self.server = server
62 | logger.info("Twitter StreamClient listening.")
63 |
64 | def handle_connect(self):
65 | pass
66 |
67 | def handle_close(self):
68 | self.close()
69 | raise ValueError("Connection closed by remote host")
70 |
71 | def handle_read(self):
72 | buf = self.recv(8192)
73 | logger.info('Twitter: %s bytes' % len(buf))
74 | self.data += buf
75 | if not self.data.endswith('\r\n\r\n'):
76 | return
77 |
78 | data = self.data.split('\r\n')
79 |
80 | if len(data) > 4:
81 | data = data[-2:]
82 | content = data[1]
83 | if not content:
84 | self.data = ''
85 | return
86 |
87 | payload = json.loads(content)
88 |
89 | if type(payload) == int:
90 | self.data = ''
91 | return
92 |
93 | self.handle_json(payload)
94 | self.data = ''
95 |
96 | def writable(self):
97 | return len(self.buffer) > 0
98 |
99 | def write(self, data):
100 | self.buffer += data
101 |
102 | def handle_write(self):
103 | sent = self.send(self.buffer)
104 | self.buffer = self.buffer[sent:]
105 |
106 | def handle_json(self, payload):
107 | if payload['user']['screen_name'] in BANNED_USERS:
108 | return
109 |
110 | dt = datetime.datetime.strptime(payload['created_at'],
111 | '%a %b %d %H:%M:%S +0000 %Y')
112 | dt = dt + datetime.timedelta(hours=1)
113 | payload['created_at'] = dt.strftime('%a %b %d %H:%M:%S +0100 %Y')
114 |
115 | dumped = json.dumps(payload)
116 | tw = Tweet(status_id=payload['id'], content=dumped).save()
117 | self.server.send_to_clients(dumped)
118 |
119 |
120 | class WebSocket(asyncore.dispatcher):
121 | handshake_75 = """HTTP/1.1 101 Web Socket Protocol Handshake\r
122 | Upgrade: WebSocket\r
123 | Connection: Upgrade\r
124 | WebSocket-Origin: %(web_server)s\r
125 | WebSocket-Location: %(websocket_server)s/\r
126 | WebSocket-Protocol: sample""" + '\r\n\r\n'
127 |
128 | handshake_76 = """HTTP/1.1 101 Web Socket Protocol Handshake\r
129 | Upgrade: WebSocket\r
130 | Connection: Upgrade\r
131 | Sec-WebSocket-Origin: %(web_server)s\r
132 | Sec-WebSocket-Location: %(websocket_server)s/\r
133 | Sec-WebSocket-Protocol: sample""" + '\r\n\r\n'
134 |
135 | def __init__(self, web_server, websocket_server):
136 | params = {'web_server': web_server,
137 | 'websocket_server': websocket_server}
138 | self.handshake_75 = self.handshake_75 % params
139 | self.handshake_76 = self.handshake_76 % params
140 | port = int(websocket_server.split(':')[-1])
141 |
142 | asyncore.dispatcher.__init__(self)
143 | self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
144 | self.set_reuse_addr()
145 | self.bind(('0.0.0.0', port))
146 | self.listen(5)
147 |
148 | self.clients = set()
149 | logger.info("Websocket server ready")
150 |
151 | def handle_accept(self):
152 | sock, addr = self.accept()
153 | handshaken = False
154 | header = ''
155 | while handshaken == False:
156 | header += sock.recv(8192)
157 |
158 | if (not header.startswith(''):
164 | sock.send(POLICY_FILE)
165 | sock.close()
166 | return
167 |
168 | if len(header) - header.find('\r\n\r\n') == 12:
169 | # WebSockets 76
170 | handshake = self.handshake_76 + self.get_challenge(header)
171 | handshaken = True
172 | sock.send(handshake)
173 |
174 | elif header.find('\r\n\r\n') != -1:
175 | # WebSockets 75
176 | handshaken = True
177 | sock.send(self.handshake_75)
178 | handler = WebSocketHandler(self, sock)
179 |
180 | def send_to_clients(self, payload):
181 | if not self.clients:
182 | logger.info("Could have send something but no client")
183 | else:
184 | logger.info("Sending payload to %s clients" % len(self.clients))
185 | for client in self.clients:
186 | client.queue.append(payload)
187 |
188 | def get_challenge(self, header_string):
189 | headers = header_string.split('\r\n')
190 | header_dict = {}
191 | for h in [head for head in headers[1:] if ': ' in head]:
192 | key, value = h.split(': ', 1)
193 | header_dict[key] = value
194 |
195 | key_1 = header_dict['Sec-WebSocket-Key1']
196 | key_2 = header_dict['Sec-WebSocket-Key2']
197 | key_3 = header_string[-8:]
198 |
199 | def key_challenge(key):
200 | key_number = int(''.join([i for i in key if 47 < ord(i) < 58]))
201 | spaces = len([i for i in key if i == ' '])
202 | part = key_number / spaces
203 | return struct.pack('!I', part)
204 |
205 | challenge = key_challenge(key_1) + key_challenge(key_2) + key_3
206 | return md5.md5(challenge).digest()
207 |
208 |
209 | class WebSocketHandler(asyncore.dispatcher):
210 |
211 | def __init__(self, server, sock):
212 | asyncore.dispatcher.__init__(self, sock=sock)
213 | self.server = server
214 | self.server.clients.add(self)
215 | self.queue = []
216 | logger.info("New client connected, count: %s" % len(self.server.clients))
217 |
218 | def handle_read(self):
219 | data = self.recv(4096)
220 | logger.debug("Got some data: %s" % data)
221 |
222 | def handle_write(self):
223 | logger.debug("Handling write")
224 | if self.queue:
225 | message = self.queue.pop(0)
226 | self.send('\x00%s\xff' % message)
227 | else:
228 | logger.info("Nothing to write")
229 |
230 | def writable(self):
231 | return len(self.queue) > 0
232 |
233 | def handle_close(self):
234 | self.server.clients.remove(self)
235 | logger.info("Client quitting, count: %s" % len(self.server.clients))
236 | self.close()
237 |
238 |
239 | def dict_to_postdata(data_dict):
240 | body = ''
241 | for key, value in data_dict.items():
242 | data = '%s=%s' % (key, value)
243 | if body:
244 | body += '&' + data
245 | else:
246 | body = data
247 | return body
248 |
249 |
250 | def all_in_one_handler():
251 | keywords = getattr(settings, 'TRACK_KEYWORDS', ())
252 | user_ids = map(str, getattr(settings, 'TRACK_USERS', ()))
253 | if not keywords and not user_ids:
254 | raise ValueError("Set at least TRACK_KEYWORDS or TRACK_USERS "
255 | "in your settings")
256 | post_params = {}
257 | if keywords:
258 | post_params['track'] = ','.join(keywords)
259 | if user_ids:
260 | post_params['follow'] = ','.join(user_ids)
261 |
262 | body = dict_to_postdata(post_params)
263 |
264 | token = oauth.Token(secret=settings.TOKEN_SECRET,
265 | key=settings.TOKEN_KEY)
266 | consumer = oauth.Consumer(secret=settings.CONSUMER_SECRET,
267 | key=settings.CONSUMER_KEY)
268 | host = 'stream.twitter.com'
269 | path = '/1/statuses/filter.json'
270 | url = 'http://%s%s' % (host, path)
271 | request = get_oauth_request(url, consumer, token, post_params)
272 |
273 | headers = [
274 | 'POST %s HTTP/1.1' % path,
275 | 'Host: %s' % host,
276 | 'Authorization: %s' % request.to_header()['Authorization'],
277 | 'Content-Type: application/x-www-form-urlencoded',
278 | 'Content-Length: %s' % len(body),
279 | ]
280 |
281 | websocket_server = WebSocket(settings.WEB_SERVER,
282 | settings.WEBSOCKET_SERVER)
283 | twitter_client = StreamClient(host, headers, body, websocket_server)
284 | asyncore.loop()
285 |
286 |
287 | class Command(NoArgsCommand):
288 |
289 | def handle_noargs(self, **options):
290 | all_in_one_handler()
291 |
--------------------------------------------------------------------------------
/twitsocket/static/twitsocket/web_socket.js:
--------------------------------------------------------------------------------
1 | // Copyright: Hiroshi Ichikawa
2 | // License: New BSD License
3 | // Reference: http://dev.w3.org/html5/websockets/
4 | // Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol
5 |
6 | (function() {
7 |
8 | if (window.WebSocket) return;
9 |
10 | var console = window.console;
11 | if (!console) console = {log: function(){ }, error: function(){ }};
12 |
13 | function hasFlash() {
14 | if ('navigator' in window && 'plugins' in navigator && navigator.plugins['Shockwave Flash']) {
15 | return !!navigator.plugins['Shockwave Flash'].description;
16 | }
17 | if ('ActiveXObject' in window) {
18 | try {
19 | return !!new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
20 | } catch (e) {}
21 | }
22 | return false;
23 | }
24 |
25 | if (!hasFlash()) {
26 | console.error("Flash Player is not installed.");
27 | return;
28 | }
29 | console.log(location.protocol);
30 | if (location.protocol == "file:") {
31 | console.error(
32 | "web-socket-js doesn't work in file:///... URL (without special configuration). " +
33 | "Open the page via Web server i.e. http://...");
34 | }
35 |
36 | WebSocket = function(url, protocol, proxyHost, proxyPort, headers) {
37 | var self = this;
38 | self.readyState = WebSocket.CONNECTING;
39 | self.bufferedAmount = 0;
40 | // Uses setTimeout() to make sure __createFlash() runs after the caller sets ws.onopen etc.
41 | // Otherwise, when onopen fires immediately, onopen is called before it is set.
42 | setTimeout(function() {
43 | WebSocket.__addTask(function() {
44 | self.__createFlash(url, protocol, proxyHost, proxyPort, headers);
45 | });
46 | }, 1);
47 | }
48 |
49 | WebSocket.prototype.__createFlash = function(url, protocol, proxyHost, proxyPort, headers) {
50 | var self = this;
51 | self.__flash =
52 | WebSocket.__flash.create(url, protocol, proxyHost || null, proxyPort || 0, headers || null);
53 |
54 | self.__flash.addEventListener("open", function(fe) {
55 | try {
56 | self.readyState = self.__flash.getReadyState();
57 | if (self.__timer) clearInterval(self.__timer);
58 | if (window.opera) {
59 | // Workaround for weird behavior of Opera which sometimes drops events.
60 | self.__timer = setInterval(function () {
61 | self.__handleMessages();
62 | }, 500);
63 | }
64 | if (self.onopen) self.onopen();
65 | } catch (e) {
66 | console.error(e.toString());
67 | }
68 | });
69 |
70 | self.__flash.addEventListener("close", function(fe) {
71 | try {
72 | self.readyState = self.__flash.getReadyState();
73 | if (self.__timer) clearInterval(self.__timer);
74 | if (self.onclose) self.onclose();
75 | } catch (e) {
76 | console.error(e.toString());
77 | }
78 | });
79 |
80 | self.__flash.addEventListener("message", function() {
81 | try {
82 | self.__handleMessages();
83 | } catch (e) {
84 | console.error(e.toString());
85 | }
86 | });
87 |
88 | self.__flash.addEventListener("error", function(fe) {
89 | try {
90 | if (self.__timer) clearInterval(self.__timer);
91 | if (self.onerror) self.onerror();
92 | } catch (e) {
93 | console.error(e.toString());
94 | }
95 | });
96 |
97 | self.__flash.addEventListener("stateChange", function(fe) {
98 | try {
99 | self.readyState = self.__flash.getReadyState();
100 | self.bufferedAmount = fe.getBufferedAmount();
101 | } catch (e) {
102 | console.error(e.toString());
103 | }
104 | });
105 |
106 | //console.log("[WebSocket] Flash object is ready");
107 | };
108 |
109 | WebSocket.prototype.send = function(data) {
110 | if (this.__flash) {
111 | this.readyState = this.__flash.getReadyState();
112 | }
113 | if (!this.__flash || this.readyState == WebSocket.CONNECTING) {
114 | throw "INVALID_STATE_ERR: Web Socket connection has not been established";
115 | }
116 | var result = this.__flash.send(encodeURIComponent(data));
117 | if (result < 0) { // success
118 | return true;
119 | } else {
120 | this.bufferedAmount = result;
121 | return false;
122 | }
123 | };
124 |
125 | WebSocket.prototype.close = function() {
126 | if (!this.__flash) return;
127 | this.readyState = this.__flash.getReadyState();
128 | if (this.readyState != WebSocket.OPEN) return;
129 | this.__flash.close();
130 | // Sets/calls them manually here because Flash WebSocketConnection.close cannot fire events
131 | // which causes weird error:
132 | // > You are trying to call recursively into the Flash Player which is not allowed.
133 | this.readyState = WebSocket.CLOSED;
134 | if (this.__timer) clearInterval(this.__timer);
135 | if (this.onclose) this.onclose();
136 | };
137 |
138 | /**
139 | * Implementation of {@link DOM 2 EventTarget Interface}
140 | *
141 | * @param {string} type
142 | * @param {function} listener
143 | * @param {boolean} useCapture !NB Not implemented yet
144 | * @return void
145 | */
146 | WebSocket.prototype.addEventListener = function(type, listener, useCapture) {
147 | if (!('__events' in this)) {
148 | this.__events = {};
149 | }
150 | if (!(type in this.__events)) {
151 | this.__events[type] = [];
152 | if ('function' == typeof this['on' + type]) {
153 | this.__events[type].defaultHandler = this['on' + type];
154 | this['on' + type] = this.__createEventHandler(this, type);
155 | }
156 | }
157 | this.__events[type].push(listener);
158 | };
159 |
160 | /**
161 | * Implementation of {@link DOM 2 EventTarget Interface}
162 | *
163 | * @param {string} type
164 | * @param {function} listener
165 | * @param {boolean} useCapture NB! Not implemented yet
166 | * @return void
167 | */
168 | WebSocket.prototype.removeEventListener = function(type, listener, useCapture) {
169 | if (!('__events' in this)) {
170 | this.__events = {};
171 | }
172 | if (!(type in this.__events)) return;
173 | for (var i = this.__events.length; i > -1; --i) {
174 | if (listener === this.__events[type][i]) {
175 | this.__events[type].splice(i, 1);
176 | break;
177 | }
178 | }
179 | };
180 |
181 | /**
182 | * Implementation of {@link DOM 2 EventTarget Interface}
183 | *
184 | * @param {WebSocketEvent} event
185 | * @return void
186 | */
187 | WebSocket.prototype.dispatchEvent = function(event) {
188 | if (!('__events' in this)) throw 'UNSPECIFIED_EVENT_TYPE_ERR';
189 | if (!(event.type in this.__events)) throw 'UNSPECIFIED_EVENT_TYPE_ERR';
190 |
191 | for (var i = 0, l = this.__events[event.type].length; i < l; ++ i) {
192 | this.__events[event.type][i](event);
193 | if (event.cancelBubble) break;
194 | }
195 |
196 | if (false !== event.returnValue &&
197 | 'function' == typeof this.__events[event.type].defaultHandler)
198 | {
199 | this.__events[event.type].defaultHandler(event);
200 | }
201 | };
202 |
203 | WebSocket.prototype.__handleMessages = function() {
204 | // Gets data using readSocketData() instead of getting it from event object
205 | // of Flash event. This is to make sure to keep message order.
206 | // It seems sometimes Flash events don't arrive in the same order as they are sent.
207 | var arr = this.__flash.readSocketData();
208 | for (var i = 0; i < arr.length; i++) {
209 | var data = decodeURIComponent(arr[i]);
210 | try {
211 | if (this.onmessage) {
212 | var e;
213 | if (window.MessageEvent) {
214 | e = document.createEvent("MessageEvent");
215 | e.initMessageEvent("message", false, false, data, null, null, window, null);
216 | } else { // IE
217 | e = {data: data};
218 | }
219 | this.onmessage(e);
220 | }
221 | } catch (e) {
222 | console.error(e.toString());
223 | }
224 | }
225 | };
226 |
227 | /**
228 | * @param {object} object
229 | * @param {string} type
230 | */
231 | WebSocket.prototype.__createEventHandler = function(object, type) {
232 | return function(data) {
233 | var event = new WebSocketEvent();
234 | event.initEvent(type, true, true);
235 | event.target = event.currentTarget = object;
236 | for (var key in data) {
237 | event[key] = data[key];
238 | }
239 | object.dispatchEvent(event, arguments);
240 | };
241 | }
242 |
243 | /**
244 | * Basic implementation of {@link DOM 2 EventInterface}
245 | *
246 | * @class
247 | * @constructor
248 | */
249 | function WebSocketEvent(){}
250 |
251 | /**
252 | *
253 | * @type boolean
254 | */
255 | WebSocketEvent.prototype.cancelable = true;
256 |
257 | /**
258 | *
259 | * @type boolean
260 | */
261 | WebSocketEvent.prototype.cancelBubble = false;
262 |
263 | /**
264 | *
265 | * @return void
266 | */
267 | WebSocketEvent.prototype.preventDefault = function() {
268 | if (this.cancelable) {
269 | this.returnValue = false;
270 | }
271 | };
272 |
273 | /**
274 | *
275 | * @return void
276 | */
277 | WebSocketEvent.prototype.stopPropagation = function() {
278 | this.cancelBubble = true;
279 | };
280 |
281 | /**
282 | *
283 | * @param {string} eventTypeArg
284 | * @param {boolean} canBubbleArg
285 | * @param {boolean} cancelableArg
286 | * @return void
287 | */
288 | WebSocketEvent.prototype.initEvent = function(eventTypeArg, canBubbleArg, cancelableArg) {
289 | this.type = eventTypeArg;
290 | this.cancelable = cancelableArg;
291 | this.timeStamp = new Date();
292 | };
293 |
294 |
295 | WebSocket.CONNECTING = 0;
296 | WebSocket.OPEN = 1;
297 | WebSocket.CLOSING = 2;
298 | WebSocket.CLOSED = 3;
299 |
300 | WebSocket.__tasks = [];
301 |
302 | WebSocket.__initialize = function() {
303 | if (WebSocket.__swfLocation) {
304 | // For backword compatibility.
305 | window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation;
306 | }
307 | if (!window.WEB_SOCKET_SWF_LOCATION) {
308 | console.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf");
309 | return;
310 | }
311 | var container = document.createElement("div");
312 | container.id = "webSocketContainer";
313 | // Puts the Flash out of the window. Note that we cannot use display: none or visibility: hidden
314 | // here because it prevents Flash from loading at least in IE.
315 | container.style.position = "absolute";
316 | container.style.left = "-100px";
317 | container.style.top = "-100px";
318 | var holder = document.createElement("div");
319 | holder.id = "webSocketFlash";
320 | container.appendChild(holder);
321 | document.body.appendChild(container);
322 | swfobject.embedSWF(
323 | WEB_SOCKET_SWF_LOCATION, "webSocketFlash", "8", "8", "9.0.0",
324 | null, {bridgeName: "webSocket"}, null, null,
325 | function(e) {
326 | if (!e.success) console.error("[WebSocket] swfobject.embedSWF failed");
327 | }
328 | );
329 | FABridge.addInitializationCallback("webSocket", function() {
330 | try {
331 | //console.log("[WebSocket] FABridge initializad");
332 | WebSocket.__flash = FABridge.webSocket.root();
333 | WebSocket.__flash.setCallerUrl(location.href);
334 | WebSocket.__flash.setDebug(!!window.WEB_SOCKET_DEBUG);
335 | for (var i = 0; i < WebSocket.__tasks.length; ++i) {
336 | WebSocket.__tasks[i]();
337 | }
338 | WebSocket.__tasks = [];
339 | } catch (e) {
340 | console.error("[WebSocket] " + e.toString());
341 | }
342 | });
343 | };
344 |
345 | WebSocket.__addTask = function(task) {
346 | if (WebSocket.__flash) {
347 | task();
348 | } else {
349 | WebSocket.__tasks.push(task);
350 | }
351 | }
352 |
353 | // called from Flash
354 | window.webSocketLog = function(message) {
355 | console.log(decodeURIComponent(message));
356 | };
357 |
358 | // called from Flash
359 | window.webSocketError = function(message) {
360 | console.error(decodeURIComponent(message));
361 | };
362 |
363 | if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) {
364 | if (window.addEventListener) {
365 | window.addEventListener("load", WebSocket.__initialize, false);
366 | } else {
367 | window.attachEvent("onload", WebSocket.__initialize);
368 | }
369 | }
370 |
371 | })();
372 |
--------------------------------------------------------------------------------
/twitsocket/static/twitsocket/FABridge.js:
--------------------------------------------------------------------------------
1 | /*
2 | /*
3 | Copyright 2006 Adobe Systems Incorporated
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
6 | to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
7 | and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10 |
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
14 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
15 | OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16 |
17 | */
18 |
19 |
20 | /*
21 | * The Bridge class, responsible for navigating AS instances
22 | */
23 | function FABridge(target,bridgeName)
24 | {
25 | this.target = target;
26 | this.remoteTypeCache = {};
27 | this.remoteInstanceCache = {};
28 | this.remoteFunctionCache = {};
29 | this.localFunctionCache = {};
30 | this.bridgeID = FABridge.nextBridgeID++;
31 | this.name = bridgeName;
32 | this.nextLocalFuncID = 0;
33 | FABridge.instances[this.name] = this;
34 | FABridge.idMap[this.bridgeID] = this;
35 |
36 | return this;
37 | }
38 |
39 | // type codes for packed values
40 | FABridge.TYPE_ASINSTANCE = 1;
41 | FABridge.TYPE_ASFUNCTION = 2;
42 |
43 | FABridge.TYPE_JSFUNCTION = 3;
44 | FABridge.TYPE_ANONYMOUS = 4;
45 |
46 | FABridge.initCallbacks = {};
47 | FABridge.userTypes = {};
48 |
49 | FABridge.addToUserTypes = function()
50 | {
51 | for (var i = 0; i < arguments.length; i++)
52 | {
53 | FABridge.userTypes[arguments[i]] = {
54 | 'typeName': arguments[i],
55 | 'enriched': false
56 | };
57 | }
58 | }
59 |
60 | FABridge.argsToArray = function(args)
61 | {
62 | var result = [];
63 | for (var i = 0; i < args.length; i++)
64 | {
65 | result[i] = args[i];
66 | }
67 | return result;
68 | }
69 |
70 | function instanceFactory(objID)
71 | {
72 | this.fb_instance_id = objID;
73 | return this;
74 | }
75 |
76 | function FABridge__invokeJSFunction(args)
77 | {
78 | var funcID = args[0];
79 | var throughArgs = args.concat();//FABridge.argsToArray(arguments);
80 | throughArgs.shift();
81 |
82 | var bridge = FABridge.extractBridgeFromID(funcID);
83 | return bridge.invokeLocalFunction(funcID, throughArgs);
84 | }
85 |
86 | FABridge.addInitializationCallback = function(bridgeName, callback)
87 | {
88 | var inst = FABridge.instances[bridgeName];
89 | if (inst != undefined)
90 | {
91 | callback.call(inst);
92 | return;
93 | }
94 |
95 | var callbackList = FABridge.initCallbacks[bridgeName];
96 | if(callbackList == null)
97 | {
98 | FABridge.initCallbacks[bridgeName] = callbackList = [];
99 | }
100 |
101 | callbackList.push(callback);
102 | }
103 |
104 | // updated for changes to SWFObject2
105 | function FABridge__bridgeInitialized(bridgeName) {
106 | var objects = document.getElementsByTagName("object");
107 | var ol = objects.length;
108 | var activeObjects = [];
109 | if (ol > 0) {
110 | for (var i = 0; i < ol; i++) {
111 | if (typeof objects[i].SetVariable != "undefined") {
112 | activeObjects[activeObjects.length] = objects[i];
113 | }
114 | }
115 | }
116 | var embeds = document.getElementsByTagName("embed");
117 | var el = embeds.length;
118 | var activeEmbeds = [];
119 | if (el > 0) {
120 | for (var j = 0; j < el; j++) {
121 | if (typeof embeds[j].SetVariable != "undefined") {
122 | activeEmbeds[activeEmbeds.length] = embeds[j];
123 | }
124 | }
125 | }
126 | var aol = activeObjects.length;
127 | var ael = activeEmbeds.length;
128 | var searchStr = "bridgeName="+ bridgeName;
129 | if ((aol == 1 && !ael) || (aol == 1 && ael == 1)) {
130 | FABridge.attachBridge(activeObjects[0], bridgeName);
131 | }
132 | else if (ael == 1 && !aol) {
133 | FABridge.attachBridge(activeEmbeds[0], bridgeName);
134 | }
135 | else {
136 | var flash_found = false;
137 | if (aol > 1) {
138 | for (var k = 0; k < aol; k++) {
139 | var params = activeObjects[k].childNodes;
140 | for (var l = 0; l < params.length; l++) {
141 | var param = params[l];
142 | if (param.nodeType == 1 && param.tagName.toLowerCase() == "param" && param["name"].toLowerCase() == "flashvars" && param["value"].indexOf(searchStr) >= 0) {
143 | FABridge.attachBridge(activeObjects[k], bridgeName);
144 | flash_found = true;
145 | break;
146 | }
147 | }
148 | if (flash_found) {
149 | break;
150 | }
151 | }
152 | }
153 | if (!flash_found && ael > 1) {
154 | for (var m = 0; m < ael; m++) {
155 | var flashVars = activeEmbeds[m].attributes.getNamedItem("flashVars").nodeValue;
156 | if (flashVars.indexOf(searchStr) >= 0) {
157 | FABridge.attachBridge(activeEmbeds[m], bridgeName);
158 | break;
159 | }
160 | }
161 | }
162 | }
163 | return true;
164 | }
165 |
166 | // used to track multiple bridge instances, since callbacks from AS are global across the page.
167 |
168 | FABridge.nextBridgeID = 0;
169 | FABridge.instances = {};
170 | FABridge.idMap = {};
171 | FABridge.refCount = 0;
172 |
173 | FABridge.extractBridgeFromID = function(id)
174 | {
175 | var bridgeID = (id >> 16);
176 | return FABridge.idMap[bridgeID];
177 | }
178 |
179 | FABridge.attachBridge = function(instance, bridgeName)
180 | {
181 | var newBridgeInstance = new FABridge(instance, bridgeName);
182 |
183 | FABridge[bridgeName] = newBridgeInstance;
184 |
185 | /* FABridge[bridgeName] = function() {
186 | return newBridgeInstance.root();
187 | }
188 | */
189 | var callbacks = FABridge.initCallbacks[bridgeName];
190 | if (callbacks == null)
191 | {
192 | return;
193 | }
194 | for (var i = 0; i < callbacks.length; i++)
195 | {
196 | callbacks[i].call(newBridgeInstance);
197 | }
198 | delete FABridge.initCallbacks[bridgeName]
199 | }
200 |
201 | // some methods can't be proxied. You can use the explicit get,set, and call methods if necessary.
202 |
203 | FABridge.blockedMethods =
204 | {
205 | toString: true,
206 | get: true,
207 | set: true,
208 | call: true
209 | };
210 |
211 | FABridge.prototype =
212 | {
213 |
214 |
215 | // bootstrapping
216 |
217 | root: function()
218 | {
219 | return this.deserialize(this.target.getRoot());
220 | },
221 | //clears all of the AS objects in the cache maps
222 | releaseASObjects: function()
223 | {
224 | return this.target.releaseASObjects();
225 | },
226 | //clears a specific object in AS from the type maps
227 | releaseNamedASObject: function(value)
228 | {
229 | if(typeof(value) != "object")
230 | {
231 | return false;
232 | }
233 | else
234 | {
235 | var ret = this.target.releaseNamedASObject(value.fb_instance_id);
236 | return ret;
237 | }
238 | },
239 | //create a new AS Object
240 | create: function(className)
241 | {
242 | return this.deserialize(this.target.create(className));
243 | },
244 |
245 |
246 | // utilities
247 |
248 | makeID: function(token)
249 | {
250 | return (this.bridgeID << 16) + token;
251 | },
252 |
253 |
254 | // low level access to the flash object
255 |
256 | //get a named property from an AS object
257 | getPropertyFromAS: function(objRef, propName)
258 | {
259 | if (FABridge.refCount > 0)
260 | {
261 | throw new Error("You are trying to call recursively into the Flash Player which is not allowed. In most cases the JavaScript setTimeout function, can be used as a workaround.");
262 | }
263 | else
264 | {
265 | FABridge.refCount++;
266 | retVal = this.target.getPropFromAS(objRef, propName);
267 | retVal = this.handleError(retVal);
268 | FABridge.refCount--;
269 | return retVal;
270 | }
271 | },
272 | //set a named property on an AS object
273 | setPropertyInAS: function(objRef,propName, value)
274 | {
275 | if (FABridge.refCount > 0)
276 | {
277 | throw new Error("You are trying to call recursively into the Flash Player which is not allowed. In most cases the JavaScript setTimeout function, can be used as a workaround.");
278 | }
279 | else
280 | {
281 | FABridge.refCount++;
282 | retVal = this.target.setPropInAS(objRef,propName, this.serialize(value));
283 | retVal = this.handleError(retVal);
284 | FABridge.refCount--;
285 | return retVal;
286 | }
287 | },
288 |
289 | //call an AS function
290 | callASFunction: function(funcID, args)
291 | {
292 | if (FABridge.refCount > 0)
293 | {
294 | throw new Error("You are trying to call recursively into the Flash Player which is not allowed. In most cases the JavaScript setTimeout function, can be used as a workaround.");
295 | }
296 | else
297 | {
298 | FABridge.refCount++;
299 | retVal = this.target.invokeASFunction(funcID, this.serialize(args));
300 | retVal = this.handleError(retVal);
301 | FABridge.refCount--;
302 | return retVal;
303 | }
304 | },
305 | //call a method on an AS object
306 | callASMethod: function(objID, funcName, args)
307 | {
308 | if (FABridge.refCount > 0)
309 | {
310 | throw new Error("You are trying to call recursively into the Flash Player which is not allowed. In most cases the JavaScript setTimeout function, can be used as a workaround.");
311 | }
312 | else
313 | {
314 | FABridge.refCount++;
315 | args = this.serialize(args);
316 | retVal = this.target.invokeASMethod(objID, funcName, args);
317 | retVal = this.handleError(retVal);
318 | FABridge.refCount--;
319 | return retVal;
320 | }
321 | },
322 |
323 | // responders to remote calls from flash
324 |
325 | //callback from flash that executes a local JS function
326 | //used mostly when setting js functions as callbacks on events
327 | invokeLocalFunction: function(funcID, args)
328 | {
329 | var result;
330 | var func = this.localFunctionCache[funcID];
331 |
332 | if(func != undefined)
333 | {
334 | result = this.serialize(func.apply(null, this.deserialize(args)));
335 | }
336 |
337 | return result;
338 | },
339 |
340 | // Object Types and Proxies
341 |
342 | // accepts an object reference, returns a type object matching the obj reference.
343 | getTypeFromName: function(objTypeName)
344 | {
345 | return this.remoteTypeCache[objTypeName];
346 | },
347 | //create an AS proxy for the given object ID and type
348 | createProxy: function(objID, typeName)
349 | {
350 | var objType = this.getTypeFromName(typeName);
351 | instanceFactory.prototype = objType;
352 | var instance = new instanceFactory(objID);
353 | this.remoteInstanceCache[objID] = instance;
354 | return instance;
355 | },
356 | //return the proxy associated with the given object ID
357 | getProxy: function(objID)
358 | {
359 | return this.remoteInstanceCache[objID];
360 | },
361 |
362 | // accepts a type structure, returns a constructed type
363 | addTypeDataToCache: function(typeData)
364 | {
365 | newType = new ASProxy(this, typeData.name);
366 | var accessors = typeData.accessors;
367 | for (var i = 0; i < accessors.length; i++)
368 | {
369 | this.addPropertyToType(newType, accessors[i]);
370 | }
371 |
372 | var methods = typeData.methods;
373 | for (var i = 0; i < methods.length; i++)
374 | {
375 | if (FABridge.blockedMethods[methods[i]] == undefined)
376 | {
377 | this.addMethodToType(newType, methods[i]);
378 | }
379 | }
380 |
381 |
382 | this.remoteTypeCache[newType.typeName] = newType;
383 | return newType;
384 | },
385 |
386 | //add a property to a typename; used to define the properties that can be called on an AS proxied object
387 | addPropertyToType: function(ty, propName)
388 | {
389 | var c = propName.charAt(0);
390 | var setterName;
391 | var getterName;
392 | if(c >= "a" && c <= "z")
393 | {
394 | getterName = "get" + c.toUpperCase() + propName.substr(1);
395 | setterName = "set" + c.toUpperCase() + propName.substr(1);
396 | }
397 | else
398 | {
399 | getterName = "get" + propName;
400 | setterName = "set" + propName;
401 | }
402 | ty[setterName] = function(val)
403 | {
404 | this.bridge.setPropertyInAS(this.fb_instance_id, propName, val);
405 | }
406 | ty[getterName] = function()
407 | {
408 | return this.bridge.deserialize(this.bridge.getPropertyFromAS(this.fb_instance_id, propName));
409 | }
410 | },
411 |
412 | //add a method to a typename; used to define the methods that can be callefd on an AS proxied object
413 | addMethodToType: function(ty, methodName)
414 | {
415 | ty[methodName] = function()
416 | {
417 | return this.bridge.deserialize(this.bridge.callASMethod(this.fb_instance_id, methodName, FABridge.argsToArray(arguments)));
418 | }
419 | },
420 |
421 | // Function Proxies
422 |
423 | //returns the AS proxy for the specified function ID
424 | getFunctionProxy: function(funcID)
425 | {
426 | var bridge = this;
427 | if (this.remoteFunctionCache[funcID] == null)
428 | {
429 | this.remoteFunctionCache[funcID] = function()
430 | {
431 | bridge.callASFunction(funcID, FABridge.argsToArray(arguments));
432 | }
433 | }
434 | return this.remoteFunctionCache[funcID];
435 | },
436 |
437 | //reutrns the ID of the given function; if it doesnt exist it is created and added to the local cache
438 | getFunctionID: function(func)
439 | {
440 | if (func.__bridge_id__ == undefined)
441 | {
442 | func.__bridge_id__ = this.makeID(this.nextLocalFuncID++);
443 | this.localFunctionCache[func.__bridge_id__] = func;
444 | }
445 | return func.__bridge_id__;
446 | },
447 |
448 | // serialization / deserialization
449 |
450 | serialize: function(value)
451 | {
452 | var result = {};
453 |
454 | var t = typeof(value);
455 | //primitives are kept as such
456 | if (t == "number" || t == "string" || t == "boolean" || t == null || t == undefined)
457 | {
458 | result = value;
459 | }
460 | else if (value instanceof Array)
461 | {
462 | //arrays are serializesd recursively
463 | result = [];
464 | for (var i = 0; i < value.length; i++)
465 | {
466 | result[i] = this.serialize(value[i]);
467 | }
468 | }
469 | else if (t == "function")
470 | {
471 | //js functions are assigned an ID and stored in the local cache
472 | result.type = FABridge.TYPE_JSFUNCTION;
473 | result.value = this.getFunctionID(value);
474 | }
475 | else if (value instanceof ASProxy)
476 | {
477 | result.type = FABridge.TYPE_ASINSTANCE;
478 | result.value = value.fb_instance_id;
479 | }
480 | else
481 | {
482 | result.type = FABridge.TYPE_ANONYMOUS;
483 | result.value = value;
484 | }
485 |
486 | return result;
487 | },
488 |
489 | //on deserialization we always check the return for the specific error code that is used to marshall NPE's into JS errors
490 | // the unpacking is done by returning the value on each pachet for objects/arrays
491 | deserialize: function(packedValue)
492 | {
493 |
494 | var result;
495 |
496 | var t = typeof(packedValue);
497 | if (t == "number" || t == "string" || t == "boolean" || packedValue == null || packedValue == undefined)
498 | {
499 | result = this.handleError(packedValue);
500 | }
501 | else if (packedValue instanceof Array)
502 | {
503 | result = [];
504 | for (var i = 0; i < packedValue.length; i++)
505 | {
506 | result[i] = this.deserialize(packedValue[i]);
507 | }
508 | }
509 | else if (t == "object")
510 | {
511 | for(var i = 0; i < packedValue.newTypes.length; i++)
512 | {
513 | this.addTypeDataToCache(packedValue.newTypes[i]);
514 | }
515 | for (var aRefID in packedValue.newRefs)
516 | {
517 | this.createProxy(aRefID, packedValue.newRefs[aRefID]);
518 | }
519 | if (packedValue.type == FABridge.TYPE_PRIMITIVE)
520 | {
521 | result = packedValue.value;
522 | }
523 | else if (packedValue.type == FABridge.TYPE_ASFUNCTION)
524 | {
525 | result = this.getFunctionProxy(packedValue.value);
526 | }
527 | else if (packedValue.type == FABridge.TYPE_ASINSTANCE)
528 | {
529 | result = this.getProxy(packedValue.value);
530 | }
531 | else if (packedValue.type == FABridge.TYPE_ANONYMOUS)
532 | {
533 | result = packedValue.value;
534 | }
535 | }
536 | return result;
537 | },
538 | //increases the reference count for the given object
539 | addRef: function(obj)
540 | {
541 | this.target.incRef(obj.fb_instance_id);
542 | },
543 | //decrease the reference count for the given object and release it if needed
544 | release:function(obj)
545 | {
546 | this.target.releaseRef(obj.fb_instance_id);
547 | },
548 |
549 | // check the given value for the components of the hard-coded error code : __FLASHERROR
550 | // used to marshall NPE's into flash
551 |
552 | handleError: function(value)
553 | {
554 | if (typeof(value)=="string" && value.indexOf("__FLASHERROR")==0)
555 | {
556 | var myErrorMessage = value.split("||");
557 | if(FABridge.refCount > 0 )
558 | {
559 | FABridge.refCount--;
560 | }
561 | throw new Error(myErrorMessage[1]);
562 | return value;
563 | }
564 | else
565 | {
566 | return value;
567 | }
568 | }
569 | };
570 |
571 | // The root ASProxy class that facades a flash object
572 |
573 | ASProxy = function(bridge, typeName)
574 | {
575 | this.bridge = bridge;
576 | this.typeName = typeName;
577 | return this;
578 | };
579 | //methods available on each ASProxy object
580 | ASProxy.prototype =
581 | {
582 | get: function(propName)
583 | {
584 | return this.bridge.deserialize(this.bridge.getPropertyFromAS(this.fb_instance_id, propName));
585 | },
586 |
587 | set: function(propName, value)
588 | {
589 | this.bridge.setPropertyInAS(this.fb_instance_id, propName, value);
590 | },
591 |
592 | call: function(funcName, args)
593 | {
594 | this.bridge.callASMethod(this.fb_instance_id, funcName, args);
595 | },
596 |
597 | addRef: function() {
598 | this.bridge.addRef(this);
599 | },
600 |
601 | release: function() {
602 | this.bridge.release(this);
603 | }
604 | };
605 |
--------------------------------------------------------------------------------