console.py - toot - Unnamed repository; edit this file 'description' to name th… | |
Log | |
Files | |
Refs | |
LICENSE | |
--- | |
console.py (9208B) | |
--- | |
1 # -*- coding: utf-8 -*- | |
2 | |
3 import os | |
4 import sys | |
5 import logging | |
6 | |
7 from argparse import ArgumentParser, FileType | |
8 from collections import namedtuple | |
9 from toot import config, commands, CLIENT_NAME, CLIENT_WEBSITE | |
10 from toot.exceptions import ApiError, ConsoleError | |
11 from toot.output import print_out, print_err | |
12 | |
13 VISIBILITY_CHOICES = ['public', 'unlisted', 'private', 'direct'] | |
14 | |
15 | |
16 def visibility(value): | |
17 """Validates the visibilty parameter""" | |
18 if value not in VISIBILITY_CHOICES: | |
19 raise ValueError("Invalid visibility value") | |
20 | |
21 return value | |
22 | |
23 | |
24 Command = namedtuple("Command", ["name", "description", "require_auth", … | |
25 | |
26 | |
27 common_args = [ | |
28 (["--no-color"], { | |
29 "help": "don't use ANSI colors in output", | |
30 "action": 'store_true', | |
31 "default": False, | |
32 }), | |
33 (["--debug"], { | |
34 "help": "show debug log in console", | |
35 "action": 'store_true', | |
36 "default": False, | |
37 }) | |
38 ] | |
39 | |
40 account_arg = (["account"], { | |
41 "help": "account name, e.g. '[email protected]'", | |
42 }) | |
43 | |
44 instance_arg = (["-i", "--instance"], { | |
45 "type": str, | |
46 "help": 'mastodon instance to log into e.g. "mastodon.social"', | |
47 }) | |
48 | |
49 email_arg = (["-e", "--email"], { | |
50 "type": str, | |
51 "help": 'email address to log in with', | |
52 }) | |
53 | |
54 | |
55 AUTH_COMMANDS = [ | |
56 Command( | |
57 name="login", | |
58 description="Log in from the console, does NOT support two facto… | |
59 arguments=[instance_arg, email_arg], | |
60 require_auth=False, | |
61 ), | |
62 Command( | |
63 name="login_browser", | |
64 description="Log in using your browser, supports regular and two… | |
65 arguments=[instance_arg], | |
66 require_auth=False, | |
67 ), | |
68 Command( | |
69 name="activate", | |
70 description="Switch between logged in accounts.", | |
71 arguments=[account_arg], | |
72 require_auth=False, | |
73 ), | |
74 Command( | |
75 name="logout", | |
76 description="Log out, delete stored access keys", | |
77 arguments=[account_arg], | |
78 require_auth=False, | |
79 ), | |
80 Command( | |
81 name="auth", | |
82 description="Show logged in accounts and instances", | |
83 arguments=[], | |
84 require_auth=False, | |
85 ), | |
86 ] | |
87 | |
88 READ_COMMANDS = [ | |
89 Command( | |
90 name="whoami", | |
91 description="Display logged in user details", | |
92 arguments=[], | |
93 require_auth=True, | |
94 ), | |
95 Command( | |
96 name="whois", | |
97 description="Display account details", | |
98 arguments=[ | |
99 (["account"], { | |
100 "help": "account name or numeric ID" | |
101 }), | |
102 ], | |
103 require_auth=True, | |
104 ), | |
105 Command( | |
106 name="instance", | |
107 description="Display instance details", | |
108 arguments=[ | |
109 (["instance"], { | |
110 "help": "instance domain (e.g. 'mastodon.social') or bla… | |
111 "nargs": "?", | |
112 }), | |
113 | |
114 ], | |
115 require_auth=False, | |
116 ), | |
117 Command( | |
118 name="search", | |
119 description="Search for users or hashtags", | |
120 arguments=[ | |
121 (["query"], { | |
122 "help": "the search query", | |
123 }), | |
124 (["-r", "--resolve"], { | |
125 "action": 'store_true', | |
126 "default": False, | |
127 "help": "Resolve non-local accounts", | |
128 }), | |
129 ], | |
130 require_auth=True, | |
131 ), | |
132 Command( | |
133 name="timeline", | |
134 description="Show recent items in your public timeline", | |
135 arguments=[ | |
136 (["tag"], { | |
137 "help" : "Search for a tag", | |
138 "nargs" : "?", | |
139 }), | |
140 ], | |
141 require_auth=True, | |
142 ), | |
143 Command( | |
144 name="curses", | |
145 description="An experimental timeline app (doesn't work on Windo… | |
146 arguments=[ | |
147 (["-p", "--public"], { | |
148 "action": 'store_true', | |
149 "default": False, | |
150 "help": "Resolve non-local accounts", | |
151 }), | |
152 (["-i", "--instance"], { | |
153 "type": str, | |
154 "help": 'instance from which to read (for public timelin… | |
155 }) | |
156 ], | |
157 require_auth=False, | |
158 ), | |
159 ] | |
160 | |
161 POST_COMMANDS = [ | |
162 Command( | |
163 name="post", | |
164 description="Post a status text to your timeline", | |
165 arguments=[ | |
166 (["text"], { | |
167 "help": "The status text to post.", | |
168 "nargs": "?", | |
169 }), | |
170 (["-m", "--media"], { | |
171 "type": FileType('rb'), | |
172 "help": "path to the media file to attach" | |
173 }), | |
174 (["-v", "--visibility"], { | |
175 "type": visibility, | |
176 "default": "public", | |
177 "help": 'post visibility, one of: %s' % ", ".join(VISIBI… | |
178 }) | |
179 ], | |
180 require_auth=True, | |
181 ), | |
182 Command( | |
183 name="upload", | |
184 description="Upload an image or video file", | |
185 arguments=[ | |
186 (["file"], { | |
187 "help": "Path to the file to upload", | |
188 "type": FileType('rb') | |
189 }) | |
190 ], | |
191 require_auth=True, | |
192 ), | |
193 ] | |
194 | |
195 ACCOUNTS_COMMANDS = [ | |
196 Command( | |
197 name="follow", | |
198 description="Follow an account", | |
199 arguments=[ | |
200 account_arg, | |
201 ], | |
202 require_auth=True, | |
203 ), | |
204 Command( | |
205 name="unfollow", | |
206 description="Unfollow an account", | |
207 arguments=[ | |
208 account_arg, | |
209 ], | |
210 require_auth=True, | |
211 ), | |
212 Command( | |
213 name="mute", | |
214 description="Mute an account", | |
215 arguments=[ | |
216 account_arg, | |
217 ], | |
218 require_auth=True, | |
219 ), | |
220 Command( | |
221 name="unmute", | |
222 description="Unmute an account", | |
223 arguments=[ | |
224 account_arg, | |
225 ], | |
226 require_auth=True, | |
227 ), | |
228 Command( | |
229 name="block", | |
230 description="Block an account", | |
231 arguments=[ | |
232 account_arg, | |
233 ], | |
234 require_auth=True, | |
235 ), | |
236 Command( | |
237 name="unblock", | |
238 description="Unblock an account", | |
239 arguments=[ | |
240 account_arg, | |
241 ], | |
242 require_auth=True, | |
243 ), | |
244 ] | |
245 | |
246 COMMANDS = AUTH_COMMANDS + READ_COMMANDS + POST_COMMANDS + ACCOUNTS_COMM… | |
247 | |
248 | |
249 def print_usage(): | |
250 max_name_len = max(len(command.name) for command in COMMANDS) | |
251 | |
252 groups = [ | |
253 ("Authentication", AUTH_COMMANDS), | |
254 ("Read", READ_COMMANDS), | |
255 ("Post", POST_COMMANDS), | |
256 ("Accounts", ACCOUNTS_COMMANDS), | |
257 ] | |
258 | |
259 print_out("<green>{}</green>".format(CLIENT_NAME)) | |
260 | |
261 for name, cmds in groups: | |
262 print_out("") | |
263 print_out(name + ":") | |
264 | |
265 for cmd in cmds: | |
266 cmd_name = cmd.name.ljust(max_name_len + 2) | |
267 print_out(" <yellow>toot {}</yellow> {}".format(cmd_name, c… | |
268 | |
269 print_out("") | |
270 print_out("To get help for each command run:") | |
271 print_out(" <yellow>toot <command> --help</yellow>") | |
272 print_out("") | |
273 print_out("<green>{}</green>".format(CLIENT_WEBSITE)) | |
274 | |
275 | |
276 def get_argument_parser(name, command): | |
277 parser = ArgumentParser( | |
278 prog='toot %s' % name, | |
279 description=command.description, | |
280 epilog=CLIENT_WEBSITE) | |
281 | |
282 for args, kwargs in command.arguments + common_args: | |
283 parser.add_argument(*args, **kwargs) | |
284 | |
285 # If the command requires auth, give an option to select account | |
286 if command.require_auth: | |
287 parser.add_argument("-u", "--using", help="the account to use, o… | |
288 | |
289 return parser | |
290 | |
291 | |
292 def run_command(app, user, name, args): | |
293 command = next((c for c in COMMANDS if c.name == name), None) | |
294 | |
295 if not command: | |
296 print_err("Unknown command '{}'\n".format(name)) | |
297 print_usage() | |
298 return | |
299 | |
300 parser = get_argument_parser(name, command) | |
301 parsed_args = parser.parse_args(args) | |
302 | |
303 # Override the active account if 'using' option is given | |
304 if command.require_auth and parsed_args.using: | |
305 user, app = config.get_user_app(parsed_args.using) | |
306 if not user or not app: | |
307 raise ConsoleError("User '{}' not found".format(parsed_args.… | |
308 | |
309 if command.require_auth and (not user or not app): | |
310 print_err("This command requires that you are logged in.") | |
311 print_err("Please run `toot login` first.") | |
312 return | |
313 | |
314 fn = commands.__dict__.get(name) | |
315 | |
316 if not fn: | |
317 raise NotImplementedError("Command '{}' does not have an impleme… | |
318 | |
319 return fn(app, user, parsed_args) | |
320 | |
321 | |
322 def main(): | |
323 # Enable debug logging if --debug is in args | |
324 if "--debug" in sys.argv: | |
325 filename = os.getenv("TOOT_LOG_FILE") | |
326 logging.basicConfig(level=logging.DEBUG, filename=filename) | |
327 | |
328 # If something is piped in, append it to commandline arguments | |
329 if not sys.stdin.isatty(): | |
330 stdin = sys.stdin.read() | |
331 if stdin: | |
332 sys.argv.append(stdin) | |
333 | |
334 command_name = sys.argv[1] if len(sys.argv) > 1 else None | |
335 args = sys.argv[2:] | |
336 | |
337 if not command_name: | |
338 return print_usage() | |
339 | |
340 user, app = config.get_active_user_app() | |
341 | |
342 try: | |
343 run_command(app, user, command_name, args) | |
344 except ConsoleError as e: | |
345 print_err(str(e)) | |
346 sys.exit(1) | |
347 except ApiError as e: | |
348 print_err(str(e)) | |
349 sys.exit(1) |