このコミットが含まれているのは:
2025-08-16 04:00:20 +09:00
コミット d735c93526
4個のファイルの変更45行の追加121行の削除
+3
ファイルの表示
@@ -4,3 +4,6 @@
[submodule "nizika_nico"] [submodule "nizika_nico"]
path = nizika_nico path = nizika_nico
url = https://git.miteruzo.com/miteruzo/nizika_nico.git url = https://git.miteruzo.com/miteruzo/nizika_nico.git
[submodule "nicolib"]
path = nicolib
url = https://git.miteruzo.com/miteruzo/nicolib.git
+40 -120
ファイルの表示
@@ -2,15 +2,14 @@ from __future__ import annotations
import asyncio import asyncio
import random import random
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 TypedDict, cast
import requests import nicolib
from bs4 import BeautifulSoup
from requests.exceptions import Timeout
import queries_to_answers as q2a import queries_to_answers as q2a
from db.models import Comment, Video, VideoHistory from db.models import Comment, Video, VideoHistory
from nicolib import VideoInfo
from nizika_ai.consts import Character, GPTModel, QueryType from nizika_ai.consts import Character, GPTModel, QueryType
from nizika_ai.models import Query from nizika_ai.models import Query
@@ -22,6 +21,7 @@ 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]]
lock = Lock ()
async def main ( async def main (
@@ -36,20 +36,32 @@ async def main (
async def queries_to_answers ( async def queries_to_answers (
) -> None: ) -> None:
while True: while True:
q2a.main () loop = asyncio.get_running_loop ()
await loop.run_in_executor (None, q2a.main)
await asyncio.sleep (10) await asyncio.sleep (10)
async def report_kiriban ( async def report_kiriban (
) -> None: ) -> None:
while True: while True:
if not kiriban_list:
await wait_until (time (15, 0))
continue
# キリ番祝ひ # キリ番祝ひ
(views_count, video_info, uploaded_at) = ( async with lock:
kiriban_list.pop (random.randint (0, len (kiriban_list) - 1))) (views_count, video_info, uploaded_at) = (
kiriban_list.pop (random.randint (0, len (kiriban_list) - 1)))
since_posted = datetime.now () - uploaded_at 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 }" uri = f"https://www.nicovideo.jp/watch/{ video_code }"
(title, description, _) = fetch_embed_info (uri) (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,
@@ -57,7 +69,7 @@ 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"{ since_posted.days }{ since_posted.seconds }秒前にニコニコに投稿された『{ video_info['title'] }』という動画が{ views_count }再生を突破しました。\n" prompt = f"{ _days }{ _hours }時間{ _mins }{ _seconds }秒前にニコニコに投稿された『{ video_info['title'] }』という動画が{ views_count }再生を突破しました。\n"
prompt += f"コメント数は{ len (comments) }件です。\n" prompt += 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"
@@ -82,14 +94,18 @@ async def report_kiriban (
query.answered = False query.answered = False
query.transfer_data = { 'video_code': video_code } query.transfer_data = { 'video_code': video_code }
query.save () query.save ()
# 待ち時間計算 # 待ち時間計算
dt = datetime.now () dt = datetime.now ()
d = dt.date () d = dt.date ()
if dt.hour >= 15: if dt.hour >= 15:
d += timedelta (days = 1) d += timedelta (days = 1)
td = datetime.combine (d, time (15, 0)) - dt remain = max (len (kiriban_list), 1)
if kiriban_list: td = (datetime.combine (d, time (15, 0)) - dt) / remain
td /= len (kiriban_list) # まれに時刻跨ぎでマイナスになるため
if td.total_seconds () < 0:
td = timedelta (seconds = 0)
await asyncio.sleep (td.total_seconds ()) await asyncio.sleep (td.total_seconds ())
@@ -97,7 +113,17 @@ async def update_kiriban_list (
) -> None: ) -> None:
while True: while True:
await wait_until (time (15, 0)) await wait_until (time (15, 0))
kiriban_list += fetch_kiriban_list (datetime.now ().date ())
new_list = fetch_kiriban_list (datetime.now ().date ())
if not new_list:
continue
async with lock:
have = { k[1]['contentId'] for k in kiriban_list }
for item in new_list:
if item[1]['contentId'] not in have:
kiriban_list.append (item)
have.add (item[1]['contentId'])
def fetch_kiriban_list ( def fetch_kiriban_list (
@@ -126,7 +152,7 @@ def fetch_kiriban_list (
previous_views_count = 0 previous_views_count = 0
if previous_views_count >= kiriban_views_count: if previous_views_count >= kiriban_views_count:
continue continue
video_info = 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))
@@ -134,105 +160,6 @@ def fetch_kiriban_list (
return _kiriban_list return _kiriban_list
def fetch_video_info (
video_code: str,
) -> VideoInfo | None:
video_info: dict[str, str | list[str]] = { 'contentId': video_code }
bs = create_bs_from_url (f"https://www.nicovideo.jp/watch/{ video_code }")
if bs is None:
return None
try:
title = bs.find ('title')
if title is None:
return None
video_info['title'] = '-'.join (title.text.split ('-')[:(-1)])[:(-1)]
tags: str = bs.find ('meta', attrs = { 'name': 'keywords' }).get ('content') # type: ignore
video_info['tags'] = tags.split (',')
video_info['description'] = bs.find ('meta', attrs = { 'name': 'description' }).get ('content') # type: ignore
except Exception:
return None
return cast (VideoInfo, video_info)
def create_bs_from_url (
url: str,
params: dict | None = None,
) -> BeautifulSoup | None:
"""
URL から BeautifulSoup インスタンス生成
Parameters
----------
url: str
捜査する URL
params: dict
パラメータ
Return
------
BeautifulSoup | None
BeautifulSoup オブゼクト(失敗したら None)
"""
if params is None:
params = { }
try:
req = requests.get (url, params = params, timeout = 60)
except Timeout:
return None
if req.status_code != 200:
return None
req.encoding = req.apparent_encoding
return BeautifulSoup (req.text, 'hecoml.parser')
def fetch_embed_info (
url: str,
) -> tuple[str, str, str]:
title: str = ''
description: str = ''
thumbnail: str = ''
try:
res = requests.get (url, timeout = 60)
except Timeout:
return ('', '', '')
if res.status_code != 200:
return ('', '', '')
soup = BeautifulSoup (res.text, 'html.parser')
tmp = soup.find ('title')
if tmp is not None:
title = tmp.text
tmp = soup.find ('meta', attrs = { 'name': 'description' })
if tmp is not None and hasattr (tmp, 'get'):
try:
description = cast (str, tmp.get ('content'))
except Exception:
pass
tmp = soup.find ('meta', attrs = { 'name': 'thumbnail' })
if tmp is not None and hasattr (tmp, 'get'):
try:
thumbnail = cast (str, tmp.get ('content'))
except Exception:
pass
return (title, description, thumbnail)
def fetch_comments ( def fetch_comments (
video_code: str, video_code: str,
) -> list[Comment]: ) -> list[Comment]:
@@ -257,13 +184,6 @@ async def wait_until (
await asyncio.sleep ((datetime.combine (d, t) - dt).total_seconds ()) await asyncio.sleep ((datetime.combine (d, t) - dt).total_seconds ())
class VideoInfo (TypedDict):
contentId: str
title: str
tags: list[str]
description: str
kiriban_list = ( kiriban_list = (
fetch_kiriban_list ((d := datetime.now ()).date () fetch_kiriban_list ((d := datetime.now ()).date ()
- timedelta (days = d.hour < 15))) - timedelta (days = d.hour < 15)))
サブモジュール
+1
サブモジュール nicolibb7a88cc774 で追加されました
サブモジュール nizika_nico が更新されました: b2f5f81ca8...baa75d68ba