コミットを比較

...

56 コミット

作成者 SHA1 メッセージ 日付
みてるぞ 99a5dca5a3 リプライが送信されないバグ修正(#17) (#18)
#17

Co-authored-by: miteruzo <miteruzo@naver.com>
Reviewed-on: #18
2025-12-03 22:48:57 +09:00
みてるぞ a6af306204 字数制限 2025-11-30 04:12:35 +09:00
みてるぞ 9f36cfea28 細部 2025-10-26 18:29:52 +09:00
みてるぞ a103093e9c Merge pull request '統合 AI への移行' (#16) from ai-migration into main
Reviewed-on: #16
2025-10-22 21:50:01 +09:00
みてるぞ 7ff729b256 #11 2025-10-21 22:27:37 +09:00
みてるぞ 3e8c2c5fe0 #11 2025-10-21 21:23:03 +09:00
みてるぞ 6eb191a7f4 #11 完了? 2025-10-19 17:03:51 +09:00
みてるぞ 82fabebe2f コミット忘れ 2025-10-19 15:56:55 +09:00
みてるぞ 47dc881537 #11 2025-08-17 05:37:36 +09:00
みてるぞ 658b15ac52 nicolib 2025-01-10 10:12:02 +00:00
みてるぞ 6d4e826439 None の場合について考慮 2025-01-03 17:40:10 +09:00
みてるぞ e6f90611fa リレーションのミス修正 2024-12-30 17:02:46 +09:00
みてるぞ 4f2056b347 botiboti 2024-12-29 20:31:48 +09:00
みてるぞ 64279b2eca キリ番祝ひ 2024-12-29 16:49:45 +09:00
みてるぞ 4bd15d0cc9 細部の修正 2024-12-11 00:22:28 +09:00
みてるぞ 26867f5269 #1 さすがに失礼すぎるコメント多めなのでポジティブな感想多めに修正 2024-11-14 23:18:31 +09:00
みてるぞ 0dd27b4ec7 #1 当日にて,より小さいキリ番は祝はなぃやぅに 2024-11-11 12:31:19 +09:00
みてるぞ fb920a1f2c #8 すでにいいねしてゐるポストは対象外に 2024-11-10 03:26:30 +09:00
みてるぞ 5041b91485 #8 いいねから自分を除外 2024-11-09 06:24:51 +09:00
みてるぞ 06b9d015d0 #8 型安全性不明 2024-11-09 06:02:57 +09:00
みてるぞ fad7ffe969 なかなか辛辣だったのでプロンプト修正 2024-11-08 01:48:13 +09:00
みてるぞ d6654c41ba #1 100 刻みはさすがに刻みすぎた 2024-11-07 12:38:30 +09:00
みてるぞ a4e0a5fcd8 #1 2024-11-07 03:15:52 +09:00
みてるぞ f3c8f5fa27 #1 ニジカがあまりにも塩対応だったので要求ã追加 2024-11-07 03:09:48 +09:00
みてるぞ f6ab471e04 #1 base_date が意味をなしてゐなかったのを修正 2024-11-07 03:03:14 +09:00
みてるぞ ad9f5256e5 #1 ほぼ完了 2024-11-07 02:54:22 +09:00
みてるぞ 0cbc20c898 #1 __future__ 忘れ 2024-11-06 04:12:14 +09:00
みてるぞ 95331ec835 なぜか AI の連携が外れてゐたので 2024-11-06 04:07:59 +09:00
みてるぞ d1b1bf2a14 #1 型定義が未完成だが,動作は問題ないと思ãはれるため本番に移す. 2024-11-06 03:57:57 +09:00
みてるぞ 5769314b6f #1 サブモジュールの最新化 2024-11-06 01:44:21 +09:00
みてるぞ 27d7cfe972 #1 2024-11-05 12:45:11 +09:00
みてるぞ 10c1218caf #1 もろもろ基盤 2024-11-05 12:40:46 +09:00
みてるぞ 9835907f8a 本日作業分 2024-11-05 02:24:47 +09:00
みてるぞ 539333bd0e AI 更新 2024-10-20 19:58:38 +09:00
みてるぞ 9ec4e67a99 ログインの前に 60 s 待機(#4) 2024-10-17 22:57:35 +09:00
みてるぞ e4ed73007f AI を最新化 2024-10-16 08:42:19 +09:00
みてるぞ 76abbe4fdd 先に待つやぅに修正(エラー落ち時の加重アクセス回避) 2024-10-07 20:28:41 +09:00
みてるぞ 0002b48269 温泉フラグのリセットのミス修正 2024-10-06 23:56:20 +09:00
みてるぞ 55049cdc2d 温泉追加とリファクタリング 2024-10-04 02:58:20 +09:00
みてるぞ d28882a241 ぼざクリ追加 2024-09-17 12:21:08 +09:00
みてるぞ 7dc19465ca fixed a small mistake 2024-09-12 04:37:31 +09:00
みてるぞ 5f748ed1c2 ニコニコ 2024-09-12 04:34:35 +09:00
みてるぞ 9690cf145b ニコニコ API は朝 5 時ごろ更新らしぃ;systemd で 1 日 1 回叩ぃたはぅがいぃかもね 2024-09-11 06:53:15 +09:00
みてるぞ 36327878e8 ニコニコ最新取得できなぃことがあるため,12 時間前までチェックするやぅに 2024-09-10 08:37:31 +09:00
みてるぞ b7433ade9f ニコニコ取得範囲の変更とリクエストのタイムアウト設定 2024-09-09 12:28:55 +09:00
みてるぞ 4ebe43dd05 サムネ取得ミス修正 2024-09-07 07:25:52 +09:00
みてるぞ e54fe9e313 サムネイルの取得方法まちがってゐたので修正 2024-09-07 06:59:50 +09:00
みてるぞ c8cff9a3dd リンク・カードちょっと修正 2024-09-07 06:52:10 +09:00
みてるぞ 5da0335c50 リンク・カード追加 2024-09-07 01:31:14 +09:00
みてるぞ 2e75eabf71 ニジカ動画投稿告知機能追加 2024-09-06 23:37:18 +09:00
みてるぞ f6ef60b619 おやつタイムの alt 属性追加 2024-09-06 08:28:10 +09:00
みてるぞ 6f8d2ef4db 新着動画取得用函数 2024-09-05 03:51:39 +09:00
みてるぞ 0e6fe12510 おやつタイム失敗時にはエラーを握り潰すやぅに 2024-09-05 01:22:59 +09:00
みてるぞ 3229f1b6c0 おやつタイム画像追加 2024-09-04 02:57:02 +09:00
みてるぞ 0e034b31b1 AI を最新化 2024-09-03 01:11:21 +09:00
みてるぞ d4a6f6d328 画像に対応 2024-09-03 00:42:41 +09:00
8個のファイルの変更283行の追加68行の削除
+2
ファイルの表示
@@ -1,3 +1,5 @@
/__pycache__
/account.py
/connection.py
/db
/eloquent.pyi
+6 -4
ファイルの表示
@@ -1,4 +1,6 @@
[submodule "ai"]
path = ai
url = https://git.miteruzo.com/miteruzo/nizika_broadcast
branch = main
[submodule "nizika_ai"]
path = nizika_ai
url = https://git.miteruzo.com/miteruzo/nizika_ai.git
[submodule "nicolib"]
path = nicolib
url = https://git.miteruzo.com/miteruzo/nicolib.git
-1
サブモジュール aidfa09e1e66 から削除されました
バイナリ
ファイルの表示
バイナリファイルは表示されません.

変更後

幅:  |  高さ:  |  サイズ: 221 KiB

バイナリ
ファイルの表示
バイナリファイルは表示されません.

変更後

幅:  |  高さ:  |  サイズ: 171 KiB

+272 -62
ファイルの表示
@@ -1,24 +1,199 @@
from datetime import datetime, timedelta
from __future__ import annotations
import asyncio
import os
import time
import sys
from datetime import datetime
from io import BytesIO
from typing import Any, TypedDict
from atproto import Client, models
import atproto # type: ignore
import requests
from atproto import Client # type: ignore
from atproto_client.models import AppBskyEmbedExternal, AppBskyEmbedImages, ComAtprotoRepoStrongRef # type: ignore
from atproto_client.models.app.bsky.feed.get_timeline import Response # type: ignore
from atproto_client.models.app.bsky.feed.post import ReplyRef # type: ignore
from requests.exceptions import Timeout
from ai.talk import Talk
import account
import nicolib
from nizika_ai.consts import Character, GPTModel, Platform, QueryType
from nizika_ai.models import Answer, AnsweredFlag, Query, User
TARGET_WORDS = ['deerjika', 'ニジカ', 'ぼっち', '虹夏', '郁代', 'バーカ',
'kfif', 'kita-flatten-ikuyo-flatten', 'ラマ田', 'ゴートう',
'ぼざクリ', 'オオミソカ', '伊地知', '喜多ちゃん',
'喜タイ', '洗澡鹿', 'シーザオ', '今日は本当に',
'ダイソーで', '変なチンチン', 'daisoで', 'だね~(笑)',
'おやつタイム', 'わさしが', 'わさび県', 'たぬマ', 'にくまる',
'ルイズマリー', '', 'ニジゴ', 'ゴニジ', 'ニジニジ',
'新年だよね', 'うんこじゃん', 'ほくほくのジャガイモ']
time.sleep (60)
client = Client (base_url = 'https://bsky.social')
client.login (account.USER_ID, account.PASSWORD)
async def main (
) -> None:
"""
メーン処理
"""
await asyncio.gather (like_posts (),
check_mentions (),
answer ())
async def like_posts (
) -> None:
while True:
try:
for post in fetch_target_posts ():
client.like (**post)
except Exception as e:
print (f"[like_posts] { type (e).__name__ }: { e }")
await asyncio.sleep (60)
async def check_mentions (
) -> None:
while True:
try:
for uri in check_notifications ():
records = fetch_thread_contents (uri, 20)
if records:
record = records[0]
image_url: str | None = None
if record['embed'] and hasattr (record['embed'], 'images'):
image_url = ('https://cdn.bsky.app/img/feed_fullsize/plain'
f"/{ record['did'] }"
f"/{ record['embed'].images[0].image.ref.link }")
user = _fetch_user (record['did'], record['name'])
_add_query (user, record['text'], image_url, {
'uri': record['strong_ref']['uri'],
'cid': record['strong_ref']['cid'] })
except Exception as e:
print (f"[check_mentions] { type (e).__name__ }: { e }")
await asyncio.sleep (60)
async def answer (
) -> None:
while True:
answered_flags = (
AnsweredFlag
.where ('platform', Platform.BLUESKY.value)
.where ('answered', False)
.get ())
for answered_flag in answered_flags:
td: dict[str, Any]
answer = answered_flag.answer
answered_flag.answered = True
answered_flag.save ()
match QueryType (answer.query_rel.query_type):
case QueryType.BLUESKY_COMMENT:
td = answer.query_rel.transfer_data or { }
uri: str | None = td.get ('uri')
cid: str | None = td.get ('cid')
if (not uri) or (not cid):
continue
strong_ref = ComAtprotoRepoStrongRef.Main (uri = uri, cid = cid)
reply_ref = ReplyRef (root = strong_ref, parent = strong_ref)
try:
client.post (answer.content[:250], reply_to = reply_ref)
except Exception as e:
print (f"[answer/reply] { type (e).__name__ }: { e }")
continue
case QueryType.KIRIBAN | QueryType.NICO_REPORT:
td = answer.query_rel.transfer_data or { }
video_code: str | None = td.get ('video_code')
if not video_code:
continue
uri = f"https://www.nicovideo.jp/watch/{ video_code }"
(title, description, thumbnail) = nicolib.fetch_embed_info (uri)
try:
resp = requests.get (thumbnail, timeout = 60)
resp.raise_for_status ()
upload = client.com.atproto.repo.upload_blob (BytesIO (resp.content))
thumb = upload.blob
except Timeout:
thumb = None
except Exception as e:
print (f"[answer/nico-thumb] { type (e).__name__ }: { e }")
thumb = None
external = AppBskyEmbedExternal.External (
title = title,
description = description,
thumb = thumb,
uri = uri)
embed_external = AppBskyEmbedExternal.Main (external = external)
try:
client.post (answer.content[:250], embed = embed_external)
except Exception as e:
print (f"[answer/nico-post] { type (e).__name__ }: { e }")
continue
case QueryType.SNACK_TIME:
try:
with open ('./assets/snack-time.jpg', 'rb') as f:
image = AppBskyEmbedImages.Image (
alt = (
'左に喜多ちゃん、右に人面鹿のニジカが'
'V字に並んでいる。'
'喜多ちゃんは右手でピースサインをして'
'片目をウインクしている。'
'ニジカは両手を広げ、'
'右手にスプーンを持って'
'ポーズを取っている。'
'背景には'
'赤と黄色の放射線状の模様が広がり、'
'下部に「おやつタイムだ!!!!」という'
'日本語のテキストが表示されている。'),
image = client.com.atproto.repo.upload_blob (f).blob)
client.post (answer.content[:250],
embed = AppBskyEmbedImages.Main (images = [image]))
except Exception:
pass
case QueryType.HOT_SPRING:
try:
with open ('./assets/hot-spring.jpg', 'rb') as f:
image = AppBskyEmbedImages.Image (
alt = ('左に喜多ちゃん、右にわさび県産滋賀県が'
'V字に並んでいる。'
'喜多ちゃんは右手でピースサインをして'
'片目をウインクしている。'
'わさび県産滋賀県はただ茫然と'
'立ち尽くしている。'
'背景には'
'血と空の色をした放射線状の模様が広がり、'
'下部に「温泉に入ろう!!!」という'
'日本語のテキストが表示されている。'),
image = client.com.atproto.repo.upload_blob (f).blob)
client.post (answer.content[:250],
embed = AppBskyEmbedImages.Main (images = [image]))
except Exception:
pass
await asyncio.sleep (10)
def check_notifications (
client: Client,
) -> list:
(uris, last_seen_at) = ([], client.get_current_time_iso ())
) -> list[str]:
uris: list[str] = []
last_seen_at = client.get_current_time_iso ()
for notification in (client.app.bsky.notification.list_notifications ()
.notifications):
notifications = client.app.bsky.notification.list_notifications ()
for notification in notifications.notifications:
if not notification.is_read:
if notification.reason in ['mention', 'reply']:
uris += [notification.uri]
elif notification.reason == 'follow':
match notification.reason:
case 'mention' | 'reply' | 'quote':
uris.append (notification.uri)
case 'follow':
client.follow (notification.author.did)
client.app.bsky.notification.update_seen ({ 'seen_at': last_seen_at })
@@ -26,70 +201,105 @@ def check_notifications (
return uris
def get_thread_contents (
client: Client,
def fetch_thread_contents (
uri: str,
parent_height: int,
) -> list:
response = (client.get_post_thread (uri = uri,
parent_height = parent_height)
.thread)
records = []
while response is not None:
records += [{ 'strong_ref': models.create_strong_ref (response.post),
'did': response.post.author.did,
'handle': response.post.author.handle,
'name': response.post.author.display_name,
'datetime': response.post.record.created_at,
'text': response.post.record.text }]
response = response.parent
) -> list[Record]:
post_thread = client.get_post_thread (uri = uri, parent_height = parent_height)
if not post_thread:
return []
res = post_thread.thread
records: list[Record] = []
while res:
if hasattr (res, 'post'):
records.append ({ 'strong_ref': { 'uri': res.post.uri,
'cid': res.post.cid },
'did': res.post.author.did,
'handle': res.post.author.handle,
'name': (res.post.author.display_name
or res.post.author.handle),
'datetime': res.post.record.created_at,
'text': getattr (res.post.record, 'text', None) or '',
'embed': getattr (res.post.record, 'embed', None) })
res = res.parent
else:
break
return records
def main () -> None:
client = Client (base_url = 'https://bsky.social')
def fetch_target_posts (
) -> list[LikeParams]:
posts: list[LikeParams] = []
client.login (account.USER_ID, account.PASSWORD)
timeline: Response = client.get_timeline ()
for feed in timeline.feed:
me = getattr (client, 'me', None)
my_did = me.did if me else ''
if (feed.post.author.did != my_did
and (feed.post.viewer.like is None)
and any (target_word in (feed.post.record.text or '').casefold ()
for target_word in TARGET_WORDS)):
posts.append (LikeParams ({ 'uri': feed.post.uri, 'cid': feed.post.cid }))
last_posted_at = datetime.now () - timedelta (hours = 6)
has_got_snack_time = False
while True:
now = datetime.now ()
return posts
for uri in check_notifications (client):
records = get_thread_contents (client, uri, 20)
if len (records) > 0:
answer = Talk.main (records[0]['text'],
records[0]['name'],
[*map (lambda record: {
'role': ('assistant'
if (record['handle']
== account.USER_ID)
else 'user'),
'content':
record['text']},
reversed (records[1:]))])
client.send_post (answer,
reply_to = models.AppBskyFeedPost.ReplyRef (
parent = records[0]['strong_ref'],
root = records[-1]['strong_ref']))
if now.hour == 14 and has_got_snack_time:
has_got_snack_time = False
def _add_query (
user: User,
content: str,
image_url: str | None = None,
transfer_data: dict[str, Any] | None = None,
) -> None:
query = Query ()
query.user_id = user.id
query.target_character = Character.DEERJIKA.value
query.content = content
query.image_url = image_url or None
query.query_type = QueryType.BLUESKY_COMMENT.value
query.model = GPTModel.GPT3_TURBO.value
query.sent_at = datetime.now ()
query.answered = False
query.transfer_data = transfer_data
query.save ()
# TODO: 履歴情報の追加
if now.hour == 15 and not has_got_snack_time:
client.send_post (Talk.main ('おやつタイムだ!!!!'))
last_posted_at = now
has_got_snack_time = True
def _fetch_user (
did: str,
name: str,
) -> User:
user = User.where ('platform', Platform.BLUESKY.value).where ('code', did).first ()
if user is None:
user = User ()
user.platform = Platform.BLUESKY.value
user.code = did
user.name = name
user.save ()
return user
if now - last_posted_at >= timedelta (hours = 6):
client.send_post (Talk.main ('今どうしてる?'))
last_posted_at = now
time.sleep (60)
class LikeParams (TypedDict):
uri: str
cid: str
class Record (TypedDict):
strong_ref: StrongRef
did: str
handle: str
name: str
datetime: str
text: str
embed: object
class StrongRef (TypedDict):
uri: str
cid: str
if __name__ == '__main__':
main (*sys.argv[1:])
asyncio.run (main ())
サブモジュール
+1
サブモジュール nicolib85670982f0 で追加されました
サブモジュール
+1
サブモジュール nizika_ai1f75763038 で追加されました