Created
February 16, 2011 23:03
-
-
Save e000/830484 to your computer and use it in GitHub Desktop.
an omegle wrapper for a connection using twisted, not really following their protocol spec, maybe I should o.O
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """ | |
| Omegle Twisted Client version 1.0 | |
| """ | |
| __author__ = "e" | |
| __version___ = "1.0" | |
| DISCONNECTED = 0 | |
| CONNECTING = 1 | |
| WAITING = 2 | |
| CONNECTED = 3 | |
| from random import choice | |
| from twisted.internet.defer import DeferredLock, inlineCallbacks, CancelledError, returnValue | |
| from urllib import urlencode | |
| from twisted.internet import reactor | |
| import re | |
| # zalgo dependancies. you can just drop in twisted.getPage, and simplejson's json_decode, utf8 is simply conversion of utf8 to byte array. | |
| from Core.ZalgoUtil.CancellableClient import getPage | |
| from Core.ZalgoUtil.ModUtil import json_decode, utf8 | |
| _userAgents = [ | |
| 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.10) Gecko/20100915 Ubuntu/10.04 (lucid) Firefox/3.6.10', | |
| 'Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.45 Safari/534.16' | |
| ] | |
| def getRandomUserAgent(): | |
| "Gives us a random user-agent to spoof with!" | |
| return choice(_userAgents) | |
| class AlreadyRunningError(Exception): | |
| pass | |
| class NotConnectedError(Exception): | |
| pass | |
| class CaptchaNotRequired(Exception): | |
| pass | |
| class SendError(Exception): | |
| pass | |
| class OmegleBot(): | |
| DISCONNECTED = 0 | |
| CONNECTING = 1 | |
| WAITING = 2 | |
| CONNECTED = 3 | |
| _serverRegex = re.compile('\<i?frame src="(.*?)"\>') | |
| _captchaImageRegex = re.compile('\<center\>\<img width="\d+" height="\d+" alt="" src="image\?c\=(.*?)"\>\<\/center\>') | |
| def __init__(self, typingCallback = None, stoppedTypingCallback = None, | |
| disconnectCallback = None, messageCallback = None, | |
| recaptchaFailedCallback = None, recaptchaRequiredCallback = None, | |
| connectCallback = None, waitingCallback = None): | |
| """ | |
| Initializes an L{OmegleBot} | |
| @param typingCallback: a callable that will fire when the user connected is typing | |
| @param stoppedTypingCallback: a callable that will fire when the user connected is no longer typing | |
| @param disconnectCallback: a callable that will fire when the user has disconnected, or the bot has disconnected from the user | |
| @param messageCallback: a callable that will fire when the user has sent us a message | |
| @param recaptchaFailedCallback: a callable that will fire when our submitted captcha fails | |
| @param recaptchaRequiredCallback: a callable that will fire when Omegle requires us to submit a captcha to connect | |
| @param connectCallback: a callable that will fire when we have found a partner | |
| @param waitingCallback: a callable that will fire when we are waiting for a partner | |
| """ | |
| self.typingCallback = typingCallback | |
| self.stoppedTypingCallback = stoppedTypingCallback | |
| self.disconnectCallback = disconnectCallback | |
| self.messageCallback = messageCallback | |
| self.recaptchaFailedCallback = recaptchaFailedCallback | |
| self.recaptchaRequiredCallback = recaptchaRequiredCallback | |
| self.connectCallback = connectCallback | |
| self.waitingCallback = waitingCallback | |
| self.status = DISCONNECTED | |
| self.server = None | |
| self.id = None | |
| self.lock = DeferredLock() | |
| self.activeRequests = set() | |
| self.challenge = None | |
| self.image = None | |
| def disconnect(self): | |
| """ Disconnects from the Omegle Server if we're connected """ | |
| oldStatus = self.status | |
| if self.status in (WAITING, CONNECTED): | |
| self.getPage('disconnect', addToActive = False, data = {'id': self.id}).addErrback(lambda r: None) ## /dev/null it lol | |
| if self.status == DISCONNECTED: | |
| return | |
| self.status = DISCONNECTED | |
| self.id = None | |
| self.challenge = None | |
| self.server = None | |
| self._cancelAllRequests() | |
| self.onDisconnect() | |
| def _cancelAllRequests(self): | |
| """ | |
| kills all active connetions, i/o and empties the message queue | |
| """ | |
| self.lock.waiting[:] = [] | |
| for d in list(self.activeRequests): | |
| d.cancel() | |
| self.activeRequests.clear() | |
| def getPage(self, url, addToActive = True, data = None, *args, **kwargs): | |
| """ | |
| retrieves a page using the twisted getPage function, and if addToActive is true, will add to the tracked requests that will cancel if we disconnect | |
| """ | |
| def removeFromActive(r): | |
| self.activeRequests.discard(d) | |
| return r | |
| if not url.startswith('http://') and self.server: | |
| url = self.server + url | |
| if data is not None: | |
| data = urlencode(data) | |
| kwargs.update({ | |
| 'method': 'POST', | |
| 'postdata': data, | |
| 'headers': { | |
| 'Content-Type': 'application/x-www-form-urlencoded', | |
| 'Content-Length': '%i' % len(data) | |
| } | |
| }) | |
| d = getPage(url, agent = self.userAgent, *args, **kwargs) | |
| if addToActive: | |
| self.activeRequests.add(d) | |
| d.addBoth(removeFromActive) | |
| return d | |
| def say(self, message): | |
| """ | |
| send a message to the connected user | |
| raises NotConnectedError if we're not connected | |
| @param message: the message to send | |
| @type message: string, unicode | |
| """ | |
| if self.status != CONNECTED: | |
| raise NotConnectedError() | |
| def sentMessage(response): | |
| if response == 'win': | |
| return True | |
| else: | |
| raise SendError("Couldn't send message.") | |
| return self._doLockedCommand( | |
| 'send', data = {'id': self.id, 'msg': message} | |
| ).addCallback(sentMessage) | |
| def typing(self): | |
| """ | |
| tells the connected user that we're typing | |
| raises NotConnectedError if we're not connected | |
| """ | |
| if self.status != CONNECTED: | |
| raise NotConnectedError() | |
| self._doLockedCommand( | |
| 'typing', data = {'id': self.id} | |
| ) | |
| def stoppedTyping(self): | |
| """ | |
| tells the connected user that we're not typing anymore | |
| raises NotConnectedError if we're not connected | |
| """ | |
| if self.status != CONNECTED: | |
| raise NotConnectedError() | |
| self._doLockedCommand( | |
| 'stoppedtyping', data = {'id': self.id} | |
| ) | |
| def solveCaptcha(self, solution): | |
| """ | |
| attempts to solve the captcha that omegle sent to us. | |
| @param solution: the solution to the captcha | |
| @type solution: string | |
| """ | |
| if not self.challenge and self.image: | |
| raise CaptchaNotRequired() | |
| self.getPage('recaptcha', data = { | |
| 'id': self.id, | |
| 'response': solution, | |
| 'challenge': self.image | |
| }) | |
| self.image, self.challenge = None, None | |
| def _doLockedCommand(self, url, data): | |
| """ | |
| internal command that adds it to our DeferredLock queue, which will fire sequentially as they finish, allowing only one request to be processed at once | |
| """ | |
| l = self.lock.acquire() | |
| def gotLock(lock): | |
| if self.status == CONNECTED: | |
| def releaseLock(r): | |
| lock.release() | |
| return r | |
| d = self.getPage(url, data = data) | |
| d.addBoth(releaseLock) | |
| return d | |
| else: | |
| lock.release() | |
| return l.addCallback(gotLock) | |
| @inlineCallbacks | |
| def connect(self): | |
| """ | |
| attempts to connect to the Omegle server. | |
| returns a deferred that will fire when we've established a connection | |
| """ | |
| if self.status != DISCONNECTED: | |
| raise AlreadyRunningError() | |
| self.userAgent = getRandomUserAgent() | |
| self.status = CONNECTING | |
| homePage = yield self.getPage('http://omegle.com/') | |
| match = self._serverRegex.search(homePage) | |
| if not match: | |
| raise ValueError("Could not find a server to connect to!") | |
| else: | |
| self.server = match.group(1) | |
| id = yield self.getPage('start?rcs=1&spid=') | |
| self.id = json_decode(id) | |
| self.status = WAITING | |
| self.doEvents() | |
| returnValue((self.id, self.server)) | |
| def doEvents(self): | |
| """ | |
| main asynchronous io loop that handles events, and asks for more | |
| """ | |
| if self.status not in (CONNECTED, WAITING): | |
| return | |
| def gotEvents(response): | |
| events = json_decode(response) | |
| if events is None: | |
| self.disconnect() | |
| else: | |
| for event in events: | |
| event, params = event[0], event[1:] | |
| callback = getattr(self, 'EVENT_%s' % event, None) | |
| if callback: | |
| callback(params) | |
| self.doEvents() | |
| def gotError(error): | |
| if not isinstance(error.value, CancelledError): | |
| self.disconnect() | |
| self.onError(error) | |
| return self.getPage('events', data = { | |
| 'id': self.id | |
| }).addCallbacks(gotEvents, gotError) | |
| def EVENT_waiting(self, params): | |
| """ we received a waiting event """ | |
| self.status = WAITING | |
| self.runCallback(self.waitingCallback) | |
| def EVENT_connected(self, params): | |
| """ we're connected to a partner """ | |
| self.status = CONNECTED | |
| self.runCallback(self.connectCallback) | |
| def EVENT_gotMessage(self, params): | |
| """ partner sent us a message! """ | |
| self.runCallback(self.messageCallback, params) | |
| def EVENT_typing(self, params): | |
| """ partner is typing """ | |
| self.runCallback(self.typingCallback) | |
| def EVENT_stoppedTyping(self, params): | |
| """ partner stopped typing """ | |
| self.runCallback(self.stoppedTypingCallback) | |
| def EVENT_strangerDisconnected(self, params): | |
| """ partner disconnected """ | |
| self.disconnect() | |
| def doCaptcha(self, challenge): | |
| """ returns a deferred that will fire when we have the location of the captcha image """ | |
| def gotImage(r): | |
| self.image = r | |
| return r | |
| def error(error): | |
| self.onError(error) | |
| self.disconnect() | |
| return None | |
| d = self.getRecaptchaImage(challenge) | |
| d.addCallback(gotImage).addErrback(error) | |
| return d | |
| @inlineCallbacks | |
| def getRecaptchaImage(self, key): | |
| """ try and find the image to solve """ | |
| pg = yield self.getPage('http://www.google.com/recaptcha/api/noscript?%s' % urlencode({'k': key}), headers = { | |
| 'referer': 'http://www.omegle.com/' | |
| }) | |
| match = self._captchaImageRegex.search(pg) | |
| if match: | |
| returnValue(match.group(1)) | |
| else: | |
| raise ValueError("Could not find the image!") | |
| def EVENT_recaptchaRequired(self, params): | |
| """ omegle says we need a captcha to connect """ | |
| #params = challenge, | |
| self.challenge = params[0] | |
| params.append(self.doCaptcha(self.challenge)) | |
| self.runCallback(self.recaptchaRequiredCallback, params) | |
| def EVENT_recaptchaRejected(self, params): | |
| """ omegle says that our captcha was wrong! """ | |
| self.challenge = params[0] | |
| params.append(self.doCaptcha(self.challenge)) | |
| self.runCallback(self.recaptchaFailedCallback, params) | |
| def onDisconnect(self): | |
| """ we've disconnected """ | |
| self.runCallback(self.disconnectCallback) | |
| def onError(self, error): | |
| """ an error has happened! """ | |
| error.printBriefTraceback() | |
| def runCallback(self, callback, params = None): | |
| """ run our callback if it's set """ | |
| if callback is None: | |
| return | |
| try: | |
| callback(self, params) | |
| except: | |
| from twisted.python import failure | |
| failure.Failure().printBriefTraceback() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment