diff --git a/.gitignore b/.gitignore index 4a2e9dd..70dffab 100644 --- a/.gitignore +++ b/.gitignore @@ -157,3 +157,6 @@ cython_debug/ # Debugging files debug.py + +# Schedule .ics files +/files/schedules/ diff --git a/alembic/versions/08d21b2d1a0a_deadlines.py b/alembic/versions/08d21b2d1a0a_deadlines.py deleted file mode 100644 index 25147cf..0000000 --- a/alembic/versions/08d21b2d1a0a_deadlines.py +++ /dev/null @@ -1,39 +0,0 @@ -"""Deadlines - -Revision ID: 08d21b2d1a0a -Revises: 3962636f3a3d -Create Date: 2022-08-12 23:44:13.947011 - -""" -import sqlalchemy as sa - -from alembic import op - -# revision identifiers, used by Alembic. -revision = "08d21b2d1a0a" -down_revision = "3962636f3a3d" -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.create_table( - "deadlines", - sa.Column("deadline_id", sa.Integer(), nullable=False), - sa.Column("course_id", sa.Integer(), nullable=True), - sa.Column("name", sa.Text(), nullable=False), - sa.Column("deadline", sa.DateTime(timezone=True), nullable=False), - sa.ForeignKeyConstraint( - ["course_id"], - ["ufora_courses.course_id"], - ), - sa.PrimaryKeyConstraint("deadline_id"), - ) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table("deadlines") - # ### end Alembic commands ### diff --git a/alembic/versions/0d03c226d881_initial_currency_models.py b/alembic/versions/0d03c226d881_initial_currency_models.py deleted file mode 100644 index feec2c1..0000000 --- a/alembic/versions/0d03c226d881_initial_currency_models.py +++ /dev/null @@ -1,56 +0,0 @@ -"""Initial currency models - -Revision ID: 0d03c226d881 -Revises: b2d511552a1f -Create Date: 2022-06-30 20:02:27.284759 - -""" -import sqlalchemy as sa - -from alembic import op - -# revision identifiers, used by Alembic. -revision = "0d03c226d881" -down_revision = "b2d511552a1f" -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.create_table("users", sa.Column("user_id", sa.BigInteger(), nullable=False), sa.PrimaryKeyConstraint("user_id")) - op.create_table( - "bank", - sa.Column("bank_id", sa.Integer(), nullable=False), - sa.Column("user_id", sa.BigInteger(), nullable=True), - sa.Column("dinks", sa.BigInteger(), server_default="0", nullable=False), - sa.Column("interest_level", sa.Integer(), server_default="1", nullable=False), - sa.Column("capacity_level", sa.Integer(), server_default="1", nullable=False), - sa.Column("rob_level", sa.Integer(), server_default="1", nullable=False), - sa.ForeignKeyConstraint( - ["user_id"], - ["users.user_id"], - ), - sa.PrimaryKeyConstraint("bank_id"), - ) - op.create_table( - "nightly_data", - sa.Column("nightly_id", sa.Integer(), nullable=False), - sa.Column("user_id", sa.BigInteger(), nullable=True), - sa.Column("last_nightly", sa.Date, nullable=True), - sa.Column("count", sa.Integer(), server_default="0", nullable=False), - sa.ForeignKeyConstraint( - ["user_id"], - ["users.user_id"], - ), - sa.PrimaryKeyConstraint("nightly_id"), - ) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table("nightly_data") - op.drop_table("bank") - op.drop_table("users") - # ### end Alembic commands ### diff --git a/alembic/versions/1716bfecf684_add_birthdays.py b/alembic/versions/1716bfecf684_add_birthdays.py deleted file mode 100644 index 5f01615..0000000 --- a/alembic/versions/1716bfecf684_add_birthdays.py +++ /dev/null @@ -1,38 +0,0 @@ -"""Add birthdays - -Revision ID: 1716bfecf684 -Revises: 581ae6511b98 -Create Date: 2022-07-19 21:46:42.796349 - -""" -import sqlalchemy as sa - -from alembic import op - -# revision identifiers, used by Alembic. -revision = "1716bfecf684" -down_revision = "581ae6511b98" -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.create_table( - "birthdays", - sa.Column("birthday_id", sa.Integer(), nullable=False), - sa.Column("user_id", sa.BigInteger(), nullable=True), - sa.Column("birthday", sa.Date, nullable=False), - sa.ForeignKeyConstraint( - ["user_id"], - ["users.user_id"], - ), - sa.PrimaryKeyConstraint("birthday_id"), - ) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table("birthdays") - # ### end Alembic commands ### diff --git a/alembic/versions/346b408c362a_create_tasks.py b/alembic/versions/346b408c362a_create_tasks.py deleted file mode 100644 index f6efeeb..0000000 --- a/alembic/versions/346b408c362a_create_tasks.py +++ /dev/null @@ -1,36 +0,0 @@ -"""Create tasks - -Revision ID: 346b408c362a -Revises: 1716bfecf684 -Create Date: 2022-07-23 19:41:07.029482 - -""" -import sqlalchemy as sa - -from alembic import op - -# revision identifiers, used by Alembic. -revision = "346b408c362a" -down_revision = "1716bfecf684" -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.create_table( - "tasks", - sa.Column("task_id", sa.Integer(), nullable=False), - sa.Column("task", sa.Enum("BIRTHDAYS", "UFORA_ANNOUNCEMENTS", name="tasktype"), nullable=False), - sa.Column("previous_run", sa.DateTime(), nullable=True), - sa.PrimaryKeyConstraint("task_id"), - sa.UniqueConstraint("task"), - ) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table("tasks") - sa.Enum("BIRTHDAYS", "UFORA_ANNOUNCEMENTS", name="tasktype").drop(op.get_bind()) - # ### end Alembic commands ### diff --git a/alembic/versions/36300b558ef1_meme_templates.py b/alembic/versions/36300b558ef1_meme_templates.py deleted file mode 100644 index 275133a..0000000 --- a/alembic/versions/36300b558ef1_meme_templates.py +++ /dev/null @@ -1,37 +0,0 @@ -"""Meme templates - -Revision ID: 36300b558ef1 -Revises: 08d21b2d1a0a -Create Date: 2022-08-25 01:34:22.845955 - -""" -import sqlalchemy as sa - -from alembic import op - -# revision identifiers, used by Alembic. -revision = "36300b558ef1" -down_revision = "08d21b2d1a0a" -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.create_table( - "meme", - sa.Column("meme_id", sa.Integer(), nullable=False), - sa.Column("name", sa.Text(), nullable=False), - sa.Column("template_id", sa.Integer(), nullable=False), - sa.Column("field_count", sa.Integer(), nullable=False), - sa.PrimaryKeyConstraint("meme_id"), - sa.UniqueConstraint("name"), - sa.UniqueConstraint("template_id"), - ) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table("meme") - # ### end Alembic commands ### diff --git a/alembic/versions/38b7c29f10ee_wordle.py b/alembic/versions/38b7c29f10ee_wordle.py deleted file mode 100644 index 8fe53b2..0000000 --- a/alembic/versions/38b7c29f10ee_wordle.py +++ /dev/null @@ -1,63 +0,0 @@ -"""Wordle - -Revision ID: 38b7c29f10ee -Revises: 36300b558ef1 -Create Date: 2022-08-29 20:21:02.413631 - -""" -import sqlalchemy as sa - -from alembic import op - -# revision identifiers, used by Alembic. -revision = "38b7c29f10ee" -down_revision = "36300b558ef1" -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.create_table( - "wordle_word", - sa.Column("word_id", sa.Integer(), nullable=False), - sa.Column("word", sa.Text(), nullable=False), - sa.Column("day", sa.Date(), nullable=False), - sa.PrimaryKeyConstraint("word_id"), - sa.UniqueConstraint("day"), - ) - op.create_table( - "wordle_guesses", - sa.Column("wordle_guess_id", sa.Integer(), nullable=False), - sa.Column("user_id", sa.BigInteger(), nullable=True), - sa.Column("guess", sa.Text(), nullable=False), - sa.ForeignKeyConstraint( - ["user_id"], - ["users.user_id"], - ), - sa.PrimaryKeyConstraint("wordle_guess_id"), - ) - op.create_table( - "wordle_stats", - sa.Column("wordle_stats_id", sa.Integer(), nullable=False), - sa.Column("user_id", sa.BigInteger(), nullable=True), - sa.Column("last_win", sa.Date(), nullable=True), - sa.Column("games", sa.Integer(), server_default="0", nullable=False), - sa.Column("wins", sa.Integer(), server_default="0", nullable=False), - sa.Column("current_streak", sa.Integer(), server_default="0", nullable=False), - sa.Column("highest_streak", sa.Integer(), server_default="0", nullable=False), - sa.ForeignKeyConstraint( - ["user_id"], - ["users.user_id"], - ), - sa.PrimaryKeyConstraint("wordle_stats_id"), - ) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table("wordle_stats") - op.drop_table("wordle_guesses") - op.drop_table("wordle_word") - # ### end Alembic commands ### diff --git a/alembic/versions/3962636f3a3d_add_custom_links.py b/alembic/versions/3962636f3a3d_add_custom_links.py deleted file mode 100644 index ef4f13e..0000000 --- a/alembic/versions/3962636f3a3d_add_custom_links.py +++ /dev/null @@ -1,35 +0,0 @@ -"""Add custom links - -Revision ID: 3962636f3a3d -Revises: 346b408c362a -Create Date: 2022-08-10 00:54:05.668255 - -""" -import sqlalchemy as sa - -from alembic import op - -# revision identifiers, used by Alembic. -revision = "3962636f3a3d" -down_revision = "346b408c362a" -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.create_table( - "links", - sa.Column("link_id", sa.Integer(), nullable=False), - sa.Column("name", sa.Text(), nullable=False), - sa.Column("url", sa.Text(), nullable=False), - sa.PrimaryKeyConstraint("link_id"), - sa.UniqueConstraint("name"), - ) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table("links") - # ### end Alembic commands ### diff --git a/alembic/versions/4ec79dd5b191_initial_migration.py b/alembic/versions/4ec79dd5b191_initial_migration.py deleted file mode 100644 index 2bf8362..0000000 --- a/alembic/versions/4ec79dd5b191_initial_migration.py +++ /dev/null @@ -1,63 +0,0 @@ -"""Initial migration - -Revision ID: 4ec79dd5b191 -Revises: -Create Date: 2022-06-19 00:31:58.384360 - -""" -import sqlalchemy as sa - -from alembic import op - -# revision identifiers, used by Alembic. -revision = "4ec79dd5b191" -down_revision = None -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.create_table( - "ufora_courses", - sa.Column("course_id", sa.Integer(), nullable=False), - sa.Column("name", sa.Text(), nullable=False), - sa.Column("code", sa.Text(), nullable=False), - sa.Column("year", sa.Integer(), nullable=False), - sa.Column("log_announcements", sa.Boolean(), nullable=False), - sa.PrimaryKeyConstraint("course_id"), - sa.UniqueConstraint("code"), - sa.UniqueConstraint("name"), - ) - op.create_table( - "ufora_announcements", - sa.Column("announcement_id", sa.Integer(), nullable=False), - sa.Column("course_id", sa.Integer(), nullable=True), - sa.Column("publication_date", sa.Date, nullable=True), - sa.ForeignKeyConstraint( - ["course_id"], - ["ufora_courses.course_id"], - ), - sa.PrimaryKeyConstraint("announcement_id"), - ) - op.create_table( - "ufora_course_aliases", - sa.Column("alias_id", sa.Integer(), nullable=False), - sa.Column("alias", sa.Text(), nullable=False), - sa.Column("course_id", sa.Integer(), nullable=True), - sa.ForeignKeyConstraint( - ["course_id"], - ["ufora_courses.course_id"], - ), - sa.PrimaryKeyConstraint("alias_id"), - sa.UniqueConstraint("alias"), - ) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table("ufora_course_aliases") - op.drop_table("ufora_announcements") - op.drop_table("ufora_courses") - # ### end Alembic commands ### diff --git a/alembic/versions/581ae6511b98_add_dad_jokes.py b/alembic/versions/581ae6511b98_add_dad_jokes.py deleted file mode 100644 index b3bed89..0000000 --- a/alembic/versions/581ae6511b98_add_dad_jokes.py +++ /dev/null @@ -1,33 +0,0 @@ -"""Add dad jokes - -Revision ID: 581ae6511b98 -Revises: 632b69cdadde -Create Date: 2022-07-15 23:37:08.147611 - -""" -import sqlalchemy as sa - -from alembic import op - -# revision identifiers, used by Alembic. -revision = "581ae6511b98" -down_revision = "632b69cdadde" -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.create_table( - "dad_jokes", - sa.Column("dad_joke_id", sa.Integer(), nullable=False), - sa.Column("joke", sa.Text(), nullable=False), - sa.PrimaryKeyConstraint("dad_joke_id"), - ) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table("dad_jokes") - # ### end Alembic commands ### diff --git a/alembic/versions/632b69cdadde_add_missing_defaults.py b/alembic/versions/632b69cdadde_add_missing_defaults.py deleted file mode 100644 index 0f326a7..0000000 --- a/alembic/versions/632b69cdadde_add_missing_defaults.py +++ /dev/null @@ -1,28 +0,0 @@ -"""Add missing defaults - -Revision ID: 632b69cdadde -Revises: 8c4ad0a1d699 -Create Date: 2022-07-03 16:29:07.387011 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '632b69cdadde' -down_revision = '8c4ad0a1d699' -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - pass - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - pass - # ### end Alembic commands ### diff --git a/alembic/versions/8c4ad0a1d699_move_dinks_over_to_bank_add_invested_.py b/alembic/versions/8c4ad0a1d699_move_dinks_over_to_bank_add_invested_.py deleted file mode 100644 index ad56f5e..0000000 --- a/alembic/versions/8c4ad0a1d699_move_dinks_over_to_bank_add_invested_.py +++ /dev/null @@ -1,32 +0,0 @@ -"""Move dinks over to Bank & add invested amount - -Revision ID: 8c4ad0a1d699 -Revises: 0d03c226d881 -Create Date: 2022-07-03 16:27:11.330746 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '8c4ad0a1d699' -down_revision = '0d03c226d881' -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - with op.batch_alter_table('bank', schema=None) as batch_op: - batch_op.add_column(sa.Column('invested', sa.BigInteger(), server_default='0', nullable=False)) - - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - with op.batch_alter_table('bank', schema=None) as batch_op: - batch_op.drop_column('invested') - - # ### end Alembic commands ### diff --git a/alembic/versions/b2d511552a1f_add_custom_commands.py b/alembic/versions/b2d511552a1f_add_custom_commands.py deleted file mode 100644 index 83b004a..0000000 --- a/alembic/versions/b2d511552a1f_add_custom_commands.py +++ /dev/null @@ -1,57 +0,0 @@ -"""Add custom commands - -Revision ID: b2d511552a1f -Revises: 4ec79dd5b191 -Create Date: 2022-06-21 22:10:05.590846 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'b2d511552a1f' -down_revision = '4ec79dd5b191' -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('custom_commands', - sa.Column('command_id', sa.Integer(), nullable=False), - sa.Column('name', sa.Text(), nullable=False), - sa.Column('indexed_name', sa.Text(), nullable=False), - sa.Column('response', sa.Text(), nullable=False), - sa.PrimaryKeyConstraint('command_id'), - sa.UniqueConstraint('name') - ) - with op.batch_alter_table('custom_commands', schema=None) as batch_op: - batch_op.create_index(batch_op.f('ix_custom_commands_indexed_name'), ['indexed_name'], unique=False) - - op.create_table('custom_command_aliases', - sa.Column('alias_id', sa.Integer(), nullable=False), - sa.Column('alias', sa.Text(), nullable=False), - sa.Column('indexed_alias', sa.Text(), nullable=False), - sa.Column('command_id', sa.Integer(), nullable=True), - sa.ForeignKeyConstraint(['command_id'], ['custom_commands.command_id'], ), - sa.PrimaryKeyConstraint('alias_id'), - sa.UniqueConstraint('alias') - ) - with op.batch_alter_table('custom_command_aliases', schema=None) as batch_op: - batch_op.create_index(batch_op.f('ix_custom_command_aliases_indexed_alias'), ['indexed_alias'], unique=False) - - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - with op.batch_alter_table('custom_command_aliases', schema=None) as batch_op: - batch_op.drop_index(batch_op.f('ix_custom_command_aliases_indexed_alias')) - - op.drop_table('custom_command_aliases') - with op.batch_alter_table('custom_commands', schema=None) as batch_op: - batch_op.drop_index(batch_op.f('ix_custom_commands_indexed_name')) - - op.drop_table('custom_commands') - # ### end Alembic commands ### diff --git a/alembic/versions/ea9811f060aa_initial_migration.py b/alembic/versions/ea9811f060aa_initial_migration.py new file mode 100644 index 0000000..dbf5580 --- /dev/null +++ b/alembic/versions/ea9811f060aa_initial_migration.py @@ -0,0 +1,244 @@ +"""Initial migration + +Revision ID: ea9811f060aa +Revises: +Create Date: 2022-09-17 17:31:20.593318 + +""" +import sqlalchemy as sa + +from alembic import op + +# revision identifiers, used by Alembic. +revision = "ea9811f060aa" +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "custom_commands", + sa.Column("command_id", sa.Integer(), nullable=False), + sa.Column("name", sa.Text(), nullable=False), + sa.Column("indexed_name", sa.Text(), nullable=False), + sa.Column("response", sa.Text(), nullable=False), + sa.PrimaryKeyConstraint("command_id"), + sa.UniqueConstraint("name"), + ) + with op.batch_alter_table("custom_commands", schema=None) as batch_op: + batch_op.create_index(batch_op.f("ix_custom_commands_indexed_name"), ["indexed_name"], unique=False) + + op.create_table( + "dad_jokes", + sa.Column("dad_joke_id", sa.Integer(), nullable=False), + sa.Column("joke", sa.Text(), nullable=False), + sa.PrimaryKeyConstraint("dad_joke_id"), + ) + op.create_table( + "links", + sa.Column("link_id", sa.Integer(), nullable=False), + sa.Column("name", sa.Text(), nullable=False), + sa.Column("url", sa.Text(), nullable=False), + sa.PrimaryKeyConstraint("link_id"), + sa.UniqueConstraint("name"), + ) + op.create_table( + "meme", + sa.Column("meme_id", sa.Integer(), nullable=False), + sa.Column("name", sa.Text(), nullable=False), + sa.Column("template_id", sa.Integer(), nullable=False), + sa.Column("field_count", sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint("meme_id"), + sa.UniqueConstraint("name"), + sa.UniqueConstraint("template_id"), + ) + op.create_table( + "tasks", + sa.Column("task_id", sa.Integer(), nullable=False), + sa.Column("task", sa.Enum("BIRTHDAYS", "SCHEDULES", "UFORA_ANNOUNCEMENTS", name="tasktype"), nullable=False), + sa.Column("previous_run", sa.DateTime(timezone=True), nullable=True), + sa.PrimaryKeyConstraint("task_id"), + sa.UniqueConstraint("task"), + ) + op.create_table( + "ufora_courses", + sa.Column("course_id", sa.Integer(), nullable=False), + sa.Column("name", sa.Text(), nullable=False), + sa.Column("code", sa.Text(), nullable=False), + sa.Column("year", sa.Integer(), nullable=False), + sa.Column("compulsory", sa.Boolean(), server_default="1", nullable=False), + sa.Column("role_id", sa.Integer(), nullable=True), + sa.Column("log_announcements", sa.Boolean(), server_default="0", nullable=False), + sa.PrimaryKeyConstraint("course_id"), + sa.UniqueConstraint("code"), + sa.UniqueConstraint("name"), + ) + op.create_table("users", sa.Column("user_id", sa.BigInteger(), nullable=False), sa.PrimaryKeyConstraint("user_id")) + op.create_table( + "wordle_word", + sa.Column("word_id", sa.Integer(), nullable=False), + sa.Column("word", sa.Text(), nullable=False), + sa.Column("day", sa.Date(), nullable=False), + sa.PrimaryKeyConstraint("word_id"), + sa.UniqueConstraint("day"), + ) + op.create_table( + "bank", + sa.Column("bank_id", sa.Integer(), nullable=False), + sa.Column("user_id", sa.BigInteger(), nullable=True), + sa.Column("dinks", sa.BigInteger(), server_default="0", nullable=False), + sa.Column("invested", sa.BigInteger(), server_default="0", nullable=False), + sa.Column("interest_level", sa.Integer(), server_default="1", nullable=False), + sa.Column("capacity_level", sa.Integer(), server_default="1", nullable=False), + sa.Column("rob_level", sa.Integer(), server_default="1", nullable=False), + sa.ForeignKeyConstraint( + ["user_id"], + ["users.user_id"], + ), + sa.PrimaryKeyConstraint("bank_id"), + ) + op.create_table( + "birthdays", + sa.Column("birthday_id", sa.Integer(), nullable=False), + sa.Column("user_id", sa.BigInteger(), nullable=True), + sa.Column("birthday", sa.Date(), nullable=False), + sa.ForeignKeyConstraint( + ["user_id"], + ["users.user_id"], + ), + sa.PrimaryKeyConstraint("birthday_id"), + ) + op.create_table( + "bookmarks", + sa.Column("bookmark_id", sa.Integer(), nullable=False), + sa.Column("label", sa.Text(), nullable=False), + sa.Column("jump_url", sa.Text(), nullable=False), + sa.Column("user_id", sa.BigInteger(), nullable=True), + sa.ForeignKeyConstraint( + ["user_id"], + ["users.user_id"], + ), + sa.PrimaryKeyConstraint("bookmark_id"), + sa.UniqueConstraint("user_id", "label"), + ) + op.create_table( + "custom_command_aliases", + sa.Column("alias_id", sa.Integer(), nullable=False), + sa.Column("alias", sa.Text(), nullable=False), + sa.Column("indexed_alias", sa.Text(), nullable=False), + sa.Column("command_id", sa.Integer(), nullable=True), + sa.ForeignKeyConstraint( + ["command_id"], + ["custom_commands.command_id"], + ), + sa.PrimaryKeyConstraint("alias_id"), + sa.UniqueConstraint("alias"), + ) + with op.batch_alter_table("custom_command_aliases", schema=None) as batch_op: + batch_op.create_index(batch_op.f("ix_custom_command_aliases_indexed_alias"), ["indexed_alias"], unique=False) + + op.create_table( + "deadlines", + sa.Column("deadline_id", sa.Integer(), nullable=False), + sa.Column("course_id", sa.Integer(), nullable=True), + sa.Column("name", sa.Text(), nullable=False), + sa.Column("deadline", sa.DateTime(timezone=True), nullable=False), + sa.ForeignKeyConstraint( + ["course_id"], + ["ufora_courses.course_id"], + ), + sa.PrimaryKeyConstraint("deadline_id"), + ) + op.create_table( + "nightly_data", + sa.Column("nightly_id", sa.Integer(), nullable=False), + sa.Column("user_id", sa.BigInteger(), nullable=True), + sa.Column("last_nightly", sa.Date(), nullable=True), + sa.Column("count", sa.Integer(), server_default="0", nullable=False), + sa.ForeignKeyConstraint( + ["user_id"], + ["users.user_id"], + ), + sa.PrimaryKeyConstraint("nightly_id"), + ) + op.create_table( + "ufora_announcements", + sa.Column("announcement_id", sa.Integer(), nullable=False), + sa.Column("course_id", sa.Integer(), nullable=True), + sa.Column("publication_date", sa.Date(), nullable=True), + sa.ForeignKeyConstraint( + ["course_id"], + ["ufora_courses.course_id"], + ), + sa.PrimaryKeyConstraint("announcement_id"), + ) + op.create_table( + "ufora_course_aliases", + sa.Column("alias_id", sa.Integer(), nullable=False), + sa.Column("alias", sa.Text(), nullable=False), + sa.Column("course_id", sa.Integer(), nullable=True), + sa.ForeignKeyConstraint( + ["course_id"], + ["ufora_courses.course_id"], + ), + sa.PrimaryKeyConstraint("alias_id"), + sa.UniqueConstraint("alias"), + ) + op.create_table( + "wordle_guesses", + sa.Column("wordle_guess_id", sa.Integer(), nullable=False), + sa.Column("user_id", sa.BigInteger(), nullable=True), + sa.Column("guess", sa.Text(), nullable=False), + sa.ForeignKeyConstraint( + ["user_id"], + ["users.user_id"], + ), + sa.PrimaryKeyConstraint("wordle_guess_id"), + ) + op.create_table( + "wordle_stats", + sa.Column("wordle_stats_id", sa.Integer(), nullable=False), + sa.Column("user_id", sa.BigInteger(), nullable=True), + sa.Column("last_win", sa.Date(), nullable=True), + sa.Column("games", sa.Integer(), server_default="0", nullable=False), + sa.Column("wins", sa.Integer(), server_default="0", nullable=False), + sa.Column("current_streak", sa.Integer(), server_default="0", nullable=False), + sa.Column("highest_streak", sa.Integer(), server_default="0", nullable=False), + sa.ForeignKeyConstraint( + ["user_id"], + ["users.user_id"], + ), + sa.PrimaryKeyConstraint("wordle_stats_id"), + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table("wordle_stats") + op.drop_table("wordle_guesses") + op.drop_table("ufora_course_aliases") + op.drop_table("ufora_announcements") + op.drop_table("nightly_data") + op.drop_table("deadlines") + with op.batch_alter_table("custom_command_aliases", schema=None) as batch_op: + batch_op.drop_index(batch_op.f("ix_custom_command_aliases_indexed_alias")) + + op.drop_table("custom_command_aliases") + op.drop_table("bookmarks") + op.drop_table("birthdays") + op.drop_table("bank") + op.drop_table("wordle_word") + op.drop_table("users") + op.drop_table("ufora_courses") + op.drop_table("tasks") + op.drop_table("meme") + op.drop_table("links") + op.drop_table("dad_jokes") + with op.batch_alter_table("custom_commands", schema=None) as batch_op: + batch_op.drop_index(batch_op.f("ix_custom_commands_indexed_name")) + + op.drop_table("custom_commands") + # ### end Alembic commands ### diff --git a/alembic/versions/f5da771a155d_bookmarks.py b/alembic/versions/f5da771a155d_bookmarks.py deleted file mode 100644 index 154b907..0000000 --- a/alembic/versions/f5da771a155d_bookmarks.py +++ /dev/null @@ -1,40 +0,0 @@ -"""Bookmarks - -Revision ID: f5da771a155d -Revises: 38b7c29f10ee -Create Date: 2022-08-30 01:08:54.323883 - -""" -import sqlalchemy as sa - -from alembic import op - -# revision identifiers, used by Alembic. -revision = "f5da771a155d" -down_revision = "38b7c29f10ee" -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.create_table( - "bookmarks", - sa.Column("bookmark_id", sa.Integer(), nullable=False), - sa.Column("label", sa.Text(), nullable=False), - sa.Column("jump_url", sa.Text(), nullable=False), - sa.Column("user_id", sa.BigInteger(), nullable=True), - sa.ForeignKeyConstraint( - ["user_id"], - ["users.user_id"], - ), - sa.PrimaryKeyConstraint("bookmark_id"), - sa.UniqueConstraint("user_id", "label"), - ) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table("bookmarks") - # ### end Alembic commands ### diff --git a/database/enums.py b/database/enums.py index 3f75130..e2cf565 100644 --- a/database/enums.py +++ b/database/enums.py @@ -10,4 +10,5 @@ class TaskType(enum.IntEnum): """Enum for the different types of tasks""" BIRTHDAYS = enum.auto() + SCHEDULES = enum.auto() UFORA_ANNOUNCEMENTS = enum.auto() diff --git a/database/schemas.py b/database/schemas.py index f8fa018..945781d 100644 --- a/database/schemas.py +++ b/database/schemas.py @@ -197,6 +197,8 @@ class UforaCourse(Base): name: str = Column(Text, nullable=False, unique=True) code: str = Column(Text, nullable=False, unique=True) year: int = Column(Integer, nullable=False) + compulsory: bool = Column(Boolean, server_default="1", nullable=False) + role_id: Optional[int] = Column(Integer, nullable=True, unique=False) log_announcements: bool = Column(Boolean, server_default="0", nullable=False) announcements: list[UforaAnnouncement] = relationship( diff --git a/database/scripts/__init__.py b/database/scripts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/didier/cogs/tasks.py b/didier/cogs/tasks.py index 64f5501..42a7d0b 100644 --- a/didier/cogs/tasks.py +++ b/didier/cogs/tasks.py @@ -41,6 +41,7 @@ class Tasks(commands.Cog): self._tasks = { "birthdays": self.check_birthdays, + "schedules": self.pull_schedules, "ufora": self.pull_ufora_announcements, "remove_ufora": self.remove_old_ufora_announcements, "wordle": self.reset_wordle_word, @@ -59,6 +60,7 @@ class Tasks(commands.Cog): # Start other tasks self.reset_wordle_word.start() + self.pull_schedules.start() @overrides def cog_unload(self) -> None: @@ -110,6 +112,32 @@ class Tasks(commands.Cog): async def _before_check_birthdays(self): await self.client.wait_until_ready() + @tasks.loop(time=DAILY_RESET_TIME) + @timed_task(enums.TaskType.SCHEDULES) + async def pull_schedules(self, **kwargs): + """Task that pulls the schedules & saves the files locally + + Schedules are then parsed & cached in memory + """ + _ = kwargs + + for data in settings.SCHEDULE_DATA: + if data.schedule_url is None: + return + + async with self.client.http_session.get(data.schedule_url) as response: + # If a schedule couldn't be fetched, log it and move on + if response.status != 200: + await self.client.log_warning( + f"Unable to fetch schedule {data.name} (status {response.status}).", log_to_discord=False + ) + continue + + # Write the content to a file + content = await response.text() + with open(f"files/schedules/{data.name}.ics", "w+") as fp: + fp.write(content) + @tasks.loop(minutes=10) @timed_task(enums.TaskType.UFORA_ANNOUNCEMENTS) async def pull_ufora_announcements(self, **kwargs): @@ -166,3 +194,4 @@ async def setup(client: Didier): cog = Tasks(client) await client.add_cog(cog) await cog.reset_wordle_word() + await cog.pull_schedules() diff --git a/didier/data/constants.py b/didier/data/constants.py index cea951c..1387822 100644 --- a/didier/data/constants.py +++ b/didier/data/constants.py @@ -1,6 +1,6 @@ -# The year in which we were in 1Ba import settings +# The year in which we were in 1Ba FIRST_YEAR = 2019 # Year to use when adding the current year of our education # to find the academic year diff --git a/didier/didier.py b/didier/didier.py index 12bd5b4..4d4434e 100644 --- a/didier/didier.py +++ b/didier/didier.py @@ -1,5 +1,6 @@ import logging import os +import pathlib import discord from aiohttp import ClientSession @@ -59,6 +60,9 @@ class Didier(commands.Bot): This hook is called once the bot is initialised """ + # Create directories that are ignored on GitHub + self._create_ignored_directories() + # Load the Wordle dictionary self._load_wordle_words() @@ -67,19 +71,26 @@ class Didier(commands.Bot): async with self.postgres_session as session: await self.database_caches.initialize_caches(session) + # Create aiohttp session + self.http_session = ClientSession() + # Load extensions await self._load_initial_extensions() await self._load_directory_extensions("didier/cogs") - # Create aiohttp session - self.http_session = ClientSession() - # Configure channel to send errors to if settings.ERRORS_CHANNEL is not None: self.error_channel = self.get_channel(settings.ERRORS_CHANNEL) else: self.error_channel = self.get_user(self.owner_id) + def _create_ignored_directories(self): + """Create directories that store ignored data""" + ignored = ["files/schedules"] + + for directory in ignored: + pathlib.Path(directory).mkdir(exist_ok=True, parents=True) + async def _load_initial_extensions(self): """Load all extensions that should be loaded before the others""" for extension in self.initial_extensions: @@ -138,13 +149,27 @@ class Didier(commands.Bot): """Add an X to a message""" await message.add_reaction("❌") - async def log_error(self, message: str, log_to_discord: bool = True): - """Send an error message to the logs, and optionally the configured channel""" - logger.error(message) + async def _log(self, level: int, message: str, log_to_discord: bool = True): + """Log a message to the logging file, and optionally to the configured channel""" + methods = { + logging.ERROR: logger.error, + logging.WARNING: logger.warning, + } + + methods.get(level, logger.error)(message) if log_to_discord: # TODO pretty embed + # different colours per level? await self.error_channel.send(message) + async def log_error(self, message: str, log_to_discord: bool = True): + """Log an error message""" + await self._log(logging.ERROR, message, log_to_discord) + + async def log_warning(self, message: str, log_to_discord: bool = True): + """Log a warning message""" + await self._log(logging.WARNING, message, log_to_discord) + async def on_ready(self): """Event triggered when the bot is ready""" print(settings.DISCORD_READY_MESSAGE) diff --git a/requirements.txt b/requirements.txt index 759cb54..f30a3c1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,6 +6,7 @@ discord.py==2.0.1 git+https://github.com/Rapptz/discord-ext-menus@8686b5d environs==9.5.0 feedparser==6.0.10 +ics==0.7.2 markdownify==0.11.2 overrides==6.1.0 pydantic==1.9.1 diff --git a/settings.py b/settings.py index 7698cc9..ad72779 100644 --- a/settings.py +++ b/settings.py @@ -1,3 +1,4 @@ +from dataclasses import dataclass from typing import Optional from environs import Env @@ -22,10 +23,13 @@ __all__ = [ "DISCORD_BOOS_REACT", "DISCORD_CUSTOM_COMMAND_PREFIX", "UFORA_ANNOUNCEMENTS_CHANNEL", + "BA3_ROLE", "UFORA_RSS_TOKEN", "URBAN_DICTIONARY_TOKEN", "IMGFLIP_NAME", "IMGFLIP_PASSWORD", + "BA3_SCHEDULE_URL", + "SCHEDULE_DATA", ] @@ -35,6 +39,7 @@ TESTING: bool = env.bool("TESTING", False) LOGFILE: str = env.str("LOGFILE", "didier.log") SEMESTER: int = env.int("SEMESTER", 2) YEAR: int = env.int("YEAR", 3) +MENU_TIMEOUT: int = env.int("MENU_TIMEOUT", 30) """Database""" # PostgreSQL @@ -56,11 +61,29 @@ BIRTHDAY_ANNOUNCEMENT_CHANNEL: Optional[int] = env.int("BIRTHDAY_ANNOUNCEMENT_CH ERRORS_CHANNEL: Optional[int] = env.int("ERRORS_CHANNEL", None) UFORA_ANNOUNCEMENTS_CHANNEL: Optional[int] = env.int("UFORA_ANNOUNCEMENTS_CHANNEL", None) -""""General config""" -MENU_TIMEOUT: int = env.int("MENU_TIMEOUT", 30) +"""Discord Role ID's""" +BA3_ROLE: Optional[int] = env.int("BA3_ROLE", 891743208248324196) """API Keys""" UFORA_RSS_TOKEN: Optional[str] = env.str("UFORA_RSS_TOKEN", None) URBAN_DICTIONARY_TOKEN: Optional[str] = env.str("URBAN_DICTIONARY_TOKEN", None) IMGFLIP_NAME: Optional[str] = env.str("IMGFLIP_NAME", None) IMGFLIP_PASSWORD: Optional[str] = env.str("IMGFLIP_PASSWORD", None) + +"""Schedule URLs""" +BA3_SCHEDULE_URL: Optional[str] = env.str("BA3_SCHEDULE_URL", None) + + +"""Computed properties""" + + +@dataclass +class ScheduleInfo: + """Dataclass to hold and combine some information about schedule-related settings""" + + role_id: Optional[int] + schedule_url: Optional[str] + name: Optional[str] = None + + +SCHEDULE_DATA = [ScheduleInfo(name="ba3", role_id=BA3_ROLE, schedule_url=BA3_SCHEDULE_URL)]