45 コミット

作成者 SHA1 メッセージ 日付
みてるぞ 495c1381c7 #22 インポート漏れ修正 2026-04-24 23:08:11 +09:00
みてるぞ 1074f09b96 #22 2026-04-24 09:46:34 +00:00
みてるぞ 2b706f1247 #22 2026-04-24 09:33:17 +00:00
みてるぞ cb72b8dd99 削除フラグが誤って付与されるバグ修正(#20) (#21)
#20

#20

#20

#020

Co-authored-by: miteruzo <miteruzo@naver.com>
Reviewed-on: #21
2026-04-11 05:13:29 +09:00
みてるぞ b2adf62090 投稿者情報追加(#17) (#18)
#17

#17

Co-authored-by: miteruzo <miteruzo@naver.com>
Reviewed-on: #18
2026-03-05 21:03:16 +09:00
みてるぞ a3d9d0bfd7 feat: タグと関係なしに追跡する動画リスト追加 (#16)
#15 タグ関係なく追跡する動画リスト

Co-authored-by: miteruzo <miteruzo@naver.com>
Reviewed-on: #16
2026-01-01 14:00:11 +09:00
みてるぞ f44637d274 コメント取得追加 2025-10-26 05:07:07 +09:00
みてるぞ f809e9faae キリ番修正 2025-10-26 01:19:15 +09:00
みてるぞ de8fd8634a キリ番修正 2025-10-26 01:07:15 +09:00
みてるぞ 88be511f6e キリ番修正 2025-10-26 01:06:41 +09:00
みてるぞ ea339f1ec9 キリ番修正 2025-10-26 01:05:48 +09:00
みてるぞ 06328a89b2 キリ番追加 2025-10-26 00:45:29 +09:00
みてるぞ 463e8bbec7 config に移した. 2025-10-22 23:25:49 +09:00
みてるぞ baa75d68ba ゑぐぃバグ修正 2025-08-15 02:51:06 +09:00
みてるぞ 48e51f97d0 'update_db.py' を更新 2025-07-19 19:33:37 +09:00
みてるぞ c5204383ed #13 2025-07-01 23:53:11 +09:00
みてるぞ bf36d05ed3 #13 2025-07-01 23:48:30 +09:00
みてるぞ c9bd6fdfa7 #13 2025-07-01 23:39:57 +09:00
みてるぞ b2f5f81ca8 型定義追加 2024-11-06 03:56:28 +09:00
みてるぞ 67b76e6dd4 #12 2024-11-05 12:34:10 +09:00
みてるぞ 6a5e6dfade ごみを削除 2024-10-17 18:59:25 +09:00
みてるぞ 6e99da7326 Python 3.10 に適合したのと __pycache__ 除外 2024-10-17 18:57:03 +09:00
みてるぞ 53ba658319 #8 の対応 2024-10-17 12:49:13 +09:00
みてるぞ 067c90890e video_histories への書込みを Upsert に 2024-10-16 22:40:42 +09:00
みてるぞ db14af1a73 もろもろ修正 2024-10-16 22:17:35 +09:00
みてるぞ da17333a80 typing.Self は Python 3.10 には未実装か,さぅか. 2024-10-16 22:16:04 +09:00
みてるぞ ee971997ad #6 の対応 2024-10-16 22:08:07 +09:00
みてるぞ b448335851 もろもろのモロヘイヤ 2024-10-16 20:20:50 +09:00
みてるぞ 9f810b23f0 Upsert 問題の修正とその他無駄を排除 2024-10-15 23:01:03 +09:00
みてるぞ c91cf19926 Eloquent の型定義ファイルと型安全性確認;本番環境に取込み可能 2024-10-15 00:18:35 +09:00
みてるぞ 6185788456 Merge branch 'main' into feature/query 2024-10-14 19:15:22 +09:00
みてるぞ 283b628053 課題 #5 に対する対応 2024-10-14 19:12:37 +09:00
みてるぞ 05182b251f Merge branch 'main' into feature/query 2024-10-14 18:23:39 +09:00
みてるぞ 5eb3fb6037 外されたタグを再登録できてなぃバグ修正 2024-10-14 18:16:56 +09:00
みてるぞ b6c041ddad Merge branch 'main' into feature/query 2024-10-12 22:21:21 +09:00
みてるぞ e23d919919 タグ削除チェックは大文字に変換して行ふやぅ修正 2024-10-12 22:10:33 +09:00
みてるぞ 726b5dc47e Eloquent への移植いちわぅ完了(後で型定義する) 2024-10-12 18:39:21 +09:00
みてるぞ feb31dc10b Eloquent 導入(ソースはエラー起きるため main に取込まなぃこと) 2024-10-12 06:01:21 +09:00
みてるぞ 980dd0ac60 ログ出力追加 2024-10-10 20:42:47 +09:00
みてるぞ faf3b44745 括弧の対応がをかしかったので修正(コミット前に MyPy チェックして) 2024-10-10 12:21:57 +09:00
みてるぞ 988cf714f8 print で出力するのは無意味だったので取消し 2024-10-10 02:59:44 +09:00
みてるぞ 033652e1b7 凡ミス(よくこれで 1 回め通ったな……) 2024-10-10 02:46:13 +09:00
みてるぞ e4f9470bc5 Merge branch 'main' of https://git.miteruzo.com/miteruzo/nizika_nico 2024-10-10 02:40:38 +09:00
みてるぞ 6535cbed70 【お試し】Upsert 時に LAST_INSERT_ID 指定 2024-10-10 02:40:29 +09:00
みてるぞ 763375c69d Python 3.10 非対応機能削除 2024-10-10 02:20:44 +09:00
13個のファイルの変更954行の追加1013行の削除
+1
ファイルの表示
@@ -0,0 +1 @@
__pycache__
+25
ファイルの表示
@@ -0,0 +1,25 @@
from __future__ import annotations
import os
from typing import TypedDict
from eloquent import DatabaseManager, Model # type: ignore
CONFIG: dict[str, DbConfig] = { 'mysql': { 'driver': 'mysql',
'host': 'localhost',
'database': 'nizika_nico',
'user': os.environ['MYSQL_USER'],
'password': os.environ['MYSQL_PASS'],
'prefix': '' } }
DB = DatabaseManager (CONFIG)
Model.set_connection_resolver (DB)
class DbConfig (TypedDict):
driver: str
host: str
database: str
user: str
password: str
prefix: str
+145
ファイルの表示
@@ -0,0 +1,145 @@
# pylint: disable = missing-class-docstring
# pylint: disable = missing-function-docstring
# pylint: disable = missing-module-docstring
# pylint: disable = unused-argument
from __future__ import annotations
from typing import Any, Generic, Type, TypeVar, overload
from typing_extensions import Self
_ModelT = TypeVar ('_ModelT', bound = 'Model')
class Connection:
def select (self, query: str, bindings: dict[str, Any] | None = None) -> Any: ...
def insert (self, query: str, bindings: dict[str, Any] | None = None) -> int: ...
def update (self, query: str, bindings: dict[str, Any] | None = None) -> int: ...
def delete (self, query: str, bindings: dict[str, Any] | None = None) -> int: ...
def transaction (self, callback: Any) -> Any: ...
def begin_transaction (self) -> None: ...
def commit (self) -> None: ...
def rollback (self) -> None: ...
class ConnectionResolver:
def connection (self, name: str | None = None) -> Any: ...
def get_default_connection (self) -> str: ...
def set_default_connection (self, name: str) -> None: ...
class DatabaseManager:
connections: dict[str, Connection]
def __init__ (self, config: dict[str, Any]) -> None: ...
def connection (self, name: str | None = None) -> Connection: ...
def disconnect (self, name: str | None = None) -> None: ...
def reconnect (self, name: str | None = None) -> Connection: ...
def get_connections (self) -> dict[str, Connection]: ...
class Model:
id: int
_Model__exists: bool
def has_one (
self,
related_model: Type[_ModelT],
foreign_key: str | None = None,
) -> _ModelT: ...
def has_many (
self,
related_model: Type[_ModelT],
foreign_key: str | None = None,
) -> list[_ModelT]: ...
def belongs_to (
self,
related_model: Type[_ModelT],
foreign_key: str | None = None,
) -> _ModelT: ...
def belongs_to_many (
self,
related_model: Type[_ModelT],
foreign_key: str | None = None,
) -> list[_ModelT]: ...
def save (self) -> None: ...
def delete (self) -> None: ...
@classmethod
def find (cls, id_: int) -> Self | None: ...
@classmethod
def query (
cls,
) -> QueryBuilder[Self]: ...
@overload
@classmethod
def where (
cls,
field: str,
operator: str,
value: Any,
) -> QueryBuilder[Self]: ...
@overload
@classmethod
def where (cls, field: str, value: Any) -> QueryBuilder[Self]: ...
@classmethod
def where_not_in (
cls,
column: str,
values: list[Any] | tuple
) -> QueryBuilder[Self]: ...
@classmethod
def where_not_null (cls, field: str) -> QueryBuilder[Self]: ...
@classmethod
def max (cls, column: str) -> Any: ...
@classmethod
def set_connection_resolver (cls, resolver: DatabaseManager) -> None: ...
class QueryBuilder (Generic[_ModelT]):
def first (self) -> _ModelT | None: ...
def get (self) -> list[_ModelT]: ...
@overload
def where (
self,
field: str,
operator: str,
value: Any,
) -> QueryBuilder[_ModelT]: ...
@overload
def where (self, field: str, value: Any) -> QueryBuilder[_ModelT]: ...
def where_null (self, field: str) -> QueryBuilder[_ModelT]: ...
def max (self, column: str) -> Any: ...
def _load_relation (self, relation_name: str) -> QueryBuilder[_ModelT]: ...
+177
ファイルの表示
@@ -0,0 +1,177 @@
# pylint: disable = missing-class-docstring
# pylint: disable = missing-function-docstring
"""
ぼざクリ DB の構成
"""
from __future__ import annotations
from datetime import date, datetime
from db.my_eloquent import Model
class Comment (Model):
# pylint: disable = too-many-instance-attributes
id: int
video_id: int
comment_no: int
user_id: int
content: str
posted_at: datetime
nico_count: int
vpos_ms: int
__timestamps__ = False
@property
def video (
self,
) -> Video:
return self.belongs_to (Video)
@property
def user (
self,
) -> User:
return self.belongs_to (User)
def upsert (
self,
*args: str,
) -> None:
super ().upsert ('video_id', 'comment_no')
class Tag (Model):
id: int
name: str
__timestamps__ = False
@property
def video_tags (
self,
) -> list[VideoTag]:
return self.has_many (VideoTag)
class TrackedVideo (Model):
id: int
code: str
__timestamps__ = False
def upsert (
self,
*args: str,
) -> None:
super ().upsert ('code')
class User (Model):
id: int
code: str
__timestamps__ = False
@property
def comments (
self,
) -> list[Comment]:
return self.has_many (Comment)
class Video (Model):
id: int
code: str
user_id: int | None
title: str
description: str
uploaded_at: datetime
deleted_at: datetime | None
__timestamps__ = False
@property
def user (
self,
) -> User | None:
if self.user_id is None:
return None
return self.belongs_to (User)
@property
def video_histories (
self,
) -> list[VideoHistory]:
return self.has_many (VideoHistory)
@property
def video_tags (
self,
) -> list[VideoTag]:
return self.has_many (VideoTag)
@property
def comments (
self,
) -> list[Comment]:
return self.has_many (Comment)
def upsert (
self,
*args: str,
) -> None:
super ().upsert ('code')
class VideoHistory (Model):
id: int
video_id: int
fetched_at: date
views_count: int
__timestamps__ = False
@property
def video (
self,
) -> Video:
return self.belongs_to (Video)
def upsert (
self,
*args: str,
) -> None:
super ().upsert ('video_id', 'fetched_at')
class VideoTag (Model):
id: int
video_id: int
tag_id: int
tagged_at: date
untagged_at: date | None
__timestamps__ = False
@property
def video (
self,
) -> Video:
return self.belongs_to (Video)
@property
def tag (
self,
) -> Tag:
return self.belongs_to (Tag)
def upsert (
self,
*args: str,
) -> None:
super ().upsert ('video_id', 'tag_id')
+50
ファイルの表示
@@ -0,0 +1,50 @@
# pylint: disable = missing-class-docstring
# pylint: disable = missing-function-docstring
"""
みてるぞ式魔改造(言ふほどか?)版 Eloquent
"""
import eloquent
class DatabaseManager (eloquent.DatabaseManager):
pass
class Model (eloquent.Model):
id: int
def upsert (
self,
*args: str,
) -> None:
row = self._find_upsert_row (*args)
if row is not None:
self.id = row.id
# pylint: disable = invalid-name
# pylint: disable = attribute-defined-outside-init
self._Model__exists = True
self.save ()
return
try:
self.save ()
except Exception:
row = self._find_upsert_row (*args)
if row is None:
raise
self.id = row.id
# pylint: disable = invalid-name
# pylint: disable = attribute-defined-outside-init
self._Model__exists = True
self.save ()
def _find_upsert_row (
self,
*args: str,
):
q = self.query ()
for arg in args:
q = q.where (arg, getattr (self, arg))
return q.first ()
シンボリックリンク
+1
ファイルの表示
@@ -0,0 +1 @@
db/eloquent.pyi
+55
ファイルの表示
@@ -0,0 +1,55 @@
# pylint: disable = missing-class-docstring
# pylint: disable = missing-function-docstring
"""
動画コードからコメントのリストを取得し,JSON 形式で出力する.
"""
from __future__ import annotations
import json
import sys
from datetime import datetime
from typing import TypedDict
from db.config import DB
from db.models import Video
DB
def main (
video_code: str,
) -> None:
video = Video.where ('code', video_code).first ()
if video:
comments: list[CommentDict] = []
for row in video.comments:
comment: CommentDict = {
'id': row.id,
'video_id': row.video_id,
'comment_no': row.comment_no,
'user_id': row.user_id,
'content': row.content,
'posted_at': row.posted_at,
'nico_count': row.nico_count,
'vpos_ms': row.vpos_ms }
comments.append (comment)
print (json.dumps (comments, default = str))
else:
print ('[]')
class CommentDict (TypedDict):
id: int
video_id: int
comment_no: int
user_id: int
content: str
posted_at: datetime
nico_count: int
vpos_ms: int
if __name__ == '__main__':
main (sys.argv[1])
+65
ファイルの表示
@@ -0,0 +1,65 @@
# pylint: disable = missing-class-docstring
# pylint: disable = missing-function-docstring
"""
動画履歴の情報を取得し,JSON 形式で出力する.
"""
from __future__ import annotations
import json
import sys
from datetime import date, datetime
from typing import cast
from db.config import DB
from db.models import Video, VideoHistory
DB
def main (
views_counts: list[int],
base_date: date,
) -> None:
kiriban_list: list[tuple[int, str, str]] = []
latest_fetched_at = cast (date | None,
(VideoHistory
.where ('fetched_at', '<=', base_date)
.max ('fetched_at')))
if latest_fetched_at is None:
print ('[]')
return
for views_count in views_counts:
targets = { vh.video.code for vh in (
VideoHistory
.where ('fetched_at', latest_fetched_at)
.where ('views_count', '>=', views_count)
.get ()) }
for code in targets:
if code in [kiriban[1] for kiriban in kiriban_list]:
continue
previous_views_count: int | None = (
VideoHistory
.where_has ('video', lambda q, code = code: q.where ('code', code))
.where ('fetched_at', '<', latest_fetched_at)
.max ('views_count'))
if previous_views_count is None:
previous_views_count = 0
if previous_views_count >= views_count:
continue
kiriban_list.append ((views_count, code,
(cast (Video, Video.where ('code', code).first ())
.uploaded_at)))
print (json.dumps (kiriban_list, default = str))
if __name__ == '__main__':
main (list (map (int, sys.argv[2:])),
datetime.strptime (sys.argv[1], '%Y-%m-%d').date ())
+52
ファイルの表示
@@ -0,0 +1,52 @@
# pylint: disable = missing-class-docstring
# pylint: disable = missing-function-docstring
"""
全動画の情報を取得し,JSON 形式で出力する.
"""
from __future__ import annotations
import json
from datetime import date, datetime
from typing import TypedDict
from db.config import DB
from db.models import Video
DB
def main (
) -> None:
videos: list[VideoDict] = []
for row in Video.all ():
deleted_at = row.deleted_at.date () if row.deleted_at else None
video: VideoDict = { 'id': row.id,
'code': row.code,
'user': getattr (row.user, 'code', None),
'title': row.title,
'description': row.description,
'tags': [],
'uploaded_at': row.uploaded_at,
'deleted_at': deleted_at }
for video_tag in row.video_tags:
if video_tag.untagged_at is None:
video['tags'].append (video_tag.tag.name)
videos.append (video)
print (json.dumps (videos, default = str))
class VideoDict (TypedDict):
id: int
code: str
user: str | None
title: str
description: str
tags: list[str]
uploaded_at: datetime
deleted_at: date | None
if __name__ == '__main__':
main ()
+2
ファイルの表示
@@ -0,0 +1,2 @@
CREATE TABLE `nizika_nico`.`tracked_videos` (`id` BIGINT NOT NULL AUTO_INCREMENT , `code` VARCHAR(16) NOT NULL COMMENT '動画コード' , PRIMARY KEY (`id`)) ENGINE = InnoDB COMMENT = '追跡対象動画';
ALTER TABLE `tracked_videos` ADD UNIQUE(`code`);
+3
ファイルの表示
@@ -0,0 +1,3 @@
ALTER TABLE `videos` ADD `user_id` BIGINT NULL DEFAULT NULL COMMENT 'ユーザ Id.' AFTER `code`;
ALTER TABLE `videos` ADD INDEX(`user_id`);
ALTER TABLE `videos` ADD FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
+19
ファイルの表示
@@ -0,0 +1,19 @@
import sys
from db.config import DB
from db.models import TrackedVideo
DB
def main (
video_codes: list[str],
) -> None:
for code in video_codes:
tv = TrackedVideo ()
tv.code = code
tv.upsert ()
if __name__ == '__main__':
main (sys.argv[1:])
+349 -1003
ファイルの表示
ファイル差分が大きすぎるため省略します 差分を読込み