summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--commands/__init__.py1
-rw-r--r--commands/event.py110
-rw-r--r--commands/movie.py44
-rw-r--r--commands/users.py38
-rw-r--r--main.py8
-rw-r--r--predicates.py31
-rw-r--r--strings.py31
-rw-r--r--utils.py12
8 files changed, 249 insertions, 26 deletions
diff --git a/commands/__init__.py b/commands/__init__.py
index 09c0867..2e0e009 100644
--- a/commands/__init__.py
+++ b/commands/__init__.py
@@ -13,3 +13,4 @@ from .meta import about
from .users import set_users, add_users, list_users, remove_users, \
chooser_user, who_is_next
from .movie import movie, movies, remove_movies
+from .event import create_event, list_events
diff --git a/commands/event.py b/commands/event.py
new file mode 100644
index 0000000..47c34ea
--- /dev/null
+++ b/commands/event.py
@@ -0,0 +1,110 @@
+# This file is part of python-cinema-club-bot
+# contributed in 2024 by Mikhail Kirillov (~w96k) <w96k@runbox.com>
+
+# To the extent possible under law, the author(s) have dedicated all copyright
+# and related and neighboring rights to this software to the public domain
+# worldwide. This software is distributed without any warranty.
+
+# You should have received a copy of the CC0 Public Domain Dedication along
+# with this software. If not, see:
+# <http://creativecommons.org/publicdomain/zero/1.0/>
+
+from telegram import Update, error
+from telegram.ext import ContextTypes
+from datetime import datetime
+
+from utils import context_init
+from predicates import is_past, has_finished_event
+from strings import EVENT_ARGS_NOT_PROVIDED, EVENT_INVALID_DATETIME, \
+ EVENT_CREATED, EVENT_EDITED, EVENT_CANT_BE_IN_PAST, \
+ EVENT_WHERE_NOT_PROVIDED, EVENT_MOVIE_NOT_CHOOSEN, EVENTS_LIST, \
+ EVENT_USER_HAD_EVENT, NO_EVENTS
+
+
+async def create_event(
+ update: Update,
+ context: ContextTypes.DEFAULT_TYPE
+) -> None:
+ context_init(context)
+ events = context.chat_data["events"]
+ movies = context.chat_data["movies"]
+
+ username = update.message.from_user.username
+ last_movie = movies[-1] if movies != [] else None
+
+ if has_finished_event(context, username):
+ raise error.TelegramError(EVENT_USER_HAD_EVENT)
+
+ if last_movie is None or last_movie["user"] != username:
+ raise error.TelegramError(EVENT_MOVIE_NOT_CHOOSEN)
+
+ if context.args == []:
+ raise error.TelegramError(EVENT_ARGS_NOT_PROVIDED)
+
+ argument_when = context.args[0]
+
+ try:
+ event_when = datetime.strptime(argument_when, "%d.%m.%Y/%H:%M")
+ except ValueError:
+ raise error.TelegramError(EVENT_INVALID_DATETIME)
+
+ if is_past(event_when):
+ raise error.TelegramError(
+ EVENT_CANT_BE_IN_PAST.format(
+ when=event_when,
+ today=datetime.today(),
+ )
+ )
+
+ arguments_where = context.args[1:]
+
+ if arguments_where == [] and events == []:
+ raise error.TelegramError(EVENT_WHERE_NOT_PROVIDED)
+
+ event_where = " ".join(arguments_where)
+
+ if event_where == "":
+ event_where = events[-1]["where"]
+
+ event_dict = dict(
+ when=event_when,
+ where=event_where,
+ movie=last_movie["title"],
+ user=last_movie["user"]
+ )
+
+ if events == [] or is_past(events[-1]["when"]):
+ events.append(event_dict)
+ await update.message.reply_text(EVENT_CREATED.format(when=event_when))
+ else:
+ events[-1] = event_dict
+ await update.message.reply_text(EVENT_EDITED.format(when=event_when))
+
+
+async def list_events(
+ update: Update,
+ context: ContextTypes.DEFAULT_TYPE
+) -> None:
+ context_init(context)
+
+ events = context.chat_data["events"]
+ movies = context.chat_data["movies"]
+
+ events_formatted: str = ""
+
+ for event in events:
+ event_finished = "FINISHED" \
+ if is_past(event["when"]) else "PLANNED"
+
+ events_formatted += EVENTS_LIST.format(
+ movie=event["movie"],
+ user=event["user"],
+ when=event["when"],
+ where=event["where"],
+ finished=event_finished
+ )
+
+ if events_formatted == "":
+ events_formatted = NO_EVENTS
+
+ await update.message.reply_text(events_formatted)
diff --git a/commands/movie.py b/commands/movie.py
index b7be8cd..256156f 100644
--- a/commands/movie.py
+++ b/commands/movie.py
@@ -12,13 +12,12 @@
from telegram import Update, error
from telegram.ext import ContextTypes
from imdb import Cinemagoer
-from pprint import pformat
-
-from utils import context_init
+from utils import context_init, choose_next_user
+from predicates import has_finished_event
from strings import MOVIE_NOT_PROVIDED, EXPECTED_ONE_MOVIE, \
MOVIE_ANOTHER_USER, FETCHING_MOVIE, FETCHING_ERROR, \
- MOVIE_REMOVE, MOVIE_SET
+ MOVIE_REMOVE, MOVIE_SET, MOVIES_LIST, NO_MOVIES, ADD_MORE_USERS
imdb = Cinemagoer()
@@ -30,9 +29,18 @@ async def movie(
) -> None:
context_init(context)
- chooser = context.chat_data["users"][0] or None
+ users = context.chat_data["users"]
+
+ if users == []:
+ raise error.TelegramError(ADD_MORE_USERS)
+
+ chooser = users[0]
username = update.message.from_user.username
+ if has_finished_event(context, chooser):
+ users = context.chat_data["users"] = choose_next_user(users)
+ raise error.TelegramError(MOVIE_ANOTHER_USER.format(users[0]))
+
if "@"+username != chooser:
raise error.TelegramError(MOVIE_ANOTHER_USER.format(user=chooser))
@@ -50,12 +58,15 @@ async def movie(
movie = imdb.get_movie(movie_id)
except:
raise error.TelegramError(FETCHING_ERROR)
-
+
movie_dict = dict(
title=movie.data.get("title"),
id=movie.getID(),
user=update.effective_user.username,
- poster=movie.data.get("cover url")
+ poster=movie.data.get("cover url"),
+ rating=movie.data.get("rating"),
+ genres=movie.data.get("genres"),
+ runtime=movie.data.get("runtimes"),
)
if len(context.chat_data["movies"]) > 0 and \
@@ -75,13 +86,21 @@ async def movies(
) -> None:
context_init(context)
- movies = context.chat_data["movies"].copy()
+ movies = context.chat_data["movies"]
+
+ movies_formatted: str = ""
for movie in movies:
- if movie["poster"] or None:
- del movie["poster"]
+ movies_formatted += MOVIES_LIST.format(
+ title=movie["title"],
+ id=movie["id"],
+ user=movie["user"]
+ )
- await update.message.reply_text(pformat(movies))
+ if movies_formatted == "":
+ movies_formatted = NO_MOVIES
+
+ await update.message.reply_text(movies_formatted)
async def remove_movies(
@@ -100,6 +119,5 @@ async def remove_movies(
if movie["id"] == movie_id:
context.chat_data["movies"].remove(movie)
await update.message.reply_text(
- MOVIE_REMOVE.format(title=movie["title"], id=["movie.id"])
+ MOVIE_REMOVE.format(title=movie["title"], id=movie["id"])
)
-
diff --git a/commands/users.py b/commands/users.py
index 497d7dd..9dcc125 100644
--- a/commands/users.py
+++ b/commands/users.py
@@ -15,9 +15,11 @@ from collections import deque
from strings import USER_NOT_PROVIDED, USERS_ADDED, USERS_REMOVED, \
EXPECTED_ONE_USER, USER_SET, USER_ADD, USER_REMOVE, ADD_MORE_USERS, \
- NEXT_MOVIE_USER, USER_NOT_FOUND, USER_CHOOSE
-from utils import context_init, create_users_string
-
+ NEXT_MOVIE_USER, USER_NOT_FOUND, USER_CHOOSE, NO_USERS, \
+ EVENT_USER_HAD_EVENT
+from utils import context_init, create_users_string, normalize_username, \
+ choose_next_user
+from predicates import has_finished_event
async def set_users(
update: Update,
@@ -32,6 +34,7 @@ async def set_users(
await update.message.reply_text(USER_SET)
+
async def add_users(
update: Update,
context: ContextTypes.DEFAULT_TYPE
@@ -56,21 +59,34 @@ async def list_users(
users = context.chat_data["users"]
- await update.message.reply_markdown(create_users_string(users))
+ if users == []:
+ await update.message.reply_text(NO_USERS)
+ else:
+ if has_finished_event(context, users[0]):
+ users = context.chat_data["users"] = choose_next_user(users)
+
+ await update.message.reply_markdown(create_users_string(users))
async def who_is_next(
update: Update,
context: ContextTypes.DEFAULT_TYPE
) -> None:
+ """
+ This commands sets the next chooser if needed and shows current
+ """
+
context_init(context)
users = context.chat_data["users"]
- if len(users) > 0:
- await update.message.reply_text(NEXT_MOVIE_USER.format(user=users[0]))
- else:
- await update.message.reply_text(ADD_MORE_USERS)
+ if users == []:
+ raise error.TelegramError(ADD_MORE_USERS)
+
+ if has_finished_event(context, users[0]):
+ users = context.chat_data["users"] = choose_next_user(users)
+
+ await update.message.reply_text(NEXT_MOVIE_USER.format(user=users[0]))
async def remove_users(
@@ -118,6 +134,12 @@ async def chooser_user(
context.chat_data["users"] = users
+ if has_finished_event(context, users[0]):
+ await update.message.reply_text(
+ EVENT_USER_HAD_EVENT.format(user=users[0])
+ )
+ users = context.chat_data["users"] = choose_next_user(users)
+
await update.message.reply_text(USER_CHOOSE.format(user=users[0]))
await update.message.reply_markdown(create_users_string(users))
diff --git a/main.py b/main.py
index 6035f76..fa194f5 100644
--- a/main.py
+++ b/main.py
@@ -35,6 +35,7 @@ async def unknown(update: Update, context: ContextTypes.DEFAULT_TYPE):
text=INVALID_COMMAND
)
+
async def error_handler(update: object, context: ContextTypes.DEFAULT_TYPE) -> None:
logging.error("Exception:", exc_info=context.error)
@@ -54,15 +55,20 @@ if __name__ == "__main__":
# Meta commands
app.add_handler(CommandHandler("about", commands.about))
+ # Event commands
+ app.add_handler(CommandHandler("event", commands.create_event))
+ app.add_handler(CommandHandler("events", commands.list_events))
+
# Movie commands
app.add_handler(CommandHandler("movie", commands.movie))
app.add_handler(CommandHandler("movies", commands.movies))
app.add_handler(CommandHandler("movies_remove", commands.remove_movies))
-
+
# Users commands
app.add_handler(CommandHandler("set", commands.set_users))
app.add_handler(CommandHandler("add", commands.add_users))
app.add_handler(CommandHandler("list", commands.list_users))
+ app.add_handler(CommandHandler("users", commands.list_users))
app.add_handler(CommandHandler("remove", commands.remove_users))
app.add_handler(CommandHandler("chooser", commands.chooser_user))
app.add_handler(CommandHandler("next", commands.who_is_next))
diff --git a/predicates.py b/predicates.py
new file mode 100644
index 0000000..992972d
--- /dev/null
+++ b/predicates.py
@@ -0,0 +1,31 @@
+# This file is part of python-cinema-club-bot
+# contributed in 2024 by Mikhail Kirillov (~w96k) <w96k@runbox.com>
+
+# To the extent possible under law, the author(s) have dedicated all copyright
+# and related and neighboring rights to this software to the public domain
+# worldwide. This software is distributed without any warranty.
+
+# You should have received a copy of the CC0 Public Domain Dedication along
+# with this software. If not, see:
+# <http://creativecommons.org/publicdomain/zero/1.0/>
+
+from telegram.ext import ContextTypes
+from datetime import datetime
+
+from utils import normalize_username
+
+
+def is_past(dt: datetime) -> bool:
+ return dt < datetime.today()
+
+
+def has_finished_event(
+ context: ContextTypes.DEFAULT_TYPE,
+ username: str
+) -> bool:
+ events = context.chat_data["events"]
+ last_event = events[-1] if events != [] else None
+
+ return last_event is not None and \
+ last_event["user"] == normalize_username(username) and \
+ is_past(last_event["when"])
diff --git a/strings.py b/strings.py
index 4a51308..53059ed 100644
--- a/strings.py
+++ b/strings.py
@@ -12,13 +12,15 @@
INVALID_COMMAND = "Invalid command. Available commands: /add /list /remove /chooser /about"
MOVIE_ANOTHER_USER = "Movie should choose another user ({user})"
-MOVIE_NOT_PROVIDED = "Movie is not provided"
+MOVIE_NOT_PROVIDED = "Movie IMDB id is not provided. Use /movie <imdb_id>"
MOVIE_NOT_FOUND = "Movie not found on IMDB"
EXPECTED_ONE_MOVIE = "Expected only one movie"
FETCHING_MOVIE = "Movie with ID {id} is being fetched: https://imdb.com/title/tt{id}/"
FETCHING_ERROR = "Couldn't fetch movie or it is not found. Provide IMDB id, for example: 0133093"
MOVIE_REMOVE = "Movie \"{title}\" with id {id} has been removed"
MOVIE_SET = "Movie \"{title}\" proposed by {user} is succesfully set as next to watch"
+MOVIES_LIST = "\"{title}\" IMDB:{id} by {user} \n"
+NO_MOVIES = "No movies"
USER_NOT_FOUND = "Provided user ({user}) not found. Check /list"
USER_NOT_PROVIDED = "User(s) is not provided"
@@ -28,11 +30,34 @@ NEXT_MOVIE_USER = "Next movie choice is up to {user}"
USER_ADD = "User {user} has been added"
USERS_ADDED = "Users have been added successfully. Use /list to view."
-
USER_REMOVE = "User {user} has been removed. Use /list to view."
USERS_REMOVED = "Users has been removed"
-
USER_SET = "Users have been set successfully. Use /list to view."
USER_CHOOSE = "Next movie should choose: {user}"
+NO_USERS = "No user added. You can add users by /add <nickname1> <nickname2>"
+
+EVENT_INVALID_DATETIME = """
+Can't parse provided datetime. It should comply template like this:
+/event 1.08.2024/18:00 <where>
+"""
+EVENT_CANT_BE_IN_PAST = "Event can't happen in the past. You set {when}, but today is {today}"
+EVENT_ARGS_NOT_PROVIDED = """
+Event commands need arguments provided:
+/event <when> <where>
+/event 1.08.2024/18:00 84 Erevan Str 1st entrance 7 floor 451 apartment
+"""
+EVENT_WHERE_NOT_PROVIDED = """
+Second <where> argument is not provided and previous event doesn't has it.
+/event <when> <where>
+/event 1.08.2024/18:00 84 Erevan Str 1st entrance 7 floor 451 apartment
+"""
+
+EVENT_CREATED = "Event at {when} was created"
+EVENT_EDITED = "Event at {when} was modified"
+EVENTS_LIST = "[{finished}] \"{movie}\" by {user} at {when} in {where}\n"
+EVENT_MOVIE_NOT_CHOOSEN = "You should choose a film first using /movie <imdb_id>"
+EVENT_MOVIE_NOT_SET = "<None>"
+EVENT_USER_HAD_EVENT = "User {user} have already finished event, so he can't choose twice. See /next or /list"
+NO_EVENTS = "No events"
UNDEFINED_ERROR = "Exception: something unexpected happened. Check the logs."
diff --git a/utils.py b/utils.py
index 93ad3ed..2acdb01 100644
--- a/utils.py
+++ b/utils.py
@@ -10,7 +10,7 @@
# <http://creativecommons.org/publicdomain/zero/1.0/>
from telegram.ext import ContextTypes
-
+from collections import deque
def context_init(context: ContextTypes.DEFAULT_TYPE):
"""
@@ -23,6 +23,9 @@ def context_init(context: ContextTypes.DEFAULT_TYPE):
if "movies" not in context.chat_data:
context.chat_data["movies"]: list[dict] = []
+ if "events" not in context.chat_data:
+ context.chat_data["events"]: list[dict] = []
+
return context
@@ -32,3 +35,10 @@ def normalize_username(username: str):
def create_users_string(users: list[str]) -> str:
return "`" + ", ".join(users) + "`"
+
+
+def choose_next_user(users: list[dict]) -> list[dict]:
+ users = deque(users)
+ users.rotate(-1) # -1 moves list to left by 1 element
+
+ return list(users)