Spaces:
Running
Running
| import os | |
| import re | |
| import time | |
| import json | |
| import requests | |
| from datetime import datetime, timezone | |
| # ===== Channel.io 設定 ===== | |
| GET_URL = "https://desk-api.channel.io/desk/channels/200605/groups/519217/messages" | |
| POST_URL = GET_URL | |
| PARAMS = { | |
| "sortOrder": "desc", | |
| "limit": 36, | |
| "logFolded": "false", | |
| } | |
| X_ACCOUNT = os.getenv("dmsendertoken") | |
| if not X_ACCOUNT: | |
| raise RuntimeError("環境変数 channeliotokenbot2 が設定されていません") | |
| HEADERS_GET = { | |
| "accept": "application/json", | |
| "accept-language": "ja", | |
| "x-account": X_ACCOUNT, | |
| } | |
| HEADERS_POST = { | |
| "accept": "application/json", | |
| "accept-language": "ja", | |
| "content-type": "application/json", | |
| "x-account": X_ACCOUNT, | |
| } | |
| # ===== Utils ===== | |
| def parse_updated_at(value): | |
| if isinstance(value, (int, float)): | |
| return datetime.fromtimestamp(value / 1000, tz=timezone.utc) | |
| elif isinstance(value, str): | |
| return datetime.fromisoformat(value.replace("Z", "+00:00")) | |
| return None | |
| def extract_youtube_id(text): | |
| patterns = [ | |
| r"(?:[?&](?:v|vi)=)([A-Za-z0-9_-]{11})", | |
| r"youtu\.be/([A-Za-z0-9_-]{11})", | |
| r"youtube(?:-nocookie)?\.com/(?:embed|v|shorts)/([A-Za-z0-9_-]{11})", | |
| r"youtube(?:-nocookie)?\.com/watch\?.*?v=([A-Za-z0-9_-]{11})", | |
| r"[#&]v=([A-Za-z0-9_-]{11})" | |
| ] | |
| for p in patterns: | |
| m = re.search(p, text) | |
| if m: | |
| return m.group(1) | |
| return None | |
| def extract_youtube_and_option(text): | |
| parts = re.split(r"[ \u3000]+", text.strip()) | |
| video_id = extract_youtube_id(parts[0]) | |
| option = None | |
| if len(parts) > 1: | |
| option = parts[1].lower() | |
| return video_id, option | |
| def normalize_quality(option): | |
| if option is None: | |
| return "720" | |
| trans_table = str.maketrans( | |
| "0123456789kKpP", | |
| "0123456789kKpP" | |
| ) | |
| option = option.translate(trans_table).lower() | |
| quality_map = { | |
| "144": "144", | |
| "240": "240", | |
| "360": "360", | |
| "480": "480", | |
| "720": "720", | |
| "1080": "1080", | |
| "2k": "1440", | |
| "1440": "1440", | |
| "4k": "2160", | |
| "2160": "2160", | |
| "8k": "4320", | |
| "4320": "4320", | |
| "144p": "144", | |
| "240p": "240", | |
| "360p": "360", | |
| "480p": "480", | |
| "720p": "720", | |
| "1080p": "1080", | |
| "2kp": "1440", | |
| "1440p": "1440", | |
| "4kp": "2160", | |
| "2160p": "2160", | |
| "8kp": "4320", | |
| "4320p": "4320", | |
| } | |
| if option in quality_map: | |
| return quality_map[option] | |
| return None # 不正な指定 | |
| # ===== izu-ytdl API ===== | |
| def download_video_from_api(video_id, quality): | |
| api_url = ( | |
| f"https://render-ytdlp-awqz.onrender.com/download" | |
| f"?v={video_id}" | |
| f"&q=bv*[height<={quality}]+ba/b" | |
| ) | |
| res = requests.get(api_url, timeout=300) | |
| res.raise_for_status() | |
| return res.content | |
| # ===== Channel送信 ===== | |
| def send_to_channel(text): | |
| payload = { | |
| "requestId": f"desk-web-{int(time.time() * 1000)}", | |
| "blocks": [ | |
| {"type": "text", "value": text} | |
| ], | |
| } | |
| res = requests.post( | |
| POST_URL, | |
| headers=HEADERS_POST, | |
| data=json.dumps(payload), | |
| timeout=30 | |
| ) | |
| res.raise_for_status() | |
| def upload_file_to_channel(file_bytes): | |
| upload_url = "https://media.channel.io/cht/v1/pri-file/200605/groups/519217/message/send_yt_video_file.mp4" | |
| headers = { | |
| "x-account": X_ACCOUNT, | |
| "Content-Type": "video/mp4", | |
| "Content-Length": str(len(file_bytes)), | |
| } | |
| res = requests.post( | |
| upload_url, | |
| headers=headers, | |
| data=file_bytes, | |
| timeout=300 | |
| ) | |
| res.raise_for_status() | |
| return res.json() | |
| def send_video_message(file_json): | |
| request_id = f"desk-web-{int(time.time() * 1000)}" | |
| payload = { | |
| "requestId": request_id, | |
| "blocks": [ | |
| {"type": "text", "value": "プレビュー:"} | |
| ], | |
| "files": [file_json], | |
| } | |
| res = requests.post( | |
| POST_URL, | |
| headers=HEADERS_POST, | |
| data=json.dumps(payload), | |
| timeout=30 | |
| ) | |
| res.raise_for_status() | |
| # ===== Main ===== | |
| def main(): | |
| processed_messages = set() | |
| while True: | |
| try: | |
| res = requests.get( | |
| GET_URL, | |
| headers=HEADERS_GET, | |
| params=PARAMS, | |
| timeout=30, | |
| ) | |
| res.raise_for_status() | |
| messages = res.json().get("messages", []) | |
| latest_msg = None | |
| latest_time = None | |
| for msg in messages: | |
| msg_id = msg.get("id") | |
| plain_text = msg.get("plainText") | |
| updated_at = msg.get("updatedAt") | |
| if not plain_text or updated_at is None: | |
| continue | |
| if msg_id in processed_messages: | |
| continue | |
| t = parse_updated_at(updated_at) | |
| if not t: | |
| continue | |
| if latest_time is None or t > latest_time: | |
| latest_time = t | |
| latest_msg = msg | |
| if not latest_msg: | |
| time.sleep(10) | |
| continue | |
| video_id, option = extract_youtube_and_option(latest_msg["plainText"]) | |
| if not video_id: | |
| time.sleep(10) | |
| continue | |
| valid_options = [ | |
| "144","240","360","480","720","1080", | |
| "1440","2160","4320","4k","8k","2k" | |
| ] | |
| if option is None: | |
| send_to_channel( | |
| "画質が指定されていないため、デフォルトの720pを使用します。\n" | |
| "指定する場合は、動画URLの後にスペースを入れて、" | |
| "144/240/360/480/720/1080/2k(または1440)/4k(または2160)/8k(または4320) のいずれかを入力してください。\n" | |
| "注意:高画質や長い動画は時間がかかる場合があります。" | |
| ) | |
| quality_opt = "720" | |
| else: | |
| if option.lower() not in valid_options: | |
| send_to_channel( | |
| "不明な画質、または予期しない形式でした。\n" | |
| "対応画質: 144, 240, 360, 480, 720, 1080, " | |
| "2k(1440), 4k(2160), 8k(4320)\n" | |
| "例: https://youtu.be/abcdEFGHijk 1080" | |
| ) | |
| processed_messages.add(latest_msg["id"]) | |
| continue | |
| quality_opt = normalize_quality(option) | |
| send_to_channel(f"{video_id} のダウンロードを開始します。({quality_opt}p)") | |
| try: | |
| video_bytes = download_video_from_api(video_id, quality_opt) | |
| send_to_channel("動画をアップロードしています...") | |
| file_json = upload_file_to_channel(video_bytes) | |
| send_video_message(file_json) | |
| send_to_channel("完了しました!") | |
| except Exception as e: | |
| send_to_channel(f"エラーが発生しました: {e}") | |
| processed_messages.add(latest_msg["id"]) | |
| except Exception as e: | |
| print("エラー:", e) | |
| time.sleep(15) | |
| if __name__ == "__main__": | |
| main() | |