File size: 6,013 Bytes
e6131ae
 
 
 
 
 
 
 
 
 
 
 
a5eb6b9
e6131ae
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5803228
e6131ae
 
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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
#!/usr/bin/env python3
"""
Set up LINE Rich Menu for Stock Predictor bot.
Run once: python setup_rich_menu.py

Layout (2 columns x 2 rows = 4 buttons):
  [๐Ÿ“Š ๆŸฅ่‚ก็ฅจ]  [๐ŸŒ ้–‹ๅ•Ÿ็ถฒ็ซ™]
  [๐Ÿ“ˆ ้ ๆธฌ]    [โ“ ่ชชๆ˜Ž]
"""
import os, json, sys, urllib.request, urllib.error

TOKEN = os.environ.get("LINE_CHANNEL_ACCESS_TOKEN") or ""
SITE_URL = os.environ.get("STOCK_SITE_URL", "https://dennischan0909-dockerspace.hf.space")

if not TOKEN:
    # Try loading from .env in same dir
    env_file = os.path.join(os.path.dirname(__file__), ".env")
    if os.path.exists(env_file):
        for line in open(env_file):
            line = line.strip()
            if line.startswith("LINE_CHANNEL_ACCESS_TOKEN="):
                TOKEN = line.split("=", 1)[1].strip().strip('"').strip("'")
            if line.startswith("STOCK_SITE_URL="):
                SITE_URL = line.split("=", 1)[1].strip().strip('"').strip("'")

if not TOKEN:
    print("ERROR: LINE_CHANNEL_ACCESS_TOKEN not set")
    sys.exit(1)

HEADERS = {
    "Authorization": f"Bearer {TOKEN}",
    "Content-Type": "application/json",
}

def api(method: str, path: str, body=None):
    url = f"https://api.line.me{path}"
    data = json.dumps(body).encode() if body else None
    req = urllib.request.Request(url, data=data, headers=HEADERS, method=method)
    try:
        with urllib.request.urlopen(req) as r:
            return json.loads(r.read())
    except urllib.error.HTTPError as e:
        err = e.read().decode()
        print(f"  HTTP {e.code}: {err}")
        raise

# โ”€โ”€ 1. Delete existing menus โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
print("Deleting existing rich menus...")
try:
    menus = api("GET", "/v2/bot/richmenu/list")
    for m in menus.get("richmenus", []):
        api("DELETE", f"/v2/bot/richmenu/{m['richMenuId']}")
        print(f"  Deleted {m['richMenuId']}")
except Exception as e:
    print(f"  (skip) {e}")

# โ”€โ”€ 2. Create new menu โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
W, H = 2500, 843          # LINE recommended size for 2-row menu
HALF_W = W // 2

menu_body = {
    "size": {"width": W, "height": H},
    "selected": True,
    "name": "Stock Menu",
    "chatBarText": "่‚ก็ฅจ้ธๅ–ฎ",
    "areas": [
        # Row 1 left โ€” ๆŸฅ่‚ก็ฅจ
        {
            "bounds": {"x": 0, "y": 0, "width": HALF_W, "height": H // 2},
            "action": {"type": "message", "text": "ๆŸฅ่‚ก็ฅจ"},
        },
        # Row 1 right โ€” ้–‹ๅ•Ÿ็ถฒ็ซ™
        {
            "bounds": {"x": HALF_W, "y": 0, "width": HALF_W, "height": H // 2},
            "action": {"type": "uri", "uri": SITE_URL},
        },
        # Row 2 left โ€” ้ ๆธฌ
        {
            "bounds": {"x": 0, "y": H // 2, "width": HALF_W, "height": H // 2},
            "action": {"type": "message", "text": "้ ๆธฌ"},
        },
        # Row 2 right โ€” ่ชชๆ˜Ž
        {
            "bounds": {"x": HALF_W, "y": H // 2, "width": HALF_W, "height": H // 2},
            "action": {"type": "message", "text": "่ชชๆ˜Ž"},
        },
    ],
}

print("Creating rich menu...")
result = api("POST", "/v2/bot/richmenu", menu_body)
menu_id = result["richMenuId"]
print(f"  Created: {menu_id}")

# โ”€โ”€ 3. Upload image โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
print("Uploading menu image...")
img_path = os.path.join(os.path.dirname(__file__), "rich_menu.png")

if not os.path.exists(img_path):
    # Generate a simple placeholder image with Pillow if available
    try:
        from PIL import Image, ImageDraw, ImageFont
        img = Image.new("RGB", (W, H), "#1a1a2e")
        draw = ImageDraw.Draw(img)
        labels = [
            ("๐Ÿ“Š ๆŸฅ่‚ก็ฅจ",  HALF_W // 2,      H // 4),
            ("๐ŸŒ ้–‹ๅ•Ÿ็ถฒ็ซ™", HALF_W + HALF_W // 2, H // 4),
            ("๐Ÿ“ˆ ้ ๆธฌ",    HALF_W // 2,      H * 3 // 4),
            ("โ“ ่ชชๆ˜Ž",    HALF_W + HALF_W // 2, H * 3 // 4),
        ]
        # Grid lines
        draw.line([(HALF_W, 0), (HALF_W, H)], fill="#444", width=4)
        draw.line([(0, H // 2), (W, H // 2)], fill="#444", width=4)
        # Labels
        try:
            font = ImageFont.truetype("arial.ttf", 80)
        except Exception:
            font = ImageFont.load_default()
        for text, x, y in labels:
            draw.text((x, y), text, fill="white", font=font, anchor="mm")
        img.save(img_path)
        print(f"  Generated placeholder image: {img_path}")
    except ImportError:
        print("  Pillow not installed โ€” skipping auto-image generation")
        print(f"  Please place a 2500ร—843 PNG at: {img_path}")
        print("  Then re-run this script (it will skip menu creation and only upload image + set default).")
        # Set default anyway so menu structure is ready
        api("POST", f"/v2/bot/richmenu/{menu_id}/default")
        print(f"  Set as default (image pending). Menu ID: {menu_id}")
        sys.exit(0)

with open(img_path, "rb") as f:
    img_data = f.read()

img_req = urllib.request.Request(
    f"https://api-data.line.me/v2/bot/richmenu/{menu_id}/content",
    data=img_data,
    headers={**HEADERS, "Content-Type": "image/png"},
    method="POST",
)
try:
    with urllib.request.urlopen(img_req) as r:
        print(f"  Image uploaded: {r.status}")
except urllib.error.HTTPError as e:
    print(f"  Image upload failed: {e.code} {e.read().decode()}")

# โ”€โ”€ 4. Set as default โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
print("Setting as default menu...")
api("POST", f"/v2/bot/user/all/richmenu/{menu_id}")
print(f"Done! Menu ID: {menu_id}")
print(f"Site URL: {SITE_URL}")