Spaces:
Running
Running
File size: 7,476 Bytes
259fab3 3f49d8c 259fab3 3f49d8c 2d4a386 259fab3 490219d 3f49d8c 259fab3 6599ddd 259fab3 677e8ae 259fab3 3f49d8c 259fab3 3f49d8c 259fab3 3f49d8c 259fab3 490219d 259fab3 3f49d8c dcf1807 0daba3a 3f49d8c 0daba3a 3f49d8c cba51eb c70897d 505db77 5f065af 505db77 c70897d 505db77 c70897d 505db77 c70897d 505db77 f92b0ea de609b1 f92b0ea 505db77 f92b0ea 505db77 f92b0ea 3f49d8c f92b0ea 3f49d8c 490219d fe97e6f 63326c9 fe97e6f 3f49d8c 505db77 2d4a386 490219d 2d4a386 3f49d8c f92b0ea a77ba8e eb2f4e1 505db77 0034130 f92b0ea 505db77 eb2f4e1 0034130 f92b0ea 0034130 505db77 f92b0ea eb2f4e1 505db77 f92b0ea 505db77 f92b0ea 505db77 eb2f4e1 505db77 259fab3 f92b0ea 259fab3 fe97e6f 3f49d8c 490219d 3f49d8c bc9968c 490219d ec8aead 490219d 4f48744 bc9968c 490219d f92b0ea ec8aead 490219d bc9968c ec8aead bc9968c c70897d bc9968c dfeb692 f92b0ea 3a3ca6d dfeb692 3a3ca6d f92b0ea 3b94571 f92b0ea 3a3ca6d f92b0ea 3a3ca6d f92b0ea 3a3ca6d f92b0ea 505db77 f92b0ea bc9968c 259fab3 bc9968c 259fab3 f92b0ea | 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 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 | 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()
|