Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# (c) Stefan Countryman, 2019
3"""
4Send messages or upload files to Slack using LLAMA's built-in methods.
5"""
7import os
8from sys import stdin
9import logging
10from argparse import Namespace
11from llama.cli import get_logging_cli, CliParser
12from llama.com.slack import (
13 SLACK_TOKENS,
14 SLACK_CHANNELS,
15 send_message,
16 get_users,
17 tag_users,
18 client,
19)
21LOGGER = logging.getLogger(__name__)
22USER_ROW_FMT = "{id:<15} {display_name:<23} {real_name:<39}"
23USER_HEADER = USER_ROW_FMT.format(id="ID", display_name="DISPLAY-NAME",
24 real_name="REAL-NAME")
27def postprocess_print_channels(_self: CliParser, namespace: Namespace):
28 """Print available channels for organizations specified in
29 ``namespace.organization`` if ``namespace.print_channels`` is ``True`` and
30 then exit.
31 """
32 if not namespace.print_channels:
33 return
34 print(f"CHANNELS AVAILABLE FOR {namespace.organization}:")
35 for chan in SLACK_CHANNELS[namespace.organization]:
36 print(f"- {chan}")
37 exit()
40def postprocess_print_users(self: CliParser, namespace: Namespace):
41 """Print users who can be tagged for organizations specified in
42 ``namespace.organization`` if ``namespace.print_users`` is ``True`` and
43 then exit.
44 """
45 if not namespace.print_users:
46 return
47 users = [u for u in get_users(namespace.organization)['members']
48 if not u['deleted']]
49 print(f"TAGGABLE USERS AVAILABLE FOR {namespace.organization}\n",
50 "Use ID, DISPLAY-NAME, or REAL-NAME when specifying users.\n",
51 USER_HEADER, '='*len(USER_HEADER), sep='\n')
52 for user in users:
53 print(USER_ROW_FMT.format(id=user['id'],
54 display_name=user['profile']['display_name'],
55 real_name=user['profile']['real_name']))
56 exit()
59def postprocess_check_slack_token(self: CliParser, namespace: Namespace):
60 """Make sure we have a slack token for the specified
61 ``namespace.organization``; if not, error out."""
62 if SLACK_TOKENS[namespace.organization] is None:
63 self.error("Must specify a slack token for organization using "
64 f"environmental variables for {namespace.organization}."
65 "To do this, run `export "
66 f"SLACK_API_TOKEN_{namespace.organization.upper()}="
67 "<your-token>`.")
70def get_parser():
71 """Get CLI Parser."""
72 parser = CliParser(description=__doc__,
73 parents=(get_logging_cli('/dev/null', 'info'),))
74 parser.POSTPROCESSORS += (
75 postprocess_print_channels,
76 postprocess_check_slack_token,
77 postprocess_print_users,
78 )
79 parser.add_argument("channels", metavar='channel', nargs="*", help="""
80 Which Slack channel to upload to.""")
81 parser.add_argument("-o", "--organization", choices=tuple(SLACK_CHANNELS),
82 default="LLAMA", help="""
83 Which oranization to send a message to. (default: LLAMA)""")
84 parser.add_argument("-m", "--message", help="""
85 Text content to send. Can be formatted using Slack's markdown-esque
86 syntax. If not specified, read message from a pipe or send with no
87 message if no pipe is being used.""")
88 parser.add_argument("-u", "--users", default=[], nargs="+", help="""
89 Names of users to tag. You can use real names, display names, or Slack
90 user IDs. They will be alerted to your message if they have access to
91 the channel you are posting to.""")
92 parser.add_argument("-f", "--file", help="""
93 Path to a file to upload as part of the message.""")
94 parser.add_argument("--print-users", action='store_true', help="""
95 Print a list of taggable users for the specified organization and then
96 exit.""")
97 parser.add_argument("--print-channels", action='store_true', help="""
98 Print a list of channels that can be posted to for the specified
99 organization and then exit.""")
100 return parser
103def main():
104 """Run CLI."""
105 parser = get_parser()
106 args = parser.parse_args()
107 if args.message is None:
108 if not stdin.isatty():
109 msg = stdin.read()
110 else:
111 msg = ''
112 else:
113 msg = args.message
114 if args.users:
115 msg = tag_users(args.organization, args.users, recover=False) + msg
116 for chan in args.channels:
117 if args.file:
118 client(SLACK_TOKENS[args.organization]).files_upload(
119 channels=chan,
120 filename=os.path.basename(args.file),
121 file=args.file,
122 initial_comment=msg,
123 )
124 else:
125 send_message(
126 organization=args.organization,
127 message=msg,
128 channel=chan,
129 recover=False
130 )