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 | ||
25 | # Command abbreviations | |
26 | _ABBREVS = { | |
27 | "q": "quit", | |
6328099f | 28 | "p": "print", |
149bc23e AS |
29 | } |
30 | ||
1882b5bc AS |
31 | _RC_PATHS = ( |
32 | "~/.config/jan-pona-mute/login", | |
33 | "~/.config/.jan-pona-mute", | |
34 | "~/.jan-pona-mute" | |
35 | ) | |
36 | ||
6328099f AS |
37 | _PAGERS = ( |
38 | "mdcat", | |
39 | "fold", | |
40 | "cat" | |
41 | ) | |
42 | ||
1882b5bc | 43 | # Init file finder |
149bc23e | 44 | def get_rcfile(): |
1882b5bc | 45 | for rc_path in _RC_PATHS: |
149bc23e AS |
46 | rcfile = os.path.expanduser(rc_path) |
47 | if os.path.exists(rcfile): | |
48 | return rcfile | |
49 | return None | |
50 | ||
6328099f AS |
51 | # Pager finder |
52 | def get_pager(): | |
53 | for cmd in _PAGERS: | |
54 | pager = shutil.which(cmd) | |
55 | if pager != None: | |
56 | return pager | |
57 | ||
149bc23e AS |
58 | class DiasporaClient(cmd.Cmd): |
59 | ||
60 | prompt = "\x1b[38;5;255m" + "> " + "\x1b[0m" | |
6328099f | 61 | intro = "Welcome to Diaspora! Use the intro command for a quick introduction." |
149bc23e | 62 | |
8bf63019 AS |
63 | header_format = "\x1b[1;38;5;255m" + "%s" + "\x1b[0m" |
64 | ||
149bc23e AS |
65 | username = None |
66 | pod = None | |
67 | password = None | |
6328099f | 68 | pager = None |
149bc23e AS |
69 | |
70 | connection = None | |
18e74209 | 71 | notifications = [] |
1882b5bc | 72 | index = None |
18e74209 | 73 | post = None |
149bc23e AS |
74 | |
75 | # dict mapping user ids to usernames | |
76 | users = {} | |
77 | ||
78 | def get_username(self, guid): | |
79 | if guid in self.users: | |
80 | return self.users[guid] | |
81 | else: | |
82 | user = diaspy.people.User(connection = self.connection, guid = guid) | |
83 | self.users[guid] = user.handle() | |
84 | return self.users[guid] | |
85 | ||
6328099f | 86 | def do_intro(self, line): |
d87d6adf | 87 | """Start here.""" |
6328099f AS |
88 | print(""" |
89 | Use the account and password commands to set up your connection, then | |
90 | use the login command to log in. If everything works as intended, use | |
91 | the save command to save these commands to an init file. | |
92 | ||
93 | Once you've listed things such as notifications, enter a number to | |
94 | select the corresponding item. Use the print command to see more. | |
95 | """) | |
96 | ||
149bc23e AS |
97 | def do_account(self, account): |
98 | """Set username and pod using the format username@pod.""" | |
99 | try: | |
100 | (self.username, self.pod) = account.split('@') | |
1882b5bc | 101 | print("Username and pod set: %s@%s" % (self.username, self.pod)) |
149bc23e AS |
102 | except ValueError: |
103 | print("The account must contain an @ character, e.g. kensanata@pluspora.com.") | |
104 | print("Use the account comand to set the account.") | |
105 | ||
106 | def do_info(self, line): | |
107 | """Get some info about things. By default, it is info about yourself.""" | |
108 | print("Info about yourself:") | |
1882b5bc AS |
109 | print("Username: %s" % self.username) |
110 | print("Password: %s" % ("None" if self.password == None else "set")) | |
111 | print("Pod: %s" % self.pod) | |
6328099f | 112 | print("Pager: %s" % self.pager) |
149bc23e AS |
113 | |
114 | def do_password(self, password): | |
115 | """Set the password.""" | |
116 | self.password = (None if self.password == "" else password) | |
1882b5bc AS |
117 | print("Password %s" % ("unset" if self.password == "" else "set")) |
118 | ||
119 | def do_save(self, line): | |
d87d6adf | 120 | """Save your login information to the init file.""" |
1882b5bc AS |
121 | if self.username == None or self.pod == None: |
122 | print("Use the account command to set username and pod") | |
123 | elif self.password == None: | |
124 | print("Use the password command") | |
125 | else: | |
126 | rcfile = get_rcfile() | |
127 | if rcfile == None: | |
128 | rfile = first(_RC_PATHS) | |
129 | seen_account = False | |
130 | seen_password = False | |
131 | seen_login = False | |
132 | changed = False | |
133 | file = [] | |
134 | with open(rcfile, "r") as fp: | |
135 | for line in fp: | |
136 | words = line.strip().split() | |
137 | if words: | |
138 | if words[0] == "account": | |
139 | seen_account = True | |
140 | account = "%s@%s" % (self.username, self.pod) | |
141 | if len(words) > 1 and words[1] != account: | |
142 | line = "account %s\n" % account | |
143 | changed = True | |
144 | elif words[0] == "password": | |
145 | seen_password = True | |
146 | if len(words) > 1 and words[1] != self.password: | |
147 | line = "password %s\n" % self.password | |
148 | changed = True | |
149 | elif words[0] == "login": | |
150 | if seen_account and seen_password: | |
151 | seen_login = True | |
152 | else: | |
153 | # skip login if no account or no password given | |
154 | line = None | |
155 | changed = True | |
156 | if line != None: | |
157 | file.append(line) | |
158 | if not seen_account: | |
159 | file.append("account %s@%s\n" % (self.username, self.pod)) | |
160 | changed = True | |
161 | if not seen_password: | |
162 | file.append("password %s\n" % self.password) | |
163 | changed = True | |
164 | if not seen_login: | |
165 | file.append("login\n") | |
166 | changed = True | |
167 | if changed: | |
168 | if os.path.isfile(rcfile): | |
169 | os.rename(rcfile, rcfile + "~") | |
170 | if not os.path.isdir(os.path.dirname(rcfile)): | |
171 | os.makedirs(os.path.dirname(rcfile)) | |
172 | with open(rcfile, "w") as fp: | |
173 | fp.write("".join(file)) | |
174 | print("Wrote %s" % rcfile) | |
175 | else: | |
176 | print("No changes made, %s left unchanged" % rcfile) | |
149bc23e AS |
177 | |
178 | def do_login(self, line): | |
179 | """Login.""" | |
180 | if line != "": | |
1882b5bc AS |
181 | self.onecmd("account %s" % line) |
182 | if self.username == None or self.pod == None: | |
183 | print("Use the account command to set username and pod") | |
184 | elif self.password == None: | |
185 | print("Use the password command") | |
186 | else: | |
187 | self.connection = diaspy.connection.Connection( | |
188 | pod = "https://%s" % self.pod, username = self.username, password = self.password) | |
189 | try: | |
190 | self.connection.login() | |
191 | self.onecmd("notifications") | |
192 | except diaspy.errors.LoginError: | |
193 | print("Login failed") | |
149bc23e | 194 | |
6328099f AS |
195 | def do_pager(self, pager): |
196 | """Set the pager, e.g. to cat""" | |
197 | self.pager = pager | |
198 | print("Pager set: %s" % self.pager) | |
199 | ||
8bf63019 AS |
200 | def header(self, line): |
201 | """Wrap line in header format.""" | |
202 | return self.header_format % line | |
203 | ||
149bc23e | 204 | def do_notifications(self, line): |
1882b5bc | 205 | """List notifications.""" |
149bc23e AS |
206 | if self.connection == None: |
207 | print("Use the login command, first.") | |
208 | return | |
18e74209 AS |
209 | self.notifications = diaspy.notifications.Notifications(self.connection).last() |
210 | for n, notification in enumerate(self.notifications): | |
8bf63019 | 211 | print(self.header("%2d. %s %s") % (n+1, notification.when(), notification)) |
6328099f | 212 | print("Enter a number to select the notification.") |
149bc23e AS |
213 | |
214 | ### The end! | |
215 | def do_quit(self, *args): | |
216 | """Exit jan-pona-mute.""" | |
217 | print("Be safe!") | |
218 | sys.exit() | |
219 | ||
220 | def default(self, line): | |
221 | if line.strip() == "EOF": | |
222 | return self.onecmd("quit") | |
223 | ||
224 | # Expand abbreviated commands | |
225 | first_word = line.split()[0].strip() | |
226 | if first_word in _ABBREVS: | |
227 | full_cmd = _ABBREVS[first_word] | |
228 | expanded = line.replace(first_word, full_cmd, 1) | |
229 | return self.onecmd(expanded) | |
230 | ||
1882b5bc AS |
231 | try: |
232 | n = int(line.strip()) | |
18e74209 AS |
233 | # Finally, see if it's a notification and show it |
234 | self.do_show(n) | |
1882b5bc | 235 | except ValueError: |
18e74209 | 236 | print("Use the help command to show available commands") |
1882b5bc | 237 | |
18e74209 AS |
238 | def do_show(self, n): |
239 | """Show the post given by the index number. | |
240 | The index number must refer to the current list of notifications.""" | |
1882b5bc | 241 | try: |
18e74209 AS |
242 | notification = self.notifications[n-1] |
243 | self.index = n | |
1882b5bc | 244 | except IndexError: |
6328099f | 245 | print("Index too high!") |
1882b5bc AS |
246 | return |
247 | ||
18e74209 AS |
248 | self.show(notification) |
249 | ||
250 | print("Loading...") | |
251 | self.post = diaspy.models.Post(connection = self.connection, id = notification.about()) | |
252 | ||
253 | print() | |
254 | self.show(self.post) | |
1882b5bc AS |
255 | |
256 | def show(self, item): | |
257 | """Show the current item.""" | |
6328099f AS |
258 | if self.pager: |
259 | subprocess.run(self.pager, input = repr(item), text = True) | |
260 | else: | |
261 | print(repr(item)) | |
262 | ||
18e74209 AS |
263 | def do_comments(self, line): |
264 | """Show the comments for the current post.""" | |
265 | if self.post == None: | |
266 | print("Use the show command to show a post, first.") | |
267 | return | |
268 | if self.post.comments == None: | |
269 | print("The current post has no comments.") | |
270 | return | |
271 | ||
272 | n = 5 | |
273 | comments = self.post.comments | |
274 | ||
275 | if line == "all": | |
276 | n = None | |
277 | elif line != "": | |
6328099f AS |
278 | try: |
279 | n = int(line.strip()) | |
280 | except ValueError: | |
18e74209 AS |
281 | print("The comments command takes a number as its argument, or 'all'") |
282 | print("The default is to show the last 5 comments") | |
6328099f | 283 | return |
6328099f | 284 | |
18e74209 AS |
285 | if n != None: |
286 | comments = comments[-n:] | |
287 | ||
288 | for n, comment in enumerate(comments): | |
289 | print() | |
8bf63019 | 290 | print(self.header("%2d. %s %s") % (n+1, comment.when(), comment.author())) |
18e74209 AS |
291 | print() |
292 | self.show(comment) | |
1882b5bc | 293 | |
149bc23e AS |
294 | # Main function |
295 | def main(): | |
296 | ||
297 | # Parse args | |
298 | parser = argparse.ArgumentParser(description='A command line Diaspora client.') | |
1882b5bc AS |
299 | parser.add_argument('--no-init-file', dest='init_file', action='store_const', |
300 | const=False, default=True, help='Do not load a init file') | |
149bc23e AS |
301 | args = parser.parse_args() |
302 | ||
303 | # Instantiate client | |
1882b5bc AS |
304 | c = DiasporaClient() |
305 | ||
306 | # Process init file | |
6328099f | 307 | seen_pager = False |
1882b5bc AS |
308 | if args.init_file: |
309 | rcfile = get_rcfile() | |
310 | if rcfile: | |
311 | print("Using init file %s" % rcfile) | |
312 | with open(rcfile, "r") as fp: | |
313 | for line in fp: | |
314 | line = line.strip() | |
6328099f AS |
315 | if line != "": |
316 | c.cmdqueue.append(line) | |
317 | if not seen_pager: | |
318 | seen_pager = line.startswith("pager "); | |
1882b5bc AS |
319 | else: |
320 | print("Use the save command to save your login sequence to an init file") | |
149bc23e | 321 | |
6328099f AS |
322 | if not seen_pager: |
323 | # prepend | |
324 | c.cmdqueue.insert(0, "pager %s" % get_pager()) | |
325 | ||
149bc23e AS |
326 | # Endless interpret loop |
327 | while True: | |
328 | try: | |
329 | c.cmdloop() | |
330 | except KeyboardInterrupt: | |
331 | print("") | |
332 | ||
333 | if __name__ == '__main__': | |
334 | main() |