|
@@ -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 |
|
|
|
|
|
from bs4 import BeautifulSoup |
|
|
|
|
|
from requests.exceptions import Timeout |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import nicolib |
|
|
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) = ( |
|
|
|
|
|
kiriban_list.pop (random.randint (0, len (kiriban_list) - 1))) |
|
|
|
|
|
|
|
|
async with lock: |
|
|
|
|
|
(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 |
|
|
|
|
|
if kiriban_list: |
|
|
|
|
|
td /= len (kiriban_list) |
|
|
|
|
|
|
|
|
remain = max (len (kiriban_list), 1) |
|
|
|
|
|
td = (datetime.combine (d, time (15, 0)) - dt) / remain |
|
|
|
|
|
# まれに時刻跨ぎでマイナスになるため |
|
|
|
|
|
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))) |
|
|