-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathreddit.py
More file actions
267 lines (229 loc) · 9.45 KB
/
reddit.py
File metadata and controls
267 lines (229 loc) · 9.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
import urwid
import webbrowser
import os
from pages import Story, RedditHandler, BadSubredditError, Navigation
from optparse import OptionParser # argparse is 2.7-only
# Main loop is global so MainWindow can update the screen asynchronously
main_loop = None
class Listing(urwid.FlowWidget):
"""contains a single story and manages its events"""
def __init__(self, story):
self.story = story
def selectable(self):
return True
def rows(self, size, focus=False):
return 2
def render(self, size, focus=False):
(maxcol,) = size
lines = self.story.format_lines(maxcol)
if focus:
pass
# pad lines to column width
fill = lambda x: x.ljust(maxcol)
return urwid.TextCanvas(text=list(map(fill, lines)))
def keypress(self, size, key):
if key in ('o', 'enter'):
webbrowser.open(self.story.url)
elif key == 'O':
if self.story.domain[:5] == "self.":
# Lynx renders mobile reddit better
url = "http://m.reddit.com" + self.story.permalink
else:
url = self.story.url
os.system("lynx -accept_all_cookies " + url)
elif key == 'h':
webbrowser.open("http://www.reddit.com" + self.story.permalink)
elif key == 'l':
url = "http://m.reddit.com" + self.story.permalink
os.system("lynx -accept_all_cookies " + url)
else:
return key
class MainWindow(object):
"""manages main window elements"""
def __init__(self):
# Handles page downloads and cookies
self.handler = RedditHandler()
self.listings = []
self.__subreddit = None
self.nav = None
self.__load_stories()
# Prep header and footer ui widgets
self.__update_header()
self.footer_content = urwid.Text(('footer', ""), wrap='clip')
self.footer = urwid.Padding(self.footer_content, left=1, right=1)
self.frame = urwid.Frame( self.__get_widget(),
header=self.header,
footer=self.footer )
def login(self, username, password):
"""attempt to login"""
login_result = self.handler.login(username, password)
if login_result:
self.__update_header()
return login_result
def set_subreddit(self, subreddit):
"""switch subreddits"""
self.nav = None
old_subreddit = self.__subreddit
if subreddit == "fp":
self.__subreddit = None
else:
self.set_status("Loading subreddit: /r/{0}".format(subreddit))
self.__subreddit = subreddit
try:
self.__load_stories()
except BadSubredditError:
self.set_status("Error loading subreddit /r/{0}!".format(subreddit))
self.__subreddit = old_subreddit
self.__load_stories()
main_widget = self.__get_widget()
self.frame.set_body(main_widget)
self.set_status()
def __update_header(self):
"""set the titlebar to the currently logged in user"""
if self.handler.user:
header_text = "[{0}] - reddit-cli - github.com/cev/reddit-cli".format(self.handler.user)
else:
header_text = "reddit-cli - github.com/cev/reddit-cli"
self.header = urwid.Text(('header',
header_text),
align='center')
if hasattr(self, 'frame'):
self.frame.set_header(self.header)
def __load_stories(self, direction=None):
"""load stories from (sub)reddit and store Listings"""
self.listings = []
data = self.handler.download_stories(self.__subreddit, self.nav, direction)
self.nav = data[1]
for s in data[0]:
current = Listing(s)
self.listings.append(urwid.Padding(current, left=1, right=1))
def __get_widget(self):
"""return TextBox widget containing all Listings"""
listings_formatted = self.listings[:]
# Separate stories with blank line & highlight on focus
for (i, l) in enumerate(listings_formatted):
filled = urwid.Filler(urwid.AttrMap(l, None, 'focus'))
listings_formatted[i] = urwid.BoxAdapter(filled, 3)
listings_formatted.append(urwid.Divider("*"))
self.listings_active = urwid.ListBox(urwid.SimpleListWalker(listings_formatted))
return self.listings_active
def __format_status(self):
"""format status text for use in footer"""
if self.__subreddit is None:
subreddit_text = "/r/front_page"
else:
subreddit_text = "/r/" + self.__subreddit
status = "[{0}] ({1}) ?: help n/m:pagination".format(self.nav.count/25+1, subreddit_text)
return status
def switch_page(self, direction):
"""load stories from the previous or next page"""
if direction == "prev":
self.set_status("(<) Loading...")
self.__load_stories(direction=direction)
elif direction == "next":
self.set_status("(>) Loading...")
self.__load_stories(direction=direction)
else:
raise Exception, "Direction must be 'prev' or 'next'"
main_widget = self.__get_widget()
self.frame.set_body(main_widget)
self.set_status()
def set_status(self, message=None):
"""write message on footer or else default status string"""
if message is None:
status = self.__format_status()
else:
status = message
self.footer_content.set_text(('footer', status))
global main_loop
if not main_loop is None:
main_loop.draw_screen()
def refresh(self):
"""reload stories in main window"""
self.set_status("Reloading...")
self.nav = None
try:
self.__load_stories()
except BadSubredditError:
self.set_status("Error loading subreddit!")
return
main_widget = self.__get_widget()
self.frame.set_body(main_widget)
self.set_status()
def main():
palette = [
('header', 'dark magenta,bold', 'default'),
('footer', 'black', 'light gray'),
('textentry', 'white,bold', 'dark red'),
('body', 'light gray', 'default'),
('focus', 'black', 'dark cyan', 'standout')
]
textentry = urwid.Edit()
assert textentry.get_text() == ('', []), textentry.get_text()
parser = OptionParser()
parser.add_option("-u", "--username")
parser.add_option("-p", "--password")
(options, args) = parser.parse_args()
if options.username and not options.password:
print "If you specify a username, you must also specify a password"
exit()
print "Loading..."
body = MainWindow()
if options.username:
print "[Logging in]"
if body.login(options.username, options.password):
print "[Login Successful]"
else:
print "[Login Failed]"
exit()
body.refresh()
def edit_handler(keys, raw):
"""respond to keys while user is editing text"""
if keys in (['enter'],[]):
if keys == ['enter']:
if textentry.get_text()[0] != '':
# We set the footer twice because the first time we
# want the updated status text (loading...) to show
# immediately, and the second time as a catch-all
body.frame.set_footer(body.footer)
body.set_subreddit(textentry.edit_text)
textentry.set_edit_text('')
# Restore original status footer
body.frame.set_footer(body.footer)
body.frame.set_focus('body')
global main_loop
main_loop.input_filter = input_handler
return
return keys
def input_handler(keys, raw):
"""respond to keys not handled by a specific widget"""
for key in keys:
if key == 's':
# Replace status footer wth edit widget
textentry.set_caption(('textentry', ' [subreddit?] ("fp" for the front page) :>'))
body.frame.set_footer(urwid.Padding(textentry, left=4))
body.frame.set_focus('footer')
global main_loop
main_loop.input_filter = edit_handler
return
elif key in ('j','k'):
direction = 'down' if key == 'j' else 'up'
return [direction]
elif key in ('n','m'):
direction = 'prev' if key == 'n' else 'next'
body.switch_page(direction)
elif key == 'u':
body.refresh()
elif key == 'b': # boss mode
os.system("man python")
elif key == '?': # help mode
os.system("less -Ce README.markdown")
elif key == 'q': # quit
raise urwid.ExitMainLoop()
return keys
# Start ui
global main_loop
main_loop = urwid.MainLoop(body.frame, palette, input_filter=input_handler)
main_loop.run()
if __name__ == "__main__":
main()