Add channel id by name message
This commit is contained in:
parent
3d7ec85a55
commit
4cd8fb80da
5 changed files with 45 additions and 12 deletions
|
|
@ -28,6 +28,7 @@ class Bot:
|
||||||
INIT_MESSAGE: str = ('Bot initialized.\nThis is the current configuration used.\n'
|
INIT_MESSAGE: str = ('Bot initialized.\nThis is the current configuration used.\n'
|
||||||
'You can upload a new one to update the configuration.')
|
'You can upload a new one to update the configuration.')
|
||||||
MAX_DOWNLOAD_SIZE: int = 50_000
|
MAX_DOWNLOAD_SIZE: int = 50_000
|
||||||
|
YT_CHANNEL_NAME_URL = 'https://www.youtube.com/@'
|
||||||
|
|
||||||
class Task(Enum):
|
class Task(Enum):
|
||||||
DELETE_MESSAGES = 1
|
DELETE_MESSAGES = 1
|
||||||
|
|
@ -41,7 +42,7 @@ class Bot:
|
||||||
raise RuntimeError('Cannot current bot version')
|
raise RuntimeError('Cannot current bot version')
|
||||||
return tomllib.loads(pyproject_path.read_text(encoding='utf-8'))['project']['version']
|
return tomllib.loads(pyproject_path.read_text(encoding='utf-8'))['project']['version']
|
||||||
|
|
||||||
def __init__(self, bot_token: str, guild_id: int, config: Config | None = None,
|
def __init__(self, bot_token: str, guild_id: int, yt_api_key: str, config: Config | None = None,
|
||||||
log_level: int = logging.INFO):
|
log_level: int = logging.INFO):
|
||||||
self.config: Config = config or Config()
|
self.config: Config = config or Config()
|
||||||
self.guild_id = guild_id
|
self.guild_id = guild_id
|
||||||
|
|
@ -86,7 +87,7 @@ class Bot:
|
||||||
raise RuntimeError("Couldn't initialize bot channel/role/permission")
|
raise RuntimeError("Couldn't initialize bot channel/role/permission")
|
||||||
self.bot_channel: TextChannel = bot_channel
|
self.bot_channel: TextChannel = bot_channel
|
||||||
|
|
||||||
self.yt_manager = YoutubeManager(logger=self.logger)
|
self.yt_manager = YoutubeManager(api_key=yt_api_key, logger=self.logger)
|
||||||
self._yt_subscriptions: Subscriptions = {}
|
self._yt_subscriptions: Subscriptions = {}
|
||||||
self._scan_bot_channel()
|
self._scan_bot_channel()
|
||||||
self.tasks.append((
|
self.tasks.append((
|
||||||
|
|
@ -144,8 +145,29 @@ class Bot:
|
||||||
new_subscriptions: Subscriptions | None = None
|
new_subscriptions: Subscriptions | None = None
|
||||||
delayed_delete: dict[int, Message] = {}
|
delayed_delete: dict[int, Message] = {}
|
||||||
immediate_delete: dict[int, Message] = {}
|
immediate_delete: dict[int, Message] = {}
|
||||||
|
|
||||||
for message in messages:
|
for message in messages:
|
||||||
if init_message_found:
|
if init_message_found:
|
||||||
|
if message.author.id != self.bot_user.id and message.content.startswith(self.YT_CHANNEL_NAME_URL):
|
||||||
|
self.logger.debug('Parsing message for youtube channel name conversion: %s', message)
|
||||||
|
answers: list[str] = []
|
||||||
|
for line in message.content.splitlines():
|
||||||
|
if line.startswith(self.YT_CHANNEL_NAME_URL):
|
||||||
|
channel_name = line.rstrip()[len(self.YT_CHANNEL_NAME_URL):]
|
||||||
|
channel_id = self.yt_manager.request_channel_id(
|
||||||
|
channel_name, request_timeout=self.config.request_timeout)
|
||||||
|
answers.append(f'{channel_name} -> {channel_id}')
|
||||||
|
bot_message = self.discord_manager.create_message(self.bot_channel, {
|
||||||
|
'content': '\n'.join(answers),
|
||||||
|
'message_reference': MessageReference(
|
||||||
|
type=MessageReferenceType.DEFAULT,
|
||||||
|
message_id=message.id,
|
||||||
|
channel_id=self.bot_channel.id,
|
||||||
|
guild_id=None,
|
||||||
|
fail_if_not_exists=None)}, request_timeout=self.config.request_timeout)
|
||||||
|
delayed_delete[bot_message.id] = bot_message
|
||||||
|
delayed_delete[message.id] = message
|
||||||
|
else:
|
||||||
self.logger.debug('Marking message for immediate deletion (init found): %s', message)
|
self.logger.debug('Marking message for immediate deletion (init found): %s', message)
|
||||||
immediate_delete[message.id] = message
|
immediate_delete[message.id] = message
|
||||||
continue
|
continue
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
import json
|
||||||
import time
|
import time
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
import urllib.error
|
import urllib.error
|
||||||
|
|
@ -26,11 +27,12 @@ class YoutubeManager:
|
||||||
remaining: int
|
remaining: int
|
||||||
next_reset: float
|
next_reset: float
|
||||||
|
|
||||||
def __init__(self, logger: logging.Logger):
|
def __init__(self, api_key: str, logger: logging.Logger):
|
||||||
|
self._api_key = api_key
|
||||||
self._logger = logger
|
self._logger = logger
|
||||||
self.rate_limit = self.RateLimit(remaining=self.DEFAULT_DAILY_POINTS, next_reset=time.time() + 24 * 3600)
|
self.rate_limit = self.RateLimit(remaining=self.DEFAULT_DAILY_POINTS, next_reset=time.time() + 24 * 3600)
|
||||||
|
|
||||||
def _request(self, url: str, request_timeout: float, expected_status: int = 200) -> tuple[HTTPHeaders, str]:
|
def _request(self, url: str, request_timeout: float, expected_status: int = 200) -> tuple[HTTPHeaders, dict]:
|
||||||
if time.time() >= self.rate_limit.next_reset:
|
if time.time() >= self.rate_limit.next_reset:
|
||||||
self.rate_limit.next_reset = time.time() + 24 * 3600
|
self.rate_limit.next_reset = time.time() + 24 * 3600
|
||||||
self.rate_limit.remaining = self.DEFAULT_DAILY_POINTS
|
self.rate_limit.remaining = self.DEFAULT_DAILY_POINTS
|
||||||
|
|
@ -41,14 +43,14 @@ class YoutubeManager:
|
||||||
self.rate_limit.remaining -= 1
|
self.rate_limit.remaining -= 1
|
||||||
|
|
||||||
request = urllib.request.Request(url)
|
request = urllib.request.Request(url)
|
||||||
# request.add_header('Accept', 'application/json')
|
request.add_header('Accept', 'application/json')
|
||||||
try:
|
try:
|
||||||
with urllib.request.urlopen(request, timeout=request_timeout) as response:
|
with urllib.request.urlopen(request, timeout=request_timeout) as response:
|
||||||
if response.status != expected_status:
|
if response.status != expected_status:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f'Unexpected YT status {response.status} (expected: {expected_status})'
|
f'Unexpected YT status {response.status} (expected: {expected_status})'
|
||||||
f' -> {response.read().decode()}')
|
f' -> {response.read().decode()}')
|
||||||
return dict(response.getheaders()), response.read().decode()
|
return dict(response.getheaders()), json.loads(response.read().decode())
|
||||||
except urllib.error.HTTPError as error:
|
except urllib.error.HTTPError as error:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f'HTTP error calling API ({url}): {error}:\n'
|
f'HTTP error calling API ({url}): {error}:\n'
|
||||||
|
|
@ -69,6 +71,13 @@ class YoutubeManager:
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
raise RuntimeError(f'Exception calling YouTube shorts ({video_id}): {error}') from error
|
raise RuntimeError(f'Exception calling YouTube shorts ({video_id}): {error}') from error
|
||||||
|
|
||||||
|
def request_channel_id(self, channel_name: str, request_timeout: float) -> str:
|
||||||
|
url = ('https://www.googleapis.com/youtube/v3/channels?part=snippet'
|
||||||
|
f'&forHandle%40={channel_name}&key={self._api_key}')
|
||||||
|
self._logger.debug('YoutubeManager: request channel id for channel %s', channel_name)
|
||||||
|
_, info = self._request(url=url, request_timeout=request_timeout)
|
||||||
|
return info['items'][0]['id']
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse_rss_data(data) -> tuple[ChannelInfo, list[VideoInfo]]:
|
def _parse_rss_data(data) -> tuple[ChannelInfo, list[VideoInfo]]:
|
||||||
videos: list[VideoInfo] = []
|
videos: list[VideoInfo] = []
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ inline-quotes = "single"
|
||||||
|
|
||||||
[tool.ruff.lint.pylint]
|
[tool.ruff.lint.pylint]
|
||||||
max-args=16
|
max-args=16
|
||||||
max-branches=32
|
max-branches=34
|
||||||
max-locals=16
|
max-locals=16
|
||||||
max-nested-blocks=8
|
max-nested-blocks=8
|
||||||
max-public-methods=16
|
max-public-methods=16
|
||||||
|
|
@ -55,7 +55,7 @@ max-returns=8
|
||||||
max-statements=96
|
max-statements=96
|
||||||
|
|
||||||
[tool.ruff.lint.mccabe]
|
[tool.ruff.lint.mccabe]
|
||||||
max-complexity = 32
|
max-complexity = 34
|
||||||
|
|
||||||
[tool.coverage.run]
|
[tool.coverage.run]
|
||||||
command_line = "-m pytest"
|
command_line = "-m pytest"
|
||||||
|
|
|
||||||
4
start.py
4
start.py
|
|
@ -16,7 +16,9 @@ def main():
|
||||||
del arguments
|
del arguments
|
||||||
|
|
||||||
bot_token = Path('data/discord_bot_token.txt').read_text(encoding='utf-8').strip()
|
bot_token = Path('data/discord_bot_token.txt').read_text(encoding='utf-8').strip()
|
||||||
manager = Bot(bot_token=bot_token, guild_id=guild_id, log_level=logging.DEBUG if debug_mode else logging.INFO)
|
yt_api_key = Path('data/google_api_key.txt').read_text(encoding='utf-8').strip()
|
||||||
|
manager = Bot(bot_token=bot_token, guild_id=guild_id, yt_api_key=yt_api_key,
|
||||||
|
log_level=logging.DEBUG if debug_mode else logging.INFO)
|
||||||
try:
|
try:
|
||||||
manager.run()
|
manager.run()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ from breadtube_bot.youtube_subscription import ChannelInfo
|
||||||
|
|
||||||
def test_rss_parsing():
|
def test_rss_parsing():
|
||||||
logger = logging.getLogger('breadtube-bot-test')
|
logger = logging.getLogger('breadtube-bot-test')
|
||||||
manager = YoutubeManager(logger=logger)
|
manager = YoutubeManager('', logger=logger)
|
||||||
channel_info, videos = manager._parse_rss_data(Path('tests/data/rss_feed_sample.xml').read_text(encoding='utf-8'))
|
channel_info, videos = manager._parse_rss_data(Path('tests/data/rss_feed_sample.xml').read_text(encoding='utf-8'))
|
||||||
assert channel_info == ChannelInfo(
|
assert channel_info == ChannelInfo(
|
||||||
channel_id='UCFemKOoYVrTGUhuVzuNPt4A', title='Actu Réfractaire',
|
channel_id='UCFemKOoYVrTGUhuVzuNPt4A', title='Actu Réfractaire',
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue