Adopted new slack api auth

This commit is contained in:
Seb Seager 2021-11-18 15:49:15 -05:00
parent 4b7fea7ec1
commit 36e9911dfc
4 changed files with 114 additions and 41 deletions

View file

@ -10,15 +10,9 @@ There are two ways to use `slack-exporter` (detailed below). Both require a Slac
1. Visit [https://api.slack.com/apps/](https://api.slack.com/apps/) and sign in to your workspace. 1. Visit [https://api.slack.com/apps/](https://api.slack.com/apps/) and sign in to your workspace.
2. Click `Create New App`, enter a name (e.g., `Slack Exporter`), and select your workspace. 2. Click `Create New App`, enter a name (e.g., `Slack Exporter`), and select your workspace.
3. In the left-hand panel, navigate to `OAuth & Permissions`, and scroll to `User Token Scopes` (**not** `Bot Token Scopes`). 3. In prior versions of the Slack API, OAuth permissions had to be specified manually. Now, when prompted for an App Manifest, just paste in the contents of the `slack.yaml` file in the root of this repo.
4. Select the following permissions: 4. Select `Install to Workspace` at the top of that page (or `Reinstall to Workspace` if you have done this previously) and accept at the prompt.
- `channels:read`, `channels:history` 5. Copy the `OAuth Access Token` (which will generally start with `xoxp` for user-level permissions)
- `groups:read`, `groups:history`
- `mpim:read`, `mpim:history`
- `im:read`, `im:history`
- `users:read`
5. Select `Install to Workspace` at the top of that page (or `Reinstall to Workspace` if you have done this previously) and accept at the prompt.
6. Copy the `OAuth Access Token` (which will generally start with `xoxp` for user-level permissions)
## Usage ## Usage
@ -56,16 +50,14 @@ To use the ngrok method:
2. Run `python bot.py` 2. Run `python bot.py`
3. Run the ngrok binary with `path/to/ngrok http 5000`, where `5000` is the port on which the Flask application (step 2) is running. Copy the forwarding HTTPS address provided. 3. Run the ngrok binary with `path/to/ngrok http 5000`, where `5000` is the port on which the Flask application (step 2) is running. Copy the forwarding HTTPS address provided.
Return to the Slack app you created in [Authentication with Slack](#authentication-with-slack) and navigate to the `Slash Commands` page in the sidebar. Create the following slash commands (one for each applicable Flask route in `bot.py`): 4. Create the following slash commands will be created (one for each applicable Flask route in `bot.py`):
| Command | Request URL | Arguments | Example Usage | | Command | Request URL | Arguments | Example Usage |
|-----------------|-------------------------------------------|--------------|----------------------| |-----------------|-------------------------------------------|--------------|----------------------|
| /export-channel | https://`[host_url]`/slack/export-channel | json \| text | /export-channel text | | /export-channel | https://`[host_url]`/slack/export-channel | json \| text | /export-channel text |
| /export-replies | https://`[host_url]`/slack/export-replies | json \| text | /export-replies json | | /export-replies | https://`[host_url]`/slack/export-replies | json \| text | /export-replies json |
where, if using ngrok, `[domain]` would be replaced with something like `https://xxxxxxxxxxxx.ngrok.io`. To do this, uncomment the `slash-commands` section in `slack.yaml` and replace `YOUR_HOST_URL_HERE` with something like `https://xxxxxxxxxxxx.ngrok.io` (if using ngrok). Then navigate back to `OAuth & Permissions` and click `(Re)install to Workspace` to add these slash commands to the workspace (ensure the OAuth token in your `.env` file is still correct).
Navigate back to `OAuth & Permissions` and click `(Re)install to Workspace` to add these slash commands to the workspace.
## Author ## Author

10
bot.py
View file

@ -113,7 +113,7 @@ def channel_replies(timestamps, channel_id, response_url):
# Flask routes # Flask routes
@app.route("/slack/export-channel", methods=["POST"]) @app.route("/slack/events/export-channel", methods=["POST"])
def export_channel(): def export_channel():
data = request.form data = request.form
@ -168,7 +168,7 @@ def export_channel():
return Response(), 200 return Response(), 200
@app.route("/slack/export-replies", methods=["POST"]) @app.route("/slack/events/export-replies", methods=["POST"])
def export_replies(): def export_replies():
data = request.form data = request.form
@ -187,7 +187,9 @@ def export_replies():
ch_hist = channel_history(ch_id, response_url) ch_hist = channel_history(ch_id, response_url)
print(ch_hist) print(ch_hist)
ch_replies = channel_replies( ch_replies = channel_replies(
[x["ts"] for x in ch_hist if "reply_count" in x], ch_id, response_url [x["ts"] for x in ch_hist if "reply_count" in x],
ch_id,
response_url=response_url,
) )
export_mode = str(command_args).lower() export_mode = str(command_args).lower()
@ -244,4 +246,4 @@ def download(filename):
if __name__ == "__main__": if __name__ == "__main__":
app.run(debug=False) app.run(debug=True)

View file

@ -11,22 +11,49 @@ if os.path.isfile(env_file):
load_dotenv(env_file) load_dotenv(env_file)
# write handling
def post_response(response_url, text):
requests.post(response_url, json={"text": text})
# use this to say anything
# will print to stdout if no response_url is given
# or post_response to given url if provided
def handle_print(text, response_url=None):
if response_url is None:
print(text)
else:
post_response(response_url, text)
# pagination handling # pagination handling
def get_at_cursor(url, params, cursor=None): def get_at_cursor(url, params, cursor=None, response_url=None):
if cursor is not None: if cursor is not None:
params["cursor"] = cursor params["cursor"] = cursor
r = requests.get(url, params=params) # slack api (OAuth 2.0) now requires auth tokens in HTTP Authorization header
if r.status_code != 200: # instead of passing it as a query parameter
print("ERROR: %s %s" % (r.status_code, r.reason)) try:
headers = {"Authorization": "Bearer %s" % os.environ["SLACK_USER_TOKEN"]}
except KeyError:
handle_print("Missing SLACK_USER_TOKEN in environment variables", response_url)
sys.exit(1) sys.exit(1)
r = requests.get(url, headers=headers, params=params)
if r.status_code != 200:
handle_print("ERROR: %s %s" % (r.status_code, r.reason), response_url)
sys.exit(1)
d = r.json() d = r.json()
try: try:
if d["ok"] is False: if d["ok"] is False:
print("I encountered an error: %s" % d) handle_print("I encountered an error: %s" % d, response_url)
sys.exit(1) sys.exit(1)
next_cursor = None next_cursor = None
@ -38,16 +65,26 @@ def get_at_cursor(url, params, cursor=None):
return next_cursor, d return next_cursor, d
except KeyError as e: except KeyError as e:
print("Something went wrong: %s." % e) handle_print("Something went wrong: %s." % e, response_url)
return None, [] return None, []
def paginated_get(url, params, combine_key=None): def paginated_get(url, params, combine_key=None, response_url=None):
next_cursor = None next_cursor = None
result = [] result = []
while True: while True:
next_cursor, data = get_at_cursor(url, params, cursor=next_cursor) next_cursor, data = get_at_cursor(
result.extend(data) if combine_key is None else result.extend(data[combine_key]) url, params, cursor=next_cursor, response_url=response_url
)
try:
result.extend(data) if combine_key is None else result.extend(
data[combine_key]
)
except KeyError:
handle_print("Something went wrong: %s." % e, response_url)
sys.exit(1)
if next_cursor is None: if next_cursor is None:
break break
@ -57,44 +94,57 @@ def paginated_get(url, params, combine_key=None):
# GET requests # GET requests
def channel_list(team_id=None): def channel_list(team_id=None, response_url=None):
params = { params = {
"token": os.environ["SLACK_USER_TOKEN"], # "token": os.environ["SLACK_USER_TOKEN"],
"team_id": team_id, "team_id": team_id,
"types": "public_channel,private_channel,mpim,im", "types": "public_channel,private_channel,mpim,im",
"limit": 200, "limit": 200,
} }
return paginated_get( return paginated_get(
"https://slack.com/api/conversations.list", params, combine_key="channels" "https://slack.com/api/conversations.list",
params,
combine_key="channels",
response_url=response_url,
) )
def channel_history(channel_id): def channel_history(channel_id, response_url=None):
params = { params = {
"token": os.environ["SLACK_USER_TOKEN"], # "token": os.environ["SLACK_USER_TOKEN"],
"channel": channel_id, "channel": channel_id,
"limit": 200, "limit": 200,
} }
return paginated_get( return paginated_get(
"https://slack.com/api/conversations.history", params, combine_key="messages" "https://slack.com/api/conversations.history",
params,
combine_key="messages",
response_url=response_url,
) )
def user_list(team_id=None): def user_list(team_id=None, response_url=None):
params = {"token": os.environ["SLACK_USER_TOKEN"], "limit": 200, "team_id": team_id} params = {
# "token": os.environ["SLACK_USER_TOKEN"],
"limit": 200,
"team_id": team_id,
}
return paginated_get( return paginated_get(
"https://slack.com/api/users.list", params, combine_key="members" "https://slack.com/api/users.list",
params,
combine_key="members",
response_url=response_url,
) )
def channel_replies(timestamps, channel_id): def channel_replies(timestamps, channel_id, response_url=None):
replies = [] replies = []
for timestamp in timestamps: for timestamp in timestamps:
params = { params = {
"token": os.environ["SLACK_USER_TOKEN"], # "token": os.environ["SLACK_USER_TOKEN"],
"channel": channel_id, "channel": channel_id,
"ts": timestamp, "ts": timestamp,
"limit": 200, "limit": 200,
@ -104,6 +154,7 @@ def channel_replies(timestamps, channel_id):
"https://slack.com/api/conversations.replies", "https://slack.com/api/conversations.replies",
params, params,
combine_key="messages", combine_key="messages",
response_url=response_url,
) )
) )

View file

@ -1,8 +1,28 @@
_metadata: _metadata:
major_version: 1 major_version: 1
minor_version: 1
display_information: display_information:
name: Slack Exporter name: Slack Exporter
description: Export publicly visible channel data as text or JSON. description: Export publicly visible channel data as text or JSON.
features:
app_home:
home_tab_enabled: false
messages_tab_enabled: true
messages_tab_read_only_enabled: false
bot_user:
display_name: Slack Exporter
always_online: true
# slash_commands:
# - command: /export-channel
# description: Export the contents of this channel in either text or JSON format
# usage_hint: json|text
# url: https://YOUR_HOST_URL_HERE/slack/export-channel
# should_escape: false
# - command: /export-replies
# description: Export reply threads in either text or JSON format
# usage_hint: json|text
# url: https://YOUR_HOST_URL_HERE/slack/export-replies
# should_escape: false
oauth_config: oauth_config:
scopes: scopes:
user: user:
@ -15,3 +35,11 @@ oauth_config:
- im:read - im:read
- im:history - im:history
- users:read - users:read
bot:
- commands
- chat:write
- chat:write.public
settings:
org_deploy_enabled: false
socket_mode_enabled: false
token_rotation_enabled: false