izuemon commited on
Commit
f87074b
·
verified ·
1 Parent(s): 0efff1a

Update watcher.py

Browse files
Files changed (1) hide show
  1. watcher.py +173 -63
watcher.py CHANGED
@@ -4,6 +4,7 @@ import time
4
  import json
5
  import requests
6
  from datetime import datetime, timezone
 
7
 
8
  # ===== Channel.io 設定 =====
9
  GET_URL = "https://desk-api.channel.io/desk/channels/200605/groups/519217/messages"
@@ -32,8 +33,8 @@ HEADERS_POST = {
32
  "x-account": X_ACCOUNT,
33
  }
34
 
35
- # ===== Vidfly API =====
36
- VIDFLY_API = "https://api.vidfly.ai/api/media/youtube/download?url="
37
 
38
  # ===== Utils =====
39
  def parse_updated_at(value):
@@ -59,26 +60,120 @@ def extract_youtube_id(text):
59
  return m.group(1)
60
  return None
61
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  def build_links(items):
 
63
  lines = []
64
  for item in items:
65
  url = item.get("url")
66
  ext = item.get("ext")
67
  height = item.get("height")
68
  media_type = item.get("type")
69
-
70
- if not url or not ext or not height:
 
 
 
71
  continue
72
-
73
- label_type = "動画" if ext in ("mp4", "webm") else "音声"
74
- audio_suffix = " (音声付き)" if media_type == "video_with_audio" else ""
75
-
76
- line = f'<link type="url" value="{url}">{label_type}{audio_suffix} {height}p</link>'
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  lines.append(line)
78
-
79
  return "\n".join(lines)
80
 
81
  def send_to_channel(text):
 
82
  payload = {
83
  "requestId": f"desk-web-{int(time.time() * 1000)}",
84
  "blocks": [
@@ -93,19 +188,28 @@ def send_to_channel(text):
93
  "files": None,
94
  "customPayload": None
95
  }
96
-
97
- res = requests.post(
98
- POST_URL,
99
- headers=HEADERS_POST,
100
- data=json.dumps(payload),
101
- timeout=30
102
- )
103
- res.raise_for_status()
 
 
 
 
 
 
104
 
105
  # ===== Main =====
106
  def main():
 
 
107
  while True:
108
  try:
 
109
  res = requests.get(
110
  GET_URL,
111
  headers=HEADERS_GET,
@@ -113,56 +217,62 @@ def main():
113
  timeout=30,
114
  )
115
  res.raise_for_status()
116
-
117
  messages = res.json().get("messages", [])
118
-
119
- latest_msg = None
120
- latest_time = None
121
-
122
  for msg in messages:
123
- plain_text = msg.get("plainText")
124
- updated_at = msg.get("updatedAt")
125
-
126
- if not plain_text or updated_at is None:
127
  continue
128
-
129
- t = parse_updated_at(updated_at)
130
- if not t:
131
  continue
132
-
133
- if latest_time is None or t > latest_time:
134
- latest_time = t
135
- latest_msg = msg
136
-
137
- if not latest_msg:
138
- time.sleep(10)
139
- continue
140
-
141
- text = latest_msg["plainText"]
142
- youtube_id = extract_youtube_id(text)
143
-
144
- if not youtube_id:
145
- time.sleep(10)
146
- continue
147
-
148
- youtube_url = f"https://www.youtube.com/watch?v={youtube_id}"
149
- vidfly_res = requests.get(VIDFLY_API + youtube_url, timeout=30)
150
- vidfly_res.raise_for_status()
151
-
152
- items = vidfly_res.json().get("data", {}).get("items", [])
153
- if not items:
154
- print("Vidfly の items が空です")
155
- time.sleep(10)
156
- continue
157
-
158
- message_text = build_links(items)
159
- send_to_channel(message_text)
160
- print("送信完了")
161
-
 
 
 
 
 
 
 
 
 
162
  except Exception as e:
163
- print("エラー:", e)
164
-
 
165
  time.sleep(15)
166
 
167
  if __name__ == "__main__":
168
- main()
 
4
  import json
5
  import requests
6
  from datetime import datetime, timezone
7
+ from bs4 import BeautifulSoup
8
 
9
  # ===== Channel.io 設定 =====
10
  GET_URL = "https://desk-api.channel.io/desk/channels/200605/groups/519217/messages"
 
33
  "x-account": X_ACCOUNT,
34
  }
35
 
36
+ # ===== YouTube Downloader API =====
37
+ YT_DETAIL_URL = "https://ssyoutube.online/yt-video-detail/"
38
 
39
  # ===== Utils =====
40
  def parse_updated_at(value):
 
60
  return m.group(1)
61
  return None
62
 
63
+ def get_video_download_links(youtube_url):
64
+ """YouTubeのダウンロードリンクを取得する"""
65
+ headers = {
66
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
67
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
68
+ "Accept-Language": "ja,en-US;q=0.7,en;q=0.3",
69
+ "Accept-Encoding": "gzip, deflate, br",
70
+ "Content-Type": "application/x-www-form-urlencoded",
71
+ "Origin": "https://ssyoutube.online",
72
+ "Referer": "https://ssyoutube.online/",
73
+ "Upgrade-Insecure-Requests": "1"
74
+ }
75
+
76
+ # フォームデータ
77
+ form_data = {
78
+ "videoURL": youtube_url
79
+ }
80
+
81
+ try:
82
+ # HTMLを取得
83
+ response = requests.post(YT_DETAIL_URL, data=form_data, headers=headers, timeout=30)
84
+ response.raise_for_status()
85
+
86
+ # BeautifulSoupで解析
87
+ soup = BeautifulSoup(response.text, 'html.parser')
88
+
89
+ # 動画品質情報を抽出
90
+ video_links = []
91
+
92
+ # テーブル行を検索
93
+ rows = soup.select('table.list tr')
94
+ for row in rows:
95
+ # ボタンからデータを抽出
96
+ button = row.select_one('button[data-url]')
97
+ if button:
98
+ data_url = button.get('data-url', '')
99
+ data_quality = button.get('data-quality', '')
100
+ data_has_audio = button.get('data-has-audio', 'false').lower() == 'true'
101
+
102
+ # 品質ラベルを取得
103
+ quality_cell = row.select_one('td:first-child')
104
+ if quality_cell:
105
+ quality_text = quality_cell.get_text(strip=True)
106
+ # 数値の品質を抽出
107
+ quality_match = re.search(r'(\d+)p', quality_text)
108
+ if quality_match:
109
+ quality = int(quality_match.group(1))
110
+ else:
111
+ # M4Aなどのオーディオ形式
112
+ quality = 0
113
+ if 'M4A' in quality_text:
114
+ data_has_audio = True
115
+ else:
116
+ quality = 0
117
+
118
+ # サイズ情報を取得
119
+ size_cell = row.select('td')[1] if len(row.select('td')) > 1 else None
120
+ size_text = size_cell.get_text(strip=True) if size_cell else ""
121
+
122
+ if data_url:
123
+ video_links.append({
124
+ "url": data_url,
125
+ "ext": "mp4" if data_quality else "m4a",
126
+ "height": quality,
127
+ "type": "video_with_audio" if data_has_audio else "video",
128
+ "quality": data_quality,
129
+ "has_audio": data_has_audio,
130
+ "size": size_text
131
+ })
132
+
133
+ return video_links
134
+
135
+ except Exception as e:
136
+ print(f"HTML解析エラー: {e}")
137
+ return []
138
+
139
  def build_links(items):
140
+ """Channel.io用のリンクテキストを構築"""
141
  lines = []
142
  for item in items:
143
  url = item.get("url")
144
  ext = item.get("ext")
145
  height = item.get("height")
146
  media_type = item.get("type")
147
+ quality = item.get("quality", "")
148
+ has_audio = item.get("has_audio", False)
149
+ size = item.get("size", "")
150
+
151
+ if not url:
152
  continue
153
+
154
+ if ext == "m4a":
155
+ label_type = "音声"
156
+ audio_suffix = ""
157
+ else:
158
+ label_type = "動画"
159
+ audio_suffix = " (音声付き)" if has_audio else " (音声なし)"
160
+
161
+ # 品質表示
162
+ if height > 0:
163
+ quality_display = f"{height}p"
164
+ else:
165
+ quality_display = quality if quality else "オーディオ"
166
+
167
+ # サイズ情報があれば追加
168
+ size_display = f" [{size}]" if size else ""
169
+
170
+ line = f'<link type="url" value="{url}">{label_type}{audio_suffix} {quality_display}{size_display}</link>'
171
  lines.append(line)
172
+
173
  return "\n".join(lines)
174
 
175
  def send_to_channel(text):
176
+ """Channel.ioにメッセージを送信"""
177
  payload = {
178
  "requestId": f"desk-web-{int(time.time() * 1000)}",
179
  "blocks": [
 
188
  "files": None,
189
  "customPayload": None
190
  }
191
+
192
+ try:
193
+ res = requests.post(
194
+ POST_URL,
195
+ headers=HEADERS_POST,
196
+ data=json.dumps(payload),
197
+ timeout=30
198
+ )
199
+ res.raise_for_status()
200
+ print("Channel.ioに送信成功")
201
+ return True
202
+ except Exception as e:
203
+ print(f"Channel.io送信エラー: {e}")
204
+ return False
205
 
206
  # ===== Main =====
207
  def main():
208
+ processed_messages = set() # 処理済みメッセージを追跡
209
+
210
  while True:
211
  try:
212
+ # Channel.ioからメッセージを取得
213
  res = requests.get(
214
  GET_URL,
215
  headers=HEADERS_GET,
 
217
  timeout=30,
218
  )
219
  res.raise_for_status()
220
+
221
  messages = res.json().get("messages", [])
222
+
223
+ # 未処理のYouTubeリンクを含むメッセージを探す
 
 
224
  for msg in messages:
225
+ msg_id = msg.get("id")
226
+ if msg_id in processed_messages:
 
 
227
  continue
228
+
229
+ plain_text = msg.get("plainText", "")
230
+ if not plain_text:
231
  continue
232
+
233
+ youtube_id = extract_youtube_id(plain_text)
234
+ if youtube_id:
235
+ print(f"YouTubeリンクを検出: {youtube_id}")
236
+
237
+ # 処理済みとしてマーク
238
+ processed_messages.add(msg_id)
239
+
240
+ # YouTube URLを構築
241
+ youtube_url = f"https://www.youtube.com/watch?v={youtube_id}"
242
+
243
+ # ダウンロードリンクを取得
244
+ print(f"ダウンロードリンクを取得中: {youtube_url}")
245
+ items = get_video_download_links(youtube_url)
246
+
247
+ if items:
248
+ print(f"{len(items)}個のダウンロードオプションを取得")
249
+
250
+ # メッセージを構築して送信
251
+ message_text = f"🎬 YouTube動画ダウンロードリンク\n\n"
252
+ message_text += build_links(items)
253
+ message_text += f"\n\n🔗 元の動画: {youtube_url}"
254
+
255
+ if send_to_channel(message_text):
256
+ print(f"ダウンロードリンクを送信完了: {youtube_id}")
257
+ else:
258
+ print(f"送信失敗: {youtube_id}")
259
+ else:
260
+ print(f"ダウンロードリンクの取得に失敗: {youtube_url}")
261
+ error_msg = f"⚠️ ダウンロードリンクの取得に失敗しました\n\n🔗 {youtube_url}"
262
+ send_to_channel(error_msg)
263
+
264
+ # 処理済みメッセージが多くなりすぎたら古いものを削除
265
+ if len(processed_messages) > 100:
266
+ # 最新の50件だけ保持
267
+ processed_messages = set(list(processed_messages)[:50])
268
+
269
+ except requests.exceptions.RequestException as e:
270
+ print(f"ネットワークエラー: {e}")
271
  except Exception as e:
272
+ print(f"予期せぬエラー: {e}")
273
+
274
+ # 15秒間隔でチェック
275
  time.sleep(15)
276
 
277
  if __name__ == "__main__":
278
+ main()