Spaces:
Running
Running
| import time | |
| import requests | |
| import os | |
| import json | |
| from datetime import datetime, timedelta | |
| from zoneinfo import ZoneInfo | |
| BASE_URL = "https://desk-api.channel.io/desk/channels/200605" | |
| TARGET_GROUP_CHAT_ID = "536194" | |
| HEADERS = { | |
| "accept-language": "ja", | |
| "x-account": os.getenv("dmsendertoken") | |
| } | |
| JST = ZoneInfo("Asia/Tokyo") | |
| # ---------------- 天気コード ---------------- | |
| { | |
| 0: "雲の発達なしまたは観測なし", | |
| 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: "激しい雷雨(雹あり)" | |
| } | |
| def format_weather_code(code: int) -> str: | |
| if code < 10: | |
| return WEATHER_CODE_MAP.get(code, str(code)) | |
| main = code // 10 | |
| sub = code % 10 | |
| main_str = WEATHER_CODE_MAP.get(main) | |
| sub_str = WEATHER_CODE_MAP.get(sub) | |
| if main_str and sub_str: | |
| return f"{main_str}、{sub_str}" | |
| return str(code) | |
| # ---------------- Channel 投稿 ---------------- | |
| def post_group_message(chat_id, body): | |
| url = f"{BASE_URL}/groups/{chat_id}/messages" | |
| r = requests.post(url, headers=HEADERS, json=body) | |
| r.raise_for_status() | |
| return r.json() | |
| # ---------------- 時報 ---------------- | |
| def create_time_signal_body(): | |
| now = datetime.now(JST) | |
| hour = now.hour | |
| if hour < 12: | |
| period = "午前" | |
| display_hour = hour if hour != 0 else 12 | |
| else: | |
| period = "午後" | |
| display_hour = hour - 12 if hour > 12 else 12 | |
| message = f"<b>時報</b>\n{period}{display_hour}時をお知らせします。" | |
| return { | |
| "requestId": f"desk-web-time-signal-{int(time.time())}", | |
| "blocks": [{"type": "text", "value": message}], | |
| } | |
| # ---------------- 天気予報 ---------------- | |
| def load_todoufuken(): | |
| with open("todoufuken.json", encoding="utf-8") as f: | |
| lines = [] | |
| for line in f: | |
| stripped = line.lstrip() | |
| if stripped.startswith("//"): | |
| continue | |
| lines.append(line) | |
| json_str = "".join(lines) | |
| return json.loads(json_str) | |
| def fetch_weather(todoufuken): | |
| lats = ",".join(str(p["lat"]) for p in todoufuken) | |
| lons = ",".join(str(p["lon"]) for p in todoufuken) | |
| url = ( | |
| "https://api.open-meteo.com/v1/forecast" | |
| f"?latitude={lats}" | |
| f"&longitude={lons}" | |
| "&hourly=relative_humidity_2m,apparent_temperature,temperature_2m," | |
| "precipitation_probability,weather_code" | |
| "&forecast_days=1" | |
| "&timezone=Asia%2FTokyo" | |
| ) | |
| r = requests.get(url) | |
| r.raise_for_status() | |
| return r.json() | |
| def create_weather_body(): | |
| now = datetime.now(JST) | |
| today_7am = now.replace(hour=7, minute=0, second=0, microsecond=0) | |
| todoufuken = load_todoufuken() | |
| data = fetch_weather(todoufuken) | |
| lines = ["<b>☀️ 本日の天気予報(7時以降・1時間ごと)</b>"] | |
| for idx, pref in enumerate(todoufuken): | |
| hourly = data[idx]["hourly"] | |
| times = hourly["time"] | |
| lines.append(f"\n<b>{pref['name']}:</b>") | |
| TARGET_HOURS = {7, 10, 13, 16, 21} | |
| for i, t in enumerate(times): | |
| t_jst = ( | |
| datetime.fromisoformat(t) | |
| .replace(tzinfo=ZoneInfo("UTC")) | |
| .astimezone(JST) | |
| ) | |
| if t_jst.hour not in TARGET_HOURS: | |
| continue | |
| wc = hourly["weather_code"][i] | |
| at = hourly["apparent_temperature"][i] | |
| temp = hourly["temperature_2m"][i] | |
| pop = hourly["precipitation_probability"][i] | |
| rh = hourly["relative_humidity_2m"][i] | |
| wc_str = format_weather_code(wc) | |
| line = ( | |
| f" {t_jst.strftime('%H時')}:" | |
| f"{wc_str} " | |
| f"{at}℃ {temp}℃ {pop}% {rh}%" | |
| ) | |
| lines.append(line) | |
| message2 = "☀️・・・晴れ、☁️・・・曇り、🌫️・・・霧・霧雨、🌧️・・・雨、🌨️・・・雪、🌦️・・・にわか雨、⛈️・・・雷雨" | |
| return { | |
| "requestId": f"desk-web-weather-{int(time.time())}", | |
| "blocks": [{"type": "text", "value": "\n".join(lines)},{"type": "text", "value": message2}], | |
| } | |
| # ---------------- メインループ ---------------- | |
| def main_loop(): | |
| while True: | |
| try: | |
| now = datetime.now(JST) | |
| # 毎正時の時報 | |
| if now.minute == 0: | |
| body = create_time_signal_body() | |
| post_group_message(TARGET_GROUP_CHAT_ID, body) | |
| # 午後7時20分に天気予報 | |
| if now.hour == 21 and now.minute == 35: | |
| body = create_weather_body() | |
| post_group_message(TARGET_GROUP_CHAT_ID, body) | |
| time.sleep(60) | |
| except Exception as e: | |
| print("Error:", e) | |
| time.sleep(10) | |
| if __name__ == "__main__": | |
| main_loop() |