| @@ -1,15 +1,17 @@ | |||||
| from __future__ import annotations | from __future__ import annotations | ||||
| import json | |||||
| import math | import math | ||||
| import os | import os | ||||
| import random | import random | ||||
| import subprocess | |||||
| import sys | import sys | ||||
| import time | import time | ||||
| import wave | import wave | ||||
| from datetime import datetime, timedelta | from datetime import datetime, timedelta | ||||
| from enum import Enum, auto | from enum import Enum, auto | ||||
| from io import BytesIO | from io import BytesIO | ||||
| from typing import Callable, TypedDict | |||||
| from typing import Callable, TypedDict, cast | |||||
| import cv2 | import cv2 | ||||
| import emoji | import emoji | ||||
| @@ -34,6 +36,8 @@ from nizika_ai.config import DB | |||||
| from nizika_ai.consts import Character, GPTModel, Platform, QueryType | from nizika_ai.consts import Character, GPTModel, Platform, QueryType | ||||
| from nizika_ai.models import Answer, AnsweredFlag, Query, User | from nizika_ai.models import Answer, AnsweredFlag, Query, User | ||||
| NIZIKA_NICO_DIR = os.environ.get ('NIZIKA_NICO_DIR') or '/root/nizika_nico' | |||||
| pygame.init () | pygame.init () | ||||
| FPS = 30 | FPS = 30 | ||||
| @@ -42,6 +46,8 @@ SYSTEM_FONT = pygame.font.SysFont ('notosanscjkjp', 11, bold = True) | |||||
| USER_FONT = pygame.font.SysFont ('notosanscjkjp', 15, italic = True) | USER_FONT = pygame.font.SysFont ('notosanscjkjp', 15, italic = True) | ||||
| DEERJIKA_FONT = pygame.font.SysFont ('07nikumarufont', 23) | DEERJIKA_FONT = pygame.font.SysFont ('07nikumarufont', 23) | ||||
| WATCHING_MSG = 'それじゃあ、さっそく見てみるぬ゛ん゛!' | |||||
| def main ( | def main ( | ||||
| ) -> None: | ) -> None: | ||||
| @@ -61,7 +67,7 @@ def main ( | |||||
| except KeyError: | except KeyError: | ||||
| broadcast = None | broadcast = None | ||||
| waiting_balloon = (False, '', '') | |||||
| waiting_balloon: tuple[bool, str | None, str] = (False, '', '') | |||||
| last_flags_poll: float = 0 | last_flags_poll: float = 0 | ||||
| traced_af_ids: list[int] = [] | traced_af_ids: list[int] = [] | ||||
| @@ -76,7 +82,10 @@ def main ( | |||||
| if (not balloon.enabled) and (not snack_time.enabled): | if (not balloon.enabled) and (not snack_time.enabled): | ||||
| if waiting_balloon[0]: | if waiting_balloon[0]: | ||||
| deerjika.talk (waiting_balloon[1], waiting_balloon[2]) | deerjika.talk (waiting_balloon[1], waiting_balloon[2]) | ||||
| waiting_balloon = (False, '', '') | |||||
| if waiting_balloon[2] == WATCHING_MSG: | |||||
| ... | |||||
| else: | |||||
| waiting_balloon = (False, '', '') | |||||
| if now_m - last_flags_poll >= 1: | if now_m - last_flags_poll >= 1: | ||||
| last_flags_poll = now_m | last_flags_poll = now_m | ||||
| @@ -104,6 +113,12 @@ def main ( | |||||
| waiting_balloon = (True, query.content, answer.content) | waiting_balloon = (True, query.content, answer.content) | ||||
| answer_flag.answered = True | answer_flag.answered = True | ||||
| answer_flag.save () | answer_flag.save () | ||||
| case QueryType.KIRIBAN: | |||||
| query = Query.find (answer.query_id) | |||||
| deerjika.talk (None, answer.content) | |||||
| waiting_balloon = (True, None, WATCHING_MSG) | |||||
| answer_flag.answered = True | |||||
| answer_flag.save () | |||||
| case _: | case _: | ||||
| traced_af_ids.append (answer_flag.id) | traced_af_ids.append (answer_flag.id) | ||||
| DB.commit () | DB.commit () | ||||
| @@ -490,7 +505,7 @@ class Deerjika (Creature): | |||||
| def talk ( | def talk ( | ||||
| self, | self, | ||||
| query: str, | |||||
| query: str | None, | |||||
| answer: str, | answer: str, | ||||
| ) -> None: | ) -> None: | ||||
| self.bell () | self.bell () | ||||
| @@ -560,7 +575,7 @@ class Balloon (GameObject): | |||||
| answer (str): 回答テキスト | answer (str): 回答テキスト | ||||
| image_url (str, None): 画像 URL | image_url (str, None): 画像 URL | ||||
| length (int): 表示する時間 (frame) | length (int): 表示する時間 (frame) | ||||
| query (str): 質問テキスト | |||||
| query (str, None): 質問テキスト | |||||
| surface (Surface): 吹出し Surface | surface (Surface): 吹出し Surface | ||||
| x_flip (bool): 左右反転フラグ | x_flip (bool): 左右反転フラグ | ||||
| y_flip (bool): 上下反転フラグ | y_flip (bool): 上下反転フラグ | ||||
| @@ -569,7 +584,7 @@ class Balloon (GameObject): | |||||
| answer: str = '' | answer: str = '' | ||||
| image_url: str | None = None | image_url: str | None = None | ||||
| length: int = 300 | length: int = 300 | ||||
| query: str = '' | |||||
| query: str | None = None | |||||
| surface: Surface | surface: Surface | ||||
| x_flip: bool = False | x_flip: bool = False | ||||
| y_flip: bool = False | y_flip: bool = False | ||||
| @@ -595,8 +610,10 @@ class Balloon (GameObject): | |||||
| self.game.last_answered_at = self.game.now | self.game.last_answered_at = self.game.now | ||||
| return | return | ||||
| query = self.query | query = self.query | ||||
| if CommonModule.len_by_full (query) > 21: | |||||
| if query and CommonModule.len_by_full (query) > 21: | |||||
| query = CommonModule.mid_by_full (query, 0, 19.5) + '...' | query = CommonModule.mid_by_full (query, 0, 19.5) + '...' | ||||
| if query is not None: | |||||
| query = '>' + query | |||||
| answer = Surface ( | answer = Surface ( | ||||
| (375, int (((CommonModule.len_by_full (self.answer) - 1) // 16 + 1) * 23.4375)), | (375, int (((CommonModule.len_by_full (self.answer) - 1) // 16 + 1) * 23.4375)), | ||||
| pygame.SRCALPHA) | pygame.SRCALPHA) | ||||
| @@ -605,7 +622,7 @@ class Balloon (GameObject): | |||||
| CommonModule.mid_by_full (self.answer, 16 * i, 16), True, (192, 0, 0)), | CommonModule.mid_by_full (self.answer, 16 * i, 16), True, (192, 0, 0)), | ||||
| (0, 23.4375 * i)) | (0, 23.4375 * i)) | ||||
| surface = self.surface.copy () | surface = self.surface.copy () | ||||
| surface.blit (USER_FONT.render ('>' + query, True, (0, 0, 0)), (56.25, 32.8125)) | |||||
| surface.blit (USER_FONT.render (query, True, (0, 0, 0)), (56.25, 32.8125)) | |||||
| y: float | y: float | ||||
| if self.frame < 30: | if self.frame < 30: | ||||
| y = 0 | y = 0 | ||||
| @@ -619,7 +636,7 @@ class Balloon (GameObject): | |||||
| def talk ( | def talk ( | ||||
| self, | self, | ||||
| query: str, | |||||
| query: str | None, | |||||
| answer: str, | answer: str, | ||||
| image_url: str | None = None, | image_url: str | None = None, | ||||
| length: int = 300, | length: int = 300, | ||||
| @@ -815,7 +832,7 @@ class Broadcast: | |||||
| def __init__ ( | def __init__ ( | ||||
| self, | self, | ||||
| broadcast_code, | |||||
| broadcast_code: str, | |||||
| ): | ): | ||||
| self.code = broadcast_code | self.code = broadcast_code | ||||
| self.chat = pytchat.create (self.code) | self.chat = pytchat.create (self.code) | ||||
| @@ -955,7 +972,13 @@ class Video (GameObject): | |||||
| class NicoVideo (Video): | class NicoVideo (Video): | ||||
| ... | |||||
| def __init__ ( | |||||
| self, | |||||
| game: Game, | |||||
| video_code: str, | |||||
| ): | |||||
| comments = fetch_comments (video_code) | |||||
| #super ().__init__ (game, ) | |||||
| class SnackTime (Video): | class SnackTime (Video): | ||||
| @@ -977,6 +1000,17 @@ def fetch_bytes_from_url ( | |||||
| return res.content | return res.content | ||||
| def fetch_comments ( | |||||
| video_code: str, | |||||
| ) -> list[CommentDict]: | |||||
| result = subprocess.run ( | |||||
| ['python3', 'get_kiriban_list.py', video_code], | |||||
| cwd = NIZIKA_NICO_DIR, | |||||
| env = os.environ, | |||||
| capture_output = True, | |||||
| text = True) | |||||
| return cast(list[CommentDict], json.loads (result.stdout)) | |||||
| def add_query ( | def add_query ( | ||||
| broadcast: Broadcast, | broadcast: Broadcast, | ||||
| ) -> None: | ) -> None: | ||||
| @@ -1007,6 +1041,11 @@ def add_query ( | |||||
| DB.commit () | DB.commit () | ||||
| class CommentDict (TypedDict): | |||||
| content: str | |||||
| vpos_ms: int | |||||
| def log ( | def log ( | ||||
| msg: str, | msg: str, | ||||
| ) -> None: | ) -> None: | ||||