Commit | Line | Data |
---|---|---|
1882b5bc | 1 | #!/usr/bin/env python3 |
149bc23e AS |
2 | # Copyright (C) 2019 Alex Schroeder <alex@gnu.org> |
3 | ||
4 | # This program is free software: you can redistribute it and/or modify it under | |
5 | # the terms of the GNU Affero General Public License as published by the Free | |
6 | # Software Foundation, either version 3 of the License, or (at your option) any | |
7 | # later version. | |
8 | # | |
9 | # This program is distributed in the hope that it will be useful, but WITHOUT | |
10 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
11 | # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more | |
12 | # details. | |
13 | # | |
14 | # You should have received a copy of the GNU Affero General Public License along | |
15 | # with this program. If not, see <https://www.gnu.org/licenses/>. | |
16 | ||
17 | import diaspy | |
6328099f | 18 | import subprocess |
149bc23e | 19 | import argparse |
6328099f | 20 | import shutil |
149bc23e AS |
21 | import cmd |
22 | import sys | |
23 | import os | |
24 | ||
1882b5bc AS |
25 | _RC_PATHS = ( |
26 | "~/.config/jan-pona-mute/login", | |
ebc699fd | 27 | "~/.jan-pona-mute.d/login" |
1882b5bc AS |
28 | "~/.jan-pona-mute" |
29 | ) | |
30 | ||
ebc699fd AS |
31 | _NOTE_DIRS = ( |
32 | "~/.config/jan-pona-mute/notes", | |
33 | "~/.jan-pona-mute.d/notes" | |
34 | ) | |
35 | ||
6328099f | 36 | _PAGERS = ( |
ebc699fd | 37 | os.getenv("PAGER"), |
6328099f AS |
38 | "mdcat", |
39 | "fold", | |
40 | "cat" | |
41 | ) | |
42 | ||
ebc699fd AS |
43 | _EDITORS = ( |
44 | os.getenv("EDITOR"), | |
45 | "vi", | |
46 | "ed" | |
47 | ) | |
48 | ||
82f0d747 AS |
49 | shortcuts = { |
50 | "q": "quit", | |
51 | "p": "preview", | |
52 | "c": "comments", | |
53 | "r": "reload", | |
54 | "n": "notifications", | |
55 | "e": "edit", | |
56 | "d": "delete", | |
57 | "g": "notifications reload", | |
58 | } | |
59 | ||
149bc23e | 60 | def get_rcfile(): |
ebc699fd AS |
61 | """Init file finder""" |
62 | for path in _RC_PATHS: | |
63 | path = os.path.expanduser(path) | |
64 | if os.path.exists(path): | |
65 | return path | |
149bc23e AS |
66 | return None |
67 | ||
ebc699fd AS |
68 | def get_notes_dir(): |
69 | """Notes directory finder""" | |
70 | dir = None | |
71 | for path in _NOTE_DIRS: | |
72 | path = os.path.expanduser(path) | |
73 | if os.path.isdir(path): | |
74 | dir = path | |
75 | break | |
76 | if dir == None: | |
77 | dir = os.path.expanduser(_NOTE_DIRS[0]) | |
78 | if not os.path.isdir(dir): | |
79 | os.makedirs(dir) | |
80 | return dir | |
81 | ||
82 | def get_binary(list): | |
83 | for cmd in list: | |
84 | if cmd != None: | |
85 | bin = shutil.which(cmd) | |
86 | if bin != None: | |
87 | return bin | |
88 | ||
6328099f | 89 | def get_pager(): |
ebc699fd AS |
90 | """Pager finder""" |
91 | return get_binary(_PAGERS) | |
92 | ||
93 | def get_editor(): | |
94 | """Editor finder""" | |
95 | return get_binary(_EDITORS) | |
6328099f | 96 | |
149bc23e AS |
97 | class DiasporaClient(cmd.Cmd): |
98 | ||
99 | prompt = "\x1b[38;5;255m" + "> " + "\x1b[0m" | |
6328099f | 100 | intro = "Welcome to Diaspora! Use the intro command for a quick introduction." |
149bc23e | 101 | |
8bf63019 AS |
102 | header_format = "\x1b[1;38;5;255m" + "%s" + "\x1b[0m" |
103 | ||
149bc23e AS |
104 | username = None |
105 | pod = None | |
106 | password = None | |
6328099f | 107 | pager = None |
ebc699fd | 108 | editor = None |
149bc23e AS |
109 | |
110 | connection = None | |
18e74209 | 111 | notifications = [] |
1ff8fa2c | 112 | home = None |
bec46251 | 113 | numbers_refer_to = None |
18e74209 | 114 | post = None |
bb8316e5 | 115 | post_cache = {} # key is self.post.uid, and notification.id |
f1bfb7bc | 116 | |
714a2f67 AS |
117 | undo = [] |
118 | ||
149bc23e AS |
119 | |
120 | # dict mapping user ids to usernames | |
121 | users = {} | |
122 | ||
123 | def get_username(self, guid): | |
124 | if guid in self.users: | |
125 | return self.users[guid] | |
126 | else: | |
127 | user = diaspy.people.User(connection = self.connection, guid = guid) | |
128 | self.users[guid] = user.handle() | |
129 | return self.users[guid] | |
130 | ||
6328099f | 131 | def do_intro(self, line): |
d87d6adf | 132 | """Start here.""" |
6328099f | 133 | print(""" |
bb8316e5 AS |
134 | Use the 'account' and 'password' commands to set up your connection, |
135 | then use the 'login' command to log in. If everything works as | |
136 | intended, use the 'save' command to save these commands to an init | |
137 | file. | |
6328099f | 138 | |
bec46251 AS |
139 | Once you've listed things such as notifications or the home stream, |
140 | enter a number to select the corresponding item. | |
6328099f AS |
141 | """) |
142 | ||
149bc23e AS |
143 | def do_account(self, account): |
144 | """Set username and pod using the format username@pod.""" | |
145 | try: | |
146 | (self.username, self.pod) = account.split('@') | |
1882b5bc | 147 | print("Username and pod set: %s@%s" % (self.username, self.pod)) |
149bc23e AS |
148 | except ValueError: |
149 | print("The account must contain an @ character, e.g. kensanata@pluspora.com.") | |
150 | print("Use the account comand to set the account.") | |
151 | ||
152 | def do_info(self, line): | |
153 | """Get some info about things. By default, it is info about yourself.""" | |
82f0d747 | 154 | print(self.header("Info")) |
1882b5bc AS |
155 | print("Username: %s" % self.username) |
156 | print("Password: %s" % ("None" if self.password == None else "set")) | |
157 | print("Pod: %s" % self.pod) | |
6328099f | 158 | print("Pager: %s" % self.pager) |
ebc699fd | 159 | print("Editor: %s" % self.editor) |
bec46251 | 160 | print("Cache: %s posts" % len(self.post_cache)) |
149bc23e AS |
161 | |
162 | def do_password(self, password): | |
163 | """Set the password.""" | |
164 | self.password = (None if self.password == "" else password) | |
1882b5bc AS |
165 | print("Password %s" % ("unset" if self.password == "" else "set")) |
166 | ||
167 | def do_save(self, line): | |
d87d6adf | 168 | """Save your login information to the init file.""" |
1882b5bc | 169 | if self.username == None or self.pod == None: |
ebc699fd | 170 | print("Use the 'account' command to set username and pod.") |
1882b5bc | 171 | elif self.password == None: |
ebc699fd | 172 | print("Use the 'password' command.") |
1882b5bc AS |
173 | else: |
174 | rcfile = get_rcfile() | |
175 | if rcfile == None: | |
ebc699fd | 176 | rfile = _RC_PATHS[0] |
1882b5bc AS |
177 | seen_account = False |
178 | seen_password = False | |
179 | seen_login = False | |
180 | changed = False | |
181 | file = [] | |
182 | with open(rcfile, "r") as fp: | |
183 | for line in fp: | |
184 | words = line.strip().split() | |
185 | if words: | |
186 | if words[0] == "account": | |
187 | seen_account = True | |
188 | account = "%s@%s" % (self.username, self.pod) | |
189 | if len(words) > 1 and words[1] != account: | |
190 | line = "account %s\n" % account | |
191 | changed = True | |
192 | elif words[0] == "password": | |
193 | seen_password = True | |
194 | if len(words) > 1 and words[1] != self.password: | |
195 | line = "password %s\n" % self.password | |
196 | changed = True | |
197 | elif words[0] == "login": | |
198 | if seen_account and seen_password: | |
199 | seen_login = True | |
200 | else: | |
201 | # skip login if no account or no password given | |
202 | line = None | |
203 | changed = True | |
204 | if line != None: | |
205 | file.append(line) | |
206 | if not seen_account: | |
207 | file.append("account %s@%s\n" % (self.username, self.pod)) | |
208 | changed = True | |
209 | if not seen_password: | |
210 | file.append("password %s\n" % self.password) | |
211 | changed = True | |
212 | if not seen_login: | |
213 | file.append("login\n") | |
214 | changed = True | |
215 | if changed: | |
216 | if os.path.isfile(rcfile): | |
217 | os.rename(rcfile, rcfile + "~") | |
218 | if not os.path.isdir(os.path.dirname(rcfile)): | |
219 | os.makedirs(os.path.dirname(rcfile)) | |
220 | with open(rcfile, "w") as fp: | |
221 | fp.write("".join(file)) | |
222 | print("Wrote %s" % rcfile) | |
223 | else: | |
224 | print("No changes made, %s left unchanged" % rcfile) | |
149bc23e AS |
225 | |
226 | def do_login(self, line): | |
227 | """Login.""" | |
228 | if line != "": | |
1882b5bc AS |
229 | self.onecmd("account %s" % line) |
230 | if self.username == None or self.pod == None: | |
ebc699fd | 231 | print("Use the 'account' command to set username and pod.") |
1882b5bc | 232 | elif self.password == None: |
ebc699fd | 233 | print("Use the 'password' command.") |
1882b5bc | 234 | else: |
1860d749 | 235 | print("Setting up a connection...") |
1882b5bc AS |
236 | self.connection = diaspy.connection.Connection( |
237 | pod = "https://%s" % self.pod, username = self.username, password = self.password) | |
238 | try: | |
1860d749 | 239 | print("Logging in...") |
1882b5bc | 240 | self.connection.login() |
1882b5bc | 241 | except diaspy.errors.LoginError: |
ebc699fd | 242 | print("Login failed.") |
149bc23e | 243 | |
6328099f AS |
244 | def do_pager(self, pager): |
245 | """Set the pager, e.g. to cat""" | |
246 | self.pager = pager | |
247 | print("Pager set: %s" % self.pager) | |
248 | ||
ebc699fd AS |
249 | def do_editor(self, editor): |
250 | """Set the editor, e.g. to ed""" | |
251 | self.editor = editor | |
252 | print("Editor set: %s" % self.editor) | |
253 | ||
8bf63019 AS |
254 | def header(self, line): |
255 | """Wrap line in header format.""" | |
256 | return self.header_format % line | |
257 | ||
149bc23e | 258 | def do_notifications(self, line): |
bb8316e5 AS |
259 | """List notifications. Use 'notifications reload' to reload them.""" |
260 | if line == "" and self.notifications: | |
261 | print("Redisplaying the notifications in the cache.") | |
262 | print("Use the 'reload' argument to reload them.") | |
263 | elif line == "reload" or not self.notifications: | |
264 | if self.connection == None: | |
265 | print("Use the 'login' command, first.") | |
266 | return | |
267 | self.notifications = diaspy.notifications.Notifications(self.connection).last() | |
ebc699fd AS |
268 | else: |
269 | print("The 'notifications' command only takes the optional argument 'reload'.") | |
270 | return | |
bb8316e5 AS |
271 | if self.notifications: |
272 | for n, notification in enumerate(self.notifications): | |
82f0d747 AS |
273 | if notification.unread: |
274 | print(self.header("%2d. %s %s") % (n+1, notification.when(), notification)) | |
275 | else: | |
276 | print("%2d. %s %s" % (n+1, notification.when(), notification)) | |
bb8316e5 | 277 | print("Enter a number to select the notification.") |
bec46251 | 278 | self.numbers_refer_to = 'notifications' |
bb8316e5 AS |
279 | else: |
280 | print("There are no notifications. 😢") | |
149bc23e AS |
281 | |
282 | ### The end! | |
283 | def do_quit(self, *args): | |
284 | """Exit jan-pona-mute.""" | |
285 | print("Be safe!") | |
286 | sys.exit() | |
287 | ||
288 | def default(self, line): | |
289 | if line.strip() == "EOF": | |
290 | return self.onecmd("quit") | |
291 | ||
292 | # Expand abbreviated commands | |
293 | first_word = line.split()[0].strip() | |
82f0d747 AS |
294 | if first_word in shortcuts: |
295 | full_cmd = shortcuts[first_word] | |
149bc23e AS |
296 | expanded = line.replace(first_word, full_cmd, 1) |
297 | return self.onecmd(expanded) | |
298 | ||
bb8316e5 AS |
299 | # Finally, see if it's a notification and show it |
300 | self.do_show(line) | |
1882b5bc | 301 | |
bb8316e5 | 302 | def do_show(self, line): |
18e74209 | 303 | """Show the post given by the index number. |
bec46251 | 304 | The index number must refer to the current list of notifications |
1ff8fa2c AS |
305 | or the home stream. If no index number is given, show the current |
306 | post again.""" | |
bec46251 AS |
307 | if not self.notifications and not self.home: |
308 | print("Use the 'login' command to load notifications.") | |
bb8316e5 | 309 | return |
1ff8fa2c | 310 | if line == "" and self.post == None: |
bec46251 | 311 | print("Please specify a number.") |
bb8316e5 | 312 | return |
1ff8fa2c AS |
313 | if line != "": |
314 | try: | |
315 | n = int(line.strip()) | |
316 | if self.numbers_refer_to == 'notifications': | |
317 | notification = self.notifications[n-1] | |
318 | self.show(notification) | |
319 | self.load(notification.about()) | |
320 | elif self.numbers_refer_to == 'home': | |
321 | self.post = self.home[n-1] | |
322 | else: | |
323 | print("Internal error: not sure what numbers '%s' refer to." % self.numbers_refer_to) | |
324 | return | |
325 | except ValueError: | |
326 | print("The 'show' command takes a notification number but '%s' is not a number" % line) | |
327 | return | |
328 | except IndexError: | |
329 | print("Index too high!") | |
bec46251 | 330 | return |
1882b5bc | 331 | |
18e74209 AS |
332 | print() |
333 | self.show(self.post) | |
1882b5bc | 334 | |
bb8316e5 AS |
335 | if(self.post.comments): |
336 | print() | |
88c1a828 AS |
337 | if len(self.post.comments) == 1: |
338 | print("There is 1 comment.") | |
339 | else: | |
340 | print("There are %d comments." % len(self.post.comments)) | |
bb8316e5 AS |
341 | print("Use the 'comments' command to list the latest comments.") |
342 | ||
343 | def load(self, id): | |
344 | """Load the post belonging to the id (from a notification), | |
345 | or get it from the cache.""" | |
346 | if id in self.post_cache: | |
347 | self.post = self.post_cache[id] | |
ebc699fd | 348 | print("Retrieved post from the cache.") |
bb8316e5 AS |
349 | else: |
350 | print("Loading...") | |
351 | self.post = diaspy.models.Post(connection = self.connection, id = id) | |
352 | self.post_cache[id] = self.post | |
353 | return self.post | |
354 | ||
355 | def do_reload(self, line): | |
356 | """Reload the current post.""" | |
357 | if self.post == None: | |
358 | print("Use the 'show' command to show a post, first.") | |
359 | return | |
360 | print("Reloading...") | |
361 | self.post = diaspy.models.Post(connection = self.connection, id = self.post.id) | |
362 | self.post_cache[id] = self.post | |
363 | ||
1882b5bc AS |
364 | def show(self, item): |
365 | """Show the current item.""" | |
6328099f | 366 | if self.pager: |
88c1a828 | 367 | subprocess.run(self.pager, input = str(item), text = True) |
6328099f | 368 | else: |
88c1a828 | 369 | print(str(item)) |
6328099f | 370 | |
18e74209 | 371 | def do_comments(self, line): |
bb8316e5 AS |
372 | """Show the comments for the current post. |
373 | Use the 'all' argument to show them all. Use a numerical argument to | |
374 | show that many. The default is to load the last five.""" | |
18e74209 | 375 | if self.post == None: |
bb8316e5 | 376 | print("Use the 'show' command to show a post, first.") |
18e74209 AS |
377 | return |
378 | if self.post.comments == None: | |
379 | print("The current post has no comments.") | |
380 | return | |
381 | ||
382 | n = 5 | |
383 | comments = self.post.comments | |
384 | ||
385 | if line == "all": | |
386 | n = None | |
387 | elif line != "": | |
6328099f AS |
388 | try: |
389 | n = int(line.strip()) | |
390 | except ValueError: | |
ebc699fd AS |
391 | print("The 'comments' command takes a number as its argument, or 'all'.") |
392 | print("The default is to show the last 5 comments.") | |
6328099f | 393 | return |
6328099f | 394 | |
ebc699fd AS |
395 | if n == None: |
396 | start = 0 | |
397 | else: | |
398 | # n is from the back | |
399 | start = max(len(comments) - n, 0) | |
18e74209 | 400 | |
42716c49 | 401 | if comments: |
ebc699fd | 402 | for n, comment in enumerate(comments[start:], start): |
42716c49 AS |
403 | print() |
404 | print(self.header("%2d. %s %s") % (n+1, comment.when(), comment.author())) | |
405 | print() | |
406 | self.show(comment) | |
407 | else: | |
408 | print("There are no comments on the selected post.") | |
1882b5bc | 409 | |
714a2f67 | 410 | def do_comment(self, line): |
ebc699fd AS |
411 | """Leave a comment on the current post. |
412 | If you just use a number as your comment, it will refer to a note. | |
413 | Use the 'edit' command to edit notes.""" | |
714a2f67 | 414 | if self.post == None: |
bb8316e5 | 415 | print("Use the 'show' command to show a post, first.") |
714a2f67 | 416 | return |
ebc699fd | 417 | try: |
9200e1e1 | 418 | # if the comment is just a number, use a note to post |
ebc699fd AS |
419 | n = int(line.strip()) |
420 | notes = self.get_notes() | |
421 | if notes: | |
422 | try: | |
7e2b259d | 423 | line = self.read_note(notes[n-1]) |
ebc699fd AS |
424 | print("Using note #%d: %s" % (n, notes[n-1])) |
425 | except IndexError: | |
426 | print("Use the 'list notes' command to list valid numbers.") | |
427 | return | |
428 | else: | |
429 | print("There are no notes to use.") | |
430 | return | |
431 | except ValueError: | |
9200e1e1 AS |
432 | # in which case we'll simply comment with the line |
433 | pass | |
714a2f67 AS |
434 | comment = self.post.comment(line) |
435 | self.post.comments.add(comment) | |
ebc699fd | 436 | self.undo.append("delete comment %s from %s" % (comment.id, self.post.id)) |
714a2f67 AS |
437 | print("Comment posted.") |
438 | ||
1ff8fa2c AS |
439 | def do_post(self, line): |
440 | """Write a post on the current stream. | |
441 | If you just use a number as your post, it will refer to a note. | |
442 | Use the 'edit' command to edit notes.""" | |
443 | if self.home == None: | |
444 | self.home = diaspy.streams.Stream(self.connection) | |
445 | try: | |
446 | # if the post is just a number, use a note to post | |
447 | n = int(line.strip()) | |
448 | notes = self.get_notes() | |
449 | if notes: | |
450 | try: | |
451 | line = self.read_note(notes[n-1]) | |
452 | print("Using note #%d: %s" % (n, notes[n-1])) | |
453 | except IndexError: | |
454 | print("Use the 'list notes' command to list valid numbers.") | |
455 | return | |
456 | else: | |
457 | print("There are no notes to use.") | |
458 | return | |
459 | except ValueError: | |
460 | # in which case we'll simply post the line | |
461 | pass | |
462 | self.post = self.home.post(text = line) | |
463 | self.post_cache[self.post.id] = self.post | |
464 | self.undo.append("delete post %s" % self.post.id) | |
465 | print("Posted. Use the 'show' command to show it.") | |
466 | ||
f1bfb7bc AS |
467 | def do_delete(self, line): |
468 | """Delete a comment.""" | |
469 | words = line.strip().split() | |
470 | if words: | |
471 | if words[0] == "comment": | |
ebc699fd | 472 | if len(words) == 4: |
b5d7f34e AS |
473 | post = self.post_cache[words[3]] |
474 | post.delete_comment(words[1]) | |
475 | comments = [c.id for c in post.comments if c.id != id] | |
476 | post.comments = diaspy.models.Comments(comments) | |
ebc699fd AS |
477 | print("Comment deleted.") |
478 | return | |
b5d7f34e AS |
479 | if self.post == None: |
480 | print("Use the 'show' command to show a post, first.") | |
481 | return | |
ebc699fd AS |
482 | if len(words) == 2: |
483 | try: | |
484 | n = int(words[1]) | |
485 | comment = self.post.comments[n-1] | |
486 | id = comment.id | |
487 | except ValueError: | |
488 | print("Deleting a comment requires an integer.") | |
489 | return | |
490 | except IndexError: | |
491 | print("Use the 'comments' command to find valid comment numbers.") | |
492 | return | |
493 | # not catching any errors from diaspy | |
494 | self.post.delete_comment(id) | |
495 | # there is no self.post.comments.remove(id) | |
496 | comments = [c.id for c in self.post.comments if c.id != id] | |
497 | self.post.comments = diaspy.models.Comments(comments) | |
498 | print("Comment deleted.") | |
499 | return | |
500 | else: | |
501 | print("Deleting a comment requires a comment id and a post id, or a number.") | |
bb8316e5 | 502 | print("delete comment <comment id> from <post id>") |
ebc699fd AS |
503 | print("delete comment 5") |
504 | return | |
505 | if words[0] == "note": | |
506 | if len(words) != 2: | |
507 | print("Deleting a note requires a number.") | |
508 | return | |
509 | try: | |
510 | n = int(words[1]) | |
511 | except ValueError: | |
512 | print("Deleting a note requires an integer.") | |
f1bfb7bc | 513 | return |
ebc699fd AS |
514 | notes = self.get_notes() |
515 | if notes: | |
516 | try: | |
517 | os.unlink(self.get_note_path(notes[n-1])) | |
518 | print("Deleted note #%d: %s" % (n, notes[n-1])) | |
519 | except IndexError: | |
520 | print("Use the 'list notes' command to list valid numbers.") | |
521 | else: | |
522 | print("There are no notes to delete.") | |
f1bfb7bc | 523 | else: |
ebc699fd | 524 | print("Things to delete: comment, note.") |
f1bfb7bc AS |
525 | return |
526 | else: | |
527 | print("Delete what?") | |
528 | ||
529 | def do_undo(self, line): | |
530 | """Undo an action.""" | |
531 | if line != "": | |
ebc699fd | 532 | print("The 'undo' command does not take an argument.") |
f1bfb7bc AS |
533 | return |
534 | if not self.undo: | |
535 | print("There is nothing to undo.") | |
536 | return | |
537 | return self.onecmd(self.undo.pop()) | |
538 | ||
ebc699fd AS |
539 | def do_edit(self, line): |
540 | """Edit a note with a given name.""" | |
541 | if line == "": | |
542 | print("Edit takes the name of a note as an argument.") | |
543 | return | |
544 | file = self.get_note_path(line) | |
545 | if self.editor: | |
546 | subprocess.run([self.editor, file]) | |
cc59946e | 547 | self.onecmd("notes") |
ebc699fd AS |
548 | else: |
549 | print("Use the 'editor' command to set an editor.") | |
550 | ||
551 | def do_notes(self, line): | |
552 | """List notes""" | |
553 | if line != "": | |
554 | print("The 'notes' command does not take an argument.") | |
555 | return | |
556 | notes = self.get_notes() | |
557 | if notes: | |
558 | for n, note in enumerate(notes): | |
559 | print(self.header("%2d. %s") % (n+1, note)) | |
1ff8fa2c AS |
560 | print("Use the 'edit' command to edit a note.") |
561 | print("Use the 'preview' command to look at a note.") | |
562 | print("Use the 'post' command to post a note.") | |
563 | print("Use the 'comment' command to post a comment.") | |
ebc699fd | 564 | else: |
1ff8fa2c | 565 | print("Use 'edit' to create a note.") |
ebc699fd AS |
566 | |
567 | def get_notes(self): | |
568 | """Get the list of notes.""" | |
569 | return os.listdir(get_notes_dir()) | |
570 | ||
571 | def get_note_path(self, filename): | |
572 | """Get the correct path for a note.""" | |
573 | return os.path.join(get_notes_dir(), filename) | |
574 | ||
7e2b259d AS |
575 | def read_note(self, filename): |
576 | """Get text of a note.""" | |
577 | with open(self.get_note_path(filename), mode = 'r', encoding = 'utf-8') as fp: | |
578 | return fp.read() | |
579 | ||
f38b656c AS |
580 | def do_preview(self, line): |
581 | """Preview a note using your pager. | |
582 | Use the 'pager' command to set your pager to something like 'mdcat'.""" | |
583 | if line == "": | |
584 | print("The 'preview' command the number of a note as an argument.") | |
585 | print("Use the 'notes' command to list all your notes.") | |
586 | return | |
587 | try: | |
588 | n = int(line.strip()) | |
589 | notes = self.get_notes() | |
590 | if notes: | |
591 | try: | |
592 | self.show(self.read_note(notes[n-1])) | |
593 | except IndexError: | |
594 | print("Use the 'list notes' command to list valid numbers.") | |
595 | return | |
596 | else: | |
597 | print("There are no notes to preview.") | |
598 | return | |
599 | except ValueError: | |
600 | print("The 'preview' command takes a number as its argument.") | |
601 | return | |
602 | ||
bec46251 AS |
603 | def do_home(self, line): |
604 | """Show the main stream containing the combined posts of the | |
605 | followed users and tags and the community spotlights posts if | |
606 | the user enabled those.""" | |
607 | if line == "": | |
608 | if self.home: | |
609 | print("Redisplaying the cached statuses of the home stream.") | |
610 | print("Use the 'reload' argument to reload them.") | |
611 | print("Use the 'all' argument to show them all.") | |
612 | print("Use a number to show only that many.") | |
613 | print("The default is 5.") | |
614 | else: | |
615 | print("Loading...") | |
616 | self.home = diaspy.streams.Stream(self.connection) | |
617 | self.home.fill() | |
618 | for post in self.home: | |
619 | if post.id not in self.post_cache: | |
620 | self.post_cache[post.id] = post | |
621 | elif line == "reload": | |
622 | if self.connection == None: | |
623 | print("Use the 'login' command, first.") | |
624 | return | |
625 | if self.home: | |
626 | print("Reloading...") | |
627 | self.home.update() | |
628 | line = "" | |
629 | else: | |
630 | self.home = diaspy.streams.Stream(self.connection) | |
631 | self.home.fill() | |
632 | ||
633 | n = 5 | |
1ff8fa2c | 634 | posts = sorted(self.home, key=lambda x: x.data()["created_at"]) |
bec46251 AS |
635 | |
636 | if line == "all": | |
637 | n = None | |
638 | elif line != "": | |
639 | try: | |
640 | n = int(line.strip()) | |
641 | except ValueError: | |
642 | print("The 'home' command takes a number as its argument, or 'reload' or 'all'.") | |
643 | print("The default is to show the last 5 posts.") | |
644 | return | |
645 | ||
646 | if n == None: | |
647 | start = 0 | |
648 | else: | |
649 | # n is from the back | |
650 | start = max(len(posts) - n, 0) | |
651 | ||
652 | if posts: | |
653 | for n, post in enumerate(posts[start:], start): | |
654 | print() | |
655 | print(self.header("%2d. %s %s") % (n+1, post.data()["created_at"], post.author())) | |
656 | print() | |
657 | self.show(post) | |
658 | ||
659 | print("Enter a number to select the post.") | |
660 | self.numbers_refer_to = 'home' | |
661 | else: | |
662 | print("The people you follow have nothing to say.") | |
663 | print("The tags you follow are empty. 😢") | |
664 | ||
82f0d747 AS |
665 | def do_shortcuts(self, line): |
666 | """List all shortcuts.""" | |
667 | if line != "": | |
668 | print("The 'shortcuts' command does not take an argument.") | |
669 | return | |
670 | print(self.header("Shortcuts")) | |
671 | for shortcut in sorted(shortcuts): | |
672 | print("%s\t%s" % (shortcut, shortcuts[shortcut])) | |
673 | print("Use the 'shortcut' command to change or add shortcuts.") | |
674 | ||
675 | def do_shortcut(self, line): | |
676 | """Define a new shortcut.""" | |
677 | words = line.strip().split(maxsplit = 1) | |
678 | if len(words) == 0: | |
679 | return self.onecmd("shortcuts") | |
680 | elif len(words) == 1: | |
681 | shortcut = words[0] | |
682 | if shortcut in shortcuts: | |
683 | print("%s\t%s" % (shortcut, shortcuts[shortcut])) | |
684 | else: | |
685 | print("%s is not a shortcut" % shortcut) | |
686 | else: | |
687 | shortcuts[words[0]] = words[1] | |
688 | ||
149bc23e AS |
689 | # Main function |
690 | def main(): | |
691 | ||
692 | # Parse args | |
693 | parser = argparse.ArgumentParser(description='A command line Diaspora client.') | |
1882b5bc AS |
694 | parser.add_argument('--no-init-file', dest='init_file', action='store_const', |
695 | const=False, default=True, help='Do not load a init file') | |
149bc23e AS |
696 | args = parser.parse_args() |
697 | ||
698 | # Instantiate client | |
1882b5bc AS |
699 | c = DiasporaClient() |
700 | ||
701 | # Process init file | |
6328099f | 702 | seen_pager = False |
ebc699fd | 703 | seen_editor = False |
1882b5bc AS |
704 | if args.init_file: |
705 | rcfile = get_rcfile() | |
706 | if rcfile: | |
707 | print("Using init file %s" % rcfile) | |
708 | with open(rcfile, "r") as fp: | |
709 | for line in fp: | |
710 | line = line.strip() | |
6328099f AS |
711 | if line != "": |
712 | c.cmdqueue.append(line) | |
713 | if not seen_pager: | |
714 | seen_pager = line.startswith("pager "); | |
ebc699fd AS |
715 | if not seen_editor: |
716 | seen_editor = line.startswith("editor "); | |
1882b5bc | 717 | else: |
ebc699fd | 718 | print("Use the 'save' command to save your login sequence to an init file.") |
149bc23e | 719 | |
6328099f AS |
720 | if not seen_pager: |
721 | # prepend | |
722 | c.cmdqueue.insert(0, "pager %s" % get_pager()) | |
ebc699fd AS |
723 | if not seen_editor: |
724 | # prepend | |
725 | c.cmdqueue.insert(0, "editor %s" % get_editor()) | |
6328099f | 726 | |
149bc23e AS |
727 | # Endless interpret loop |
728 | while True: | |
729 | try: | |
730 | c.cmdloop() | |
731 | except KeyboardInterrupt: | |
732 | print("") | |
733 | ||
734 | if __name__ == '__main__': | |
735 | main() |