|
|
@@ -10,15 +10,20 @@ import random |
|
|
|
import string |
|
|
|
import time |
|
|
|
from dataclasses import dataclass |
|
|
|
from datetime import datetime, timedelta |
|
|
|
from typing import Any, TypedDict, cast |
|
|
|
from datetime import date, datetime, timedelta |
|
|
|
from typing import Any, Self, Type, TypedDict, cast |
|
|
|
|
|
|
|
import mysql.connector |
|
|
|
import requests |
|
|
|
from mysql.connector.connection import MySQLConnectionAbstract |
|
|
|
|
|
|
|
# TODO: “何もしなぃ” を意味する None と区別可能にするため,NULL クラスを別途用意すること |
|
|
|
DbNull = None |
|
|
|
DbNullType = type (None) |
|
|
|
|
|
|
|
class DbNull: |
|
|
|
def __new__ ( |
|
|
|
cls, |
|
|
|
): |
|
|
|
delattr (cls, '__init__') |
|
|
|
DbNullType = Type[DbNull] |
|
|
|
|
|
|
|
|
|
|
|
class VideoSearchParam (TypedDict): |
|
|
@@ -55,11 +60,58 @@ class CommentResult (TypedDict): |
|
|
|
isMyPost: bool |
|
|
|
|
|
|
|
|
|
|
|
class CommentRow (TypedDict): |
|
|
|
id: int |
|
|
|
video_id: int |
|
|
|
comment_no: int |
|
|
|
user_id: int |
|
|
|
content: str |
|
|
|
posted_at: datetime |
|
|
|
nico_count: int |
|
|
|
vpos_ms: int | None |
|
|
|
|
|
|
|
|
|
|
|
class TagRow (TypedDict): |
|
|
|
id: int |
|
|
|
name: str |
|
|
|
|
|
|
|
|
|
|
|
class UserRow (TypedDict): |
|
|
|
id: int |
|
|
|
code: str |
|
|
|
|
|
|
|
|
|
|
|
class VideoRow (TypedDict): |
|
|
|
id: int |
|
|
|
code: str |
|
|
|
title: str |
|
|
|
description: str |
|
|
|
uploaded_at: datetime |
|
|
|
deleted_at: datetime | None |
|
|
|
|
|
|
|
|
|
|
|
class VideoHistoryRow (TypedDict): |
|
|
|
id: int |
|
|
|
video_id: int |
|
|
|
fetched_at: date |
|
|
|
views_count: int |
|
|
|
|
|
|
|
|
|
|
|
class VideoTagRow (TypedDict): |
|
|
|
id: int |
|
|
|
video_id: int |
|
|
|
tag_id: int |
|
|
|
tagged_at: date |
|
|
|
untagged_at: date | None |
|
|
|
|
|
|
|
|
|
|
|
def main ( |
|
|
|
) -> None: |
|
|
|
conn = mysql.connector.connect (user = os.environ['MYSQL_USER'], |
|
|
|
password = os.environ['MYSQL_PASS'], |
|
|
|
database = 'nizika_nico') |
|
|
|
if not isinstance (conn, MySQLConnectionAbstract): |
|
|
|
raise TypeError |
|
|
|
|
|
|
|
now = datetime.now () |
|
|
|
|
|
|
@@ -248,7 +300,7 @@ def search_nico_by_tags ( |
|
|
|
class VideoDao: |
|
|
|
def __init__ ( |
|
|
|
self, |
|
|
|
conn |
|
|
|
conn: MySQLConnectionAbstract, |
|
|
|
): |
|
|
|
self.conn = conn |
|
|
|
|
|
|
@@ -272,7 +324,7 @@ class VideoDao: |
|
|
|
id = %s |
|
|
|
ORDER BY |
|
|
|
id""", (video_id,)) |
|
|
|
row = c.fetchone () |
|
|
|
row = cast (VideoRow | None, c.fetchone ()) |
|
|
|
if row is None: |
|
|
|
return None |
|
|
|
return self._create_dto_from_row (row, with_relation_tables) |
|
|
@@ -295,7 +347,7 @@ class VideoDao: |
|
|
|
ORDER BY |
|
|
|
id""") |
|
|
|
videos: list[VideoDto] = [] |
|
|
|
for row in c.fetchall (): |
|
|
|
for row in cast (list[VideoRow], c.fetchall ()): |
|
|
|
videos.append (self._create_dto_from_row (row, with_relation_tables)) |
|
|
|
return videos |
|
|
|
|
|
|
@@ -316,7 +368,7 @@ class VideoDao: |
|
|
|
WHERE |
|
|
|
deleted_at IS NULL""") |
|
|
|
videos: list[VideoDto] = [] |
|
|
|
for row in c.fetchall (): |
|
|
|
for row in cast (list[VideoRow], c.fetchall ()): |
|
|
|
videos.append (self._create_dto_from_row (row, False)) |
|
|
|
return videos |
|
|
|
|
|
|
@@ -325,6 +377,13 @@ class VideoDao: |
|
|
|
video: VideoDto, |
|
|
|
with_relation_tables: bool, |
|
|
|
) -> None: |
|
|
|
deleted_at: datetime | DbNullType | None = video.deleted_at |
|
|
|
if deleted_at is None: |
|
|
|
raise TypeError ('未実装') |
|
|
|
if deleted_at is DbNull: |
|
|
|
deleted_at = None |
|
|
|
deleted_at = cast (datetime | None, deleted_at) |
|
|
|
|
|
|
|
with self.conn.cursor (dictionary = True) as c: |
|
|
|
c.execute (""" |
|
|
|
INSERT INTO |
|
|
@@ -350,7 +409,7 @@ class VideoDao: |
|
|
|
video.title, |
|
|
|
video.description, |
|
|
|
video.uploaded_at, |
|
|
|
video.deleted_at)) |
|
|
|
deleted_at)) |
|
|
|
video.id_ = c.lastrowid |
|
|
|
if with_relation_tables: |
|
|
|
if video.video_tags is not None: |
|
|
@@ -386,7 +445,7 @@ class VideoDao: |
|
|
|
|
|
|
|
def _create_dto_from_row ( |
|
|
|
self, |
|
|
|
row, |
|
|
|
row: VideoRow, |
|
|
|
with_relation_tables: bool, |
|
|
|
) -> VideoDto: |
|
|
|
video = VideoDto (id_ = row['id'], |
|
|
@@ -394,7 +453,7 @@ class VideoDao: |
|
|
|
title = row['title'], |
|
|
|
description = row['description'], |
|
|
|
uploaded_at = row['uploaded_at'], |
|
|
|
deleted_at = row['deleted_at']) |
|
|
|
deleted_at = row['deleted_at'] or DbNull) |
|
|
|
if with_relation_tables and video.id_ is not None: |
|
|
|
video.video_tags = VideoTagDao (self.conn).fetch_by_video_id (video.id_, False) |
|
|
|
for i in range (len (video.video_tags)): |
|
|
@@ -424,7 +483,7 @@ class VideoDto: |
|
|
|
class VideoTagDao: |
|
|
|
def __init__ ( |
|
|
|
self, |
|
|
|
conn, |
|
|
|
conn: MySQLConnectionAbstract, |
|
|
|
): |
|
|
|
self.conn = conn |
|
|
|
|
|
|
@@ -448,7 +507,7 @@ class VideoTagDao: |
|
|
|
ORDER BY |
|
|
|
id""", (video_id,)) |
|
|
|
video_tags: list[VideoTagDto] = [] |
|
|
|
for row in c.fetchall (): |
|
|
|
for row in cast (list[VideoTagRow], c.fetchall ()): |
|
|
|
video_tags.append (self._create_dto_from_row (row, with_relation_tables)) |
|
|
|
return video_tags |
|
|
|
|
|
|
@@ -473,7 +532,7 @@ class VideoTagDao: |
|
|
|
ORDER BY |
|
|
|
id""", (video_id,)) |
|
|
|
video_tags: list[VideoTagDto] = [] |
|
|
|
for row in c.fetchall (): |
|
|
|
for row in cast (list[VideoTagRow], c.fetchall ()): |
|
|
|
video_tags.append (self._create_dto_from_row (row, with_relation_tables)) |
|
|
|
return video_tags |
|
|
|
|
|
|
@@ -496,7 +555,7 @@ class VideoTagDao: |
|
|
|
WHERE |
|
|
|
video_id = %s |
|
|
|
AND tag_id = %s""", (video_id, tag_id)) |
|
|
|
row = c.fetchone () |
|
|
|
row = cast (VideoTagRow, c.fetchone ()) |
|
|
|
if row is None: |
|
|
|
return None |
|
|
|
return self._create_dto_from_row (row, with_relation_tables) |
|
|
@@ -506,6 +565,13 @@ class VideoTagDao: |
|
|
|
video_tag: VideoTagDto, |
|
|
|
with_relation_tables: bool, |
|
|
|
) -> None: |
|
|
|
untagged_at: date | DbNullType | None = video_tag.untagged_at |
|
|
|
if untagged_at is None: |
|
|
|
raise TypeError ('未実装') |
|
|
|
if untagged_at is DbNull: |
|
|
|
untagged_at = None |
|
|
|
untagged_at = cast (date | None, untagged_at) |
|
|
|
|
|
|
|
with self.conn.cursor (dictionary = True) as c: |
|
|
|
c.execute (""" |
|
|
|
INSERT INTO |
|
|
@@ -520,7 +586,7 @@ class VideoTagDao: |
|
|
|
%s, |
|
|
|
%s, |
|
|
|
%s)""", (video_tag.video_id, video_tag.tag_id, |
|
|
|
video_tag.tagged_at, video_tag.untagged_at)) |
|
|
|
video_tag.tagged_at, untagged_at)) |
|
|
|
video_tag.id_ = c.lastrowid |
|
|
|
if with_relation_tables: |
|
|
|
if video_tag.video is not None: |
|
|
@@ -533,6 +599,13 @@ class VideoTagDao: |
|
|
|
video_tag: VideoTagDto, |
|
|
|
with_relation_tables: bool, |
|
|
|
) -> None: |
|
|
|
untagged_at: date | DbNullType | None = video_tag.untagged_at |
|
|
|
if untagged_at is None: |
|
|
|
raise TypeError ('未実装') |
|
|
|
if untagged_at is DbNull: |
|
|
|
untagged_at = None |
|
|
|
untagged_at = cast (date | None, untagged_at) |
|
|
|
|
|
|
|
with self.conn.cursor (dictionary = True) as c: |
|
|
|
c.execute (""" |
|
|
|
INSERT INTO |
|
|
@@ -554,7 +627,7 @@ class VideoTagDao: |
|
|
|
untagged_at = VALUES(untagged_at)""", (video_tag.video_id, |
|
|
|
video_tag.tag_id, |
|
|
|
video_tag.tagged_at, |
|
|
|
video_tag.untagged_at)) |
|
|
|
untagged_at)) |
|
|
|
video_tag.id_ = c.lastrowid |
|
|
|
if with_relation_tables: |
|
|
|
if video_tag.video is not None: |
|
|
@@ -590,14 +663,14 @@ class VideoTagDao: |
|
|
|
|
|
|
|
def _create_dto_from_row ( |
|
|
|
self, |
|
|
|
row, |
|
|
|
row: VideoTagRow, |
|
|
|
with_relation_tables: bool, |
|
|
|
) -> VideoTagDto: |
|
|
|
video_tag = VideoTagDto (id_ = row['id'], |
|
|
|
video_id = row['video_id'], |
|
|
|
tag_id = row['tag_id'], |
|
|
|
tagged_at = row['tagged_at'], |
|
|
|
untagged_at = row['untagged_at']) |
|
|
|
untagged_at = row['untagged_at'] or DbNull) |
|
|
|
if with_relation_tables: |
|
|
|
video_tag.video = VideoDao (self.conn).find (video_tag.video_id, True) |
|
|
|
video_tag.tag = TagDao (self.conn).find (video_tag.tag_id) |
|
|
@@ -608,17 +681,17 @@ class VideoTagDao: |
|
|
|
class VideoTagDto: |
|
|
|
video_id: int |
|
|
|
tag_id: int |
|
|
|
tagged_at: datetime |
|
|
|
id_: int | None = None |
|
|
|
untagged_at: datetime | DbNullType = DbNull |
|
|
|
video: VideoDto | None = None |
|
|
|
tag: TagDto | None = None |
|
|
|
tagged_at: date |
|
|
|
id_: int | None = None |
|
|
|
untagged_at: date | DbNullType = DbNull |
|
|
|
video: VideoDto | None = None |
|
|
|
tag: TagDto | None = None |
|
|
|
|
|
|
|
|
|
|
|
class TagDao: |
|
|
|
def __init__ ( |
|
|
|
self, |
|
|
|
conn, |
|
|
|
conn: MySQLConnectionAbstract, |
|
|
|
): |
|
|
|
self.conn = conn |
|
|
|
|
|
|
@@ -635,7 +708,7 @@ class TagDao: |
|
|
|
tags |
|
|
|
WHERE |
|
|
|
id = %s""", (tag_id,)) |
|
|
|
row = c.fetchone () |
|
|
|
row = cast (TagRow | None, c.fetchone ()) |
|
|
|
if row is None: |
|
|
|
return None |
|
|
|
return self._create_dto_from_row (row) |
|
|
@@ -653,7 +726,7 @@ class TagDao: |
|
|
|
tags |
|
|
|
WHERE |
|
|
|
name = %s""", (tag_name,)) |
|
|
|
row = c.fetchone () |
|
|
|
row = cast (TagRow | None, c.fetchone ()) |
|
|
|
if row is None: |
|
|
|
return None |
|
|
|
return self._create_dto_from_row (row) |
|
|
@@ -686,7 +759,7 @@ class TagDao: |
|
|
|
|
|
|
|
def _create_dto_from_row ( |
|
|
|
self, |
|
|
|
row, |
|
|
|
row: TagRow, |
|
|
|
) -> TagDto: |
|
|
|
return TagDto (id_ = row['id'], |
|
|
|
name = row['name']) |
|
|
@@ -701,7 +774,7 @@ class TagDto: |
|
|
|
class VideoHistoryDao: |
|
|
|
def __init__ ( |
|
|
|
self, |
|
|
|
conn, |
|
|
|
conn: MySQLConnectionAbstract, |
|
|
|
): |
|
|
|
self.conn = conn |
|
|
|
|
|
|
@@ -722,7 +795,7 @@ class VideoHistoryDao: |
|
|
|
WHERE |
|
|
|
video_id = %s""", (video_id,)) |
|
|
|
video_histories: list[VideoHistoryDto] = [] |
|
|
|
for row in c.fetchall (): |
|
|
|
for row in cast (list[VideoHistoryRow], c.fetchall ()): |
|
|
|
video_histories.append (self._create_dto_from_row (row, with_relation_tables)) |
|
|
|
return video_histories |
|
|
|
|
|
|
@@ -777,7 +850,7 @@ class VideoHistoryDao: |
|
|
|
|
|
|
|
def _create_dto_from_row ( |
|
|
|
self, |
|
|
|
row, |
|
|
|
row: VideoHistoryRow, |
|
|
|
with_relation_tables: bool, |
|
|
|
) -> VideoHistoryDto: |
|
|
|
video_history = VideoHistoryDto (id_ = row['id'], |
|
|
@@ -792,7 +865,7 @@ class VideoHistoryDao: |
|
|
|
@dataclass (slots = True) |
|
|
|
class VideoHistoryDto: |
|
|
|
video_id: int |
|
|
|
fetched_at: datetime |
|
|
|
fetched_at: date |
|
|
|
views_count: int |
|
|
|
id_: int | None = None |
|
|
|
video: VideoDto | None = None |
|
|
@@ -801,7 +874,7 @@ class VideoHistoryDto: |
|
|
|
class CommentDao: |
|
|
|
def __init__ ( |
|
|
|
self, |
|
|
|
conn, |
|
|
|
conn: MySQLConnectionAbstract, |
|
|
|
): |
|
|
|
self.conn = conn |
|
|
|
|
|
|
@@ -826,7 +899,7 @@ class CommentDao: |
|
|
|
WHERE |
|
|
|
video_id = %s""", (video_id,)) |
|
|
|
comments: list[CommentDto] = [] |
|
|
|
for row in c.fetchall (): |
|
|
|
for row in cast (list[CommentRow], c.fetchall ()): |
|
|
|
comments.append (self._create_dto_from_row (row, with_relation_tables)) |
|
|
|
return comments |
|
|
|
|
|
|
@@ -835,6 +908,13 @@ class CommentDao: |
|
|
|
comment: CommentDto, |
|
|
|
with_relation_tables: bool, |
|
|
|
) -> None: |
|
|
|
vpos_ms: int | DbNullType | None = comment.vpos_ms |
|
|
|
if vpos_ms is None: |
|
|
|
raise TypeError ('未実装') |
|
|
|
if vpos_ms is DbNull: |
|
|
|
vpos_ms = None |
|
|
|
vpos_ms = cast (int | None, vpos_ms) |
|
|
|
|
|
|
|
with self.conn.cursor (dictionary = True) as c: |
|
|
|
c.execute (""" |
|
|
|
INSERT INTO |
|
|
@@ -868,7 +948,7 @@ class CommentDao: |
|
|
|
comment.content, |
|
|
|
comment.posted_at, |
|
|
|
comment.nico_count, |
|
|
|
comment.vpos_ms)) |
|
|
|
vpos_ms)) |
|
|
|
|
|
|
|
def upsert_all ( |
|
|
|
self, |
|
|
@@ -880,7 +960,7 @@ class CommentDao: |
|
|
|
|
|
|
|
def _create_dto_from_row ( |
|
|
|
self, |
|
|
|
row, |
|
|
|
row: CommentRow, |
|
|
|
with_relation_tables: bool, |
|
|
|
) -> CommentDto: |
|
|
|
comment = CommentDto (id_ = row['id'], |
|
|
@@ -890,7 +970,7 @@ class CommentDao: |
|
|
|
content = row['content'], |
|
|
|
posted_at = row['posted_at'], |
|
|
|
nico_count = row['nico_count'], |
|
|
|
vpos_ms = row['vpos_ms']) |
|
|
|
vpos_ms = row['vpos_ms'] or DbNull) |
|
|
|
if with_relation_tables: |
|
|
|
comment.video = VideoDao (self.conn).find (comment.video_id, True) |
|
|
|
return comment |
|
|
@@ -913,7 +993,7 @@ class CommentDto: |
|
|
|
class UserDao: |
|
|
|
def __init__ ( |
|
|
|
self, |
|
|
|
conn, |
|
|
|
conn: MySQLConnectionAbstract, |
|
|
|
): |
|
|
|
self.conn = conn |
|
|
|
|
|
|
@@ -930,7 +1010,7 @@ class UserDao: |
|
|
|
users |
|
|
|
WHERE |
|
|
|
code = %s""", (user_code,)) |
|
|
|
row = c.fetchone () |
|
|
|
row = cast (UserRow | None, c.fetchone ()) |
|
|
|
if row is None: |
|
|
|
return None |
|
|
|
return self._create_dto_from_row (row) |
|
|
@@ -949,7 +1029,7 @@ class UserDao: |
|
|
|
|
|
|
|
def _create_dto_from_row ( |
|
|
|
self, |
|
|
|
row, |
|
|
|
row: UserRow, |
|
|
|
) -> UserDto: |
|
|
|
return UserDto (id_ = row['id'], |
|
|
|
code = row['code']) |
|
|
|