mirror of
https://github.com/Sea-Haven-Industries/expense-approval-bot.git
synced 2026-05-18 20:20:14 +00:00
148 lines
4.1 KiB
Python
148 lines
4.1 KiB
Python
import json
|
|
import os
|
|
import re
|
|
import urllib.parse
|
|
import urllib.request
|
|
|
|
import boto3
|
|
|
|
secrets_client = boto3.client("secretsmanager")
|
|
_cached_token: str | None = None
|
|
|
|
SLACK_BOT_TOKEN_SECRET_ARN = os.environ["SLACK_BOT_TOKEN_SECRET_ARN"]
|
|
|
|
SUBMITTED_CHANNEL = "C0AQ2AWLNEN"
|
|
|
|
STAGES = {
|
|
SUBMITTED_CHANNEL: {"next": "C0APLSGABAB", "label": "Processed"},
|
|
"C0APLSGABAB": {"next": "C0AQ09CDJH4", "label": "Authorized"},
|
|
"C0AQ09CDJH4": {"next": "C0APYUM1JFP", "label": "Matched"},
|
|
}
|
|
|
|
REACT_HINT_RE = re.compile(r"_React_ :white_check_mark: _to advance to \w+_")
|
|
|
|
|
|
def handler(event, context):
|
|
if event.get("reaction") != "white_check_mark":
|
|
print("Not white_check_mark reaction, skipping")
|
|
return
|
|
|
|
from_channel = event["item"]["channel"]
|
|
message_ts = event["item"]["ts"]
|
|
|
|
route = STAGES.get(from_channel)
|
|
if not route:
|
|
print(f"Channel {from_channel} not in STAGES, skipping")
|
|
return
|
|
|
|
to_channel = route["next"]
|
|
label = route["label"]
|
|
is_origin = from_channel == SUBMITTED_CHANNEL
|
|
token = get_bot_token()
|
|
|
|
history = slack_get(
|
|
"conversations.history",
|
|
token,
|
|
{
|
|
"channel": from_channel,
|
|
"latest": message_ts,
|
|
"limit": 1,
|
|
"inclusive": "true",
|
|
},
|
|
)
|
|
original_text = history["messages"][0]["text"]
|
|
|
|
permalink_resp = slack_get(
|
|
"chat.getPermalink",
|
|
token,
|
|
{
|
|
"channel": from_channel,
|
|
"message_ts": message_ts,
|
|
},
|
|
)
|
|
permalink = permalink_resp["permalink"]
|
|
|
|
text = REACT_HINT_RE.sub("", original_text).strip()
|
|
next_stage = STAGES.get(to_channel)
|
|
next_label = next_stage["label"] if next_stage else None
|
|
react_line = (
|
|
f"\n\n_React_ :white_check_mark: _to advance to {next_label}_"
|
|
if next_label
|
|
else ""
|
|
)
|
|
permalink_line = (
|
|
f"\n\n📎 *Original Submission:* <{permalink}|View Original Message>"
|
|
if is_origin
|
|
else ""
|
|
)
|
|
full_text = f"{text}{permalink_line}{react_line}"
|
|
|
|
slack_post(
|
|
"chat.postMessage",
|
|
token,
|
|
{
|
|
"channel": to_channel,
|
|
"text": full_text,
|
|
"mrkdwn": True,
|
|
"unfurl_links": False,
|
|
"unfurl_media": False,
|
|
},
|
|
)
|
|
|
|
if is_origin:
|
|
slack_post(
|
|
"chat.postMessage",
|
|
token,
|
|
{
|
|
"channel": from_channel,
|
|
"thread_ts": message_ts,
|
|
"text": f"➡️ Advanced to {label}",
|
|
},
|
|
)
|
|
print(f"Advanced user submission to {label} (origin preserved)")
|
|
else:
|
|
slack_post(
|
|
"chat.delete",
|
|
token,
|
|
{
|
|
"channel": from_channel,
|
|
"ts": message_ts,
|
|
},
|
|
)
|
|
print(f"Advanced to {label} and deleted previous copy")
|
|
|
|
|
|
def get_bot_token() -> str:
|
|
global _cached_token
|
|
if _cached_token:
|
|
return _cached_token
|
|
result = secrets_client.get_secret_value(SecretId=SLACK_BOT_TOKEN_SECRET_ARN)
|
|
_cached_token = result["SecretString"]
|
|
return _cached_token
|
|
|
|
|
|
def slack_get(method: str, token: str, params: dict) -> dict:
|
|
url = f"https://slack.com/api/{method}?{urllib.parse.urlencode(params)}"
|
|
req = urllib.request.Request(url, headers={"Authorization": f"Bearer {token}"})
|
|
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
data = json.loads(resp.read())
|
|
if not data.get("ok"):
|
|
raise RuntimeError(f"{method} failed: {data.get('error')}")
|
|
return data
|
|
|
|
|
|
def slack_post(method: str, token: str, body: dict) -> dict:
|
|
req = urllib.request.Request(
|
|
f"https://slack.com/api/{method}",
|
|
data=json.dumps(body).encode("utf-8"),
|
|
headers={
|
|
"Authorization": f"Bearer {token}",
|
|
"Content-Type": "application/json; charset=utf-8",
|
|
},
|
|
method="POST",
|
|
)
|
|
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
data = json.loads(resp.read())
|
|
if not data.get("ok"):
|
|
raise RuntimeError(f"{method} failed: {data.get('error')}")
|
|
return data
|