2 # Copyright (C) 2019 Alex Schroeder <alex@gnu.org>
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
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
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/>.
25 # Command abbreviations
33 "~/.config/jan-pona-mute/login",
34 "~/.config/.jan-pona-mute",
46 for rc_path
in _RC_PATHS
:
47 rcfile
= os
.path
.expanduser(rc_path
)
48 if os
.path
.exists(rcfile
):
55 pager
= shutil
.which(cmd
)
59 class DiasporaClient(cmd
.Cmd
):
61 prompt
= "\x1b[38;5;255m" + "> " + "\x1b[0m"
62 intro
= "Welcome to Diaspora! Use the intro command for a quick introduction."
64 header_format
= "\x1b[1;38;5;255m" + "%s" + "\x1b[0m"
75 post_cache
= {} # key is self.post.uid
80 # dict mapping user ids to usernames
83 def get_username(self
, guid
):
84 if guid
in self
.users
:
85 return self
.users
[guid
]
87 user
= diaspy
.people
.User(connection
= self
.connection
, guid
= guid
)
88 self
.users
[guid
] = user
.handle()
89 return self
.users
[guid
]
91 def do_intro(self
, line
):
94 Use the account and password commands to set up your connection, then
95 use the login command to log in. If everything works as intended, use
96 the save command to save these commands to an init file.
98 Once you've listed things such as notifications, enter a number to
99 select the corresponding item. Use the print command to see more.
102 def do_account(self
, account
):
103 """Set username and pod using the format username@pod."""
105 (self
.username
, self
.pod
) = account
.split('@')
106 print("Username and pod set: %s@%s" % (self
.username
, self
.pod
))
108 print("The account must contain an @ character, e.g. kensanata@pluspora.com.")
109 print("Use the account comand to set the account.")
111 def do_info(self
, line
):
112 """Get some info about things. By default, it is info about yourself."""
113 print("Info about yourself:")
114 print("Username: %s" % self
.username
)
115 print("Password: %s" % ("None" if self
.password
== None else "set"))
116 print("Pod: %s" % self
.pod
)
117 print("Pager: %s" % self
.pager
)
119 def do_password(self
, password
):
120 """Set the password."""
121 self
.password
= (None if self
.password
== "" else password
)
122 print("Password %s" % ("unset" if self
.password
== "" else "set"))
124 def do_save(self
, line
):
125 """Save your login information to the init file."""
126 if self
.username
== None or self
.pod
== None:
127 print("Use the account command to set username and pod")
128 elif self
.password
== None:
129 print("Use the password command")
131 rcfile
= get_rcfile()
133 rfile
= first(_RC_PATHS
)
135 seen_password
= False
139 with
open(rcfile
, "r") as fp
:
141 words
= line
.strip().split()
143 if words
[0] == "account":
145 account
= "%s@%s" % (self
.username
, self
.pod
)
146 if len(words
) > 1 and words
[1] != account
:
147 line
= "account %s\n" % account
149 elif words
[0] == "password":
151 if len(words
) > 1 and words
[1] != self
.password
:
152 line
= "password %s\n" % self
.password
154 elif words
[0] == "login":
155 if seen_account
and seen_password
:
158 # skip login if no account or no password given
164 file.append("account %s@%s\n" % (self
.username
, self
.pod
))
166 if not seen_password
:
167 file.append("password %s\n" % self
.password
)
170 file.append("login\n")
173 if os
.path
.isfile(rcfile
):
174 os
.rename(rcfile
, rcfile
+ "~")
175 if not os
.path
.isdir(os
.path
.dirname(rcfile
)):
176 os
.makedirs(os
.path
.dirname(rcfile
))
177 with
open(rcfile
, "w") as fp
:
178 fp
.write("".join(file))
179 print("Wrote %s" % rcfile
)
181 print("No changes made, %s left unchanged" % rcfile
)
183 def do_login(self
, line
):
186 self
.onecmd("account %s" % line
)
187 if self
.username
== None or self
.pod
== None:
188 print("Use the account command to set username and pod")
189 elif self
.password
== None:
190 print("Use the password command")
192 self
.connection
= diaspy
.connection
.Connection(
193 pod
= "https://%s" % self
.pod
, username
= self
.username
, password
= self
.password
)
195 self
.connection
.login()
196 self
.onecmd("notifications")
197 except diaspy
.errors
.LoginError
:
198 print("Login failed")
200 def do_pager(self
, pager
):
201 """Set the pager, e.g. to cat"""
203 print("Pager set: %s" % self
.pager
)
205 def header(self
, line
):
206 """Wrap line in header format."""
207 return self
.header_format
% line
209 def do_notifications(self
, line
):
210 """List notifications."""
211 if self
.connection
== None:
212 print("Use the login command, first.")
214 self
.notifications
= diaspy
.notifications
.Notifications(self
.connection
).last()
215 for n
, notification
in enumerate(self
.notifications
):
216 print(self
.header("%2d. %s %s") % (n
+1, notification
.when(), notification
))
217 print("Enter a number to select the notification.")
220 def do_quit(self
, *args
):
221 """Exit jan-pona-mute."""
225 def default(self
, line
):
226 if line
.strip() == "EOF":
227 return self
.onecmd("quit")
229 # Expand abbreviated commands
230 first_word
= line
.split()[0].strip()
231 if first_word
in _ABBREVS
:
232 full_cmd
= _ABBREVS
[first_word
]
233 expanded
= line
.replace(first_word
, full_cmd
, 1)
234 return self
.onecmd(expanded
)
237 n
= int(line
.strip())
238 # Finally, see if it's a notification and show it
241 print("Use the help command to show available commands")
243 def do_show(self
, n
):
244 """Show the post given by the index number.
245 The index number must refer to the current list of notifications."""
247 notification
= self
.notifications
[n
-1]
250 print("Index too high!")
253 self
.show(notification
)
256 self
.post
= diaspy
.models
.Post(connection
= self
.connection
, id = notification
.about())
257 self
.post_cache
[self
.post
.guid
] = self
.post
262 def show(self
, item
):
263 """Show the current item."""
265 subprocess
.run(self
.pager
, input = repr(item
), text
= True)
269 def do_comments(self
, line
):
270 """Show the comments for the current post."""
271 if self
.post
== None:
272 print("Use the show command to show a post, first.")
274 if self
.post
.comments
== None:
275 print("The current post has no comments.")
279 comments
= self
.post
.comments
285 n
= int(line
.strip())
287 print("The comments command takes a number as its argument, or 'all'")
288 print("The default is to show the last 5 comments")
292 comments
= comments
[-n
:]
294 for n
, comment
in enumerate(comments
):
296 print(self
.header("%2d. %s %s") % (n
+1, comment
.when(), comment
.author()))
300 def do_comment(self
, line
):
301 """Leave a comment on the current post."""
302 if self
.post
== None:
303 print("Use the show command to show a post, first.")
305 comment
= self
.post
.comment(line
)
306 self
.post
.comments
.add(comment
)
307 self
.undo
.append("delete comment %s from %s" % (comment
.id, self
.post
.guid
))
308 print("Comment posted.")
310 def do_delete(self
, line
):
311 """Delete a comment."""
312 words
= line
.strip().split()
314 if words
[0] == "comment":
315 if self
.post
== None:
316 print("Use the show command to show a post, first.")
319 print("Deleting a comment requires a comment id and a post guid.")
320 print("delete comment <id> from <guid>")
322 self
.post_cache
[words
[3]].delete_comment(words
[1])
323 print("Comment deleted.")
325 print("Deleting '%s' is not supported." % words
[0])
328 print("Delete what?")
330 def do_undo(self
, line
):
331 """Undo an action."""
333 print("Undo does not take an argument.")
336 print("There is nothing to undo.")
338 return self
.onecmd(self
.undo
.pop())
344 parser
= argparse
.ArgumentParser(description
='A command line Diaspora client.')
345 parser
.add_argument('--no-init-file', dest
='init_file', action
='store_const',
346 const
=False, default
=True, help='Do not load a init file')
347 args
= parser
.parse_args()
355 rcfile
= get_rcfile()
357 print("Using init file %s" % rcfile
)
358 with
open(rcfile
, "r") as fp
:
362 c
.cmdqueue
.append(line
)
364 seen_pager
= line
.startswith("pager ");
366 print("Use the save command to save your login sequence to an init file")
370 c
.cmdqueue
.insert(0, "pager %s" % get_pager())
372 # Endless interpret loop
376 except KeyboardInterrupt:
379 if __name__
== '__main__':