aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorFuwn <[email protected]>2025-07-05 06:05:06 -0700
committerFuwn <[email protected]>2025-07-05 06:05:06 -0700
commitecc92ea9c1fbe84f67b7c9ec02e10a330651155c (patch)
tree13357b08744922afe021e516cd8be945233ce7bf /src
parentfeat: Initial commit (diff)
downloadoguri-ecc92ea9c1fbe84f67b7c9ec02e10a330651155c.tar.xz
oguri-ecc92ea9c1fbe84f67b7c9ec02e10a330651155c.zip
feat: Add simple schedule subcommand
Diffstat (limited to 'src')
-rw-r--r--src/oguri/__init__.py94
-rw-r--r--src/oguri/__main__.py7
-rw-r--r--src/oguri/cli.py37
-rw-r--r--src/oguri/schedule.py147
4 files changed, 190 insertions, 95 deletions
diff --git a/src/oguri/__init__.py b/src/oguri/__init__.py
index 3720668..3e66273 100644
--- a/src/oguri/__init__.py
+++ b/src/oguri/__init__.py
@@ -1,92 +1,4 @@
-from anilist_client import Client
-from anilist_client.custom_fields import (
- AiringScheduleFields,
- MediaFields,
- MediaTitleFields,
- PageFields,
-)
-from anilist_client.custom_queries import Query
-from datetime import datetime
+from .cli import main_script
-
-async def main() -> int:
- client = Client(url="https://graphql.anilist.co")
- airing_schedules_query = Query.page().fields(
- PageFields.airing_schedules().fields(
- AiringScheduleFields.airing_at,
- AiringScheduleFields.episode,
- AiringScheduleFields.media().fields(
- MediaFields.title().fields(
- MediaTitleFields.english(),
- MediaTitleFields.romaji(),
- MediaTitleFields.native(),
- )
- ),
- )
- )
-
- try:
- response = await client.query(
- airing_schedules_query, operation_name="get_airing_schedules"
- )
-
- if response:
- page = response.get("Page")
-
- if page:
- airing_schedules = page.get("airingSchedules")
-
- if airing_schedules:
- for schedule in airing_schedules:
- airing_at = schedule.get("airingAt")
- episode = schedule.get("episode")
- titles = schedule.get("media").get("title")
- title = (
- titles.get("english")
- or titles.get("romaji")
- or titles.get("native")
- )
-
- if airing_at:
- airing_at_date = datetime.fromtimestamp(airing_at)
- relative_airing_at = relative_time(airing_at_date)
- to_print = f"{title} Ep. {episode} "
-
- if datetime.now() > airing_at_date:
- to_print += f"has already aired {relative_airing_at}"
- else:
- to_print += f"is airing at {airing_at_date}"
-
- print(to_print)
- except Exception as exception:
- print(exception)
-
- return 1
-
- return 0
-
-
-def relative_time(date):
- now = datetime.now()
- delta = now - date
-
- if delta.days > 365:
- return f"{delta.days // 365} years ago"
- elif delta.days > 30:
- return f"{delta.days // 30} months ago"
- elif delta.days > 7:
- return f"{delta.days // 7} weeks ago"
- elif delta.days > 1:
- return f"{delta.days} days ago"
- elif delta.seconds > 3600:
- return f"{delta.seconds // 3600} hours ago"
- elif delta.seconds > 60:
- return f"{delta.seconds // 60} minutes ago"
- else:
- return f"{delta.seconds} seconds ago"
-
-
-def main_script():
- import asyncio
-
- asyncio.run(main())
+if __name__ == "__main__":
+ main_script()
diff --git a/src/oguri/__main__.py b/src/oguri/__main__.py
index 7b4d22f..6cda171 100644
--- a/src/oguri/__main__.py
+++ b/src/oguri/__main__.py
@@ -1,5 +1,4 @@
-import oguri
-import sys
-import asyncio
+from oguri.cli import main_script
-sys.exit(asyncio.run(oguri.main()))
+if __name__ == "__main__":
+ main_script()
diff --git a/src/oguri/cli.py b/src/oguri/cli.py
new file mode 100644
index 0000000..a9b010b
--- /dev/null
+++ b/src/oguri/cli.py
@@ -0,0 +1,37 @@
+import asyncio
+import click
+from . import schedule as schedule_logic
+
+
+def cli():
+ """A command-line tool for AniList."""
+
+ pass
+
+
[email protected]("day", required=False, default="today")
[email protected]("--reverse", is_flag=True, help="Reverse the order of the schedule.")
+def schedule(day, reverse):
+ """
+ Shows the airing schedule for a given day.
+
+ DAY can be 'today', 'tomorrow', or an integer representing the number of days from now.
+ """
+
+ days_offset = 0
+
+ if day == "tomorrow":
+ days_offset = 1
+ elif day != "today":
+ try:
+ days_offset = int(day)
+ except ValueError:
+ raise click.BadParameter('DAY must be "today", "tomorrow", or an integer.')
+
+ asyncio.run(schedule_logic.show_schedule(days_offset, reverse))
+
+
+def main_script():
+ cli()
diff --git a/src/oguri/schedule.py b/src/oguri/schedule.py
new file mode 100644
index 0000000..cbd0d2a
--- /dev/null
+++ b/src/oguri/schedule.py
@@ -0,0 +1,147 @@
+from datetime import datetime, timedelta
+from rich.console import Console
+from rich.table import Table
+from anilist_client import Client
+from anilist_client.custom_fields import (
+ AiringScheduleFields,
+ MediaFields,
+ MediaTitleFields,
+ PageFields,
+)
+from anilist_client.custom_queries import Query
+
+
+async def show_schedule(days_offset: int, reverse_order: bool = False):
+ client = Client(url="https://graphql.anilist.co")
+ start_of_day = datetime.now().replace(
+ hour=0, minute=0, second=0, microsecond=0
+ ) + timedelta(days=days_offset)
+ end_of_day = start_of_day.replace(hour=23, minute=59, second=59, microsecond=999999)
+ airing_schedules_query = Query.page().fields(
+ PageFields.airing_schedules(
+ airing_at_greater=int(start_of_day.timestamp()),
+ episode=1,
+ airing_at_lesser=int(end_of_day.timestamp()),
+ ).fields(
+ AiringScheduleFields.airing_at,
+ AiringScheduleFields.episode,
+ AiringScheduleFields.media().fields(
+ MediaFields.site_url,
+ MediaFields.title().fields(
+ MediaTitleFields.english(),
+ MediaTitleFields.romaji(),
+ MediaTitleFields.native(),
+ ),
+ ),
+ )
+ )
+
+ try:
+ response = await client.query(
+ airing_schedules_query, operation_name="get_airing_schedules"
+ )
+
+ if response:
+ page = response.get("Page")
+
+ if page:
+ airing_schedules = page.get("airingSchedules")
+
+ if airing_schedules:
+ if reverse_order:
+ airing_schedules.sort(key=lambda x: x.get("airingAt"))
+ else:
+ airing_schedules.sort(
+ key=lambda x: x.get("airingAt"), reverse=True
+ )
+
+ console = Console()
+ table = Table(show_header=True, header_style="bold magenta")
+
+ table.add_column("Title")
+ table.add_column("Episode")
+ table.add_column("Airing Time")
+
+ for schedule in airing_schedules:
+ airing_at = schedule.get("airingAt")
+ episode = schedule.get("episode")
+ media = schedule.get("media")
+ site_url = media.get("siteUrl")
+ titles = media.get("title")
+ title = (
+ titles.get("english")
+ or titles.get("romaji")
+ or titles.get("native")
+ )
+
+ if airing_at:
+ airing_at_date = datetime.fromtimestamp(airing_at)
+ airing_time_string = ""
+
+ if datetime.now() > airing_at_date:
+ airing_time_string = (
+ f"Aired {relative_time(airing_at_date)}"
+ )
+ else:
+ airing_time_string = format_future_airing_time(
+ airing_at_date
+ )
+
+ table.add_row(
+ f"[link={site_url}]{title}[/link]",
+ str(episode),
+ airing_time_string,
+ )
+
+ console.print(table)
+ except Exception as exception:
+ print(exception)
+
+
+def format_future_airing_time(date):
+ now = datetime.now()
+ delta = date - now
+ hours, remainder = divmod(int(delta.total_seconds()), 3600)
+ minutes, _ = divmod(remainder, 60)
+ time_str = date.strftime("%I:%M %p")
+ parts = []
+
+ if hours > 0:
+ parts.append(f"{hours}h")
+ if minutes > 0:
+ parts.append(f"{minutes}m")
+
+ if not parts:
+ return f"Airing soon at {time_str}"
+
+ return f"In {' '.join(parts)} at {time_str}"
+
+
+def relative_time(date):
+ now = datetime.now()
+ delta = now - date
+
+ if delta.days > 365:
+ years = delta.days // 365
+
+ return f"{years} year{'s' if years > 1 else ''} ago"
+ elif delta.days > 30:
+ months = delta.days // 30
+
+ return f"{months} month{'s' if months > 1 else ''} ago"
+ elif delta.days > 7:
+ weeks = delta.days // 7
+
+ return f"{weeks} week{'s' if weeks > 1 else ''} ago"
+ elif delta.days > 1:
+ return f"{delta.days} day{'s' if delta.days > 1 else ''} ago"
+ elif delta.seconds > 3600:
+ hours = delta.seconds // 3600
+
+ return f"{hours} hour{'s' if hours > 1 else ''} ago"
+ elif delta.seconds > 60:
+ minutes = delta.seconds // 60
+
+ return f"{minutes} minute{'s' if minutes > 1 else ''} ago"
+ else:
+ return f"{delta.seconds} second{'s' if delta.seconds > 1 else ''} ago"