console.py - toot - Unnamed repository; edit this file 'description' to name the repository.
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) 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", "arguments"])
           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. 'Gargron@mastodon.social'",
           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 factor authentication",
           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 factor authentication",
           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 blank to use current",
          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 Windows)",
          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 timeline only)',
          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(VISIBILITY_CHOICES),
          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_COMMANDS
          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, cmd.description))
          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, overrides active account")
          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.using))
          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 implementation.".format(name))
          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)