Adopted new slack api auth
This commit is contained in:
parent
4b7fea7ec1
commit
36e9911dfc
4 changed files with 114 additions and 41 deletions
26
README.md
26
README.md
|
@ -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
10
bot.py
|
@ -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)
|
||||||
|
|
91
exporter.py
91
exporter.py
|
@ -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,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
28
slack.yaml
28
slack.yaml
|
@ -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
|
Loading…
Add table
Add a link
Reference in a new issue