├── .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 |
--------------------------------------------------------------------------------