みてるぞ 4 weeks ago
parent
commit
1eaebaa724
3 changed files with 191 additions and 20 deletions
  1. +141
    -17
      main.py
  2. +1
    -1
      nicolib
  3. +49
    -2
      queries_to_answers.py

+ 141
- 17
main.py View File

@@ -1,10 +1,14 @@
"""
AI ニジカ常時稼動バッチ
"""

from __future__ import annotations from __future__ import annotations


import asyncio import asyncio
import random import random
from asyncio import Lock from asyncio import Lock
from datetime import date, datetime, time, timedelta from datetime import date, datetime, time, timedelta
from typing import TypedDict, cast
from typing import cast


import nicolib import nicolib
import queries_to_answers as q2a import queries_to_answers as q2a
@@ -21,11 +25,18 @@ KIRIBAN_VIEWS_COUNTS: list[int] = sorted ({ *range (1_000, 10_000, 1_000),
reverse = True) reverse = True)


kiriban_list: list[tuple[int, VideoInfo, datetime]] kiriban_list: list[tuple[int, VideoInfo, datetime]]

watched_videos: set[str] = set ()

lock = Lock () lock = Lock ()




async def main ( async def main (
) -> None: ) -> None:
"""
メーン処理
"""

await asyncio.gather ( await asyncio.gather (
queries_to_answers (), queries_to_answers (),
report_kiriban (), report_kiriban (),
@@ -35,6 +46,10 @@ async def main (


async def queries_to_answers ( async def queries_to_answers (
) -> None: ) -> None:
"""
クエリ処理
"""

while True: while True:
loop = asyncio.get_running_loop () loop = asyncio.get_running_loop ()
await loop.run_in_executor (None, q2a.main) await loop.run_in_executor (None, q2a.main)
@@ -43,6 +58,10 @@ async def queries_to_answers (


async def report_kiriban ( async def report_kiriban (
) -> None: ) -> None:
"""
キリ番祝ひ
"""

while True: while True:
if not kiriban_list: if not kiriban_list:
await wait_until (time (15, 0)) await wait_until (time (15, 0))
@@ -53,15 +72,7 @@ async def report_kiriban (
(views_count, video_info, uploaded_at) = ( (views_count, video_info, uploaded_at) = (
kiriban_list.pop (random.randint (0, len (kiriban_list) - 1))) kiriban_list.pop (random.randint (0, len (kiriban_list) - 1)))


since_posted = datetime.now () - uploaded_at
_days = since_posted.days
_seconds = since_posted.seconds
(_hours, _seconds) = divmod (_seconds, 3600)
(_mins, _seconds) = divmod (_seconds, 60)

video_code = video_info['contentId'] video_code = video_info['contentId']
uri = f"https://www.nicovideo.jp/watch/{ video_code }"
(title, description, _) = nicolib.fetch_embed_info (uri)
comments = fetch_comments (video_code) comments = fetch_comments (video_code)
popular_comments = sorted (comments, popular_comments = sorted (comments,
key = lambda c: c.nico_count, key = lambda c: c.nico_count,
@@ -69,8 +80,9 @@ async def report_kiriban (
latest_comments = sorted (comments, latest_comments = sorted (comments,
key = lambda c: c.posted_at, key = lambda c: c.posted_at,
reverse = True)[:10] reverse = True)[:10]
prompt = f"{ _days }日{ _hours }時間{ _mins }分{ _seconds }秒前にニコニコに投稿された『{ video_info['title'] }』という動画が{ views_count }再生を突破しました。\n"
prompt += f"コメント数は{ len (comments) }件です。\n"
prompt = (f"{ _format_elapsed (uploaded_at) }前にニコニコに投稿された"
f"『{ video_info['title'] }』という動画が{ views_count }再生を突破しました。\n"
f"コメント数は{ len (comments) }件です。\n")
if video_info['tags']: if video_info['tags']:
prompt += f"つけられたタグは「{ '」、「'.join (video_info['tags']) }」です。\n" prompt += f"つけられたタグは「{ '」、「'.join (video_info['tags']) }」です。\n"
if comments: if comments:
@@ -111,6 +123,10 @@ async def report_kiriban (


async def update_kiriban_list ( async def update_kiriban_list (
) -> None: ) -> None:
"""
キリ番リストの更新
"""

while True: while True:
await wait_until (time (15, 0)) await wait_until (time (15, 0))


@@ -129,6 +145,20 @@ async def update_kiriban_list (
def fetch_kiriban_list ( def fetch_kiriban_list (
base_date: date, base_date: date,
) -> list[tuple[int, VideoInfo, datetime]]: ) -> list[tuple[int, VideoInfo, datetime]]:
"""
キリ番を迎へた動画のリストを取得する.

Parameters
----------
base_date: date
基準日

Return
------
list[tuple[int, VideoInfo, datetime]]
動画リスト(キリ番基準再生数、対象動画情報、投稿日時のタプル)
"""

_kiriban_list: list[tuple[int, VideoInfo, datetime]] = [] _kiriban_list: list[tuple[int, VideoInfo, datetime]] = []


latest_fetched_at = cast (date, (VideoHistory latest_fetched_at = cast (date, (VideoHistory
@@ -145,7 +175,7 @@ def fetch_kiriban_list (
continue continue
previous_views_count: int | None = ( previous_views_count: int | None = (
VideoHistory VideoHistory
.where_has ('video', lambda q: q.where ('code', code))
.where_has ('video', lambda q, code = code: q.where ('code', code))
.where ('fetched_at', '<', latest_fetched_at) .where ('fetched_at', '<', latest_fetched_at)
.max ('views_count')) .max ('views_count'))
if previous_views_count is None: if previous_views_count is None:
@@ -155,7 +185,8 @@ def fetch_kiriban_list (
video_info = nicolib.fetch_video_info (code) video_info = nicolib.fetch_video_info (code)
if video_info is not None: if video_info is not None:
_kiriban_list.append ((kiriban_views_count, video_info, _kiriban_list.append ((kiriban_views_count, video_info,
cast (Video, Video.where ('code', code).first ()).uploaded_at))
(cast (Video, Video.where ('code', code).first ())
.uploaded_at)))


return _kiriban_list return _kiriban_list


@@ -163,20 +194,87 @@ def fetch_kiriban_list (
def fetch_comments ( def fetch_comments (
video_code: str, video_code: str,
) -> list[Comment]: ) -> list[Comment]:
"""
動画のコメント・リストを取得する.

Parameters
----------
video_code: str
ニコニコの動画コード

Return
------
list[Comment]
コメント・リスト
"""

video = Video.where ('code', video_code).first () video = Video.where ('code', video_code).first ()
if video is None: if video is None:
return [] return []
return video.comments return video.comments




def fetch_latest_deerjika (
) -> VideoInfo | None:
"""
最新のぼざクリ動画を取得する.

Return
------
VideoInfo | None
動画情報
"""

return nicolib.fetch_latest_video (['伊地知ニジカ',
'ぼざろクリーチャーシリーズ',
'ぼざろクリーチャーシリーズ外伝'])


async def report_nico ( async def report_nico (
) -> None: ) -> None:
...
"""
ニコニコから最新のぼざクリを取得し,まだ報知してゐなかったら報知する.
"""

while True:
latest_deerjika = fetch_latest_deerjika ()
if latest_deerjika and latest_deerjika['contentId'] not in watched_videos:
video = latest_deerjika
watched_videos.add (video['contentId'])

prompt = f"""ニコニコに『{ video['title'] }』という動画がアップされました。
つけられたタグは「{ '」、「'.join (video['tags']) }」です。
概要には次のように書かれています:
```html
{ video['description'] }
```
このことについて、みんなに告知するとともに、ニジカちゃんの感想を教えてください。"""
query = Query ()
query.user_id = None
query.target_character = Character.DEERJIKA.value
query.content = prompt
query.query_type = QueryType.NICO_REPORT.value
query.model = GPTModel.GPT3_TURBO.value
query.sent_at = datetime.now ()
query.answered = False
query.transfer_data = { 'video_code': video['contentId'] }
query.save ()

await asyncio.sleep (60)




async def wait_until ( async def wait_until (
t: time, t: time,
):
) -> None:
"""
指定した時刻まで待つ.

Parameters
----------
t: time
次に実行を続行するまでの時刻
"""

dt = datetime.now () dt = datetime.now ()
d = dt.date () d = dt.date ()
if dt.time () >= t: if dt.time () >= t:
@@ -184,9 +282,35 @@ async def wait_until (
await asyncio.sleep ((datetime.combine (d, t) - dt).total_seconds ()) await asyncio.sleep ((datetime.combine (d, t) - dt).total_seconds ())




def _format_elapsed (
uploaded_at: datetime,
) -> str:
"""
指定した時刻から現在までの時間を見やすぃ文字列に変換する.

Parameters
----------
uploaded_at: datetime
基準日時

Return
------
str
変換後文字列
"""

delta = datetime.now () - uploaded_at
days = delta.days
seconds = delta.seconds
(hours, seconds) = divmod (seconds, 3600)
(mins, seconds) = divmod (seconds, 60)

return f"{ days }日{ hours }時間{ mins }分{ seconds }秒"


kiriban_list = ( kiriban_list = (
fetch_kiriban_list ((d := datetime.now ()).date ()
- timedelta (days = d.hour < 15)))
fetch_kiriban_list ((now := datetime.now ()).date ()
- timedelta (days = now.hour < 15)))


if __name__ == '__main__': if __name__ == '__main__':
asyncio.run (main ()) asyncio.run (main ())

+ 1
- 1
nicolib

@@ -1 +1 @@
Subproject commit b7a88cc774aa7869678c00abf9f93982e5b6cfb9
Subproject commit 9fec1cf5f99ca2cb2a9b3ff158027a3416cc5f06

+ 49
- 2
queries_to_answers.py View File

@@ -1,3 +1,7 @@
"""
DB の queries テーブルにたまってゐるクエリを AI に処理させ answers テーブルに流す.
"""

from __future__ import annotations from __future__ import annotations


import random import random
@@ -6,12 +10,16 @@ from typing import TypedDict


from nizika_ai.config import DB from nizika_ai.config import DB
from nizika_ai.consts import AnswerType, Character, Platform from nizika_ai.consts import AnswerType, Character, Platform
from nizika_ai.models import Answer, AnsweredFlag, Query, User
from nizika_ai.models import Answer, AnsweredFlag, Query
from nizika_ai.talk import Talk from nizika_ai.talk import Talk




def main ( def main (
) -> None: ) -> None:
"""
メーン処理
"""

queries: list[Query] = Query.where ('answered', False).get () queries: list[Query] = Query.where ('answered', False).get ()
if not queries: if not queries:
return return
@@ -48,6 +56,21 @@ def add_answer (
user_name: str | None, user_name: str | None,
histories: list[History], histories: list[History],
) -> None: ) -> None:
"""
AI の返答を DB に積む.

Parameters
----------
query: Query
クエリ
character: Character
返答するキャラクタ
user_name: str | None
クエリの主
histories: list[History]
履歴
"""

message: str | list[dict[str, str | dict[str, str]]] message: str | list[dict[str, str | dict[str, str]]]
if query.image_url: if query.image_url:
message = [{ 'type': 'text', 'text': query.content }, message = [{ 'type': 'text', 'text': query.content },
@@ -69,10 +92,19 @@ def add_answer (
def add_answered_flags ( def add_answered_flags (
answer: Answer, answer: Answer,
) -> None: ) -> None:
"""
返答済フラグを付与する.

Parameters
----------
answer: Answer
返答モデル
"""

answer_type: AnswerType answer_type: AnswerType
try: try:
answer_type = AnswerType (answer.answer_type) answer_type = AnswerType (answer.answer_type)
except Exception:
except (TypeError, ValueError):
return return


if answer_type in (AnswerType.YOUTUBE_REPLY,): if answer_type in (AnswerType.YOUTUBE_REPLY,):
@@ -85,6 +117,17 @@ def add_answered_flag (
answer: Answer, answer: Answer,
platform: Platform, platform: Platform,
) -> None: ) -> None:
"""
返答済フラグを付与する.

Parameters
----------
answer: Answer
返答モデル
platform: Platform
プラットフォーム
"""

answered_flag = AnsweredFlag () answered_flag = AnsweredFlag ()
answered_flag.answer_id = answer.id answered_flag.answer_id = answer.id
answered_flag.platform = platform.value answered_flag.platform = platform.value
@@ -93,6 +136,10 @@ def add_answered_flag (




class History (TypedDict): class History (TypedDict):
"""
会話履歴の 1 要素;ユーザや AI の発話を簡易に保持する型
"""

role: str role: str
content: str content: str




Loading…
Cancel
Save