seawolf2357 commited on
Commit
302cdd6
โ€ข
1 Parent(s): f05454f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +227 -40
app.py CHANGED
@@ -1,18 +1,18 @@
1
- import os
2
- import google_auth_oauthlib.flow
3
- from google.oauth2.credentials import Credentials
4
- from googleapiclient.discovery import build
5
- from google.auth.transport.requests import Request
6
  import discord
7
  import logging
 
8
  import re
9
  import asyncio
 
10
  import subprocess
11
  from huggingface_hub import InferenceClient
 
 
 
 
12
  from youtube_transcript_api import YouTubeTranscriptApi
13
  from youtube_transcript_api.formatters import TextFormatter
14
  from dotenv import load_dotenv
15
- import time
16
 
17
  # ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋กœ๋“œ
18
  load_dotenv()
@@ -20,7 +20,6 @@ load_dotenv()
20
  # JSON_TOKEN.json ํŒŒ์ผ์˜ ๊ฒฝ๋กœ
21
  credentials_path = 'JSON_TOKEN.json'
22
  token_path = 'token.json'
23
- auth_code_path = 'auth_code.txt' # ์ธ์ฆ ์ฝ”๋“œ๋ฅผ ์ €์žฅํ•  ํŒŒ์ผ ๊ฒฝ๋กœ
24
 
25
  # ๋กœ๊น… ์„ค์ •
26
  logging.basicConfig(level=logging.DEBUG, format='%(asctime)s:%(levelname)s:%(name)s:%(message)s', handlers=[logging.StreamHandler()])
@@ -39,50 +38,239 @@ hf_client = InferenceClient("CohereForAI/c4ai-command-r-plus", token=os.getenv("
39
  SCOPES = ["https://www.googleapis.com/auth/youtube.force-ssl"]
40
  creds = None
41
 
42
- def authorize():
43
- flow = google_auth_oauthlib.flow.InstalledAppFlow.from_client_secrets_file(
44
- credentials_path, SCOPES)
45
- auth_url, _ = flow.authorization_url(prompt='consent')
46
- print('Please go to this URL and finish the authentication: {}'.format(auth_url))
 
 
 
 
 
 
 
47
 
48
- print(f'Enter the authorization code in the file {auth_code_path}')
49
-
50
- # ํŒŒ์ผ์— ์ธ์ฆ ์ฝ”๋“œ๊ฐ€ ์ €์žฅ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆผ
51
- while not os.path.exists(auth_code_path):
52
- print("Waiting for authorization code...")
53
- time.sleep(5)
54
 
55
- with open(auth_code_path, 'r') as file:
56
- code = file.read().strip()
57
-
58
- flow.fetch_token(code=code)
59
- creds = flow.credentials
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
 
61
- with open(token_path, 'w') as token:
62
- token.write(creds.to_json())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
- # ์ธ์ฆ ์ฝ”๋“œ ํŒŒ์ผ ์‚ญ์ œ (๋ณด์•ˆ)
65
- os.remove(auth_code_path)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
 
67
- return creds
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
 
69
- # ๊ธฐ์กด ํ† ํฐ์„ ๋กœ๋“œํ•˜๊ฑฐ๋‚˜ ์ƒˆ๋กœ ์ธ์ฆ
70
  if os.path.exists(token_path):
71
  creds = Credentials.from_authorized_user_file(token_path, SCOPES)
72
- else:
73
- creds = authorize()
74
-
75
- # ํ† ํฐ ๊ฐฑ์‹  ๋˜๋Š” ์žฌ์ธ์ฆ
76
  if not creds or not creds.valid:
77
  if creds and creds.expired and creds.refresh_token:
78
  creds.refresh(Request())
79
  else:
80
- creds = authorize()
 
 
 
81
 
82
- # YouTube API ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ
83
  youtube_service = build('youtube', 'v3', credentials=creds)
84
 
85
- # ๋””์Šค์ฝ”๋“œ ๋ด‡ ์„ค์ •
86
  SPECIFIC_CHANNEL_ID = int(os.getenv("DISCORD_CHANNEL_ID"))
87
 
88
  class MyClient(discord.Client):
@@ -233,7 +421,7 @@ async def post_replies_to_youtube(video_id, comments, replies):
233
  """
234
  for (comment, comment_id), reply in zip(comments, replies):
235
  try:
236
- response = youtube_service.comments().insert(
237
  part='snippet',
238
  body={
239
  'snippet': {
@@ -242,11 +430,10 @@ async def post_replies_to_youtube(video_id, comments, replies):
242
  }
243
  }
244
  ).execute()
245
- logging.debug(f'Posted reply to comment: {comment_id} with response: {response}')
246
  except Exception as e:
247
  logging.error(f'Error posting reply to comment {comment_id}: {e}')
248
- logging.debug(f'Response: {e.resp} | Content: {e.content}')
249
 
250
  if __name__ == "__main__":
251
  discord_client = MyClient(intents=intents)
252
- discord_client.run(os.getenv('DISCORD_TOKEN'))
 
 
 
 
 
 
1
  import discord
2
  import logging
3
+ import os
4
  import re
5
  import asyncio
6
+ import json
7
  import subprocess
8
  from huggingface_hub import InferenceClient
9
+ from googleapiclient.discovery import build
10
+ from google.oauth2.credentials import Credentials
11
+ from google_auth_oauthlib.flow import InstalledAppFlow
12
+ from google.auth.transport.requests import Request
13
  from youtube_transcript_api import YouTubeTranscriptApi
14
  from youtube_transcript_api.formatters import TextFormatter
15
  from dotenv import load_dotenv
 
16
 
17
  # ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋กœ๋“œ
18
  load_dotenv()
 
20
  # JSON_TOKEN.json ํŒŒ์ผ์˜ ๊ฒฝ๋กœ
21
  credentials_path = 'JSON_TOKEN.json'
22
  token_path = 'token.json'
 
23
 
24
  # ๋กœ๊น… ์„ค์ •
25
  logging.basicConfig(level=logging.DEBUG, format='%(asctime)s:%(levelname)s:%(name)s:%(message)s', handlers=[logging.StreamHandler()])
 
38
  SCOPES = ["https://www.googleapis.com/auth/youtube.force-ssl"]
39
  creds = None
40
 
41
+ if os.path.exists(token_path):
42
+ creds = Credentials.from_authorized_user_file(token_path, SCOPES)
43
+ if not creds or not creds.valid:
44
+ if creds and creds.expired and creds.refresh_token:
45
+ creds.refresh(Request())
46
+ else:
47
+ flow = InstalledAppFlow.from_client_secrets_file(credentials_path, SCOPES)
48
+ creds = flow.run_local_server(port=0)
49
+ with open(token_path, 'w') as token:
50
+ token.write(creds.to_json())
51
+
52
+ youtube_service = build('youtube', 'v3', credentials=creds)
53
 
54
+ # ํŠน์ • ์ฑ„๋„ ID
55
+ SPECIFIC_CHANNEL_ID = int(os.getenv("DISCORD_CHANNEL_ID"))
 
 
 
 
56
 
57
+ class MyClient(discord.Client):
58
+ def __init__(self, *args, **kwargs):
59
+ super().__init__(*args, **kwargs)
60
+ self.is_processing = False
61
+
62
+ async def on_ready(self):
63
+ logging.info(f'{self.user}๋กœ ๋กœ๊ทธ์ธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!')
64
+
65
+ # web.py ํŒŒ์ผ ์‹คํ–‰
66
+ subprocess.Popen(["python", "web.py"])
67
+ logging.info("Web.py server has been started.")
68
+
69
+ # ๋ด‡์ด ์‹œ์ž‘๋  ๋•Œ ์•ˆ๋‚ด ๋ฉ”์‹œ์ง€๋ฅผ ์ „์†ก
70
+ channel = self.get_channel(SPECIFIC_CHANNEL_ID)
71
+ if channel:
72
+ await channel.send("์œ ํŠœ๋ธŒ ๋น„๋””์˜ค URL์„ ์ž…๋ ฅํ•˜๋ฉด, ์ž๋ง‰๊ณผ ๋Œ“๊ธ€์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๋‹ต๊ธ€์„ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.")
73
+
74
+ async def on_message(self, message):
75
+ if message.author == self.user:
76
+ return
77
+ if not self.is_message_in_specific_channel(message):
78
+ return
79
+ if self.is_processing:
80
+ return
81
+ self.is_processing = True
82
+ try:
83
+ video_id = extract_video_id(message.content)
84
+ if video_id:
85
+ transcript = await get_best_available_transcript(video_id)
86
+ comments = await get_video_comments(video_id)
87
+ if comments and transcript:
88
+ replies = await generate_replies(comments, transcript)
89
+ await create_thread_and_send_replies(message, video_id, comments, replies)
90
+ await post_replies_to_youtube(video_id, comments, replies)
91
+ else:
92
+ await message.channel.send("์ž๋ง‰์ด๋‚˜ ๋Œ“๊ธ€์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
93
+ else:
94
+ await message.channel.send("์œ ํšจํ•œ ์œ ํŠœ๋ธŒ ๋น„๋””์˜ค URL์„ ์ œ๊ณตํ•ด ์ฃผ์„ธ์š”.")
95
+ finally:
96
+ self.is_processing = False
97
+
98
+ def is_message_in_specific_channel(self, message):
99
+ # ๋ฉ”์‹œ์ง€๊ฐ€ ์ง€์ •๋œ ์ฑ„๋„์ด๊ฑฐ๋‚˜, ํ•ด๋‹น ์ฑ„๋„์˜ ์“ฐ๋ ˆ๋“œ์ธ ๊ฒฝ์šฐ True ๋ฐ˜ํ™˜
100
+ return message.channel.id == SPECIFIC_CHANNEL_ID or (
101
+ isinstance(message.channel, discord.Thread) and message.channel.parent_id == SPECIFIC_CHANNEL_ID
102
+ )
103
+
104
+ def extract_video_id(url):
105
+ """
106
+ YouTube ๋น„๋””์˜ค URL์—์„œ ๋น„๋””์˜ค ID๋ฅผ ์ถ”์ถœํ•ฉ๋‹ˆ๋‹ค.
107
+ """
108
+ video_id = None
109
+ youtube_regex = (
110
+ r'(https?://)?(www\.)?'
111
+ '(youtube|youtu|youtube-nocookie)\.(com|be)/'
112
+ '(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})')
113
+
114
+ match = re.match(youtube_regex, url)
115
+ if match:
116
+ video_id = match.group(6)
117
+ logging.debug(f'Extracted video ID: {video_id}')
118
+ return video_id
119
+
120
+ async def get_best_available_transcript(video_id):
121
+ """
122
+ YouTube ๋น„๋””์˜ค์˜ ์ž๋ง‰์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
123
+ """
124
+ try:
125
+ # ํ•œ๊ตญ์–ด ์ž๋ง‰ ์‹œ๋„
126
+ transcript = YouTubeTranscriptApi.get_transcript(video_id, languages=['ko'])
127
+ except Exception as e:
128
+ logging.warning(f'Error fetching Korean transcript: {e}')
129
+ try:
130
+ # ํ•œ๊ตญ์–ด ์ž๋ง‰์ด ์—†์œผ๋ฉด ์˜์–ด ์ž๋ง‰ ์‹œ๋„
131
+ transcript = YouTubeTranscriptApi.get_transcript(video_id, languages=['en'])
132
+ except Exception as e:
133
+ logging.warning(f'Error fetching English transcript: {e}')
134
+ try:
135
+ # ์˜์–ด ์ž๋ง‰๋„ ์—†์œผ๋ฉด ๋‹ค๋ฅธ ์–ธ์–ด ์ž๋ง‰ ์‹œ๋„
136
+ transcripts = YouTubeTranscriptApi.list_transcripts(video_id)
137
+ transcript = transcripts.find_manually_created_transcript().fetch()
138
+ except Exception as e:
139
+ logging.error(f'Error fetching alternative transcript: {e}')
140
+ return None
141
+
142
+ # ์ž๋ง‰ ํฌ๋งทํŒ…
143
+ formatter = TextFormatter()
144
+ transcript_text = formatter.format_transcript(transcript)
145
+ logging.debug(f'Fetched transcript: {transcript_text}')
146
+ return transcript_text
147
+
148
+ async def get_video_comments(video_id):
149
+ """
150
+ YouTube ๋น„๋””์˜ค์˜ ๋Œ“๊ธ€์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
151
+ """
152
+ comments = []
153
+ response = youtube_service.commentThreads().list(
154
+ part='snippet',
155
+ videoId=video_id,
156
+ maxResults=100 # ์ตœ๋Œ€ 100๊ฐœ์˜ ๋Œ“๊ธ€ ๊ฐ€์ ธ์˜ค๊ธฐ
157
+ ).execute()
158
+
159
+ for item in response.get('items', []):
160
+ comment = item['snippet']['topLevelComment']['snippet']['textOriginal']
161
+ comment_id = item['snippet']['topLevelComment']['id']
162
+ comments.append((comment, comment_id)) # ๋Œ“๊ธ€๊ณผ ๋Œ“๊ธ€ ID๋ฅผ ํ•จ๊ป˜ ์ €์žฅ
163
 
164
+ logging.debug(f'Fetched comments: {comments}')
165
+ return comments
166
+
167
+ async def generate_replies(comments, transcript):
168
+ """
169
+ ๋Œ“๊ธ€๊ณผ ์ž๋ง‰์„ ๊ธฐ๋ฐ˜์œผ๋กœ LLM ๋‹ต๊ธ€์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
170
+ """
171
+ replies = []
172
+ for comment, _ in comments:
173
+ messages = [
174
+ {"role": "system", "content": f"๋น„๋””์˜ค ์ž๋ง‰: {transcript}"},
175
+ {"role": "user", "content": comment}
176
+ ]
177
+ loop = asyncio.get_event_loop()
178
+ response = await loop.run_in_executor(None, lambda: hf_client.chat_completion(
179
+ messages, max_tokens=400, temperature=0.7, top_p=0.85)) # max_tokens ๊ฐ’์„ ์กฐ์ •
180
+
181
+ if response.choices and response.choices[0].message:
182
+ reply = response.choices[0].message['content'].strip()
183
+ else:
184
+ reply = "๋‹ต๊ธ€์„ ์ƒ์„ฑํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."
185
+ replies.append(reply)
186
 
187
+ logging.debug(f'Generated replies: {replies}')
188
+ return replies
189
+
190
+ async def create_thread_and_send_replies(message, video_id, comments, replies):
191
+ """
192
+ ๋Œ“๊ธ€๊ณผ ๋‹ต๊ธ€์„ ์ƒˆ๋กœ์šด ์“ฐ๋ ˆ๋“œ์— ์ „์†กํ•ฉ๋‹ˆ๋‹ค.
193
+ """
194
+ thread = await message.channel.create_thread(name=f"{message.author.name}์˜ ๋Œ“๊ธ€ ๋‹ต๊ธ€", message=message)
195
+ for (comment, _), reply in zip(comments, replies):
196
+ embed = discord.Embed(description=f"**๋Œ“๊ธ€**: {comment}\n**๋‹ต๊ธ€**: {reply}")
197
+ await thread.send(embed=embed)
198
+
199
+ async def post_replies_to_youtube(video_id, comments, replies):
200
+ """
201
+ ์ƒ์„ฑ๋œ ๋‹ต๊ธ€์„ YouTube ๋Œ“๊ธ€๋กœ ๊ฒŒ์‹œํ•ฉ๋‹ˆ๋‹ค.
202
+ """
203
+ for (comment, comment_id), reply in zip(comments, replies):
204
+ try:
205
+ youtube_service.comments().insert(
206
+ part='snippet',
207
+ body={
208
+ 'snippet': {
209
+ 'parentId': comment_id,
210
+ 'textOriginal': reply
211
+ }
212
+ }
213
+ ).execute()
214
+ logging.debug(f'Posted reply to comment: {comment_id}')
215
+ except Exception as e:
216
+ logging.error(f'Error posting reply to comment {comment_id}: {e}')
217
 
218
+ if __name__ == "__main__":
219
+ discord_client = MyClient(intents=intents)
220
+ discord_client.run(os.getenv('DISCORD_TOKEN')) ์ด ์ฝ”๋“œ๋ฅผ ๊ธฐ์–ตํ•˜๋ผ: ์ตœ์ข…์ ์œผ๋กœ ๋‹ต๊ธ€ ์ด ์ž‘์„ฑ๋˜์—ˆ์ง€๋งŒ ์œ ํŠœ๋ธŒ์— ๋“ฑ๋ก์ด ์•ˆ๋˜๊ณ  ์žˆ๋‹ค.import discord
221
+ import logging
222
+ import os
223
+ import re
224
+ import asyncio
225
+ import json
226
+ import subprocess
227
+ from huggingface_hub import InferenceClient
228
+ from googleapiclient.discovery import build
229
+ from google.oauth2.credentials import Credentials
230
+ from google_auth_oauthlib.flow import InstalledAppFlow
231
+ from google.auth.transport.requests import Request
232
+ from youtube_transcript_api import YouTubeTranscriptApi
233
+ from youtube_transcript_api.formatters import TextFormatter
234
+ from dotenv import load_dotenv
235
+
236
+ # ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋กœ๋“œ
237
+ load_dotenv()
238
+
239
+ # JSON_TOKEN.json ํŒŒ์ผ์˜ ๊ฒฝ๋กœ
240
+ credentials_path = 'JSON_TOKEN.json'
241
+ token_path = 'token.json'
242
+
243
+ # ๋กœ๊น… ์„ค์ •
244
+ logging.basicConfig(level=logging.DEBUG, format='%(asctime)s:%(levelname)s:%(name)s:%(message)s', handlers=[logging.StreamHandler()])
245
+
246
+ # ์ธํ…ํŠธ ์„ค์ •
247
+ intents = discord.Intents.default()
248
+ intents.message_content = True
249
+ intents.messages = True
250
+ intents.guilds = True
251
+ intents.guild_messages = True
252
+
253
+ # ์ถ”๋ก  API ํด๋ผ์ด์–ธํŠธ ์„ค์ •
254
+ hf_client = InferenceClient("CohereForAI/c4ai-command-r-plus", token=os.getenv("HF_TOKEN"))
255
+
256
+ # OAuth 2.0 ์ธ์ฆ ์„ค์ •
257
+ SCOPES = ["https://www.googleapis.com/auth/youtube.force-ssl"]
258
+ creds = None
259
 
 
260
  if os.path.exists(token_path):
261
  creds = Credentials.from_authorized_user_file(token_path, SCOPES)
 
 
 
 
262
  if not creds or not creds.valid:
263
  if creds and creds.expired and creds.refresh_token:
264
  creds.refresh(Request())
265
  else:
266
+ flow = InstalledAppFlow.from_client_secrets_file(credentials_path, SCOPES)
267
+ creds = flow.run_local_server(port=0)
268
+ with open(token_path, 'w') as token:
269
+ token.write(creds.to_json())
270
 
 
271
  youtube_service = build('youtube', 'v3', credentials=creds)
272
 
273
+ # ํŠน์ • ์ฑ„๋„ ID
274
  SPECIFIC_CHANNEL_ID = int(os.getenv("DISCORD_CHANNEL_ID"))
275
 
276
  class MyClient(discord.Client):
 
421
  """
422
  for (comment, comment_id), reply in zip(comments, replies):
423
  try:
424
+ youtube_service.comments().insert(
425
  part='snippet',
426
  body={
427
  'snippet': {
 
430
  }
431
  }
432
  ).execute()
433
+ logging.debug(f'Posted reply to comment: {comment_id}')
434
  except Exception as e:
435
  logging.error(f'Error posting reply to comment {comment_id}: {e}')
 
436
 
437
  if __name__ == "__main__":
438
  discord_client = MyClient(intents=intents)
439
+ discord_client.run(os.getenv('DISCORD_TOKEN')) # ์ด ์ฝ”๋“œ๋ฅผ ๊ธฐ์–ตํ•˜๋ผ: ์ตœ์ข…์ ์œผ๋กœ ๋‹ต๊ธ€ ์ด ์ž‘์„ฑ๋˜์—ˆ์ง€๋งŒ ์œ ํŠœ๋ธŒ์— ๋“ฑ๋ก์ด ์•ˆ๋˜๊ณ  ์žˆ๋‹ค.