Print post that belongs to a notification
[jan-pona-mute.git] / jan-pona-mute.py
1 #!/usr/bin/env python3
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
18 import subprocess
19 import argparse
20 import shutil
21 import cmd
22 import sys
23 import os
24
25 # Command abbreviations
26 _ABBREVS = {
27 "q": "quit",
28 "p": "print",
29 }
30
31 _RC_PATHS = (
32 "~/.config/jan-pona-mute/login",
33 "~/.config/.jan-pona-mute",
34 "~/.jan-pona-mute"
35 )
36
37 _PAGERS = (
38 "mdcat",
39 "fold",
40 "cat"
41 )
42
43 # Init file finder
44 def get_rcfile():
45 for rc_path in _RC_PATHS:
46 rcfile = os.path.expanduser(rc_path)
47 if os.path.exists(rcfile):
48 return rcfile
49 return None
50
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
58 class DiasporaClient(cmd.Cmd):
59
60 prompt = "\x1b[38;5;255m" + "> " + "\x1b[0m"
61 intro = "Welcome to Diaspora! Use the intro command for a quick introduction."
62
63 username = None
64 pod = None
65 password = None
66 pager = None
67
68 connection = None
69 things = []
70 index = None
71 thing = None
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
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
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('@')
98 print("Username and pod set: %s@%s" % (self.username, self.pod))
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:")
106 print("Username: %s" % self.username)
107 print("Password: %s" % ("None" if self.password == None else "set"))
108 print("Pod: %s" % self.pod)
109 print("Pager: %s" % self.pager)
110
111 def do_password(self, password):
112 """Set the password."""
113 self.password = (None if self.password == "" else password)
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)
173
174 def do_login(self, line):
175 """Login."""
176 if line != "":
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")
190
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
196 def do_notifications(self, line):
197 """List notifications."""
198 if self.connection == None:
199 print("Use the login command, first.")
200 return
201 self.things = diaspy.notifications.Notifications(self.connection).last()
202 for n, notification in enumerate(self.things):
203 print("%2d. %s %s" % (n+1, notification.when(), notification))
204 print("Enter a number to select the notification.")
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
223 # Try to parse numerical index for lookup table
224 try:
225 n = int(line.strip())
226 except ValueError:
227 print("Use the help command")
228 return
229
230 try:
231 item = self.things[n-1]
232 except IndexError:
233 print("Index too high!")
234 return
235
236 self.thing = item
237 self.index = n
238 self.show(item)
239
240 def show(self, item):
241 """Show the current item."""
242 if self.pager:
243 subprocess.run(self.pager, input = repr(item), text = True)
244 else:
245 print(repr(item))
246
247 def do_print(self, line):
248 """Print more about the current item, or the item at the index given."""
249 n = self.index
250 if line != "":
251 try:
252 n = int(line.strip())
253 except ValueError:
254 print("The print argument takes an index as its argument")
255 return
256 if n == None:
257 print("Select an item by entering it's number")
258 try:
259 item = self.things[n-1]
260 self.index = n
261 except IndexError:
262 print("Index too high!")
263 return
264
265 item = diaspy.models.Post(connection = self.connection, id = item.about())
266 self.show(item)
267
268 # Main function
269 def main():
270
271 # Parse args
272 parser = argparse.ArgumentParser(description='A command line Diaspora client.')
273 parser.add_argument('--no-init-file', dest='init_file', action='store_const',
274 const=False, default=True, help='Do not load a init file')
275 args = parser.parse_args()
276
277 # Instantiate client
278 c = DiasporaClient()
279
280 # Process init file
281 seen_pager = False
282 if args.init_file:
283 rcfile = get_rcfile()
284 if rcfile:
285 print("Using init file %s" % rcfile)
286 with open(rcfile, "r") as fp:
287 for line in fp:
288 line = line.strip()
289 if line != "":
290 c.cmdqueue.append(line)
291 if not seen_pager:
292 seen_pager = line.startswith("pager ");
293 else:
294 print("Use the save command to save your login sequence to an init file")
295
296 if not seen_pager:
297 # prepend
298 c.cmdqueue.insert(0, "pager %s" % get_pager())
299
300 # Endless interpret loop
301 while True:
302 try:
303 c.cmdloop()
304 except KeyboardInterrupt:
305 print("")
306
307 if __name__ == '__main__':
308 main()