Skip to content

Commit

Permalink
Basic youtube support
Browse files Browse the repository at this point in the history
  • Loading branch information
thomaserlang committed Oct 11, 2024
1 parent b28b2f3 commit 61ee260
Show file tree
Hide file tree
Showing 13 changed files with 539 additions and 40 deletions.
35 changes: 35 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[project]
name = "klips"
version = "2.0"
description = "Patient management system"
dependencies = [
"bottom==2.2.0",
"pyyaml>=6.0",
"click==8.0.1",
"python-dateutil==2.8.2",
"pytz==2021.3",
"websockets==10.3",
"tornado==6.1",
"aiohttp<3.8.0",
"aiomysql==0.1.1",
"honcho==1.1.0",
"aioredis<2.0",
"pytest==6.2.5",
"good==0.0.8",
"yoyo-migrations==7.3.2",
"discord2==2.0.1",
"sentry-sdk==1.5.12",
"pydantic==1.10.9",
"apprise==1.4.0",
"httpx==0.27.2",
]

[build-system]
build-backend = "flit_core.buildapi"
requires = ["flit_core>=3.2,<4"]

[tool.ruff.format]
quote-style = "single"

[tool.ruff.lint]
select = ["E4", "E7", "E9", "F", "I"]
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ yoyo-migrations==7.3.2
discord2==2.0.1
sentry-sdk==1.5.12
pydantic==1.10.9
apprise==1.4.0
apprise==1.4.0
httpx==0.27.2
14 changes: 12 additions & 2 deletions tbot/config.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import os, yaml
from typing import List, Optional, Literal
import os
from typing import List, Literal, Optional

import yaml
from pydantic import BaseModel, BaseSettings, DirectoryPath


class ConfigWebModel(BaseModel):
port = 8001
cookie_secret: Optional[str]
Expand Down Expand Up @@ -46,6 +49,12 @@ class ConfigDiscordCustomNotificaitonsModel(BaseModel):
message: str


class ConfigYoutubeModel(BaseModel):
client_id: Optional[str] = None
client_secret: Optional[str] = None
twitch_bot_channel_id: Optional[str] = None


class ConfigDiscordModel(BaseModel):
client_id: Optional[str]
client_secret: Optional[str]
Expand Down Expand Up @@ -84,6 +93,7 @@ class ConfigModel(BaseSettings):
web = ConfigWebModel()
twitch = ConfigTwitchModel()
discord = ConfigDiscordModel()
youtube = ConfigYoutubeModel()
spotify = ConfigSpotifyConfig()
logging = ConfigLoggingModel()
mysql = ConfigMySQLModel()
Expand Down
4 changes: 3 additions & 1 deletion tbot/dev_server.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import os, sys
import os
from subprocess import list2cmdline

from honcho.manager import Manager


def main():
path = os.path.dirname(os.path.realpath(__file__))

Expand Down
20 changes: 20 additions & 0 deletions tbot/migrations/20241011_01_FR34g.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""
"""

from yoyo import step

__depends__ = {'20240227_03_Yv2xH'}

steps = [
step('''
CREATE TABLE IF NOT EXISTS `twitch_youtube` (
`channel_id` VARCHAR(32) NOT NULL,
`token` VARCHAR(500) NOT NULL,
`refresh_token` VARCHAR(500) NOT NULL,
`handle` VARCHAR(500) NULL DEFAULT NULL,
PRIMARY KEY (`channel_id`))
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8mb4;
'''),
]
13 changes: 10 additions & 3 deletions tbot/twitch_bot/tasks/channels_check.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import asyncio, json, copy
import asyncio
import copy
import json
from datetime import datetime, timedelta, timezone

from dateutil.parser import parse
from datetime import datetime, timezone, timedelta
from tbot import config, utils, logger

from tbot import config, logger, utils
from tbot.twitch_bot.bot_base import bot

"""
Expand Down Expand Up @@ -34,6 +38,7 @@ def channel_extra_default():
'users': [],
'last_check': None,
'uptime': 0,
'youtube_chat_next_page_token': None,
}

@bot.on('AFTER_CHANNELS_JOINED')
Expand Down Expand Up @@ -148,6 +153,7 @@ async def cache_channel(channel_id):
'went_offline_at_delay': went_offline_at_delay.isoformat() \
if went_offline_at_delay else None,
'uptime': bot.channels_check[channel_id]['uptime'],
'youtube_chat_next_page_token': bot.channels_check[channel_id]['youtube_chat_next_page_token'],
})
))

Expand All @@ -164,6 +170,7 @@ async def load_channels_cache():
bot.channels_check[r['channel_id']]['went_offline_at_delay'] = \
parse(data['went_offline_at_delay']) if data.get('went_offline_at_delay') else None
bot.channels_check[r['channel_id']]['uptime'] = data['uptime'] if data.get('uptime') else 0
bot.channels_check[r['channel_id']]['youtube_chat_next_page_token'] = data.get('youtube_chat_next_page_token')

async def set_chatters(channel_id: str):
try:
Expand Down
120 changes: 87 additions & 33 deletions tbot/twitch_bot/tasks/command.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import asyncio

from tbot import logger
from tbot.twitch_bot.bot_base import bot
from tbot.twitch_bot import var_filler
from tbot.twitch_bot.bot_base import bot
from tbot.utils.twitch import Twitch_request_error

cmds = []
_cmd_lookup = {}


def command(cmd, alias=None, arg_desc=None):
def wrapper(f):
wrapped = f
Expand All @@ -22,8 +24,10 @@ def wrapper(f):
_cmd_lookup[cmd] = d
if alias:
_cmd_lookup[alias] = d

return wrapper


@bot.on('PRIVMSG')
def handle_command(nick, target, message, **kwargs):
if not message.startswith('!'):
Expand All @@ -36,19 +40,42 @@ def handle_command(nick, target, message, **kwargs):
cmd = '__thebotname'
if cmd in _cmd_lookup:
f = _cmd_lookup[cmd]['func']
bot.loop.create_task(f(
bot=bot,
nick=nick,
channel=target.strip('#'),
channel_id=kwargs['room-id'],
bot.loop.create_task(
f(
bot=bot,
nick=nick,
channel=target.strip('#'),
channel_id=kwargs['room-id'],
target=target,
message=message,
args=args,
**kwargs,
)
)
bot.loop.create_task(
twitch_command(
cmd=cmd,
target=target,
message=message,
args=args,
**kwargs
))
bot.loop.create_task(db_command(
data={
'bot': bot,
'args': args,
'user': kwargs['user'],
'display_name': kwargs['display-name'] or kwargs['user'],
'user_id': kwargs['user-id'],
'channel': target.strip('#'),
'channel_id': kwargs['room-id'],
'badges': kwargs['badges'],
'emotes': kwargs['emotes'],
'cmd': cmd,
},
)
)


async def twitch_command(cmd, target, data, args):
msg = await db_command(
cmd=cmd,
target=target,
data={
'bot': bot,
'args': args,
Expand All @@ -60,53 +87,76 @@ def handle_command(nick, target, message, **kwargs):
'badges': kwargs['badges'],
'emotes': kwargs['emotes'],
'cmd': cmd,
}
))
},
)
if msg:
bot.send('PRIVMSG', target=target, message=msg)


async def db_command(cmd, target, data):
cmds = await bot.db.fetchall('''
async def db_command(cmd, data):
cmds = await bot.db.fetchall(
"""
SELECT
cmd, response, user_level, global_cooldown,
user_cooldown, mod_cooldown, enabled_status
FROM twitch_commands
WHERE channel_id=%s AND cmd=%s AND enabled=1
ORDER BY user_level DESC, enabled_status DESC, id ASC
''', (data['channel_id'], cmd))
""",
(data['channel_id'], cmd),
)
if not cmds:
return
user_level = await get_user_level(data['badges'], data['user_id'], data['channel_id'], max([cmd['user_level'] for cmd in cmds]))
user_level = await get_user_level(
data['badges'],
data['user_id'],
data['channel_id'],
max([cmd['user_level'] for cmd in cmds]),
)
for cmd in cmds:
try:
if user_level < cmd['user_level']:
continue

if cmd['enabled_status'] > 0 and cmd['enabled_status'] != get_enabled_status(data['channel_id']):
if cmd['enabled_status'] > 0 and cmd[
'enabled_status'
] != get_enabled_status(data['channel_id']):
continue

cd = await has_cooldown(cmd=cmd['cmd'], channel_id=data['channel_id'], user_id=data['user_id'],
user_level=user_level, user_cooldown=cmd['user_cooldown'], mod_cooldown=cmd['mod_cooldown'],
global_cooldown=cmd['global_cooldown'])
cd = await has_cooldown(
cmd=cmd['cmd'],
channel_id=data['channel_id'],
user_id=data['user_id'],
user_level=user_level,
user_cooldown=cmd['user_cooldown'],
mod_cooldown=cmd['mod_cooldown'],
global_cooldown=cmd['global_cooldown'],
)
if cd:
continue
data.setdefault('cmd_history', []).append(cmd['cmd'])
msg = await var_filler.fill_message(cmd['response'], **data)
bot.send("PRIVMSG", target=target, message=msg)
return msg
except (var_filler.Send_error, var_filler.Send) as e:
bot.send("PRIVMSG", target=target, message='@{}, {}'.format(data['display_name'], e))
return '@{}, {}'.format(data['display_name'], e)
except var_filler.Send_break:
pass
except Twitch_request_error as e:
bot.send("PRIVMSG", target=target, message=str(e))
return str(e)
except Exception as e:
logger.exception(str(e))


async def get_user_level(badges, user_id, channel_id, required_level):
if 'broadcaster' in badges:
return 9
if required_level == 8:
r = await bot.db.fetchone(
'SELECT user_id FROM twitch_channel_admins WHERE channel_id=%s AND user_id=%s',
(channel_id, user_id,)
(
channel_id,
user_id,
),
)
if r:
return 8
Expand All @@ -118,27 +168,30 @@ async def get_user_level(badges, user_id, channel_id, required_level):
return 1
return 0

async def has_cooldown(cmd, channel_id, user_id, user_level, user_cooldown, mod_cooldown, global_cooldown):

async def has_cooldown(
cmd, channel_id, user_id, user_level, user_cooldown, mod_cooldown, global_cooldown
):
keys = []

global_cooldown_key = 'tbot:cooldown:{}:{}:global'.format(channel_id, cmd)
keys.append({'key': global_cooldown_key, 'expire': global_cooldown})
keys.append({'key': global_cooldown_key, 'expire': global_cooldown})

user_cooldown_key = 'tbot:cooldown:{}:{}:user:{}'.format(channel_id, cmd, user_id)
keys.append({'key': user_cooldown_key, 'expire': user_cooldown})

mod_key = 'tbot:cooldown:{}:{}:mod:{}'.format(channel_id, cmd, user_id)
keys.append({'key': mod_key, 'expire': mod_cooldown})

r = bot.redis.multi_exec()
r = bot.redis.multi_exec()
if user_level < 7:
if global_cooldown:
r.get(global_cooldown_key)
if user_cooldown:
r.get(user_cooldown_key)
else:
r.get(mod_key)

result = await r.execute()
if result and any(result):
return True
Expand All @@ -151,7 +204,8 @@ async def has_cooldown(cmd, channel_id, user_id, user_level, user_cooldown, mod_

return False


def get_enabled_status(channel_id):
if bot.channels_check[channel_id]['is_streaming']:
return 1
return 2
return 2
Loading

0 comments on commit 61ee260

Please sign in to comment.