伊地知ニジカ放送局だぬ゛ん゛. https://www.youtube.com/@deerjika
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

test.py 22 KiB

4 weeks ago
4 weeks ago
3 weeks ago
4 weeks ago
3 weeks ago
1 month ago
1 month ago
3 weeks ago
1 month ago
1 month ago
1 month ago
3 weeks ago
1 month ago
3 weeks ago
3 weeks ago
3 weeks ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
4 weeks ago
4 weeks ago
3 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794
  1. from __future__ import annotations
  2. import math
  3. import os
  4. import random
  5. import sys
  6. import wave
  7. from datetime import datetime, timedelta
  8. from enum import Enum, auto
  9. from typing import Callable, TypedDict
  10. import cv2
  11. import emoji
  12. import ephem
  13. import pygame
  14. import pygame.gfxdraw
  15. import pytchat
  16. import requests
  17. from cv2 import VideoCapture
  18. from ephem import Moon, Observer, Sun
  19. from pygame import Rect, Surface
  20. from pygame.font import Font
  21. from pygame.mixer import Sound
  22. from pygame.time import Clock
  23. from pytchat.core.pytchat import PytchatCore
  24. from pytchat.processors.default.processor import Chat
  25. from aques import Aques
  26. from common_module import CommonModule
  27. from nizika_ai.config import DB
  28. from nizika_ai.consts import (AnswerType, Character, GPTModel, Platform,
  29. QueryType)
  30. from nizika_ai.models import Answer, AnsweredFlag, Query, User
  31. pygame.init ()
  32. FPS = 30
  33. SYSTEM_FONT = pygame.font.SysFont ('notosanscjkjp', 24, bold = True)
  34. USER_FONT = pygame.font.SysFont ('notosanscjkjp', 32, italic = True)
  35. DEERJIKA_FONT = pygame.font.SysFont ('07nikumarufont', 50)
  36. def main (
  37. ) -> None:
  38. game = Game ()
  39. Bg (game)
  40. balloon = Balloon (game)
  41. deerjika = Deerjika (game, DeerjikaPattern.RELAXED,
  42. x = CWindow.WIDTH * 3 / 4,
  43. y = CWindow.HEIGHT - 120,
  44. balloon = balloon)
  45. CurrentTime (game, SYSTEM_FONT)
  46. try:
  47. broadcast = Broadcast (os.environ['BROADCAST_CODE'])
  48. except Exception:
  49. pass
  50. while True:
  51. for event in pygame.event.get ():
  52. if event.type == pygame.QUIT:
  53. pygame.quit ()
  54. sys.exit ()
  55. if not balloon.enabled:
  56. try:
  57. DB.begin_transaction ()
  58. answer_flags = (AnsweredFlag.where ('platform', Platform.YOUTUBE.value)
  59. .where ('answered', False)
  60. .get ())
  61. if answer_flags:
  62. answer_flag = random.choice (answer_flags)
  63. answer = Answer.find (answer_flag.answer_id)
  64. if answer.answer_type == AnswerType.YOUTUBE_REPLY.value:
  65. query = Query.find (answer.query_id)
  66. deerjika.talk (query.content, answer.content)
  67. answer_flag.answered = True
  68. answer_flag.save ()
  69. DB.commit ()
  70. add_query (broadcast)
  71. except Exception:
  72. pass
  73. game.redraw ()
  74. class Bg:
  75. """
  76. 背景オブゼクト管理用クラス
  77. Attributes:
  78. base (BgBase): 最背面
  79. grass (BgGrass): 草原部分
  80. jojoko (Jojoko): 大月ヨヨコ
  81. kita (KitaSun): き太く陽
  82. """
  83. base: BgBase
  84. grass: BgGrass
  85. jojoko: Jojoko
  86. kita: KitaSun
  87. def __init__ (
  88. self,
  89. game: Game,
  90. ):
  91. self.base = BgBase (game)
  92. self.jojoko = Jojoko (game)
  93. self.kita = KitaSun (game)
  94. self.grass = BgGrass (game)
  95. class DeerjikaPattern (Enum):
  96. """
  97. ニジカの状態
  98. Members:
  99. NORMAL: 通常
  100. RELAXED: 足パタパタ
  101. SLEEPING: 寝ニジカ
  102. DANCING: ダンシング・ニジカ
  103. """
  104. NORMAL = auto ()
  105. RELAXED = auto ()
  106. SLEEPING = auto ()
  107. DANCING = auto ()
  108. class Direction (Enum):
  109. """
  110. クリーチャの向き
  111. Members:
  112. LEFT: 左向き
  113. RIGHT: 右向き
  114. """
  115. LEFT = auto ()
  116. RIGHT = auto ()
  117. class Game:
  118. """
  119. ゲーム・クラス
  120. Attributes:
  121. clock (Clock): Clock オブゼクト
  122. frame (int): フレーム・カウンタ
  123. last_answered_at (datetime): 最後に回答した時刻
  124. now (datetime): 基準日時
  125. objects (list[GameObject]): 再描画するクラスのリスト
  126. screen (Surface): 基底スクリーン
  127. sky (Sky): 天体情報
  128. """
  129. bgm: Sound
  130. clock: Clock
  131. frame: int
  132. last_answered_at: datetime
  133. now: datetime
  134. objects: list[GameObject]
  135. screen: Surface
  136. sky: Sky
  137. def __init__ (
  138. self,
  139. ):
  140. self.now = datetime.now ()
  141. self.screen = pygame.display.set_mode ((CWindow.WIDTH, CWindow.HEIGHT))
  142. self.clock = Clock ()
  143. self.frame = 0
  144. self.objects = []
  145. self.bgm = Sound ('assets/bgm.mp3')
  146. self.bgm.set_volume (.15)
  147. self.bgm.play (loops = -1)
  148. self._create_sky ()
  149. def redraw (
  150. self,
  151. ) -> None:
  152. self.now = datetime.now ()
  153. self.sky.observer.date = self.now - timedelta (hours = 9)
  154. for obj in sorted (self.objects, key = lambda obj: obj.layer):
  155. if obj.enabled:
  156. obj.redraw ()
  157. pygame.display.update ()
  158. self.clock.tick (FPS)
  159. def _create_sky (
  160. self,
  161. ) -> None:
  162. self.sky = Sky ()
  163. self.sky.observer = Observer ()
  164. self.sky.observer.lat = '35'
  165. self.sky.observer.lon = '139'
  166. class GameObject:
  167. """
  168. 各ゲーム・オブゼクトの基底クラス
  169. Attributes:
  170. arg (float): 回転角度 (rad)
  171. ax (float): X 軸に対する加速度 (px/frame^2)
  172. ay (float): y 軸に対する加速度 (px/frame^2)
  173. enabled (bool): オブゼクトの表示可否
  174. frame (int): フレーム・カウンタ
  175. game (Game): ゲーム基盤
  176. height (int): 高さ (px)
  177. vx (float): x 軸に対する速度 (px/frame)
  178. vy (float): y 軸に対する速度 (px/frame)
  179. width (int): 幅 (px)
  180. x (float): X 座標 (px)
  181. y (float): Y 座標 (px)
  182. """
  183. arg: float = 0
  184. ax: float = 0
  185. ay: float = 0
  186. enabled: bool = True
  187. frame: int
  188. game: Game
  189. height: int
  190. layer: float
  191. vx: float = 0
  192. vy: float = 0
  193. width: int
  194. x: float
  195. y: float
  196. def __init__ (
  197. self,
  198. game: Game,
  199. layer: float | None = None,
  200. enabled: bool = True,
  201. x: float = 0,
  202. y: float = 0,
  203. ):
  204. self.game = game
  205. self.enabled = enabled
  206. self.frame = 0
  207. if layer is None:
  208. if self.game.objects:
  209. layer = max (obj.layer for obj in self.game.objects) + 10
  210. else:
  211. layer = 0
  212. self.layer = layer
  213. self.x = x
  214. self.y = y
  215. self.game.objects.append (self)
  216. def redraw (
  217. self,
  218. ) -> None:
  219. self.x += self.vx
  220. self.y += self.vy
  221. self.vx += self.ax
  222. self.vy += self.ay
  223. self.frame += 1
  224. class BgBase (GameObject):
  225. """
  226. 背景
  227. Attributes:
  228. surface (Surface): 背景 Surface
  229. """
  230. bg: Surface
  231. bg_evening: Surface
  232. bg_grass: Surface
  233. bg_night: Surface
  234. surface: Surface
  235. def __init__ (
  236. self,
  237. game: Game,
  238. ):
  239. super ().__init__ (game)
  240. self.bg = pygame.image.load ('assets/bg.jpg')
  241. self.bg_evening = pygame.image.load ('assets/bg-evening.jpg')
  242. self.bg_grass = pygame.image.load ('assets/bg-grass.png')
  243. self.bg_night = pygame.image.load ('assets/bg-night.jpg')
  244. self.surface = pygame.transform.scale (self.bg, (CWindow.WIDTH, CWindow.HEIGHT))
  245. def redraw (
  246. self,
  247. ) -> None:
  248. self.game.screen.blit (self.surface, (self.x, self.y))
  249. super ().redraw ()
  250. class BgGrass (GameObject):
  251. """
  252. 背景の草原部分
  253. Attributes:
  254. surface (Surface): 草原 Surface
  255. """
  256. surface: Surface
  257. def __init__ (
  258. self,
  259. game: Game,
  260. ):
  261. super ().__init__ (game)
  262. self.game = game
  263. self.surface = pygame.image.load ('assets/bg-grass.png')
  264. self.surface = pygame.transform.scale (self.surface, (CWindow.WIDTH, CWindow.HEIGHT))
  265. def redraw (
  266. self,
  267. ) -> None:
  268. self.game.screen.blit (self.surface, (self.x, self.y))
  269. super ().redraw ()
  270. class Creature (GameObject):
  271. sound: Sound
  272. def bell (
  273. self,
  274. ) -> None:
  275. self.sound.play ()
  276. class Deerjika (Creature):
  277. """
  278. 伊地知ニジカ
  279. Attributes:
  280. height (int): 高さ (px)
  281. scale (float): 拡大率
  282. surfaces (list[Surface]): ニジカの各フレームを Surface にしたリスト
  283. width (int): 幅 (px)
  284. """
  285. height: int
  286. scale: float = .8
  287. surfaces: list[Surface]
  288. width: int
  289. talking: bool = False
  290. wav: bytearray | None = None
  291. balloon: Balloon
  292. def __init__ (
  293. self,
  294. game: Game,
  295. pattern: DeerjikaPattern = DeerjikaPattern.NORMAL,
  296. direction: Direction = Direction.LEFT,
  297. layer: float | None = None,
  298. x: float = 0,
  299. y: float = 0,
  300. balloon: Balloon | None = None,
  301. ):
  302. if balloon is None:
  303. raise Exception
  304. super ().__init__ (game, layer, x = x, y = y)
  305. self.pattern = pattern
  306. self.direction = direction
  307. self.balloon = balloon
  308. match pattern:
  309. case DeerjikaPattern.NORMAL:
  310. ...
  311. case DeerjikaPattern.RELAXED:
  312. match direction:
  313. case Direction.LEFT:
  314. self.width = 1280
  315. self.height = 720
  316. surface = pygame.image.load ('assets/deerjika_relax_left.png')
  317. self.surfaces = []
  318. for x in range (0, surface.get_width (), self.width):
  319. self.surfaces.append (
  320. surface.subsurface (x, 0, self.width, self.height))
  321. case Direction.RIGHT:
  322. ...
  323. self.sound = Sound ('assets/noon.wav')
  324. def redraw (
  325. self,
  326. ) -> None:
  327. surface = pygame.transform.scale (self.surfaces[self.frame % len (self.surfaces)],
  328. (self.width * self.scale, self.height * self.scale))
  329. self.game.screen.blit (surface, surface.get_rect (center = (self.x, self.y)))
  330. super ().redraw ()
  331. if (not self.balloon.enabled) and self.talking:
  332. self.talking = False
  333. if (self.balloon.enabled and self.balloon.frame >= FPS * 1.5
  334. and not self.talking):
  335. self.read_out ()
  336. def talk (
  337. self,
  338. query: str,
  339. answer: str,
  340. ) -> None:
  341. self.bell ()
  342. self._create_wav (answer)
  343. length = 300
  344. if self.wav is not None:
  345. with wave.open ('./nizika_talking.wav', 'rb') as f:
  346. length = int (FPS * (f.getnframes () / f.getframerate () + 4))
  347. self.balloon.talk (query, answer, length = length)
  348. def read_out (
  349. self,
  350. ) -> None:
  351. Sound ('./nizika_talking.wav').play ()
  352. self.talking = True
  353. def _create_wav (
  354. self,
  355. message: str,
  356. ) -> None:
  357. try:
  358. self.wav = Aques.main (message, False)
  359. except:
  360. self.wav = None
  361. if self.wav is None:
  362. return
  363. with open ('./nizika_talking.wav', 'wb') as f:
  364. f.write (self.wav)
  365. class CurrentTime (GameObject):
  366. """
  367. 現在日時表示
  368. Attributes:
  369. font (Font): フォント
  370. """
  371. font: Font
  372. def __init__ (
  373. self,
  374. game: Game,
  375. font: Font,
  376. ):
  377. super ().__init__ (game)
  378. self.font = font
  379. def redraw (
  380. self,
  381. ) -> None:
  382. for i in range (4):
  383. self.game.screen.blit (
  384. self.font.render (str (self.game.now), True, (0, 0, 0)),
  385. (i % 2, i // 2 * 2))
  386. super ().redraw ()
  387. class Balloon (GameObject):
  388. """
  389. 吹出し
  390. Attributes:
  391. answer (str): 回答テキスト
  392. image_url (str, None): 画像 URL
  393. length (int): 表示する時間 (frame)
  394. query (str): 質問テキスト
  395. surface (Surface): 吹出し Surface
  396. x_flip (bool): 左右反転フラグ
  397. y_flip (bool): 上下反転フラグ
  398. """
  399. answer: str = ''
  400. image_url: str | None = None
  401. length: int = 300
  402. query: str = ''
  403. surface: Surface
  404. x_flip: bool = False
  405. y_flip: bool = False
  406. def __init__ (
  407. self,
  408. game: Game,
  409. x_flip: bool = False,
  410. y_flip: bool = False,
  411. ):
  412. super ().__init__ (game, enabled = False)
  413. self.x_flip = x_flip
  414. self.y_flip = y_flip
  415. self.surface = pygame.transform.scale (pygame.image.load ('assets/balloon.png'),
  416. (CWindow.WIDTH, CWindow.HEIGHT / 2))
  417. self.surface = pygame.transform.flip (self.surface, self.x_flip, self.y_flip)
  418. def redraw (
  419. self,
  420. ) -> None:
  421. if self.frame >= self.length:
  422. self.enabled = False
  423. self.game.last_answered_at = self.game.now
  424. return
  425. query = self.query
  426. if CommonModule.len_by_full (query) > 21:
  427. query = CommonModule.mid_by_full (query, 0, 19.5) + '...'
  428. answer = Surface ((800, ((CommonModule.len_by_full (self.answer) - 1) // 16 + 1) * 50),
  429. pygame.SRCALPHA)
  430. for i in range (int (CommonModule.len_by_full (self.answer) - 1) // 16 + 1):
  431. answer.blit (DEERJIKA_FONT.render (
  432. CommonModule.mid_by_full (self.answer, 16 * i, 16), True, (192, 0, 0)),
  433. (0, 50 * i))
  434. surface = self.surface.copy ()
  435. surface.blit (USER_FONT.render ('>' + query, True, (0, 0, 0)), (120, 70))
  436. y: int
  437. if self.frame < 30:
  438. y = 0
  439. elif self.frame >= self.length - 90:
  440. y = answer.get_height () - 100
  441. else:
  442. y = int ((answer.get_height () - 100) * (self.frame - 30) / (self.length - 120))
  443. surface.blit (answer, (100, 150), Rect (0, y, 800, 100))
  444. self.game.screen.blit (surface, (0, 0))
  445. super ().redraw ()
  446. def talk (
  447. self,
  448. query: str,
  449. answer: str,
  450. image_url: str | None = None,
  451. length: int = 300,
  452. ) -> None:
  453. self.query = query
  454. self.answer = answer
  455. self.image_url = image_url
  456. self.length = length
  457. self.frame = 0
  458. self.enabled = True
  459. class KitaSun (GameObject):
  460. """
  461. き太く陽
  462. Attributes:
  463. sun (Sun): ephem の太陽オブゼクト
  464. surface (Surface): き太く陽 Surface
  465. """
  466. alt: float
  467. az: float
  468. sun: Sun
  469. surface: Surface
  470. def __init__ (
  471. self,
  472. game: Game,
  473. ):
  474. super ().__init__ (game)
  475. self.surface = pygame.transform.scale (pygame.image.load ('assets/sun.png'), (200, 200))
  476. self.sun = Sun ()
  477. def redraw (
  478. self,
  479. ) -> None:
  480. surface = pygame.transform.rotate (self.surface, -(90 + math.degrees (self.arg)))
  481. self.game.screen.blit (surface, surface.get_rect (center = (self.x, self.y)))
  482. super ().redraw ()
  483. self.sun.compute (self.game.sky.observer)
  484. self.alt = self.sun.alt
  485. self.az = self.sun.az
  486. if abs (self.new_arg - self.arg) > math.radians (15):
  487. self.arg = self.new_arg
  488. self.x = self.new_x
  489. self.y = self.new_y
  490. @property
  491. def new_x (
  492. self,
  493. ) -> float:
  494. return CWindow.WIDTH * (math.degrees (self.az) - 80) / 120
  495. @property
  496. def new_y (
  497. self,
  498. ) -> float:
  499. return ((CWindow.HEIGHT / 2)
  500. - ((CWindow.HEIGHT / 2 + 100) * math.sin (self.alt)
  501. / math.sin (math.radians (60))))
  502. @property
  503. def new_arg (
  504. self,
  505. ) -> float:
  506. return math.atan2 (self.new_y - self.y, self.new_x - self.x)
  507. class Jojoko (GameObject):
  508. """
  509. 大月ヨヨコ
  510. Attributes:
  511. base (Surface): 満月ヨヨコ Surface
  512. moon (Moon): ephem の月オブゼクト
  513. surface (Surface): 缺けたヨヨコ
  514. """
  515. alt: float
  516. az: float
  517. base: Surface
  518. moon: Moon
  519. surface: Surface
  520. def __init__ (
  521. self,
  522. game: Game,
  523. ):
  524. super ().__init__ (game)
  525. self.base = pygame.transform.scale (pygame.image.load ('assets/moon.png'), (200, 200))
  526. self.moon = Moon ()
  527. self.surface = self._get_surface ()
  528. def redraw (
  529. self,
  530. ) -> None:
  531. if self.frame % (FPS * 3600) == 0:
  532. self.surface = self._get_surface ()
  533. surface = pygame.transform.rotate (self.surface, -(90 + math.degrees (self.arg)))
  534. surface.set_colorkey ((0, 255, 0))
  535. self.game.screen.blit (surface, surface.get_rect (center = (self.x, self.y)))
  536. super ().redraw ()
  537. self.moon.compute (self.game.sky.observer)
  538. self.alt = self.moon.alt
  539. self.az = self.moon.az
  540. if abs (self.new_arg - self.arg) > math.radians (15):
  541. self.arg = self.new_arg
  542. self.x = self.new_x
  543. self.y = self.new_y
  544. @property
  545. def phase (
  546. self,
  547. ) -> float:
  548. dt: datetime = ephem.localtime (ephem.previous_new_moon (self.game.sky.observer.date))
  549. return (self.game.now - dt).total_seconds () / 60 / 60 / 24
  550. def _get_surface (
  551. self,
  552. ) -> Surface:
  553. """
  554. ヨヨコを月齢に応じて缺かす.
  555. Returns:
  556. Surface: 缺けたヨヨコ
  557. """
  558. jojoko = self.base.copy ()
  559. for i in range (200):
  560. if 1 <= self.phase < 15:
  561. pygame.gfxdraw.bezier (jojoko, ((0, 100 + i), (100, 180 * self.phase / 7 - 80 + i), (200, 100 + i)), 3, (0, 255, 0))
  562. elif self.phase < 16:
  563. pass
  564. elif self.phase < 30:
  565. pygame.gfxdraw.bezier (jojoko, ((0, 100 - i), (100, 180 * (self.phase - 15) / 7 - 80 - i), (200, 100 - i)), 3, (0, 255, 0))
  566. else:
  567. jojoko.fill ((0, 255, 0))
  568. return jojoko
  569. @property
  570. def new_x (
  571. self,
  572. ) -> float:
  573. return CWindow.WIDTH * (math.degrees (self.az) - 80) / 120
  574. @property
  575. def new_y (
  576. self,
  577. ) -> float:
  578. return ((CWindow.HEIGHT / 2)
  579. - ((CWindow.HEIGHT / 2 + 100) * math.sin (self.alt)
  580. / math.sin (math.radians (60))))
  581. @property
  582. def new_arg (
  583. self,
  584. ) -> float:
  585. return math.atan2 (self.new_y - self.y, self.new_x - self.x)
  586. class Sky:
  587. """
  588. 天体に関する情報を保持するクラス
  589. Attributes:
  590. observer (Observer): 観測値
  591. """
  592. observer: Observer
  593. class CWindow:
  594. """
  595. ウィンドゥに関する定数クラス
  596. Attributes:
  597. WIDTH (int): ウィンドゥ幅
  598. HEIGHT (int): ウィンドゥ高さ
  599. """
  600. WIDTH = 1024
  601. HEIGHT = 768
  602. def get_surfaces_from_video (
  603. video_path: str,
  604. ) -> list[Surface]:
  605. cap = VideoCapture (video_path)
  606. if not cap.isOpened ():
  607. return []
  608. fps = cap.get (cv2.CAP_PROP_FPS)
  609. surfaces: list[Surface] = []
  610. while cap.isOpened ():
  611. (ret, frame) = cap.read ()
  612. if not ret:
  613. break
  614. frame = cv2.cvtColor (frame, cv2.COLOR_BGR2RGB)
  615. frame_surface = pygame.surfarray.make_surface (frame)
  616. frame_surface = pygame.transform.rotate (frame_surface, -90)
  617. surfaces.append (pygame.transform.flip (frame_surface, True, False))
  618. cap.release ()
  619. return surfaces
  620. class Broadcast:
  621. chat: PytchatCore
  622. def __init__ (
  623. self,
  624. broadcast_code,
  625. ):
  626. self.chat = pytchat.create (broadcast_code)
  627. def fetch_chat (
  628. self,
  629. ) -> Chat | None:
  630. if not self.chat.is_alive ():
  631. return None
  632. chats = self.chat.get ().items
  633. print (chats)
  634. if not chats:
  635. return None
  636. return random.choice (chats)
  637. class Log:
  638. ...
  639. def fetch_bytes_from_url (
  640. url: str,
  641. ) -> bytes | None:
  642. res = requests.get (url, timeout = 60)
  643. if res.status_code != 200:
  644. return None
  645. return res.content
  646. def add_query (
  647. broadcast: Broadcast,
  648. ) -> None:
  649. chat = broadcast.fetch_chat ()
  650. if chat is None:
  651. return
  652. DB.begin_transaction ()
  653. chat.message = emoji.emojize (chat.message)
  654. message: str = chat.message
  655. user = (User.where ('platform', Platform.YOUTUBE.value)
  656. .where ('code', chat.author.channelId)
  657. .first ())
  658. if user is None:
  659. user = User ()
  660. user.platform = Platform.YOUTUBE.value
  661. user.code = chat.author.channelId
  662. user.name = chat.author.name
  663. user.icon = fetch_bytes_from_url (chat.author.imageUrl)
  664. user.save ()
  665. query = Query ()
  666. query.user_id = user.id
  667. query.target_character = Character.DEERJIKA.value
  668. query.content = chat.message
  669. query.query_type = QueryType.YOUTUBE_COMMENT.value
  670. query.model = GPTModel.GPT3_TURBO.value
  671. query.sent_at = datetime.now ()
  672. query.answered = False
  673. query.save ()
  674. DB.commit ()
  675. if __name__ == '__main__':
  676. main ()