def PANIC(exc_info): # raiseされたら実行する。別途スクリプトで例外処理する必要がある now = datetime.datetime.now(timezone('Asia/Tokyo')) nowFormat = now.strftime("%Y/%m/%d %H:%M:%S%z") nowFileFormat = now.strftime("%Y%m%d") os.makedirs('panic-log', exist_ok=True) with open(f'panic-log/PANIC-{nowFileFormat}.LOG', 'a', encoding="utf-8") as f: f.write('--- PANIC! {} ---\n'.format(nowFormat)) traceback.print_exc(file=f) f.write('\n') log.logErr("*ユウちゃんパニックですぅ・・・!\n{}".format(traceback.format_exc())) if config['linenotify']['enable'] == True: headers = {"Authorization": f"Bearer {config['linenotify']['token']}"} payload = { "message": f"\n*ユウちゃんがパニックになりました。\nパニック時刻: \n{nowFormat}\nエラーメッセージ:\n{traceback.format_exception(*exc_info)[-1]}" } req = requests.post("https://notify-api.line.me/api/notify", headers=headers, params=payload) if req.status_code != 200: log.logErr("[FATAL] LINE NOTIFY ACCESS FAILED") else: log.logInfo("LINET NOTIFY SENT") # パニックした時にトゥートできるか試しますっ!できなくてもエラーを出さないようにしますっ! try: mastodon.toot("ユウちゃんパニックですぅ・・・!٩(ŏ﹏ŏ、)۶") except: pass
def main(): # スレッド化するための初期化 features = [] # タイムライン系 features.append( threading.Thread(target=local, name='Timeline-Local', args=(os.getpid(), ))) features.append( threading.Thread(target=home, name='Timeline-Home', args=(os.getpid(), ))) # cron系 features.append(threading.Thread(target=run_scheduler, name='Schedule')) try: # スレッド開始 for ft in features: ft.setDaemon(True) ft.start() log.logInfo("ALL SYSTEMS READY!") for ft in features: ft.join() except KeyboardInterrupt: if not DATABASE.is_closed(): DATABASE.close() sys.exit() except: KotohiraUtil.PANIC(sys.exc_info())
def home_onUpdate(status): try: with DATABASE.transaction(): # 公開範囲が「公開」であればここのリスナーでは無視する if status['visibility'] == 'public': return # 連合アカウントである場合(@が含まれている)は無視する if status['account']['acct'].find('@') != -1: return # Botアカウントは応答しないようにする if status['account']['bot'] == True: return # 自分のトゥートは無視 if config['user']['me'] == status['account']['acct']: return # トゥート内のHTMLタグを除去 txt = KotohiraUtil.h2t(status['content']) # 自分宛てのメンションはここのリスナーでは無視する(ユーザー絵文字の場合は例外) isMeMention = re.search( '(?!.*:)@({}+)(?!.*:)'.format(config['user']['me']), txt) if isMeMention: return calledYuChan = re.search( f'(琴平|ことひら|コトヒラ|コトヒラ|:@{config["user"]["me"]}:|((ゆう|ユウ|ユゥ|ユウ|ユゥ)(ちゃん|チャン|チャン|くん|クン|君|クン))|ユウ)', txt) # ユウちゃん etc... とか呼ばれたらふぁぼる if calledYuChan: log.logInfo('呼ばれたっ!:@{0} < {1}'.format( status['account']['acct'], txt)) if not status['favourited']: mastodon.status_favourite(status['id']) # 好感度ちょいアップ calledBy = known_users.get( known_users.ID_Inst == status['account']['id']) calledByRate = fav_rate.get(fav_rate.ID_Inst == calledBy) calledByRate.rate += 5 calledByRate.save() else: log.logWarn('ふぁぼってましたっ!') except Exception as e: DATABASE.rollback() # Timelines.pyの方へエラーを送出させる raise e else: DATABASE.commit()
def local_onDelete(status_id): try: # メモのトゥートが削除されたらデータベースから削除する if YuChan.cancel_memo(status_id): log.logInfo(f"メモを削除っ!: {str(status_id)}") # 投票再通知の取り消し(該当する場合) if config['features']['voteRenotify'] and VOTE_RENOTIFY_THREAD.get( int(status_id), False): if type(VOTE_RENOTIFY_THREAD[int(status_id)]) == threading.Timer: VOTE_RENOTIFY_THREAD[int(status_id)].cancel() log.logInfo(f"投票再通知を解除っ!: {str(status_id)}") except Exception as e: # 上と同じ raise e
def run_scheduler(): # スケジュール一覧 schedule.every().hour.at(":00").do(timeReport) schedule.every().hour.at(":55").do(toot_memo) schedule.every().day.at("22:22").do(meow_time) try: log.logInfo("Start feature: scheduler") while True: schedule.run_pending() time.sleep(1) except KeyboardInterrupt: pass except: PANIC(sys.exc_info()) log.logErr("*5秒後にスケジュールを再起動しますっ!") time.sleep(5) run_scheduler()
def local(main_pid): log.logInfo('Initializing feature: local') # SSE status test try: requests.get( f'https://{config["instance"]["address"]}/api/v1/streaming/health' ).raise_for_status() except requests.exceptions.HTTPError as exc: log.logCritical('*Server-sent eventsが使えませんっ!ユウちゃん寝ますっ!') os.kill(main_pid, signal.SIGKILL) return try: while True: client = sseclient.SSEClient( requests.get( f'https://{config["instance"]["address"]}/api/v1/streaming/public/local?access_token={config["instance"]["access_token"]}', stream=True)) for event in client.events(): if event.event == "update": local_onUpdate(json.loads(event.data)) elif event.event == "delete": local_onDelete(event.data) else: log.logWarn(f"Unknown event: {event.event}") log.logErr('サーバーからの通信が切れましたっ!1分後にやり直しますっ!') time.sleep(60) except OperationalError as exc: log.logCritical('*データベースにアクセスできませんっ!ユウちゃん寝ますっ!') os.kill(main_pid, signal.SIGKILL) return except (requests.exceptions.ReadTimeout, requests.exceptions.ChunkedEncodingError, MastodonNetworkError, MastodonServerError, MastodonBadGatewayError): log.logErr('*ローカルタイムラインが繋がんないみたいですっ・・・。1分後にやり直しますっ!') time.sleep(60) local(main_pid) except KeyboardInterrupt: pass except: KotohiraUtil.PANIC(sys.exc_info()) log.logErr('ローカルタイムラインを30秒待って読み込みし直しますねっ!') time.sleep(30) local(main_pid)
def home(main_pid): log.logInfo('Initializing feature: home') log.logInfo('Connect address: {}'.format(config['instance']['address'])) # SSE status test try: requests.get( f'https://{config["instance"]["address"]}/api/v1/streaming/health' ).raise_for_status() except requests.exceptions.HTTPError as exc: log.logCritical('*Server-sent eventsが使えませんっ!ユウちゃん寝ますっ!') os.kill(main_pid, signal.SIGKILL) return try: res = mastodon.account_verify_credentials() log.logInfo('Fetched account: @{}'.format(res.acct)) while True: client = sseclient.SSEClient( requests.get( f'https://{config["instance"]["address"]}/api/v1/streaming/user?access_token={config["instance"]["access_token"]}', stream=True)) for event in client.events(): if event.event == "update": home_onUpdate(json.loads(event.data)) elif event.event == "notification": home_onNotification(json.loads(event.data)) elif event.event == "delete": pass else: log.logWarn(f"Unknown event: {event.event}") log.logErr('サーバーからの通信が切れましたっ!1分後にやり直しますっ!') time.sleep(60) except OperationalError as exc: log.logCritical('*データベースにアクセスできませんっ!ユウちゃん寝ますっ!') os.kill(main_pid, signal.SIGKILL) return except (requests.exceptions.ReadTimeout, requests.exceptions.ChunkedEncodingError, MastodonNetworkError, MastodonServerError, MastodonBadGatewayError): log.logErr('*ホームタイムラインが繋がんないみたいですっ・・・。1分後にやり直しますっ!') time.sleep(60) home(main_pid) except KeyboardInterrupt: pass except: KotohiraUtil.PANIC(sys.exc_info()) log.logErr('ホームタイムラインを十秒待って読み込みし直しますねっ!') time.sleep(10) home(main_pid)
def home_onNotification(notification): try: with DATABASE.transaction(): # bot属性のアカウントの場合は無視する if notification['account']['bot'] == True: return # 連合アカウントである場合(@が含まれている)は無視する if notification['account']['acct'].find('@') != -1: return # 代入してちょっと見栄え良く notifyType = notification['type'] if notifyType == 'mention': # 知っているユーザーであるか # 知らないユーザーの場合はここで弾く user = known_users.get_or_none( known_users.ID_Inst == notification['account']['id']) if user == None: return # テキスト化 txt = KotohiraUtil.h2t(notification['status']['content']) # 口頭のメンションを除去 txt = re.sub(r'^(@[a-zA-Z0-9_]+)?(\s|\n)*', '', txt) # とりあえずふぁぼる log.logInfo('お手紙っ!:@{0} < {1}'.format( notification['account']['acct'], txt)) mastodon.status_favourite(notification['status']['id']) # NGワードを検知した場合は弾いて好感度下げ if YuChan.ngWordHook(txt): log.logInfo('変なことを言ってはいけませんっ!!(*`ω´*): @{0}'.format( notification['account']['acct'])) hooked = fav_rate.get(fav_rate.ID_Inst == user) hooked.rate -= config['follow']['down_step'] hooked.save() time.sleep(0.5) mastodon.status_post( '@{}\n変なこと言っちゃいけませんっ!!(*`ω´*)'.format( notification['account']['acct']), in_reply_to_id=notification['status']['id'], visibility=notification['status']['visibility']) YuChan.unfollow_attempt(notification['account']['id']) return # 好感度を少し上げる notifyBy = fav_rate.get(fav_rate.ID_Inst == user) notifyBy.rate += 1 notifyBy.save() # 正規表現とか followReq = re.search( r'(フォロー|[Ff]ollow|ふぉろー)(して|.?頼(む|みたい|もう)|.?たの(む|みたい|もう)|お願い|おねがい)?', txt) fortune = re.search(r'(占|うらな)(って|い)', txt) showNick = re.search( r'(ぼく|ボク|僕|わたし|ワタシ|私|俺|おれ|オレ|うち|わし|あたし|あたい)の(ニックネーム|あだな|あだ名|名前|なまえ)', txt) deleteNick = re.search(r'^(ニックネーム|あだ名)を?(消して|削除|けして|さくじょ)', txt) otherNick = re.search( r'^:@([a-zA-Z0-9_]+):\sの(あだ名|あだな|ニックネーム)[::は]\s?(.+)', txt) nick = re.search( r'^(@[a-zA-Z0-9_]+(\s|\n)+)?(あだ名|あだな|ニックネーム)[::は]\s?(.+)', txt) rspOtt = re.search(r'じゃんけん\s?(グー|✊|👊|チョキ|✌|パー|✋)', txt) isPing = re.search(r'[pP][iI][nN][gG]', txt) love = re.search(r'(すき|好き|しゅき|ちゅき)', txt) aboutYou = re.search( r'(ぼく|ボク|僕|わたし|ワタシ|私|俺|おれ|オレ|うち|わし|あたし|あたい)の(事|こと)', txt) # メンションでフォローリクエストされたとき if followReq: reqRela = mastodon.account_relationships( notification['account']['id'])[0] # フォローしていないこと if reqRela['following'] == False: if reqRela['followed_by'] == True: # フォローされていること if int(notifyBy.rate) >= int( config['follow'] ['condition_rate']): # 設定で決めた好感度レート以上だったら合格 log.logInfo('フォローっ!:@{}'.format( notification['account']['acct'])) mastodon.account_follow( notification['account']['id']) mastodon.status_post( '@{}\nフォローしましたっ!これからもよろしくねっ!'.format( notification['account']['acct']), in_reply_to_id=notification['status'] ['id'], visibility=notification['status'] ['visibility']) else: # 不合格の場合はレスポンスして終了 log.logInfo('もうちょっと仲良くなってからっ!:@{}'.format( notification['account']['acct'])) mastodon.status_post( '@{}\nもうちょっと仲良くなってからですっ!'.format( notification['account']['acct']), in_reply_to_id=notification['status'] ['id'], visibility=notification['status'] ['visibility']) else: log.logInfo('先にフォローしてっ!:@{}'.format( notification['account']['acct'])) mastodon.status_post( '@{}\nユウちゃんをフォローしてくれたら考えますっ!'.format( notification['account']['acct']), in_reply_to_id=notification['status']['id'], visibility=notification['status'] ['visibility']) else: # フォローしている場合は省く log.logInfo('フォロー済みっ!:@{}'.format( notification['account']['acct'])) mastodon.status_post( '@{}\nもうフォローしてますっ!'.format( notification['account']['acct']), in_reply_to_id=notification['status']['id'], visibility=notification['status']['visibility']) # 占いのリクエストがされたとき elif fortune: YuChan.fortune(notification['status']['id'], notification['account']['acct'], notification['status']['visibility']) # 更に4つ加算 notifyBy.rate += 4 notifyBy.save() # ニックネームの照会 elif showNick: YuChan.show_nickname(notification['status']['id'], notification['account']['id'], notification['account']['acct'], notification['status']['visibility']) # ニックネームの削除 elif deleteNick: YuChan.del_nickname(notification['status']['id'], notification['account']['id'], notification['account']['acct'], notification['status']['visibility']) # 他人のニックネームの設定 elif otherNick: YuChan.set_otherNickname( txt, notification['status']['id'], notification['account']['id'], notification['account']['acct'], notification['status']['visibility']) # ニックネームの設定 elif nick: newNicknameParse = re.search( r"^(@[a-zA-Z0-9_]+(\s|\n)+)?(あだ名|あだな|ニックネーム)[::は]\s?(.+)", txt) newNickname = newNicknameParse.group(4) YuChan.set_nickname(newNickname, notification['status']['id'], notification['account']['id'], notification['account']['acct'], notification['status']['visibility']) # ユウちゃんとじゃんけんっ! elif rspOtt: YuChan.rsp(txt, notification) # 更に4つ加算 notifyBy.rate += 4 notifyBy.save() # 応答チェッカー elif isPing: log.logInfo('PINGっ!:@{}'.format( notification['account']['acct'])) mastodon.status_post( '@{}\nPONG!'.format(notification['account']['acct']), in_reply_to_id=notification['status']['id'], visibility=notification['status']['visibility']) elif love: if int(notifyBy.rate) >= int( config['follow']['condition_rate']): log.logInfo('❤:@{}'.format( notification['account']['acct'])) mastodon.status_post( '@{}\nユウちゃんも好きですっ!❤'.format( notification['account']['acct']), in_reply_to_id=notification['status']['id'], visibility=notification['status']['visibility']) elif int(notifyBy.rate) < 0: log.logInfo('...: @{}'.format( notification['account']['acct'])) else: log.logInfo('//:@{}'.format( notification['account']['acct'])) mastodon.status_post( '@{}\nは、恥ずかしいですっ・・・//'.format( notification['account']['acct']), in_reply_to_id=notification['status']['id'], visibility=notification['status']['visibility']) elif aboutYou: log.logInfo("@{}の事、教えますっ!".format( notification['account']['acct'])) YuChan.about_you(notification['account']['id'], notification['status']['id'], notification['status']['visibility']) elif notifyType == 'favourite': # ふぁぼられ log.logInfo('ふぁぼられたっ!:@{0}'.format( notification['account']['acct'])) # ふぁぼ連対策 user = known_users.get( known_users.ID_Inst == notification['account']['id']) userRate = fav_rate.get(fav_rate.ID_Inst == user) favInfo, created = recent_fav.get_or_create(ID_Inst=user) if created: # データが作成された場合は好感度アップ favInfo.tootID = notification['status']['id'] favInfo.save() userRate.rate += 1 userRate.save() else: # 最後にふぁぼったトゥートが同じものでないこと if notification['status']['id'] != favInfo.tootID: favInfo.tootID = notification['status']['id'] favInfo.save() userRate.rate += 1 userRate.save() elif notifyType == 'reblog': # ブーストされ log.logInfo('ブーストされたっ!:@{0}'.format( notification['account']['acct'])) # ふぁぼられと同様な機能とか elif notifyType == 'follow': # フォローされ log.logInfo('フォローされたっ!:@{0}'.format( notification['account']['acct'])) except Exception as e: DATABASE.rollback() # Timelines.pyの方へエラーを送出させる raise e else: DATABASE.commit()
def local_onUpdate(status): try: with DATABASE.transaction(): # Botアカウントは応答しないようにする if status['account']['bot'] == True: return # 自分のトゥートは無視 if config['user']['me'] == status['account']['acct']: return # トゥート内のHTMLタグを除去 txt = KotohiraUtil.h2t(status['content']) # CWのテキストが空っぽでなければ付け足す if status['spoiler_text'] != '': txt = status['spoiler_text'] + "\n\n" + txt txt.strip() # 自分宛てのメンションはここのリスナーでは無視する(ユーザー絵文字の場合は例外) isMeMention = re.search( '(?!.*:)@({}+)(?!.*:)'.format(config['user']['me']), txt) if isMeMention: return # ユウちゃんが知ってるユーザーか調べる # 知らない場合はユウちゃんは記憶しますっ! user = known_users.get_or_none( known_users.ID_Inst == int(status['account']['id'])) if user == None: user = known_users.create(ID_Inst=int(status['account']['id']), acct=status['account']['acct']) fav_rate.create(ID_Inst=user) updated_users.create(ID_Inst=user) log.logInfo(f'覚えたっ!: @{status["account"]["acct"]}') newUser = True # トゥートカウントが10以下で、設定で有効な場合は新規さん向けの挨拶しますっ! if status['account']['statuses_count'] <= 10 and config[ 'features']['newComerGreeting'] == True: log.logInfo(f'新規さん!: @{status["account"]["acct"]}') mastodon.status_reblog(status['id']) time.sleep(0.5) mastodon.toot( '新規さんっ!はじめましてっ!琴平ユウって言いますっ!\nよろしくねっ!\n\n:@{0}: @{0}'. format(status['account']['acct'])) else: newUser = False # NGワードを検知した場合は弾いて好感度下げ if YuChan.ngWordHook(txt): log.logInfo('変なことを言ってはいけませんっ!!(*`ω´*): @{0}'.format( status['account']['acct'])) hooked = fav_rate.get(fav_rate.ID_Inst == user) hooked.rate -= config['follow']['down_step'] hooked.save() YuChan.unfollow_attempt(status['account']['id']) return # 名前 nameDic = nickname.get_or_none(nickname.ID_Inst == user) if nameDic == None: # ニックネームが指定されていない場合は基の名前を使用する # 名前が設定されていない場合はユーザーIDを使用する if status['account']['display_name'] == '': name = status['account']['acct'] else: # デコードして、\u202e(文字が逆さまになるやつ)を削除して戻してどーん dpname = status['account']['display_name'].encode( 'unicode-escape') dpname = dpname.replace(b"\\u202e", b'') name = dpname.decode('unicode-escape') else: # ニックネームが設定されている場合はそちらを優先 name = nameDic.nickname name = re.sub(r'(?!.*:)@([a-zA-Z0-9_]+)(?!.*:)', '', name) name = re.sub(r'(.*):$', r'\g<1>: ', name) # 名前に語尾がない場合は付け足す if re.search(r'(さん|ちゃん|どの|殿|くん|君|様|さま|教授|たん|きゅん)$', name) == None: name += "さん" # 最終更新を変更 now = datetime.datetime.now() now_utc = datetime.datetime.now(timezone('UTC')) # 正規表現チェック calledYuChan = re.search( f'(琴平|ことひら|コトヒラ|コトヒラ|:@{config["user"]["me"]}:|((ゆう|ユウ|ユゥ|ユウ|ユゥ)(ちゃん|チャン|チャン|くん|クン|君|クン))|ユウ)', txt) otherNick = re.search( r'^:@([a-zA-Z0-9_]+):\sの(あだ名|あだな|ニックネーム)[::は]\s?(.+)', txt) nick = re.search(r'^(あだ(名|な)|ニックネーム)[::は]\s?(.+)', txt) iBack = re.search( r'(帰宅|ただいま|帰った|帰還)(?!.*(する|します|しちゃう|しよう|中|ちゅう|してる))', txt) goodNight = re.search( r'寝(ます|る|マス)([よかぞね]?|[...。うぅー~!・]+)$|^寝(ます|る|よ)[...。うぅー~!・]*$|寝(ます|る|マス)(.*)[ぽお]や[ユすしー]|(ユウ|ユウ|ゆう|ことひら|コトヒラ|コトヒラ)(ちゃん)?(.*)[ぽお]や[ユすしー]|^(\s*:shushin:\s*)+$', txt) seeYou = re.search(r'((行|い)って(きます|くる)|ノシ|ノシ)', txt) passage = re.search(r'(通過|つうか|ツウカ)(?!.*(おめ|した))', txt) sinkiSagi = re.search(r'(新規|しんき)(です|だよ|なのじゃ)', txt) nullPoint = re.search(r'(ぬるぽ|ヌルポ|ヌルポ|[nN][uU][lL]{2}[pP][oO])', txt) notNicoFri = re.search(r'(にこふれ|ニコフレ|ニコフレ)', txt) sad = re.search( r'((泣|な)いてる|しくしく|シクシク|シクシク|ぐすん|グスン|グスン|ぶわっ|ブワッ|ブワッ)', txt) noNow = re.search(r'(いまのなし|イマノナシ|イマノナシ)', txt) writeDict = re.search( r'^:@[a-zA-Z0-9_]+:(さん|くん|君|殿|どの|ちゃん)?はこんな人[::]', txt) writeMemo = re.search(r'^(メモ|めも|[Mm][Ee][Mm][Oo])[::](.+)', txt) # ユウちゃん etc... とか呼ばれたらふぁぼる if calledYuChan: log.logInfo('呼ばれたっ!:@{0} < {1}'.format( status['account']['acct'], txt)) mastodon.status_favourite(status['id']) # 好感度ちょいアップ fav = fav_rate.get(fav_rate.ID_Inst == user) fav.rate += 5 fav.save() # 投票型のトゥートだったら投票する(期限切れでないかつ投票してないこと) if status['poll'] != None: if status['poll']['expired'] == False and not ( 'voted' in status['poll'] and status['poll']['voted'] == True): voteOptions = status['poll']['options'] # NGワードを検知した場合は弾いて好感度下げ for voteSection in voteOptions: if YuChan.ngWordHook(voteSection['title']): log.logInfo( '変なことを言ってはいけませんっ!!(*`ω´*): @{0}'.format( status['account']['acct'])) hooked = fav_rate.get(fav_rate.ID_Inst == user) hooked.rate -= config['follow']['down_step'] hooked.save() return # 設定で指定されたハッシュタグが含まれていない場合は投票をする if not KotohiraUtil.isVoteOptout(status['tags']): # ここで投票する場所を抽選 voteChoose = random.randint(0, len(voteOptions) - 1) mastodon.poll_vote(status['poll']['id'], voteChoose) # 投票したものをトゥートする log.logInfo('投票っ!:@{0} => {1}'.format( status['account']['acct'], status['poll']['options'][voteChoose]['title'])) mastodon.status_post( 'ユウちゃんは「{0}」がいいと思いますっ!\n\n{1}'.format( status['poll']['options'][voteChoose]['title'], status['url'])) # 投票の再通知機能(設定で有効になっている場合のみ機能) if config['features']['voteRenotify']: # 投票締め切り時間を読み取って現在時刻からの差分でおおよその投票時間を逆算 expires_at = duParser.parse( status['poll']['expires_at']) poll_time_delta = expires_at - now_utc poll_time = poll_time_delta.seconds # 小数点を1桁ずらして切り上げして1桁戻して、投票時間を算出 poll_time_ceil = math.ceil(poll_time / 10) * 10 # 約5分間投票だったら2分前ぐらいに通知、それ以外は5分前 if poll_time <= 300: renotify_timer = float(poll_time_ceil - 120) else: renotify_timer = float(poll_time_ceil - 300) log.logInfo( f'投票時間は{poll_time}ですので、{str(renotify_timer)}秒後に知らせますっ!' ) VOTE_RENOTIFY_THREAD[int( status['id'])] = threading.Timer( renotify_timer, vote_renotify, kwargs={ "url": status['url'], "id": status['id'] }) VOTE_RENOTIFY_THREAD[int(status['id'])].start() elif otherNick: # 他人のニックネームの設定 YuChan.set_otherNickname(txt, status['id'], status['account']['id'], status['account']['acct'], status['visibility']) elif nick: # ニックネームの設定 newNicknameParse = re.search(r"^(あだ名|あだな|ニックネーム)[::は]\s?(.+)", txt) newNickname = newNicknameParse.group(2) YuChan.set_nickname(newNickname, status['id'], status['account']['id'], status['account']['acct'], status['visibility']) elif iBack: # おかえりとか言ったら実行 if YuChan.msg_hook( 'wel_back', 600, ":@{0}: {1}、おかえりなさいませっ!".format( status['account']['acct'], name)): log.logInfo('おかえりっ!:@{0} < {1}'.format( status['account']['acct'], txt)) elif goodNight: # おやすみですっ! if YuChan.msg_hook( 'good_night', 600, ":@{0}: {1}、おやすみなさいっ!🌙".format( status['account']['acct'], name)): log.logInfo('おやすみっ!:@{0} < {1}'.format( status['account']['acct'], txt)) elif seeYou: # いってらっしゃいなのですっ! if YuChan.msg_hook( 'see_you', 600, ":@{0}: {1}、いってらっしゃいっ!🚪".format( status['account']['acct'], name)): log.logInfo('いってらっしゃいっ!:@{0} < {1}'.format( status['account']['acct'], txt)) elif passage: # 通過 とか言ったら阻止しちゃうよっ! if YuChan.msg_hook('passage', 300, "阻止っ!!(*`ω´*)"): log.logInfo('阻止っ!:@{0} < {1}'.format( status['account']['acct'], txt)) elif sinkiSagi: # 現在時刻をUTCに変換し、該当アカウントの作成時刻から1日後のものを算出。 # 作成から丸一日以上かつトゥートが10より上であれば作動 created_at = duParser.parse(status['account']['created_at']) created_a1d = created_at + datetime.timedelta(days=1) if status['account'][ 'statuses_count'] > 10 and created_a1d < now_utc: # 新規詐欺見破りっ! if YuChan.msg_hook('sin_sagi', 600, "新規詐欺はいけませんっ!!(*`ω´*)"): log.logInfo('新規詐欺っ!:@{0} < {1}'.format( status['account']['acct'], txt)) elif nullPoint: # ぬるぽって、言ったら■━⊂( ・∀・)彡ガッ☆`Д゚) if YuChan.msg_hook('null_point', 180, ":gaxtsu:"): log.logInfo('がっ:@{0} < {1}'.format( status['account']['acct'], txt)) elif notNicoFri: # ニコフレじゃないよっ! if YuChan.msg_hook('not_nikofure', 600, "ここはニコフレじゃないですっ!!ベスフレですっ!(*`ω´*)"): log.logInfo('ベスフレですっ!:@{0} < {1}'.format( status['account']['acct'], txt)) elif sad: # よしよしっ if YuChan.msg_hook('yoshiyoshi', 180, "(´・ω・`)ヾ(・ω・。)ヨシヨシ"): log.logInfo('よしよしっ:@{0} < {1}'.format( status['account']['acct'], txt)) elif noNow: # いまのなしは封印ですっ! if YuChan.msg_hook('no_now', 180, "いまのなしは封印ですっ!!(*`ω´*)"): log.logInfo('いまのなしは封印ですっ!:@{0} < {1}'.format( status['account']['acct'], txt)) if writeDict: # 辞書登録っ # (実装中) # YuChan.update_userdict() pass elif writeMemo: # メモの書き込みっ memoBody = re.sub(r'^(メモ|めも|[Mm][Ee][Mm][Oo])[::]\s*(.*)', r'\g<2>', txt, 1) mastodon.status_reblog(status['id']) log.logInfo('メモっ!:@{0} < {1}'.format(status['account']['acct'], txt)) res = YuChan.write_memo(status['account']['acct'], memoBody, status['id']) if res == False: mastodon.status_post('@{}\n長いのでまとめられそうにありませんっ・・・'.format( status['account']['acct']), in_reply_to_id=status['id']) # 2重更新防策 if not newUser: updated_at = updated_users.get(updated_users.ID_Inst == user) greetableTime = updated_at.date + datetime.timedelta(hours=3) shouldGreet = now >= greetableTime # 3時間以上更新がなかった場合は挨拶する if shouldGreet: time.sleep(0.5) if now.hour < 12 and now.hour >= 5: log.logInfo("おはようございますっ!:@{0} < {1}".format( status['account']['acct'], txt)) mastodon.toot(""":@{1}: {0}、おはようございますっ!🌄""".format( name, status['account']['acct'])) elif now.hour >= 12 and now.hour < 17: log.logInfo("こんにちはっ!:@{0} < {1}".format( status['account']['acct'], txt)) mastodon.toot(""":@{1}: {0}、こんにちはっ!☀""".format( name, status['account']['acct'])) elif now.hour >= 17 and now.hour < 5: log.logInfo("こんばんはっ!:@{0} < {1}".format( status['account']['acct'], txt)) mastodon.toot(""":@{1}: {0}、こんばんはっ!🌙""".format( name, status['account']['acct'])) YuChan.drill_count(user, name, status['account']['statuses_count']) # 最終更新を変更 updated_at.date = now updated_at.save() except Exception as e: DATABASE.rollback() # エラーが出た場合はデータベースのトランザクションを破棄 # Timelines.pyの方へエラーを送出させる raise e else: DATABASE.commit() # 異常なければコミット