-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathmain.py
More file actions
110 lines (94 loc) · 3.4 KB
/
Copy pathmain.py
File metadata and controls
110 lines (94 loc) · 3.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
"""Fetch the weekly content and post it to Discord."""
import json
import logging
import os
import sys
from datetime import datetime, timedelta, timezone
from pathlib import Path
from urllib.error import HTTPError
from urllib.request import Request, urlopen
import yaml
from dotenv import load_dotenv
from utils.blog_fetcher import BlogFetcher
from utils.embeds import build_blog_embed, build_youtube_embed
from utils.youtube_api import YouTubeClient
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
logger = logging.getLogger(__name__)
CONFIG_PATH = Path("config/config.yaml")
def load_config() -> dict:
if not CONFIG_PATH.exists():
raise FileNotFoundError(f"Configuration file not found: {CONFIG_PATH}")
with CONFIG_PATH.open() as config_file:
config = yaml.safe_load(config_file)
if not isinstance(config, dict):
raise ValueError("Configuration file is empty or malformed")
return config
def post_to_discord(token: str, channel_id: int, embed: dict) -> None:
request = Request(
f"https://discord.com/api/v10/channels/{channel_id}/messages",
data=json.dumps({"embeds": [embed]}).encode(),
headers={
"Authorization": f"Bot {token}",
"Content-Type": "application/json",
"User-Agent": "Mona Media",
},
method="POST",
)
try:
with urlopen(request, timeout=30):
return
except HTTPError as exc:
detail = exc.read().decode(errors="replace")
raise RuntimeError(f"Discord API returned HTTP {exc.code}: {detail}") from exc
def run_digest(config: dict, token: str) -> None:
now = datetime.now(tz=timezone.utc)
since = now - timedelta(days=7)
blog = config["blog"]
posts = BlogFetcher(blog["feed_url"]).get_posts_since_by_keywords(
since=since,
keywords=blog["keywords"],
max_results=int(blog.get("digest_count", 3)),
search_pool=int(blog.get("search_pool", 20)),
)
if posts:
post_to_discord(
token,
int(blog["discord_channel_id"]),
build_blog_embed(posts, blog["keywords"], since, now),
)
logger.info("Posted blog digest with %d post(s).", len(posts))
else:
logger.warning("No matching blog posts found.")
youtube = config["youtube"]
videos = YouTubeClient(os.environ["YOUTUBE_API_KEY"]).get_top_recent_videos(
channel_id=youtube["channel_id"],
published_after=since,
top_n=int(youtube.get("digest_count", 3)),
search_pool=int(youtube.get("search_pool", 20)),
)
if videos:
post_to_discord(
token,
int(youtube["discord_channel_id"]),
build_youtube_embed(videos, since, now),
)
logger.info("Posted YouTube digest with %d video(s).", len(videos))
else:
logger.warning("No YouTube videos found.")
def main() -> None:
load_dotenv()
required = ["DISCORD_TOKEN", "YOUTUBE_API_KEY"]
missing = [name for name in required if not os.environ.get(name)]
if missing:
raise RuntimeError(f"Missing required environment variables: {', '.join(missing)}")
run_digest(load_config(), os.environ["DISCORD_TOKEN"])
if __name__ == "__main__":
try:
main()
except Exception:
logger.exception("Digest job failed.")
sys.exit(1)