Show comments
[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
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("""
86Use the account and password commands to set up your connection, then
87use the login command to log in. If everything works as intended, use
88the save command to save these commands to an init file.
89
90Once you've listed things such as notifications, enter a number to
91select 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.
232The 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
287def 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
325if __name__ == '__main__':
326 main()