Add doc strings
[jan-pona-mute.git] / jan-pona-mute.py
CommitLineData
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
17import diaspy
6328099f 18import subprocess
149bc23e 19import argparse
6328099f 20import shutil
149bc23e
AS
21import cmd
22import sys
23import 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 44def 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
52def get_pager():
53 for cmd in _PAGERS:
54 pager = shutil.which(cmd)
55 if pager != None:
56 return pager
57
149bc23e
AS
58class 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("""
89Use the account and password commands to set up your connection, then
90use the login command to log in. If everything works as intended, use
91the save command to save these commands to an init file.
92
93Once you've listed things such as notifications, enter a number to
94select 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.
240The 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
295def 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
333if __name__ == '__main__':
334 main()