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.
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`).
4. Select the following permissions:
- `channels:read`, `channels:history`
- `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)
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 `Install to Workspace` at the top of that page (or `Reinstall to Workspace` if you have done this previously) and accept at the prompt.
5. Copy the `OAuth Access Token` (which will generally start with `xoxp` for user-level permissions)
## Usage
@ -56,16 +50,14 @@ To use the ngrok method:
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.
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 |
|-----------------|-------------------------------------------|--------------|----------------------|
| /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 |
| Command | Request URL | Arguments | Example Usage |
|-----------------|-------------------------------------------|--------------|----------------------|
| /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 |
where, if using ngrok, `[domain]` would be replaced with something like `https://xxxxxxxxxxxx.ngrok.io`.
Navigate back to `OAuth & Permissions` and click `(Re)install to Workspace` to add these slash commands to the workspace.
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).
## Author

10
bot.py
View file

@ -113,7 +113,7 @@ def channel_replies(timestamps, channel_id, response_url):
# Flask routes
@app.route("/slack/export-channel", methods=["POST"])
@app.route("/slack/events/export-channel", methods=["POST"])
def export_channel():
data = request.form
@ -168,7 +168,7 @@ def export_channel():
return Response(), 200
@app.route("/slack/export-replies", methods=["POST"])
@app.route("/slack/events/export-replies", methods=["POST"])
def export_replies():
data = request.form
@ -187,7 +187,9 @@ def export_replies():
ch_hist = channel_history(ch_id, response_url)
print(ch_hist)
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()
@ -244,4 +246,4 @@ def download(filename):
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)
# 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
def get_at_cursor(url, params, cursor=None):
def get_at_cursor(url, params, cursor=None, response_url=None):
if cursor is not None:
params["cursor"] = cursor
r = requests.get(url, params=params)
if r.status_code != 200:
print("ERROR: %s %s" % (r.status_code, r.reason))
# slack api (OAuth 2.0) now requires auth tokens in HTTP Authorization header
# instead of passing it as a query parameter
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)
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()
try:
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)
next_cursor = None
@ -38,16 +65,26 @@ def get_at_cursor(url, params, cursor=None):
return next_cursor, d
except KeyError as e:
print("Something went wrong: %s." % e)
handle_print("Something went wrong: %s." % e, response_url)
return None, []
def paginated_get(url, params, combine_key=None):
def paginated_get(url, params, combine_key=None, response_url=None):
next_cursor = None
result = []
while True:
next_cursor, data = get_at_cursor(url, params, cursor=next_cursor)
result.extend(data) if combine_key is None else result.extend(data[combine_key])
next_cursor, data = get_at_cursor(
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:
break
@ -57,44 +94,57 @@ def paginated_get(url, params, combine_key=None):
# GET requests
def channel_list(team_id=None):
def channel_list(team_id=None, response_url=None):
params = {
"token": os.environ["SLACK_USER_TOKEN"],
# "token": os.environ["SLACK_USER_TOKEN"],
"team_id": team_id,
"types": "public_channel,private_channel,mpim,im",
"limit": 200,
}
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 = {
"token": os.environ["SLACK_USER_TOKEN"],
# "token": os.environ["SLACK_USER_TOKEN"],
"channel": channel_id,
"limit": 200,
}
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):
params = {"token": os.environ["SLACK_USER_TOKEN"], "limit": 200, "team_id": team_id}
def user_list(team_id=None, response_url=None):
params = {
# "token": os.environ["SLACK_USER_TOKEN"],
"limit": 200,
"team_id": team_id,
}
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 = []
for timestamp in timestamps:
params = {
"token": os.environ["SLACK_USER_TOKEN"],
# "token": os.environ["SLACK_USER_TOKEN"],
"channel": channel_id,
"ts": timestamp,
"limit": 200,
@ -104,6 +154,7 @@ def channel_replies(timestamps, channel_id):
"https://slack.com/api/conversations.replies",
params,
combine_key="messages",
response_url=response_url,
)
)

View file

@ -1,8 +1,28 @@
_metadata:
major_version: 1
minor_version: 1
display_information:
name: Slack Exporter
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:
scopes:
user:
@ -15,3 +35,11 @@ oauth_config:
- im:read
- im:history
- users:read
bot:
- commands
- chat:write
- chat:write.public
settings:
org_deploy_enabled: false
socket_mode_enabled: false
token_rotation_enabled: false