any-env-code / yt.py
izuemon's picture
Update yt.py
de609b1 verified
raw
history blame
7.48 kB
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()