├── .gitignore ├── Times_New_Roman.ttf ├── infinitweet.py ├── readme.md ├── requirements.txt └── utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.ipynb 3 | *.jpg 4 | *.png 5 | *.json 6 | /env 7 | KEYS.py -------------------------------------------------------------------------------- /Times_New_Roman.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thekindlyone/infinitweet/623bd25cb9480331e82b4c80e9e46edd15c10ce9/Times_New_Roman.ttf -------------------------------------------------------------------------------- /infinitweet.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #-*- coding:utf-8 -*- 3 | 4 | from PyQt4 import QtCore, QtGui 5 | from utils import * 6 | from PyQt4.QtWebKit import * 7 | from PyQt4.QtCore import * 8 | from bs4 import BeautifulSoup as bs 9 | 10 | 11 | class MyDialog(QtGui.QDialog): 12 | 13 | def __init__(self, parent=None): 14 | super(MyDialog, self).__init__(parent) 15 | self.resize(800, 600) 16 | self.browser = Browser(self) 17 | 18 | 19 | class Browser(QWebView): 20 | 21 | def __init__(self, parent=None): 22 | super(Browser, self).__init__(parent) 23 | self.auth_done = False 24 | self.auth, auth_url = auth_step1() 25 | self.loadFinished.connect(self._result_available) 26 | self.load(QUrl(auth_url)) 27 | 28 | def _result_available(self, ok): 29 | if not self.auth_done: 30 | frame = self.page().mainFrame() 31 | html = unicode(frame.toHtml()).encode('utf-8') 32 | soup = bs(html, 'lxml') 33 | elem = soup.find('code') 34 | if elem: 35 | pincode = elem.text 36 | twitter = auth_step2(self.auth, pincode) 37 | self.auth_done = True 38 | self.parent().close() 39 | 40 | # def load_userdata(self,twitter): 41 | # html = json2html.convert(json = twitter.verify_credentials()) 42 | # self.setHtml(html) 43 | 44 | 45 | class MyWindow(QtGui.QWidget): 46 | 47 | def __init__(self, parent=None): 48 | super(MyWindow, self).__init__(parent) 49 | 50 | self.textbox = QtGui.QTextEdit(self) 51 | self.textbox.textChanged.connect(self.update_char_count) 52 | 53 | self.count_display = QtGui.QLabel(self) 54 | self.count_display.setAlignment(Qt.AlignCenter) 55 | 56 | self.status_display = QtGui.QLabel(self) 57 | self.status_display.setOpenExternalLinks(True) 58 | 59 | self.tweetbutton = QtGui.QPushButton('Tweet!', self) 60 | self.tweetbutton.clicked.connect(self.send_tweet) 61 | 62 | self.layout = QtGui.QVBoxLayout(self) 63 | self.layout.addWidget(self.textbox) 64 | self.layout.addWidget(self.count_display) 65 | self.layout.addWidget(self.tweetbutton) 66 | self.layout.addWidget(self.status_display) 67 | self.browser = MyDialog(self) 68 | self.twitter = check_stored_tokens() 69 | if not self.twitter: 70 | self.browser.exec_() 71 | self.twitter = check_stored_tokens() 72 | 73 | @QtCore.pyqtSlot() 74 | def send_tweet(self): 75 | text = to_bytes(self.textbox.toPlainText()) 76 | if len(text) < 140: 77 | r = self.twitter.update_status(status=text) 78 | else: 79 | status_text = get_tweet_components(text) 80 | fn = imagify(text) 81 | media = self.twitter.media_upload(fn) 82 | r = self.twitter.update_status( 83 | status=status_text, media_ids=[media.media_id_string]) 84 | 85 | template = 'https://twitter.com/{}/status/{}'.format 86 | url = 'Tweeted Succesfully'.format( 87 | template(r.user.screen_name, r.id_str)) 88 | self.textbox.clear() 89 | self.status_display.setText(url) 90 | 91 | @QtCore.pyqtSlot() 92 | def update_char_count(self): 93 | self.count_display.setNum(len(self.textbox.toPlainText())) 94 | 95 | 96 | if __name__ == "__main__": 97 | import sys 98 | 99 | app = QtGui.QApplication(['Infinitweet']) 100 | screen_resolution = app.desktop().screenGeometry() 101 | width, height = screen_resolution.width(), screen_resolution.height() 102 | app.setApplicationName('Infinitweet') 103 | 104 | main = MyWindow() 105 | main.resize(width / 4, height / 3) 106 | main.show() 107 | 108 | sys.exit(app.exec_()) 109 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Sends tweets > 140 chars. No more sms lingo. 2 | 3 | 4 | 5 | 6 | Demo: https://www.youtube.com/watch?v=-ZPNsWltBPk 7 | 8 | Requirements: 9 | qt, pyqt, tweepy, requests==2.7, Pillow 10 | 11 | Needs a file called KEYS.py to store app credentials (get them from [here](https://apps.twitter.com/) ) 12 | 13 | Format of KEYS.py 14 | 15 | consumer_token= 16 | consumer_secret= 17 | 18 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Pillow==3.0.0 2 | argparse==1.2.1 3 | beautifulsoup4==4.4.1 4 | cffi==1.3.0 5 | cryptography==1.1 6 | enum34==1.0.4 7 | gevent==1.0.2 8 | greenlet==0.4.9 9 | idna==2.0 10 | ipaddress==1.0.14 11 | json2html==0.3 12 | kitchen==1.2.2 13 | lxml==3.4.4 14 | mechanize==0.2.5 15 | ndg-httpsclient==0.4.0 16 | oauthlib==1.0.3 17 | ordereddict==1.1 18 | pyOpenSSL==0.15.1 19 | pyasn1==0.1.9 20 | pycparser==2.14 21 | requests==2.7.0 22 | requests-oauthlib==0.5.0 23 | six==1.10.0 24 | tweepy==3.4.0 25 | twitter-text-python==1.1.0 26 | wsgiref==0.1.2 27 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | from itertools import chain 3 | import tweepy 4 | import json 5 | from PIL import Image, ImageFont, ImageOps, ImageDraw 6 | from kitchen.text.converters import to_unicode, to_bytes 7 | import textwrap 8 | from KEYS import * 9 | from ttp import ttp 10 | 11 | 12 | def auth_step1(): 13 | auth = tweepy.OAuthHandler(consumer_token, consumer_secret) 14 | auth_url = auth.get_authorization_url() 15 | 16 | return auth, auth_url 17 | 18 | 19 | def auth_step2(auth, pincode): 20 | auth.get_access_token(pincode) 21 | final_token = auth.access_token 22 | final_secret = auth.access_token_secret 23 | auth.set_access_token(final_token, final_secret) 24 | twitter = tweepy.API(auth) 25 | with open('creds.json', 'w') as f: 26 | json.dump( 27 | {'oauth_token': final_token, 'oauth_token_secret': final_secret}, f) 28 | return(twitter) 29 | 30 | 31 | def check_stored_tokens(): 32 | if os.path.exists('creds.json'): 33 | with open('creds.json') as f: 34 | try: 35 | final_step = json.load(f) 36 | final_token = final_step['oauth_token'] 37 | final_secret = final_step['oauth_token_secret'] 38 | auth = tweepy.OAuthHandler(consumer_token, consumer_secret) 39 | auth.set_access_token(final_token, final_secret) 40 | twitter = tweepy.API(auth) 41 | return twitter 42 | except Exception as e: 43 | print e 44 | return None 45 | else: 46 | return None 47 | 48 | 49 | def flatten(listOfLists): 50 | "Flatten one level of nesting" 51 | return chain.from_iterable(listOfLists) 52 | 53 | 54 | def imagify(text, fn='temp.png'): 55 | FOREGROUND = (0, 0, 0) 56 | font_path = 'Times_New_Roman.ttf' 57 | font = ImageFont.truetype(font_path, 18, encoding='unic') 58 | # text = to_bytes(text) 59 | orilines = text.split('\n') 60 | lines = list(flatten( 61 | [textwrap.wrap(oriline, width=80) if oriline else ' ' for oriline in orilines])) 62 | widths, heights = zip( 63 | *[font.getsize(line) if line else font.getsize('A') for line in lines]) 64 | h = sum(heights) + 6 * len(lines) + 10 65 | w = max(widths) + 30 66 | bg = Image.new('RGBA', (w, h), "#FFFFFF") 67 | draw = ImageDraw.Draw(bg) 68 | y_text = 10 69 | for line in lines: 70 | if line: 71 | width, height = font.getsize(line) 72 | else: 73 | width, height = font.getsize('A') 74 | draw.text(((w - width) / 2, y_text), line, font=font, fill=FOREGROUND) 75 | y_text += height + 5 76 | 77 | with open(fn, 'w') as f: 78 | bg.save(f) 79 | return fn 80 | 81 | parser = ttp.Parser() 82 | 83 | 84 | def get_tweet_components(text): 85 | result = parser.parse(text) 86 | urls = ' '.join(result.urls) 87 | tags = ' '.join(['#' + tag for tag in result.tags]) 88 | if result.reply: 89 | result.users.remove(result.reply) 90 | result.reply = '@' + result.reply 91 | else: 92 | result.reply = '' 93 | users = ' '.join(['@' + user for user in result.users]) 94 | return ' '.join([result.reply, urls, users, tags]).strip() 95 | --------------------------------------------------------------------------------