diff --git a/bottools/methods.py b/bottools/methods.py new file mode 100644 index 0000000..a636ba1 --- /dev/null +++ b/bottools/methods.py @@ -0,0 +1,220 @@ +import ast, dbtools, html, io, logging, moviepy.editor, PIL.Image, random, setuptools, string, bottools.strings, telegram.ext, twitools, urllib.request, tweepy + +def getTwo(message): + try: + return twitools.twoBotHelper(message.chat_id) + except ValueError: + message.reply_text(bottools.strings.noauth) + except tweepy.error.TweepError as e: + raise + +def twoExceptions(e, message): + text = { + 32: bottools.strings.badToken, + 36: bottools.strings.selfSpam, + 64: bottools.strings.accountSuspended, + 88: bottools.strings.rateLimit, + 89: bottools.strings.badToken, + 99: bottools.strings.badToken, + 130: bottools.strings.overload, + 131: bottools.strings.twitterError, + 161: bottools.strings.followLimit, + 185: bottools.strings.tweetLimit, + 186: bottools.strings.longTweet, + 187: bottools.strings.dupTweet, + 205: bottools.strings.rateLimit, + 226: bottools.strings.automatedTweet, + 271: bottools.strings.selfMute, + 272: bottools.strings.notMuted, + 323: bottools.strings.multipleGIFs, + 326: bottools.strings.accountLocked, + 354: bottools.strings.longTweet + }.get(e.api_code) + + message.reply_text(text or bottools.strings.twoFail) + +# Actual methods: +# --------------- + +def start(bot, update): + update.message.reply_text(bottools.strings.start % (setuptools.botname(), setuptools.botname())) + +def fish(bot, update): + dbtools.dbHelper().addFish(update.message.chat_id) + update.message.reply_text(bottools.strings.fishThanks) + +def getTweetID(tlid, cid): + try: + db = dbtools.dbHelper() + db.executeQuery("SELECT tid FROM timelines WHERE nr = %i AND cid = %i;" % (int(tlid), int(cid))) + return db.getNext()[0] + except Exception: + raise ValueError("No such tweet in timeline") + +def toggleTweet(bot, update): + try: + update.message.reply_text(bottools.strings.toggleTweet % (bottools.strings.toggleTweetOn if dbtools.dbHelper().toggleTweet(update.message.chat_id) else bottools.strings.toggleTweetOff)) + except tweepy.error.TweepError as e: + bottools.methods.twoExceptions(e, update.message) + +def unknown(bot, update): + update.message.reply_text(bottools.strings.unknownCommand) + +# Authentication process + +def auth(bot, update): + db = dbtools.dbHelper() + cid = update.message.chat_id + + if not (db.ato(cid) or db.ase(cid)): + auth = tweepy.OAuthHandler(setuptools.cke(), setuptools.cse()) + update.message.reply_text(bottools.strings.auth % auth.get_authorization_url()) + dbtools.dbHelper().storeToken(cid, auth.request_token) + + else: + update.message.reply_text(bottools.strings.authimp) + +def verify(bot, update, args): + db = dbtools.dbHelper() + cid = update.message.chat_id + + if db.ato(cid) and not db.ase(cid): + auth = tweepy.OAuthHandler(setuptools.cke(), setuptools.cse()) + auth.request_token = ast.literal_eval(db.ato(cid)) + + try: + auth.get_access_token(args[0]) + dbtools.dbHelper().storeUser(cid, auth.access_token, auth.access_token_secret) + update.message.reply_text(bottools.strings.verify) + + except Exception as e: + dbtools.dbHelper().deleteUser(update.message.chat_id) + update.message.reply_text(bottools.strings.verifyfail) + + else: + update.message.reply_text(bottools.strings.verifyimp) + +def unauth(bot, update): + dbtools.dbHelper().deleteUser(update.message.chat_id) + update.message.reply_text(bottools.strings.unauth % setuptools.url()) + +# User methods + +def follow(bot, update, args): + try: + two = bottools.methods.getTwo(update.message) + for user in args: + two.api.create_friendship(screen_name = user) + except tweepy.error.TweepError as e: + bottools.methods.twoExceptions(e, update.message) + +def unfollow(bot, update, args): + try: + two = bottools.methods.getTwo(update.message) + for user in args: + two.api.destroy_friendship(screen_name = user) + except tweepy.error.TweepError as e: + bottools.methods.twoExceptions(e, update.message) + +# Tweet methods + +def explicitTweet(bot, update, args, reply = None): + try: + two = bottools.methods.getTwo(update.message) + + if update.message.photo or update.message.document or update.message.video or update.message.sticker: + fid = update.message.document.file_id if update.message.document else update.message.sticker.file_id if update.message.sticker else update.message.video.file_id if update.message.video else update.message.photo[-1].file_id + path = bot.getFile(fid).file_path + media = urllib.request.urlopen(path) + mobj = io.BytesIO(media.read()) + + filename = path.split("/")[-1] + + if filename.split(".")[-1].lower() == "webp": + out = io.BytesIO() + PIL.Image.open(mobj).convert('RGB').save(out, format="JPEG") + filename = "%s.jpg" % filename.split(".")[0] + + if update.message.document and filename.split(".")[-1].lower() == "mp4": + temp = ''.join(random.choice(string.ascii_letters + string.digits) for i in range(32)) + + with open("tmp/%s.%s" % (temp, filename.split(".")[-1]), "wb") as f: + f.write(mobj.getvalue()) + + moviepy.editor.VideoFileClip("tmp/%s.%s" % (temp, filename.split(".")[-1])).resize(0.3).write_gif("tmp/%s.gif" % temp) + + filename = "%s.gif" % temp + out = open("tmp/%s.gif" % temp, "rb") + + else: + out = mobj + + two.api.update_with_media(filename, update.message.caption, reply, file=out) + out.close() + + else: + two.tweet(' '.join(args), reply) + + except tweepy.error.TweepError as e: + bottools.methods.twoExceptions(e, update.message) + +def reply(bot, update, args): + try: + reply = bottools.methods.getTweetID(args[0], update.message.chat_id) + except: + update.message.reply_text(bottools.strings.cantfind % args[0]) + + bottools.methods.explicitTweet(bot, update, args[1:], reply) + +def retweet(bot, update, args): + for tweet in args: + try: + tid = bottools.methods.getTweetID(tweet, update.message.chat_id) + bottools.methods.getTwo(update.message).api.retweet(tid) + except ValueError: + update.message.reply_text(bottools.strings.cantfind % tweet) + except tweepy.error.TweepError as e: + bottools.methods.twoExceptions(e, update.message) + +def like(bot, update, args): + for tweet in args: + try: + tid = bottools.methods.getTweetID(tweet, update.message.chat_id) + bottools.methods.getTwo(update.message).api.create_favorite(tid) + except ValueError: + update.message.reply_text(bottools.strings.cantfind % tweet) + except tweepy.error.TweepError as e: + bottools.methods.twoExceptions(e, update.message) + +def tweet(bot, update): + try: + if dbtools.dbHelper().getTStatus(update.message.chat_id): + bottools.methods.explicitTweet(bot, update, [update.message.text]) + except twepy.error.TweepError as e: + bottools.methods.twoExceptions(e, update.message) + +# Timelines + +def timeline(bot, update, args = [10]): + try: + count = int(args[0]) + except: + count = 10 + + db = dbtools.dbHelper() + db.executeQuery("DELETE FROM timelines WHERE cid = %i;" % int(update.message.chat_id)) + + try: + i = 1 + two = bottools.methods.getTwo(update.message) + + for status in two.api.home_timeline(count=count): + db.executeQuery("INSERT INTO timelines VALUES(%i, %i, %i);" % (update.message.chat_id, i, status.id)) + update.message.reply_text("Tweet %i:\n%s (@%s) at %s:\n%s" % (i, status.author.name, status.author.screen_name, status.created_at, html.unescape(status.text))) + i += 1 + + except tweepy.error.TweepError as e: + bottools.methods.twoExceptions(e, update.message) + + db.commit() + diff --git a/bottools/strings.py b/bottools/strings.py index d987468..860eab6 100644 --- a/bottools/strings.py +++ b/bottools/strings.py @@ -7,12 +7,12 @@ You will receive a six-digit PIN. Please send it to me like this: * /verify 123456''' -authimp = '''I can't currently start a new authentication process for you as you are either already authenticated or an authentication process has been started. +authimp = '''You can't currently log in to a new account as you are either already logged in or I'm waiting for your verification code. -Please unauthenticate using /unauth first if you are sure you want to re-authenticate.''' +If you wish to log in to a different account or start the login process from the top, please /logout first..''' -noauth = '''You are not authenticated. Please use /auth to sign in with Twitter.''' +noauth = '''You are not logged in. Please use /login to sign in with Twitter.''' start = '''Hey there! @@ -21,21 +21,29 @@ I'm @%s, everybody's favorite Twitter bot on Telegram! For me to help you, you will first have to authenticate with Twitter: -* /auth +* /login After authentication, you will be able to tweet from your account. Just drop me a note! Additionally, you will be able to use the following commands: -* /follow USER - Follow a user. -* /like TWEET - Like a tweet. You will need to pass a tweet ID (TWEET) as returned by /timeline. -* /reply TWEET TEXT - Reply TEXT to a tweet. You will need to pass a tweet ID (TWEET) as returned by /timeline. -* /retweet TWEET - Retweet a tweet. You will need to pass a tweet ID (TWEET) as returned by /timeline. -* /timeline - Shows you the latest tweets in your timeline. Returns 10 tweets by default, but you may also use it like /timeline 20, which would return 20 tweets. -* /toggletweet - Turn automatic tweeting of messages on/off. Useful in groups. -* /tweet TEXT - Explicitly tweet TEXT even if automatic tweeting is off (/toggletweet). -* /unauth - Revoke your authenticaton and stop using %s. -* /unfollow USER - Unfollow a user. +* User functions: +** /follow USER - Follow a user. +** /unfollow USER - Unfollow a user. + +* Tweet functions: +** /like TWEET - Like a tweet. You will need to pass a tweet ID (TWEET) as returned by /timeline. +** /reply TWEET TEXT - Reply TEXT to a tweet. You will need to pass a tweet ID (TWEET) as returned by /timeline. +** /retweet TWEET - Retweet a tweet. You will need to pass a tweet ID (TWEET) as returned by /timeline. +** /toggletweet - Turn automatic tweeting of messages on/off. Useful in groups. +** /tweet TEXT - Explicitly tweet TEXT even if automatic tweeting is off (/toggletweet). + +* Timeline functions: +** /timeline - Shows you the latest tweets in your timeline. Returns 10 tweets by default, but you may also use it like /timeline 20, which would return 20 tweets. + +If you have any further questions about these commands, I'll try to help you. Just use /help COMMAND for that. For instance, "/help timeline" will show you further details about the /timeline command. + +In case you ever wish to leave, you can use /logout. Of course, you're always welcome to login again after that. Have fun!''' @@ -61,13 +69,13 @@ verify = '''Thanks for authenticating. You can now use all of my features!''' verifyfail = '''Oops, something went wrong during the authentication. Please try again: -* /auth''' +* /login''' -verifyimp = '''There is not currently an authentication process running for you. You may already be logged in, or you have not yet sent me an /auth command.''' +verifyimp = '''Huh. I wasn't expecting a verification code from you. You may already be logged in, or you have not yet sent me a /login command.''' twoFail = '''Well, something went wrong. Try again later.''' -twoAuthFail = '''Could not authenticate you for some reason. Please try again later. If this problem persists, please try reauthenticating using /unauth and /auth.''' +twoAuthFail = '''Could not authenticate you for some reason. Please try again later. If this problem persists, please try reauthenticating using /logout and /login.''' toggleTweetOn = "on" @@ -81,7 +89,7 @@ accountSuspended = '''I'm afraid your account is suspended. I can't currently he rateLimit = '''Whoa, buddy! Slow it down! You ran into a rate limit. Please try again later.''' -badToken = '''Oops. Something appears to be wrong with your credentials. Please try reauthenticating using /unauth and /auth.''' +badToken = '''Oops. Something appears to be wrong with your credentials. Please try reauthenticating using /logout and /login.''' overload = '''So, I've good good news and bad news. The bad news is, I was unable to process your request. The good news is, it isn't our fault. Seems like Twitter is experiencing higher load than usual. Please try again later.''' diff --git a/telegrambot.py b/telegrambot.py index 4e2611d..507ba5f 100755 --- a/telegrambot.py +++ b/telegrambot.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -import ast, dbtools, html, io, logging, moviepy.editor, PIL.Image, random, setuptools, string, bottools.strings, telegram.ext, twitools, urllib.request, tweepy +import ast, bottools.methods, dbtools, html, io, logging, moviepy.editor, PIL.Image, random, setuptools, string, bottools.strings, telegram.ext, twitools, urllib.request, tweepy logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO) logger = logging.getLogger(__name__) @@ -8,242 +8,32 @@ logger = logging.getLogger(__name__) def log(bot, update, error): logger.warn("Error %s caused by '%s'" % (error, update)) -def twoExceptions(e, message): - text = { - 32: bottools.strings.badToken, - 36: bottools.strings.selfSpam, - 64: bottools.strings.accountSuspended, - 88: bottools.strings.rateLimit, - 89: bottools.strings.badToken, - 99: bottools.strings.badToken, - 130: bottools.strings.overload, - 131: bottools.strings.twitterError, - 161: bottools.strings.followLimit, - 185: bottools.strings.tweetLimit, - 186: bottools.strings.tweetLimit, - 187: bottools.strings.dupTweet, - 205: bottools.strings.rateLimit, - 226: bottools.strings.automatedTweet, - 271: bottools.strings.selfMute, - 272: bottools.strings.notMuted, - 323: bottools.strings.multipleGIFs, - 326: bottools.strings.accountLocked, - 354: bottools.strings.longTweet - }.get(e.api_code) - - message.reply_text(text or bottools.strings.twoFail) - -def getTwo(message): - try: - return twitools.twoBotHelper(message.chat_id) - except ValueError: - message.reply_text(bottools.strings.noauth) - except tweepy.error.TweepError as e: - raise - -def start(bot, update): - update.message.reply_text(bottools.strings.start % (setuptools.botname(), setuptools.botname())) - -def auth(bot, update): - db = dbtools.dbHelper() - cid = update.message.chat_id - - if not (db.ato(cid) or db.ase(cid)): - auth = tweepy.OAuthHandler(setuptools.cke(), setuptools.cse()) - update.message.reply_text(bottools.strings.auth % auth.get_authorization_url()) - dbtools.dbHelper().storeToken(cid, auth.request_token) - - else: - update.message.reply_text(bottools.strings.authimp) - -def verify(bot, update, args): - db = dbtools.dbHelper() - cid = update.message.chat_id - - if db.ato(cid) and not db.ase(cid): - auth = tweepy.OAuthHandler(setuptools.cke(), setuptools.cse()) - auth.request_token = ast.literal_eval(db.ato(cid)) - - try: - auth.get_access_token(args[0]) - dbtools.dbHelper().storeUser(cid, auth.access_token, auth.access_token_secret) - update.message.reply_text(bottools.strings.verify) - - except Exception as e: - dbtools.dbHelper().deleteUser(update.message.chat_id) - update.message.reply_text(bottools.strings.verifyfail) - - else: - update.message.reply_text(bottools.strings.verifyimp) - -def unauth(bot, update): - dbtools.dbHelper().deleteUser(update.message.chat_id) - update.message.reply_text(bottools.strings.unauth % setuptools.url()) - -def fish(bot, update): - dbtools.dbHelper().addFish(update.message.chat_id) - update.message.reply_text(bottools.strings.fishThanks) - -def follow(bot, update, args): - try: - two = getTwo(update.message) - for user in args: - two.api.create_friendship(screen_name = user) - except tweepy.error.TweepError as e: - twoExceptions(e, update.message) - -def unfollow(bot, update, args): - try: - two = getTwo(update.message) - for user in args: - two.api.destroy_friendship(screen_name = user) - except tweepy.error.TweepError as e: - twoExceptions(e, update.message) - -def getTweetID(tlid, cid): - try: - db = dbtools.dbHelper() - db.executeQuery("SELECT tid FROM timelines WHERE nr = %i AND cid = %i;" % (int(tlid), int(cid))) - return db.getNext()[0] - except Exception: - raise ValueError("No such tweet in timeline") - -def explicitTweet(bot, update, args, reply = None): - try: - two = getTwo(update.message) - - if update.message.photo or update.message.document or update.message.video or update.message.sticker: - fid = update.message.document.file_id if update.message.document else update.message.sticker.file_id if update.message.sticker else update.message.video.file_id if update.message.video else update.message.photo[-1].file_id - path = bot.getFile(fid).file_path - media = urllib.request.urlopen(path) - mobj = io.BytesIO(media.read()) - - filename = path.split("/")[-1] - - if filename.split(".")[-1].lower() == "webp": - out = io.BytesIO() - PIL.Image.open(mobj).convert('RGB').save(out, format="JPEG") - filename = "%s.jpg" % filename.split(".")[0] - - if update.message.document and filename.split(".")[-1].lower() == "mp4": - temp = ''.join(random.choice(string.ascii_letters + string.digits) for i in range(32)) - - with open("tmp/%s.%s" % (temp, filename.split(".")[-1]), "wb") as f: - f.write(mobj.getvalue()) - - moviepy.editor.VideoFileClip("tmp/%s.%s" % (temp, filename.split(".")[-1])).resize(0.3).write_gif("tmp/%s.gif" % temp) - - filename = "%s.gif" % temp - out = open("tmp/%s.gif" % temp, "rb") - - else: - out = mobj - - two.api.update_with_media(filename, update.message.caption, reply, file=out) - out.close() - - else: - two.tweet(' '.join(args), reply) - - except tweepy.error.TweepError as e: - twoExceptions(e, update.message) - -def reply(bot, update, args): - try: - reply = getTweetID(args[0], update.message.chat_id) - except: - update.message.reply_text(bottools.strings.cantfind % args[0]) - - explicitTweet(bot, update, args[1:], reply) - -def retweet(bot, update, args): - for tweet in args: - try: - tid = getTweetID(tweet, update.message.chat_id) - getTwo(update.message).api.retweet(tid) - except ValueError: - update.message.reply_text(bottools.strings.cantfind % tweet) - except tweepy.error.TweepError as e: - twoExceptions(e, update.message) - -def like(bot, update, args): - for tweet in args: - try: - tid = getTweetID(tweet, update.message.chat_id) - getTwo(update.message).api.create_favorite(tid) - except ValueError: - update.message.reply_text(bottools.strings.cantfind % tweet) - except tweepy.error.TweepError as e: - twoExceptions(e, update.message) - -def tweet(bot, update): - try: - if dbtools.dbHelper().getTStatus(update.message.chat_id): - explicitTweet(bot, update, [update.message.text]) - except twepy.error.TweepError as e: - twoExceptions(e, update.message) - -def timeline(bot, update, args = [10]): - try: - count = int(args[0]) - except: - count = 10 - - db = dbtools.dbHelper() - db.executeQuery("DELETE FROM timelines WHERE cid = %i;" % int(update.message.chat_id)) - - try: - i = 1 - two = getTwo(update.message) - - for status in two.api.home_timeline(count=count): - db.executeQuery("INSERT INTO timelines VALUES(%i, %i, %i);" % (update.message.chat_id, i, status.id)) - update.message.reply_text("Tweet %i:\n%s (@%s) at %s:\n%s" % (i, status.author.name, status.author.screen_name, status.created_at, html.unescape(status.text))) - i += 1 - - except tweepy.error.TweepError as e: - twoExceptions(e, update.message) - - db.commit() - -def toggleTweet(bot, update): - try: - update.message.reply_text(bottools.strings.toggleTweet % (bottools.strings.toggleTweetOn if dbtools.dbHelper().toggleTweet(update.message.chat_id) else bottools.strings.toggleTweetOff)) - except tweepy.error.TweepError as e: - twoExceptions(e, update.message) - -def unknown(bot, update): - update.message.reply_text(bottools.strings.unknownCommand) - -def test(bot, update, args): - print(args) - unknown(bot, update) - if __name__ == "__main__": updater = telegram.ext.Updater(token=setuptools.token()) - updater.dispatcher.add_handler(telegram.ext.CommandHandler("auth", auth)) - updater.dispatcher.add_handler(telegram.ext.CommandHandler("fish", fish)) - updater.dispatcher.add_handler(telegram.ext.CommandHandler("follow", follow, pass_args=True)) - updater.dispatcher.add_handler(telegram.ext.CommandHandler("help", start)) - updater.dispatcher.add_handler(telegram.ext.CommandHandler("like", like, pass_args=True)) - updater.dispatcher.add_handler(telegram.ext.CommandHandler("reply", reply, pass_args=True)) - updater.dispatcher.add_handler(telegram.ext.CommandHandler("retweet", retweet, pass_args=True)) - updater.dispatcher.add_handler(telegram.ext.CommandHandler("start", start)) - updater.dispatcher.add_handler(telegram.ext.CommandHandler("test", test, pass_args=True)) - updater.dispatcher.add_handler(telegram.ext.CommandHandler("timeline", timeline, pass_args=True)) - updater.dispatcher.add_handler(telegram.ext.CommandHandler("toggletweet", toggleTweet)) - updater.dispatcher.add_handler(telegram.ext.CommandHandler("tweet", explicitTweet, pass_args=True)) - updater.dispatcher.add_handler(telegram.ext.CommandHandler("unauth", unauth)) - updater.dispatcher.add_handler(telegram.ext.CommandHandler("unfollow", unfollow, pass_args=True)) - updater.dispatcher.add_handler(telegram.ext.CommandHandler("verify", verify, pass_args=True)) + updater.dispatcher.add_handler(telegram.ext.CommandHandler("auth", bottools.methods.auth)) + updater.dispatcher.add_handler(telegram.ext.CommandHandler("fish", bottools.methods.fish)) + updater.dispatcher.add_handler(telegram.ext.CommandHandler("follow", bottools.methods.follow, pass_args=True)) + updater.dispatcher.add_handler(telegram.ext.CommandHandler("help", bottools.methods.start)) + updater.dispatcher.add_handler(telegram.ext.CommandHandler("like", bottools.methods.like, pass_args=True)) + updater.dispatcher.add_handler(telegram.ext.CommandHandler("login", bottools.methods.auth)) + updater.dispatcher.add_handler(telegram.ext.CommandHandler("logout", bottools.methods.unauth)) + updater.dispatcher.add_handler(telegram.ext.CommandHandler("reply", bottools.methods.reply, pass_args=True)) + updater.dispatcher.add_handler(telegram.ext.CommandHandler("retweet", bottools.methods.retweet, pass_args=True)) + updater.dispatcher.add_handler(telegram.ext.CommandHandler("start", bottools.methods.start)) + updater.dispatcher.add_handler(telegram.ext.CommandHandler("timeline", bottools.methods.timeline, pass_args=True)) + updater.dispatcher.add_handler(telegram.ext.CommandHandler("toggletweet", bottools.methods.toggleTweet)) + updater.dispatcher.add_handler(telegram.ext.CommandHandler("tweet", bottools.methods.explicitTweet, pass_args=True)) + updater.dispatcher.add_handler(telegram.ext.CommandHandler("unauth", bottools.methods.unauth)) + updater.dispatcher.add_handler(telegram.ext.CommandHandler("unfollow", bottools.methods.unfollow, pass_args=True)) + updater.dispatcher.add_handler(telegram.ext.CommandHandler("verify", bottools.methods.verify, pass_args=True)) - updater.dispatcher.add_handler(telegram.ext.MessageHandler(telegram.ext.Filters.text, tweet)) - updater.dispatcher.add_handler(telegram.ext.MessageHandler(telegram.ext.Filters.photo, tweet)) - updater.dispatcher.add_handler(telegram.ext.MessageHandler(telegram.ext.Filters.sticker, tweet)) - updater.dispatcher.add_handler(telegram.ext.MessageHandler(telegram.ext.Filters.video, tweet)) - updater.dispatcher.add_handler(telegram.ext.MessageHandler(telegram.ext.Filters.command, unknown)) - updater.dispatcher.add_handler(telegram.ext.MessageHandler(telegram.ext.Filters.document, tweet)) + updater.dispatcher.add_handler(telegram.ext.MessageHandler(telegram.ext.Filters.text, bottools.methods.tweet)) + updater.dispatcher.add_handler(telegram.ext.MessageHandler(telegram.ext.Filters.photo, bottools.methods.tweet)) + updater.dispatcher.add_handler(telegram.ext.MessageHandler(telegram.ext.Filters.sticker, bottools.methods.tweet)) + updater.dispatcher.add_handler(telegram.ext.MessageHandler(telegram.ext.Filters.video, bottools.methods.tweet)) + updater.dispatcher.add_handler(telegram.ext.MessageHandler(telegram.ext.Filters.command, bottools.methods.unknown)) + updater.dispatcher.add_handler(telegram.ext.MessageHandler(telegram.ext.Filters.document, bottools.methods.tweet)) updater.dispatcher.add_error_handler(log)