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