Repository navigation
Expand file tree
/
Copy pathwebhook_reference.py
More file actions
95 lines (76 loc) · 3.15 KB
/
Copy pathwebhook_reference.py
File metadata and controls
95 lines (76 loc) · 3.15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
"""
=== 別サーバー(公開HTTPS)で動かす LINE Webhook の雛形 ===
このファイルは Spark では動かさない。LINEと通信できる公開サーバーに置く。
役割: LINEから来た画像を取得 → Sparkの /roast_b64 に投げ → 返ったコメントをLINEに返信。
事前準備:
1. LINE Developers で Messaging API チャネルを作成
2. チャネルアクセストークン と チャネルシークレット を取得
3. Webhook URL を https://<このサーバー>/callback に設定し Webhook を ON
4. 環境変数を設定:
LINE_CHANNEL_ACCESS_TOKEN=...
LINE_CHANNEL_SECRET=...
SPARK_API=http://192.168.0.30:8080/roast_b64 # SparkにLAN/VPNで到達できる前提
SPARK_API_TOKEN=... # Spark側 ROAST_API_TOKEN と同じ値
依存: pip install flask requests
起動: python webhook_reference.py (本番は gunicorn 等を推奨)
"""
import os
import base64
import hmac
import hashlib
import requests
from flask import Flask, request, abort
LINE_TOKEN = os.environ["LINE_CHANNEL_ACCESS_TOKEN"]
LINE_SECRET = os.environ["LINE_CHANNEL_SECRET"]
SPARK_API = os.environ.get("SPARK_API", "http://192.168.0.30:8080/roast_b64")
SPARK_TOKEN = os.environ.get("SPARK_API_TOKEN", "")
app = Flask(__name__)
def verify_signature(body: bytes, signature: str) -> bool:
mac = hmac.new(LINE_SECRET.encode(), body, hashlib.sha256).digest()
return hmac.compare_digest(base64.b64encode(mac).decode(), signature)
def fetch_image(message_id: str) -> bytes:
r = requests.get(
f"https://api-data.line.me/v2/bot/message/{message_id}/content",
headers={"Authorization": f"Bearer {LINE_TOKEN}"},
timeout=30,
)
r.raise_for_status()
return r.content
def reply(reply_token: str, text: str) -> None:
requests.post(
"https://api.line.me/v2/bot/message/reply",
headers={"Authorization": f"Bearer {LINE_TOKEN}"},
json={"replyToken": reply_token, "messages": [{"type": "text", "text": text}]},
timeout=30,
)
def ask_spark(image_bytes: bytes) -> str:
b64 = base64.b64encode(image_bytes).decode()
r = requests.post(
SPARK_API,
headers={"Authorization": f"Bearer {SPARK_TOKEN}"},
json={"image_b64": b64},
timeout=300,
)
r.raise_for_status()
return r.json().get("comment", "(コメント生成に失敗したで)")
@app.post("/callback")
def callback():
signature = request.headers.get("X-Line-Signature", "")
body = request.get_data()
if not verify_signature(body, signature):
abort(400)
for ev in request.get_json().get("events", []):
if ev.get("type") != "message":
continue
msg = ev["message"]
if msg.get("type") == "image":
try:
comment = ask_spark(fetch_image(msg["id"]))
except Exception as e: # noqa: BLE001
comment = f"(処理中にエラー: {e})"
reply(ev["replyToken"], comment)
else:
reply(ev["replyToken"], "画像を送ってくれや、ネタにしたるで")
return "OK"
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)