izuemon commited on
Commit
2d4a386
·
verified ·
1 Parent(s): 0ab7cca

Update watcher.py

Browse files
Files changed (1) hide show
  1. watcher.py +96 -182
watcher.py CHANGED
@@ -2,7 +2,7 @@ import os
2
  import re
3
  import time
4
  import json
5
- import requests
6
  from datetime import datetime, timezone
7
  from bs4 import BeautifulSoup
8
 
@@ -33,8 +33,8 @@ HEADERS_POST = {
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):
@@ -45,11 +45,6 @@ def parse_updated_at(value):
45
  return None
46
 
47
  def extract_youtube_id(text):
48
- """
49
- 対応例:
50
- https://www.youtube.com/watch?v=ID
51
- https://youtu.be/ID
52
- """
53
  patterns = [
54
  r"v=([A-Za-z0-9_-]{11})",
55
  r"youtu\.be/([A-Za-z0-9_-]{11})",
@@ -60,120 +55,58 @@ def extract_youtube_id(text):
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,28 +121,19 @@ def send_to_channel(text):
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,62 +141,52 @@ def main():
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()
 
2
  import re
3
  import time
4
  import json
5
+ import requests
6
  from datetime import datetime, timezone
7
  from bs4 import BeautifulSoup
8
 
 
33
  "x-account": X_ACCOUNT,
34
  }
35
 
36
+ # ===== ssyoutube =====
37
+ SSYOUTUBE_URL = "https://ssyoutube.online/yt-video-detail/"
38
 
39
  # ===== Utils =====
40
  def parse_updated_at(value):
 
45
  return None
46
 
47
  def extract_youtube_id(text):
 
 
 
 
 
48
  patterns = [
49
  r"v=([A-Za-z0-9_-]{11})",
50
  r"youtu\.be/([A-Za-z0-9_-]{11})",
 
55
  return m.group(1)
56
  return None
57
 
58
+ # ===== ssyoutube HTML 解析 =====
59
+ def fetch_download_links(youtube_url):
60
+ res = requests.post(
61
+ SSYOUTUBE_URL,
62
+ data={"videoURL": youtube_url},
63
+ timeout=30,
64
+ headers={
65
+ "User-Agent": "Mozilla/5.0",
66
+ "Referer": "https://ssyoutube.online/",
67
+ }
68
+ )
69
+ res.raise_for_status()
70
+
71
+ soup = BeautifulSoup(res.text, "lxml")
72
+ buttons = soup.select("button[data-url]")
73
+
74
+ results = []
75
+ for btn in buttons:
76
+ url = btn.get("data-url")
77
+ quality = btn.get("data-quality") # 例: 1080p / 720p / None
78
+ has_audio = btn.get("data-has-audio") # "true" / "false" / None
79
+
80
+ if not url:
81
+ continue
82
+
83
+ results.append({
84
+ "url": url,
85
+ "quality": quality or "audio",
86
+ "has_audio": has_audio,
87
+ })
88
+
89
+ return results
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
 
91
  def build_links(items):
 
92
  lines = []
93
  for item in items:
94
+ url = item["url"]
95
+ quality = item["quality"]
96
+ has_audio = item["has_audio"]
97
+
98
+ audio_label = ""
99
+ if has_audio == "false":
100
+ audio_label = "(映像のみ)"
101
+ elif has_audio == "true":
102
+ audio_label = "(音声付き)"
103
+
104
+ line = f'<link type="url" value="{url}"> {quality} {audio_label}</link>'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  lines.append(line)
106
+
107
  return "\n".join(lines)
108
 
109
  def send_to_channel(text):
 
110
  payload = {
111
  "requestId": f"desk-web-{int(time.time() * 1000)}",
112
  "blocks": [
 
121
  "files": None,
122
  "customPayload": None
123
  }
124
+
125
+ res = requests.post(
126
+ POST_URL,
127
+ headers=HEADERS_POST,
128
+ data=json.dumps(payload),
129
+ timeout=30
130
+ )
131
+ res.raise_for_status()
 
 
 
 
 
 
132
 
133
  # ===== Main =====
134
  def main():
 
 
135
  while True:
136
  try:
 
137
  res = requests.get(
138
  GET_URL,
139
  headers=HEADERS_GET,
 
141
  timeout=30,
142
  )
143
  res.raise_for_status()
144
+
145
  messages = res.json().get("messages", [])
146
+ latest_msg = None
147
+ latest_time = None
148
+
149
  for msg in messages:
150
+ plain_text = msg.get("plainText")
151
+ updated_at = msg.get("updatedAt")
152
+
153
+ if not plain_text or updated_at is None:
154
  continue
155
+
156
+ t = parse_updated_at(updated_at)
157
+ if not t:
158
  continue
159
+
160
+ if latest_time is None or t > latest_time:
161
+ latest_time = t
162
+ latest_msg = msg
163
+
164
+ if not latest_msg:
165
+ time.sleep(10)
166
+ continue
167
+
168
+ text = latest_msg["plainText"]
169
+ youtube_id = extract_youtube_id(text)
170
+ if not youtube_id:
171
+ time.sleep(10)
172
+ continue
173
+
174
+ youtube_url = f"https://www.youtube.com/watch?v={youtube_id}"
175
+
176
+ items = fetch_download_links(youtube_url)
177
+ if not items:
178
+ print("ダウンロードリンクが取得できませんでした")
179
+ time.sleep(10)
180
+ continue
181
+
182
+ message_text = build_links(items)
183
+ send_to_channel(message_text)
184
+ print("送信完了")
185
+
 
 
 
 
 
 
 
 
 
 
 
 
186
  except Exception as e:
187
+ print("エラー:", e)
188
+
 
189
  time.sleep(15)
190
 
191
  if __name__ == "__main__":
192
+ main()