├── discord ├── __init__.py ├── models │ └── .gitkeep ├── web │ ├── views │ │ ├── .gitkeep │ │ └── login.py │ ├── template │ │ └── .gitkeep │ └── main.py ├── alembic │ ├── README │ └── script.py.mako ├── TODO.md ├── settings.py ├── pyproject.toml └── database.py ├── tracker ├── backend │ ├── README.md │ ├── .python-version │ ├── coreproject_tracker │ │ ├── __init__.py │ │ ├── constants │ │ │ ├── udp.py │ │ │ ├── peers.py │ │ │ ├── ttl.py │ │ │ ├── websocket.py │ │ │ ├── redis.py │ │ │ ├── interval.py │ │ │ └── __init__.py │ │ ├── exceptions │ │ │ ├── redis.py │ │ │ └── __init__.py │ │ ├── datastructures │ │ │ ├── mutable │ │ │ │ ├── __init__.py │ │ │ │ └── box.py │ │ │ ├── immutable │ │ │ │ ├── __init__.py │ │ │ │ ├── http.py │ │ │ │ ├── redis.py │ │ │ │ └── udp.py │ │ │ └── __init__.py │ │ ├── transaction │ │ │ ├── __init__.py │ │ │ └── rollback.py │ │ ├── singletons │ │ │ └── __init__.py │ │ ├── enums │ │ │ ├── ip.py │ │ │ ├── redis.py │ │ │ ├── actions.py │ │ │ ├── __init__.py │ │ │ └── enum.py │ │ ├── servers │ │ │ └── __init__.py │ │ ├── converters │ │ │ ├── numbers.py │ │ │ ├── url.py │ │ │ ├── bytes.py │ │ │ ├── __init__.py │ │ │ └── ip.py │ │ ├── functions │ │ │ ├── convertion.py │ │ │ ├── dictionary.py │ │ │ ├── array.py │ │ │ ├── bytes.py │ │ │ ├── __init__.py │ │ │ └── events.py │ │ ├── envs │ │ │ ├── __init__.py │ │ │ ├── redis.py │ │ │ └── workers.py │ │ ├── validators │ │ │ ├── peer.py │ │ │ ├── __init__.py │ │ │ ├── ip.py │ │ │ ├── length.py │ │ │ ├── port.py │ │ │ └── connection.py │ │ └── app.py │ ├── .gitattributes │ ├── tests_primitive │ │ ├── websocket_client.py │ │ └── udp_client.py │ ├── .dockerignore │ ├── pyproject.toml │ └── Dockerfile ├── frontend │ ├── .npmrc │ ├── .prettierrc │ ├── src │ │ ├── types │ │ │ ├── iframe.ts │ │ │ └── api.ts │ │ ├── app │ │ │ ├── favicon.ico │ │ │ └── layout.tsx │ │ ├── fonts │ │ │ └── Kokoro │ │ │ │ ├── Kokoro-Bold.woff2 │ │ │ │ ├── Kokoro-Italic.woff2 │ │ │ │ ├── Kokoro-Regular.woff2 │ │ │ │ ├── Kokoro-SemiBold.woff2 │ │ │ │ ├── Kokoro-BoldItalic.woff2 │ │ │ │ └── Kokoro-SemiBoldItalic.woff2 │ │ ├── lib │ │ │ └── utils.ts │ │ ├── functions │ │ │ └── bencode.ts │ │ ├── components │ │ │ └── theme-provider.tsx │ │ ├── constants │ │ │ └── url.ts │ │ └── hooks │ │ │ ├── useBackendData.ts │ │ │ └── useHttpData.ts │ ├── .dockerignore │ ├── postcss.config.mjs │ ├── app.json │ ├── svgr.d.ts │ ├── eslint.config.mjs │ ├── components.json │ ├── next.config.ts │ ├── .gitignore │ ├── tsconfig.json │ ├── package.json │ └── README.md ├── deploy.sh ├── nginx │ ├── Dockerfile │ └── nginx.conf └── docker-compose.yml ├── backend ├── README.md ├── django_core │ ├── apps │ │ ├── anime │ │ │ ├── __init__.py │ │ │ ├── migrations │ │ │ │ ├── __init__.py │ │ │ │ ├── 0006_merge_20230326_1819.py │ │ │ │ ├── 0002_alter_animemodel_rating.py │ │ │ │ ├── 0008_rename_updated_animemodel_updated_at.py │ │ │ │ ├── 0009_animegenremodel_description.py │ │ │ │ ├── 0010_animemodel_is_locked.py │ │ │ │ ├── 0013_animethememodel_description.py │ │ │ │ ├── 0020_alter_animegenremodel_type.py │ │ │ │ ├── 0005_alter_animeendingmodel_unique_together_and_more.py │ │ │ │ ├── 0025_alter_animemodel_comments_delete_animecommentmodel.py │ │ │ │ ├── 0019_animemodel_anime_name_name_japanese_idx.py │ │ │ │ ├── 0002_animemodel_staffs_alter_animemodel_rating.py │ │ │ │ ├── 0007_animemodel_created_at_alter_animemodel_updated.py │ │ │ │ ├── 0018_alter_animenamesynonymmodel_name_and_more.py │ │ │ │ ├── 0014_alter_animegenremodel_is_locked_and_more.py │ │ │ │ ├── 0015_animemodel_anime_anime_name_301f1e_gin_and_more.py │ │ │ │ ├── 0026_alter_animegenremodel_is_locked_and_more.py │ │ │ │ ├── 0023_alter_animegenremodel_is_locked_and_more.py │ │ │ │ ├── 0011_animegenremodel_created_at_animegenremodel_is_locked_and_more.py │ │ │ │ ├── 0012_animethememodel_created_at_animethememodel_is_locked_and_more.py │ │ │ │ ├── 0021_alter_animemodel_banner_background_color_and_more.py │ │ │ │ ├── 0022_alter_animegenremodel_created_at_and_more.py │ │ │ │ ├── 0004_alter_animeendingmodel_options_and_more.py │ │ │ │ ├── 0017_remove_animemodel_anime_name_name_japanese_idx_and_more.py │ │ │ │ └── 0016_remove_animemodel_anime_anime_name_301f1e_gin_and_more.py │ │ │ ├── apps.py │ │ │ ├── admin │ │ │ │ ├── anime_genre.py │ │ │ │ ├── anime_theme.py │ │ │ │ └── anime_openings_and_endings.py │ │ │ ├── forms.py │ │ │ ├── models │ │ │ │ ├── anime_genre.py │ │ │ │ ├── anime_theme.py │ │ │ │ └── anime_openings_and_endings.py │ │ │ └── signals.py │ │ ├── api │ │ │ ├── __init__.py │ │ │ ├── schemas │ │ │ │ ├── user │ │ │ │ │ ├── login.py │ │ │ │ │ ├── username_validity.py │ │ │ │ │ └── __init__.py │ │ │ │ ├── stats │ │ │ │ │ └── histogram.py │ │ │ │ ├── episodes │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── episode_timestamp.py │ │ │ │ ├── anime │ │ │ │ │ ├── anime_opening_and_ending.py │ │ │ │ │ ├── anime_genre.py │ │ │ │ │ └── anime_theme.py │ │ │ │ ├── trackers │ │ │ │ │ ├── mal.py │ │ │ │ │ ├── kitsu.py │ │ │ │ │ ├── anilist.py │ │ │ │ │ └── __init__.py │ │ │ │ ├── characters │ │ │ │ │ └── __init__.py │ │ │ │ ├── producers │ │ │ │ │ └── __init__.py │ │ │ │ └── staffs │ │ │ │ │ └── __init__.py │ │ │ ├── filters │ │ │ │ ├── genres.py │ │ │ │ ├── themes.py │ │ │ │ ├── openings_and_endings.py │ │ │ │ ├── studios.py │ │ │ │ ├── producers.py │ │ │ │ ├── characters.py │ │ │ │ ├── staffs.py │ │ │ │ └── anime.py │ │ │ ├── admin.py │ │ │ ├── http.py │ │ │ ├── permissions.py │ │ │ ├── views │ │ │ │ ├── user │ │ │ │ │ ├── logout.py │ │ │ │ │ ├── username_validity.py │ │ │ │ │ ├── signup.py │ │ │ │ │ ├── login.py │ │ │ │ │ └── __init__.py │ │ │ │ └── anime │ │ │ │ │ ├── anime_staff.py │ │ │ │ │ ├── anime_producer.py │ │ │ │ │ ├── anime_studio.py │ │ │ │ │ ├── anime_genre.py │ │ │ │ │ ├── anime_theme.py │ │ │ │ │ ├── anime_endings.py │ │ │ │ │ ├── anime_openings.py │ │ │ │ │ ├── genres.py │ │ │ │ │ ├── themes.py │ │ │ │ │ ├── endings.py │ │ │ │ │ ├── openings.py │ │ │ │ │ └── anime_character.py │ │ │ ├── parser.py │ │ │ ├── models.py │ │ │ ├── auth.py │ │ │ └── decorator.py │ │ ├── staffs │ │ │ ├── __init__.py │ │ │ ├── migrations │ │ │ │ ├── __init__.py │ │ │ │ ├── 0005_alter_staffmodel_is_locked.py │ │ │ │ ├── 0009_alter_staffmodel_is_locked.py │ │ │ │ ├── 0008_alter_staffmodel_is_locked.py │ │ │ │ ├── 0004_staffmodel_is_locked.py │ │ │ │ ├── 0002_alter_staffalternatenamemodel_options.py │ │ │ │ ├── 0007_alter_staffmodel_created_at.py │ │ │ │ ├── 0003_staffmodel_created_at_staffmodel_updated_at.py │ │ │ │ └── 0006_staffalternatenamemodel_staff_alternate_idx_and_more.py │ │ │ ├── apps.py │ │ │ └── admin.py │ │ ├── user │ │ │ ├── __init__.py │ │ │ ├── migrations │ │ │ │ ├── __init__.py │ │ │ │ ├── 0009_merge_20240317_1131.py │ │ │ │ ├── 0003_delete_token.py │ │ │ │ ├── 0005_remove_customuser_ip.py │ │ │ │ ├── 0002_alter_token_unique_together.py │ │ │ │ ├── 0008_remove_customuser_created_at.py │ │ │ │ ├── 0011_remove_customuser_created_at.py │ │ │ │ ├── 0006_customuser_created_at.py │ │ │ │ ├── 0004_alter_customuser_avatar.py │ │ │ │ ├── 0008_alter_customuser_username.py │ │ │ │ ├── 0010_customuser_created_at_alter_customuser_username.py │ │ │ │ └── 0007_alter_customuser_unique_together_and_more.py │ │ │ ├── tests.py │ │ │ ├── validators │ │ │ │ ├── __init__.py │ │ │ │ └── username.py │ │ │ ├── apps.py │ │ │ ├── urls.py │ │ │ ├── forms.py │ │ │ ├── backends.py │ │ │ └── managers.py │ │ ├── characters │ │ │ ├── __init__.py │ │ │ ├── migrations │ │ │ │ ├── __init__.py │ │ │ │ ├── 0004_alter_charactermodel_is_locked.py │ │ │ │ ├── 0007_alter_charactermodel_is_locked.py │ │ │ │ ├── 0006_alter_charactermodel_is_locked.py │ │ │ │ ├── 0003_charactermodel_is_locked.py │ │ │ │ ├── 0005_alter_charactermodel_created_at.py │ │ │ │ ├── 0002_charactermodel_created_at_charactermodel_updated_at.py │ │ │ │ └── 0001_initial.py │ │ │ ├── apps.py │ │ │ ├── admin.py │ │ │ └── models.py │ │ ├── comments │ │ │ ├── __init__.py │ │ │ ├── migrations │ │ │ │ ├── __init__.py │ │ │ │ ├── 0002_alter_commentmodel_options.py │ │ │ │ ├── 0006_commentmodel_deleted.py │ │ │ │ ├── 0007_alter_commentmodel_user.py │ │ │ │ ├── 0003_commentmodel_dislikes_commentmodel_likes.py │ │ │ │ ├── 0004_alter_commentmodel_dislikes_alter_commentmodel_likes.py │ │ │ │ └── 0005_remove_commentmodel_dislikes_and_more.py │ │ │ ├── tests.py │ │ │ ├── views.py │ │ │ ├── apps.py │ │ │ ├── admin.py │ │ │ └── models.py │ │ ├── episodes │ │ │ ├── __init__.py │ │ │ ├── migrations │ │ │ │ ├── __init__.py │ │ │ │ ├── 0007_remove_episodetimestampmodel_episode.py │ │ │ │ ├── 0002_episodemodel_episode_type.py │ │ │ │ ├── 0008_alter_episodecommentmodel_options.py │ │ │ │ ├── 0010_alter_episodemodel_episode_comments_and_more.py │ │ │ │ ├── 0004_episodemodel_created_at_episodemodel_updated_at.py │ │ │ │ ├── 0005_remove_episodecommentmodel_comment_added_and_more.py │ │ │ │ └── 0009_alter_episodecommentmodel_created_at_and_more.py │ │ │ ├── admin.py │ │ │ ├── apps.py │ │ │ ├── forms.py │ │ │ ├── admin │ │ │ │ ├── __init__.py │ │ │ │ └── episode_timestamp.py │ │ │ └── models │ │ │ │ └── episode_timestamp.py │ │ └── producers │ │ │ ├── __init__.py │ │ │ ├── migrations │ │ │ ├── __init__.py │ │ │ ├── 0003_remove_producermodel_default_title.py │ │ │ ├── 0006_alter_producermodel_is_locked.py │ │ │ ├── 0004_rename_japanese_title_producermodel_name_japanese.py │ │ │ ├── 0009_alter_producermodel_is_locked.py │ │ │ ├── 0008_alter_producermodel_is_locked.py │ │ │ ├── 0005_producermodel_is_locked.py │ │ │ ├── 0007_alter_producermodel_created_at.py │ │ │ ├── 0002_producermodel_created_at_producermodel_updated_at.py │ │ │ └── 0001_initial.py │ │ │ ├── apps.py │ │ │ ├── admin.py │ │ │ └── models.py │ ├── static_src │ │ └── .gitkeep │ ├── media │ │ ├── episode │ │ │ └── .gitignore │ │ ├── anime │ │ │ └── .gitignore │ │ ├── avatars │ │ │ └── .gitignore │ │ ├── banner │ │ │ └── .gitignore │ │ ├── cover │ │ │ └── .gitignore │ │ ├── ending │ │ │ └── .gitignore │ │ ├── opening │ │ │ └── .gitignore │ │ ├── staffs │ │ │ └── .gitignore │ │ ├── characters │ │ │ └── .gitignore │ │ └── episode_cover │ │ │ └── .gitignore │ ├── core │ │ ├── __init__.py │ │ ├── asgi.py │ │ ├── storages.py │ │ └── celery.py │ ├── templates │ │ ├── anime │ │ │ ├── episode │ │ │ │ └── index.html │ │ │ ├── explore │ │ │ │ └── index.html │ │ │ ├── index.html │ │ │ └── info │ │ │ │ └── index.html │ │ ├── admin │ │ │ └── base.html │ │ ├── upload │ │ │ └── index.html │ │ ├── home │ │ │ └── index.html │ │ ├── user │ │ │ ├── login │ │ │ │ └── index.html │ │ │ ├── register │ │ │ │ └── index.html │ │ │ ├── reset_password │ │ │ │ └── index.html │ │ │ ├── _layout.html │ │ │ └── user_does_not_exist.php │ │ └── errors │ │ │ └── base.html │ ├── mixins │ │ └── models │ │ │ ├── is_locked.py │ │ │ ├── updated_at.py │ │ │ └── created_at.py │ ├── utilities │ │ ├── rgb_to_hex.py │ │ └── format.py │ └── manage.py ├── .gitattributes └── .env.example ├── CREDITS.md ├── .github ├── workflows │ └── tracker.yml └── dependabot.yml └── FAQ.md /discord/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord/models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord/web/views/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord/web/views/login.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tracker/backend/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord/web/template/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/README.md: -------------------------------------------------------------------------------- 1 | # Django-Template 2 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/django_core/apps/staffs/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/django_core/apps/user/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/django_core/static_src/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tracker/backend/.python-version: -------------------------------------------------------------------------------- 1 | 3.13 2 | -------------------------------------------------------------------------------- /backend/django_core/apps/characters/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/django_core/apps/comments/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/django_core/apps/episodes/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/django_core/apps/producers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tracker/frontend/.npmrc: -------------------------------------------------------------------------------- 1 | legacy-peer-deps=true -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/django_core/apps/user/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/django_core/apps/characters/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/django_core/apps/comments/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/django_core/apps/episodes/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/django_core/apps/producers/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/django_core/apps/staffs/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/django_core/apps/user/tests.py: -------------------------------------------------------------------------------- 1 | # Create your tests here. 2 | -------------------------------------------------------------------------------- /backend/django_core/apps/comments/tests.py: -------------------------------------------------------------------------------- 1 | # Create your tests here. 2 | -------------------------------------------------------------------------------- /backend/django_core/apps/comments/views.py: -------------------------------------------------------------------------------- 1 | # Create your views here. 2 | -------------------------------------------------------------------------------- /backend/django_core/media/episode/.gitignore: -------------------------------------------------------------------------------- 1 | *.mkv 2 | *.mp4 3 | *.ts 4 | -------------------------------------------------------------------------------- /backend/django_core/apps/episodes/admin.py: -------------------------------------------------------------------------------- 1 | # Register your models here. 2 | -------------------------------------------------------------------------------- /discord/alembic/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration with an async dbapi. 2 | -------------------------------------------------------------------------------- /CREDITS.md: -------------------------------------------------------------------------------- 1 | # Backend Testing 2 | 3 | [HoriDesu](https://github.com/Praveensenpai) 4 | -------------------------------------------------------------------------------- /backend/.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /tracker/deploy.sh: -------------------------------------------------------------------------------- 1 | docker compose down && docker compose build --no-cache && docker compose up 2 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/constants/udp.py: -------------------------------------------------------------------------------- 1 | CONNECTION_ID = (0x417 << 32) | 0x27101980 2 | -------------------------------------------------------------------------------- /tracker/backend/.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /backend/django_core/core/__init__.py: -------------------------------------------------------------------------------- 1 | from .celery import app as celery_app 2 | 3 | __all__ = ("celery_app",) 4 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/exceptions/redis.py: -------------------------------------------------------------------------------- 1 | class RedisNotInitialized(Exception): 2 | pass 3 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/constants/peers.py: -------------------------------------------------------------------------------- 1 | DEFAULT_ANNOUNCE_PEERS = 50 2 | MAX_ANNOUNCE_PEERS = 82 3 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/datastructures/mutable/__init__.py: -------------------------------------------------------------------------------- 1 | from .box import MutableBox as MutableBox 2 | -------------------------------------------------------------------------------- /backend/django_core/apps/user/validators/__init__.py: -------------------------------------------------------------------------------- 1 | from .username import username_validator as username_validator 2 | -------------------------------------------------------------------------------- /backend/django_core/templates/anime/episode/index.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tracker/frontend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["prettier-plugin-tailwindcss", "prettier-plugin-packagejson"] 3 | } 4 | -------------------------------------------------------------------------------- /tracker/frontend/src/types/iframe.ts: -------------------------------------------------------------------------------- 1 | export type IframeMessage = { 2 | from: string; 3 | message: string; 4 | }; 5 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/exceptions/__init__.py: -------------------------------------------------------------------------------- 1 | from .redis import RedisNotInitialized as RedisNotInitialized 2 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/transaction/__init__.py: -------------------------------------------------------------------------------- 1 | from .rollback import rollback_on_exception as rollback_on_exception 2 | -------------------------------------------------------------------------------- /tracker/frontend/.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | .dockerignore 3 | node_modules 4 | npm-debug.log 5 | README.md 6 | .next 7 | .git -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/singletons/__init__.py: -------------------------------------------------------------------------------- 1 | from .redis import RedisHandler as RedisHandler, get_redis as get_redis 2 | -------------------------------------------------------------------------------- /tracker/frontend/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/coreproject/HEAD/tracker/frontend/src/app/favicon.ico -------------------------------------------------------------------------------- /backend/django_core/apps/api/schemas/user/login.py: -------------------------------------------------------------------------------- 1 | from ninja import Schema 2 | 3 | 4 | class LoginSchema(Schema): 5 | token: str 6 | -------------------------------------------------------------------------------- /tracker/frontend/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: ["@tailwindcss/postcss"], 3 | }; 4 | 5 | export default config; 6 | -------------------------------------------------------------------------------- /backend/.env.example: -------------------------------------------------------------------------------- 1 | # POSTGRESQL SETTINGS 2 | POSTGRES_NAME = 3 | POSTGRES_USER = 4 | POSTGRES_PASSWORD = 5 | POSTGRES_HOST = 6 | POSTGRES_PORT = 7 | -------------------------------------------------------------------------------- /tracker/frontend/src/fonts/Kokoro/Kokoro-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/coreproject/HEAD/tracker/frontend/src/fonts/Kokoro/Kokoro-Bold.woff2 -------------------------------------------------------------------------------- /tracker/frontend/src/fonts/Kokoro/Kokoro-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/coreproject/HEAD/tracker/frontend/src/fonts/Kokoro/Kokoro-Italic.woff2 -------------------------------------------------------------------------------- /tracker/frontend/src/fonts/Kokoro/Kokoro-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/coreproject/HEAD/tracker/frontend/src/fonts/Kokoro/Kokoro-Regular.woff2 -------------------------------------------------------------------------------- /tracker/frontend/src/fonts/Kokoro/Kokoro-SemiBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/coreproject/HEAD/tracker/frontend/src/fonts/Kokoro/Kokoro-SemiBold.woff2 -------------------------------------------------------------------------------- /tracker/frontend/src/fonts/Kokoro/Kokoro-BoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/coreproject/HEAD/tracker/frontend/src/fonts/Kokoro/Kokoro-BoldItalic.woff2 -------------------------------------------------------------------------------- /backend/django_core/apps/api/filters/genres.py: -------------------------------------------------------------------------------- 1 | from ninja import Schema 2 | 3 | 4 | class GenreFilter(Schema): 5 | name: str | None = None 6 | mal_id: int | None = None 7 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/filters/themes.py: -------------------------------------------------------------------------------- 1 | from ninja import Schema 2 | 3 | 4 | class ThemeFilter(Schema): 5 | name: str | None = None 6 | mal_id: int | None = None 7 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/enums/ip.py: -------------------------------------------------------------------------------- 1 | from enum import Enum, auto 2 | 3 | __all__ = ["IP"] 4 | 5 | 6 | class IP(Enum): 7 | IPV4 = auto() 8 | IPV6 = auto() 9 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/schemas/user/username_validity.py: -------------------------------------------------------------------------------- 1 | from ninja import Schema 2 | 3 | 4 | class UsernameValiditySchema(Schema): 5 | status: int 6 | message: str 7 | -------------------------------------------------------------------------------- /tracker/frontend/src/fonts/Kokoro/Kokoro-SemiBoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/coreproject/HEAD/tracker/frontend/src/fonts/Kokoro/Kokoro-SemiBoldItalic.woff2 -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/enums/redis.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class REDIS_NAMESPACE_ENUM(str, Enum): 5 | HTTP_UDP = "http_udp" 6 | WEBSOCKET = "websocket" 7 | -------------------------------------------------------------------------------- /backend/django_core/templates/anime/explore/index.html: -------------------------------------------------------------------------------- 1 | {% block head %} 2 | Explore - AnimeCore 3 | {% endblock %} 4 | 5 | 6 | -------------------------------------------------------------------------------- /backend/django_core/apps/user/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class UserConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "apps.user" 7 | -------------------------------------------------------------------------------- /backend/django_core/apps/user/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from . import views 4 | 5 | urlpatterns = [ 6 | path("avatar/", views.avatar_view, name="avatar_view"), 7 | ] 8 | -------------------------------------------------------------------------------- /backend/django_core/apps/staffs/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class PeopleConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "apps.staffs" 7 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/constants/ttl.py: -------------------------------------------------------------------------------- 1 | from datetime import timedelta 2 | 3 | PEER_TTL = int(timedelta(hours=1).total_seconds()) 4 | CONNECTION_TTL = int(timedelta(minutes=5).total_seconds()) 5 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/constants/websocket.py: -------------------------------------------------------------------------------- 1 | from datetime import timedelta 2 | 3 | WEBSOCKET_PEER_TTL = int(timedelta(minutes=1).total_seconds()) 4 | WEBSOCKET_INTERVAL = WEBSOCKET_PEER_TTL / 2 5 | -------------------------------------------------------------------------------- /backend/django_core/apps/comments/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CommentsConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "apps.comments" 7 | -------------------------------------------------------------------------------- /backend/django_core/apps/episodes/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class EpisodesConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "apps.episodes" 7 | -------------------------------------------------------------------------------- /backend/django_core/apps/producers/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ProducersConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "apps.producers" 7 | -------------------------------------------------------------------------------- /discord/TODO.md: -------------------------------------------------------------------------------- 1 | Try to use Discord.py 2 | 3 | # Sync permissions from django to discord users 4 | 5 | | Django | Discord | 6 | | --------- | ------- | 7 | | Superuser | Admin | 8 | | Staff | Mod | 9 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/servers/__init__.py: -------------------------------------------------------------------------------- 1 | from .http import http_blueprint as http_blueprint 2 | from .udp import run_udp_server as run_udp_server 3 | from .websocket import ws_blueprint as ws_blueprint 4 | -------------------------------------------------------------------------------- /backend/django_core/apps/characters/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CharactersConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "apps.characters" 7 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/converters/numbers.py: -------------------------------------------------------------------------------- 1 | _all_ = ["convert_str_int_to_float"] 2 | 3 | 4 | def convert_str_int_to_float(num: str | int | None) -> float | None: 5 | if num: 6 | return float(num) 7 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Token 4 | 5 | 6 | @admin.register(Token) 7 | class TokenAdmin(admin.ModelAdmin[Token]): 8 | autocomplete_fields = ["user"] 9 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/enums/actions.py: -------------------------------------------------------------------------------- 1 | import enum 2 | 3 | __all__ = ["ACTIONS"] 4 | 5 | 6 | class ACTIONS(enum.IntEnum): 7 | CONNECT = 0 8 | ANNOUNCE = 1 9 | SCRAPE = 2 10 | ERROR = 3 11 | -------------------------------------------------------------------------------- /tracker/frontend/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | -------------------------------------------------------------------------------- /backend/django_core/templates/admin/base.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/base.html" %} 2 | {% load static %} 3 | {% block extrahead %} 4 | 5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/constants/redis.py: -------------------------------------------------------------------------------- 1 | from datetime import timedelta 2 | 3 | HASH_EXPIRE_TIME = int(timedelta(days=1).total_seconds()) 4 | 5 | # Minimum redis version we support 6 | REDIS_SERVER_VERSION = "7.4.2" 7 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/converters/url.py: -------------------------------------------------------------------------------- 1 | import urllib.parse 2 | 3 | __all__ = ["convert_to_url_bytes"] 4 | 5 | 6 | def convert_to_url_bytes(value: str) -> bytes: 7 | return urllib.parse.unquote_to_bytes(value) 8 | -------------------------------------------------------------------------------- /backend/django_core/mixins/models/is_locked.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class IsLockedMixin(models.Model): 5 | is_locked = models.BooleanField(db_default=False) 6 | 7 | class Meta: 8 | abstract = True 9 | -------------------------------------------------------------------------------- /backend/django_core/mixins/models/updated_at.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class UpdatedAtMixin(models.Model): 5 | updated_at = models.DateTimeField(auto_now=True) 6 | 7 | class Meta: 8 | abstract = True 9 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/enums/__init__.py: -------------------------------------------------------------------------------- 1 | from .actions import ACTIONS as ACTIONS 2 | from .enum import EVENT_NAMES as EVENT_NAMES 3 | from .ip import IP as IP 4 | from .redis import REDIS_NAMESPACE_ENUM as REDIS_NAMESPACE_ENUM 5 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/constants/interval.py: -------------------------------------------------------------------------------- 1 | from datetime import timedelta 2 | 3 | ANNOUNCE_INTERVAL = int(timedelta(hours=1).total_seconds() / 60) # 1 hour 4 | WEBSOCKET_INTERVAL = int(timedelta(minutes=2).total_seconds()) # 2 min 5 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/converters/bytes.py: -------------------------------------------------------------------------------- 1 | __all__ = ["convert_binary_string_to_bytes"] 2 | 3 | 4 | def convert_binary_string_to_bytes(value: str | None) -> bytes | None: 5 | if value: 6 | return value.encode("latin1") 7 | -------------------------------------------------------------------------------- /tracker/frontend/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs", 3 | "options": { 4 | "allow-unauthenticated": true, 5 | "memory": "256Mi", 6 | "cpu": "1", 7 | "port": 3000, 8 | "http2": false 9 | } 10 | } -------------------------------------------------------------------------------- /backend/django_core/apps/api/schemas/stats/histogram.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from ninja.schema import Schema 4 | 5 | 6 | class HistogramSchema(Schema): 7 | year: datetime.datetime 8 | month: datetime.datetime 9 | count: int 10 | -------------------------------------------------------------------------------- /backend/django_core/utilities/rgb_to_hex.py: -------------------------------------------------------------------------------- 1 | # https://www.educative.io/answers/how-to-convert-hex-to-rgb-and-rgb-to-hex-in-python 2 | 3 | 4 | def rgb_to_hex(red: int, green: int, blue: int) -> str: 5 | return ("{:X}{:X}{:X}").format(red, green, blue) 6 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/filters/openings_and_endings.py: -------------------------------------------------------------------------------- 1 | from ninja import Schema 2 | 3 | 4 | class OpeningAndEndingFilter(Schema): 5 | # Opening number 6 | entry: int | None = None 7 | # Opening/closing theme name 8 | name: str | None = None 9 | -------------------------------------------------------------------------------- /backend/django_core/templates/upload/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'tailwind_base.html' %} 2 | 3 | {% block head %} 4 | Upload on AnimeCore 5 | {% endblock %} 6 | 7 | {%block body%} 8 | 9 | {% endblock%} -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/functions/convertion.py: -------------------------------------------------------------------------------- 1 | async def bytes_to_bin_str(byte: bytes) -> str: 2 | return byte.decode("latin-1") 3 | 4 | 5 | async def hex_str_to_bin_str(hex_str: str) -> str: 6 | return bytes.fromhex(hex_str).decode("latin1") 7 | -------------------------------------------------------------------------------- /backend/django_core/templates/home/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'tailwind_base.html' %} 2 | 3 | {% block head %} 4 | CoreProject - Imagine a platform 5 | {% endblock %} 6 | 7 | {%block body%} 8 | 9 | {% endblock%} -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/envs/__init__.py: -------------------------------------------------------------------------------- 1 | from .redis import ( 2 | REDIS_DATABASE as REDIS_DATABASE, 3 | REDIS_HOST as REDIS_HOST, 4 | REDIS_PORT as REDIS_PORT, 5 | REDIS_URI as REDIS_URI, 6 | ) 7 | from .workers import WORKERS_COUNT as WORKERS_COUNT 8 | -------------------------------------------------------------------------------- /tracker/frontend/src/functions/bencode.ts: -------------------------------------------------------------------------------- 1 | import bencode from "bencode"; 2 | 3 | export function isDataBencoded(data: string) { 4 | try { 5 | bencode.decode(Buffer.from(data)); 6 | return true; 7 | } catch { 8 | return false; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/schemas/episodes/__init__.py: -------------------------------------------------------------------------------- 1 | from apps.episodes.models import EpisodeModel 2 | from ninja import ModelSchema 3 | 4 | 5 | class EpisodeGETSchema(ModelSchema): 6 | class Config: 7 | model = EpisodeModel 8 | model_fields = "__all__" 9 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/enums/enum.py: -------------------------------------------------------------------------------- 1 | from enum import Enum, auto 2 | 3 | __all__ = ["EVENT_NAMES"] 4 | 5 | 6 | class EVENT_NAMES(Enum): 7 | UPDATE = auto() 8 | COMPLETE = auto() 9 | START = auto() 10 | STOP = auto() 11 | PAUSE = auto() 12 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/http.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpRequest as DjangoHttpRequest 2 | from apps.user.models import CustomUser 3 | from django.contrib.auth.models import AnonymousUser 4 | 5 | 6 | class HttpRequest(DjangoHttpRequest): 7 | auth: CustomUser | AnonymousUser 8 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class AnimeConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "apps.anime" 7 | 8 | def ready(self) -> None: 9 | from . import signals as signals 10 | -------------------------------------------------------------------------------- /backend/django_core/mixins/models/created_at.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.db.models.functions import Now 3 | 4 | 5 | class CreatedAtMixin(models.Model): 6 | created_at = models.DateTimeField(db_default=Now()) 7 | 8 | class Meta: 9 | abstract = True 10 | -------------------------------------------------------------------------------- /backend/django_core/templates/user/login/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'user/_layout.html'%} 2 | 3 | {% block title %} 4 | Login | CoreProject 5 | {% endblock %} 6 | 7 | 8 | {% block sidebody %} 9 | 10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /tracker/frontend/svgr.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.svg" { 2 | import { FC, SVGProps } from "react"; 3 | const content: FC>; 4 | export default content; 5 | } 6 | 7 | declare module "*.svg?url" { 8 | const content: any; 9 | export default content; 10 | } 11 | -------------------------------------------------------------------------------- /backend/django_core/apps/producers/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import ProducerModel 4 | 5 | # Register your models here. 6 | 7 | 8 | @admin.register(ProducerModel) 9 | class ProducerAdmin(admin.ModelAdmin[ProducerModel]): 10 | search_fields = ["name"] 11 | -------------------------------------------------------------------------------- /backend/django_core/templates/user/register/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'user/_layout.html'%} 2 | 3 | {% block title %} 4 | Register | CoreProject 5 | {% endblock %} 6 | 7 | {% block sidebody %} 8 | 9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/datastructures/mutable/box.py: -------------------------------------------------------------------------------- 1 | from typing import Generic, TypeVar 2 | 3 | from attrs import define 4 | 5 | T = TypeVar("T") 6 | 7 | 8 | @define 9 | class MutableBox(Generic[T]): 10 | """A simple mutable container for any type T.""" 11 | 12 | value: T 13 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/filters/studios.py: -------------------------------------------------------------------------------- 1 | from ninja import Schema 2 | 3 | 4 | class StudioFilter(Schema): 5 | mal_id: int | None = None 6 | 7 | # icontains based search 8 | # Allowed : 9 | # A1 Pictures, Studio Ghibli 10 | # A1 Pictures 11 | name: str | None = None 12 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/filters/producers.py: -------------------------------------------------------------------------------- 1 | from ninja import Schema 2 | 3 | 4 | class ProducerFilter(Schema): 5 | mal_id: int | None = None 6 | 7 | # icontains based search 8 | # Allowed : 9 | # A1 Pictures, Studio Ghibli 10 | # A1 Pictures 11 | name: str | None = None 12 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/datastructures/immutable/__init__.py: -------------------------------------------------------------------------------- 1 | from .http import HttpDatastructure as HttpDatastructure 2 | from .redis import RedisDatastructure as RedisDatastructure 3 | from .udp import UdpDatastructure as UdpDatastructure 4 | from .websocket import WebsocketDatastructure as WebsocketDatastructure 5 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/admin/anime_genre.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from ..models.anime_genre import AnimeGenreModel 4 | 5 | # Register your models here. 6 | 7 | 8 | @admin.register(AnimeGenreModel) 9 | class AnimeGenreAdmin(admin.ModelAdmin[AnimeGenreModel]): 10 | search_fields = ["name"] 11 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/admin/anime_theme.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from ..models.anime_theme import AnimeThemeModel 4 | 5 | # Register your models here. 6 | 7 | 8 | @admin.register(AnimeThemeModel) 9 | class AnimeThemeAdmin(admin.ModelAdmin[AnimeThemeModel]): 10 | search_fields = ["name"] 11 | -------------------------------------------------------------------------------- /backend/django_core/templates/user/reset_password/index.html: -------------------------------------------------------------------------------- 1 | {% extends "user/_layout.html" %} 2 | 3 | {% block title %} 4 | Reset Password | CoreProject 5 | {% endblock %} 6 | 7 | {% block sidebody %} 8 | 9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/converters/__init__.py: -------------------------------------------------------------------------------- 1 | from .bytes import convert_binary_string_to_bytes as convert_binary_string_to_bytes 2 | from .ip import convert_ip as convert_ip 3 | from .numbers import convert_str_int_to_float as convert_str_int_to_float 4 | from .url import convert_to_url_bytes as convert_to_url_bytes 5 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/schemas/anime/anime_opening_and_ending.py: -------------------------------------------------------------------------------- 1 | from apps.anime.models.anime_openings_and_endings import AnimeOpeningModel 2 | from ninja import ModelSchema 3 | 4 | 5 | class AnimeOpeningAndEndingGETSchema(ModelSchema): 6 | class Config: 7 | model = AnimeOpeningModel 8 | model_fields = "__all__" 9 | -------------------------------------------------------------------------------- /backend/django_core/templates/anime/index.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | {% block head %} 4 | AnimeCore 5 | {% endblock %} 6 | 7 | 12 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/validators/peer.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Optional 2 | 3 | from attrs import Attribute 4 | 5 | 6 | def validate_peer_length(inst: Any, attr: Attribute, value: Optional[str]) -> None: 7 | if value and len(value) != 20: 8 | raise ValueError(f"`{attr}` of `{inst}` is {value} which not 20 bytes") 9 | -------------------------------------------------------------------------------- /backend/django_core/apps/characters/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import CharacterModel 4 | 5 | # Register your models here. 6 | 7 | 8 | @admin.register(CharacterModel) 9 | class CharacterAdmin(admin.ModelAdmin[CharacterModel]): 10 | search_fields = [ 11 | "name", 12 | "name_kanji", 13 | ] 14 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/validators/__init__.py: -------------------------------------------------------------------------------- 1 | from .connection import validate_connection_id as validate_connection_id 2 | from .ip import validate_ip as validate_ip 3 | from .length import validate_20_length as validate_20_length 4 | from .peer import validate_peer_length as validate_peer_length 5 | from .port import validate_port as validate_port 6 | -------------------------------------------------------------------------------- /backend/django_core/apps/comments/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import CommentModel 4 | 5 | 6 | # Register your models here. 7 | @admin.register(CommentModel) 8 | class CommentAdmin(admin.ModelAdmin[CommentModel]): 9 | autocomplete_fields = ["user"] 10 | list_filters = ["user"] 11 | search_fields = ["user__username", "text"] 12 | -------------------------------------------------------------------------------- /backend/django_core/apps/episodes/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django_hstore_widget.forms import HStoreFormField 3 | 4 | from .models import EpisodeModel 5 | 6 | 7 | class EpisodeAdminModelForm(forms.ModelForm[EpisodeModel]): 8 | providers = HStoreFormField() 9 | 10 | class Meta: 11 | model = EpisodeModel 12 | fields = "__all__" 13 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/envs/redis.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | __all__ = ["REDIS_HOST", "REDIS_PORT", "REDIS_DATABASE"] 4 | 5 | REDIS_HOST = os.environ.get("REDIS_HOST", "localhost") 6 | REDIS_PORT = os.environ.get("REDIS_PORT", 6379) 7 | REDIS_DATABASE = os.environ.get("REDIS_DATABASE", 0) 8 | 9 | REDIS_URI = f"redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_DATABASE}" 10 | -------------------------------------------------------------------------------- /backend/django_core/apps/user/validators/username.py: -------------------------------------------------------------------------------- 1 | from django.core.exceptions import ValidationError 2 | 3 | INVALID_USERNAMES = { 4 | "admin", 5 | } 6 | 7 | 8 | # This will only get invoked when using modelform 9 | def username_validator(username: str) -> None: 10 | if username in INVALID_USERNAMES: 11 | raise ValidationError(f"Username {username} not allowed") 12 | -------------------------------------------------------------------------------- /tracker/nginx/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:1.23-alpine 2 | 3 | # Copy configuration 4 | COPY nginx.conf /etc/nginx/nginx.conf 5 | 6 | # Expose ports 7 | EXPOSE 80 8 | EXPOSE 5000/udp 9 | 10 | # Health check (optional) 11 | HEALTHCHECK --interval=30s --timeout=3s \ 12 | CMD wget --quiet --tries=1 --spider http://localhost/health || exit 1 13 | 14 | # Start NGINX 15 | CMD ["nginx", "-g", "daemon off;"] -------------------------------------------------------------------------------- /tracker/frontend/src/components/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import { ThemeProvider as NextThemesProvider } from "next-themes"; 5 | 6 | export function ThemeProvider({ 7 | children, 8 | ...props 9 | }: React.ComponentProps) { 10 | return {children}; 11 | } 12 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django_hstore_widget.forms import HStoreFormField 3 | 4 | from .models import AnimeModel 5 | 6 | 7 | class AnimeAdminModelForm(forms.ModelForm[AnimeModel]): 8 | theme_openings = HStoreFormField() 9 | theme_endings = HStoreFormField() 10 | 11 | class Meta: 12 | model = AnimeModel 13 | fields = "__all__" 14 | -------------------------------------------------------------------------------- /backend/django_core/apps/user/migrations/0009_merge_20240317_1131.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0 on 2024-03-17 05:31 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('user', '0008_alter_customuser_username'), 10 | ('user', '0008_remove_customuser_created_at'), 11 | ] 12 | 13 | operations = [ 14 | ] 15 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/validators/ip.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Optional 2 | 3 | from attrs import Attribute 4 | 5 | from coreproject_tracker.functions import convert_str_to_ip_object 6 | 7 | 8 | def validate_ip(inst: Any, attr: Attribute, value: Optional[str]) -> None: 9 | if value and not convert_str_to_ip_object(value): 10 | raise ValueError(f"`{value}` is not a valid ip.") 11 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0006_merge_20230326_1819.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.7 on 2023-03-26 18:19 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("anime", "0002_alter_animemodel_rating"), 9 | ("anime", "0005_alter_animeendingmodel_unique_together_and_more"), 10 | ] 11 | 12 | operations = [] 13 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/filters/characters.py: -------------------------------------------------------------------------------- 1 | from ninja import Schema 2 | 3 | 4 | class CharacterFilter(Schema): 5 | mal_id: int | None = None 6 | kitsu_id: int | None = None 7 | anilist_id: int | None = None 8 | 9 | # icontains based search 10 | # Allowed : 11 | # Sora Amamiya, Natsukawa Shiina, Momo Asakura 12 | # Sora Amamiya 13 | name: str | None = None 14 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/datastructures/__init__.py: -------------------------------------------------------------------------------- 1 | # Immutable data structures 2 | from .immutable import ( 3 | HttpDatastructure as HttpDatastructure, 4 | RedisDatastructure as RedisDatastructure, 5 | UdpDatastructure as UdpDatastructure, 6 | WebsocketDatastructure as WebsocketDatastructure, 7 | ) 8 | 9 | # Mutable data structures 10 | from .mutable import MutableBox as MutableBox 11 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/validators/length.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Optional 2 | 3 | from attrs import Attribute 4 | 5 | __all__ = ["validate_20_length"] 6 | 7 | 8 | def validate_20_length(inst: Any, attr: Attribute, value: Optional[bytes]) -> None: 9 | if length := value: 10 | if len(length) != 20: 11 | raise ValueError(f"`{attr}` of `{inst}` is {value} which not 20 bytes") 12 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/schemas/trackers/mal.py: -------------------------------------------------------------------------------- 1 | from apps.trackers.models import MalModel 2 | from ninja import ModelSchema 3 | 4 | 5 | class MALGETSchema(ModelSchema): 6 | class Config: 7 | model = MalModel 8 | model_fields = "__all__" 9 | 10 | 11 | class MALPOSTSchema(ModelSchema): 12 | class Config: 13 | model = MalModel 14 | model_exclude = ["user", "id", "created_at"] 15 | -------------------------------------------------------------------------------- /backend/django_core/apps/user/migrations/0003_delete_token.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.7 on 2023-03-14 01:24 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("user", "0002_alter_token_unique_together"), 9 | ] 10 | 11 | operations = [ 12 | migrations.DeleteModel( 13 | name="Token", 14 | ), 15 | ] 16 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/functions/dictionary.py: -------------------------------------------------------------------------------- 1 | async def decode_dictionary(data): 2 | if isinstance(data, dict): 3 | return {k.decode(): await decode_dictionary(v) for k, v in data.items()} 4 | elif isinstance(data, list): 5 | return [await decode_dictionary(i) for i in data] 6 | elif isinstance(data, bytes): 7 | return data.decode() 8 | else: 9 | return data 10 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/schemas/trackers/kitsu.py: -------------------------------------------------------------------------------- 1 | from apps.trackers.models import KitsuModel 2 | from ninja import ModelSchema 3 | 4 | 5 | class KitsuGETSchema(ModelSchema): 6 | class Config: 7 | model = KitsuModel 8 | model_fields = "__all__" 9 | 10 | 11 | class KitsuPOSTSchema(ModelSchema): 12 | class Config: 13 | model = KitsuModel 14 | model_exclude = ["user", "id", "created_at"] 15 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/schemas/trackers/anilist.py: -------------------------------------------------------------------------------- 1 | from apps.trackers.models import AnilistModel 2 | from ninja import ModelSchema 3 | 4 | 5 | class AnilistGETSchema(ModelSchema): 6 | class Config: 7 | model = AnilistModel 8 | model_fields = "__all__" 9 | 10 | 11 | class AnilistPOSTSchema(ModelSchema): 12 | class Config: 13 | model = AnilistModel 14 | model_exclude = ["user", "id", "created_at"] 15 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/validators/port.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Optional 2 | 3 | from attrs import Attribute 4 | 5 | __all__ = ["validate_port"] 6 | 7 | 8 | def validate_port(inst: Any, attr: Attribute, value: Optional[int]) -> None: 9 | if value: 10 | if value <= 0 and value >= 65535: 11 | raise ValueError( 12 | f"`{attr}` of `{inst}` is {value} which is not in range(0, 65535)" 13 | ) 14 | -------------------------------------------------------------------------------- /backend/django_core/apps/user/forms.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.forms import UserChangeForm, UserCreationForm 2 | 3 | from .models import CustomUser 4 | 5 | 6 | class CustomUserCreationForm(UserCreationForm["CustomUser"]): 7 | class Meta: 8 | model = CustomUser 9 | fields = ("email",) 10 | 11 | 12 | class CustomUserChangeForm(UserChangeForm["CustomUser"]): 13 | class Meta: 14 | model = CustomUser 15 | fields = ("email",) 16 | -------------------------------------------------------------------------------- /backend/django_core/apps/user/migrations/0005_remove_customuser_ip.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.7 on 2023-03-26 16:34 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("user", "0004_alter_customuser_avatar"), 9 | ] 10 | 11 | operations = [ 12 | migrations.RemoveField( 13 | model_name="customuser", 14 | name="ip", 15 | ), 16 | ] 17 | -------------------------------------------------------------------------------- /backend/django_core/templates/anime/info/index.html: -------------------------------------------------------------------------------- 1 | 11 | 12 | -------------------------------------------------------------------------------- /backend/django_core/apps/user/migrations/0002_alter_token_unique_together.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.7 on 2023-03-09 03:14 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("user", "0001_initial"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterUniqueTogether( 13 | name="token", 14 | unique_together={("token", "user")}, 15 | ), 16 | ] 17 | -------------------------------------------------------------------------------- /backend/django_core/core/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for core project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings") 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/admin/anime_openings_and_endings.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from ..models.anime_openings_and_endings import AnimeEndingModel, AnimeOpeningModel 4 | 5 | 6 | @admin.register(AnimeEndingModel) 7 | class AnimeEndingAdmin(admin.ModelAdmin[AnimeEndingModel]): 8 | search_fields = ["name"] 9 | 10 | 11 | @admin.register(AnimeOpeningModel) 12 | class AnimeOpeningAdmin(admin.ModelAdmin[AnimeOpeningModel]): 13 | search_fields = ["name"] 14 | -------------------------------------------------------------------------------- /backend/django_core/apps/episodes/admin/__init__.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from ..forms import EpisodeAdminModelForm 4 | from ..models import EpisodeModel 5 | 6 | # Register your models here. 7 | 8 | 9 | @admin.register(EpisodeModel) 10 | class EpisodeAdmin(admin.ModelAdmin[EpisodeModel]): 11 | form = EpisodeAdminModelForm 12 | search_fields = ["episode_name"] 13 | 14 | 15 | from .episode_timestamp import EpisodeTimestampAdmin as EpisodeTimestampAdmin 16 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/filters/staffs.py: -------------------------------------------------------------------------------- 1 | from ninja import Schema 2 | 3 | 4 | class StaffFilter(Schema): 5 | mal_id: int | None = None 6 | kitsu_id: int | None = None 7 | anilist_id: int | None = None 8 | 9 | # icontains based search 10 | # Allowed : 11 | # Sora Amamiya, Natsukawa Shiina, Momo Asakura 12 | # Sora Amamiya 13 | name: str | None = None 14 | 15 | given_name: str | None = None 16 | family_name: str | None = None 17 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/converters/ip.py: -------------------------------------------------------------------------------- 1 | from coreproject_tracker.functions import convert_ipv4_coded_ipv6_to_ipv4 2 | 3 | __all__ = ["convert_ip"] 4 | 5 | 6 | def convert_ip(value: str) -> str: 7 | ipv4_address = convert_ipv4_coded_ipv6_to_ipv4(value) 8 | if ipv4_address is False: 9 | raise ValueError(f"Invalid IPv4 address: {value}") 10 | 11 | if isinstance(ipv4_address, str): 12 | return ipv4_address 13 | else: 14 | return value 15 | -------------------------------------------------------------------------------- /backend/django_core/apps/user/migrations/0008_remove_customuser_created_at.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.5 on 2023-09-09 04:19 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("user", "0007_alter_customuser_unique_together_and_more"), 9 | ] 10 | 11 | operations = [ 12 | migrations.RemoveField( 13 | model_name="customuser", 14 | name="created_at", 15 | ), 16 | ] 17 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/envs/workers.py: -------------------------------------------------------------------------------- 1 | import multiprocessing 2 | import os 3 | 4 | __all__ = ["WORKERS_COUNT"] 5 | 6 | WORKERS_COUNT = int( 7 | os.environ.get( 8 | "WORKERS_COUNT", 9 | max( 10 | 2, # 2 is here cause of `http and websocket server` and `UDP server` each should use 1 core 11 | multiprocessing.cpu_count() 12 | - 2, # 2 is here cause of `redis` and `UDP server` each needs 1 core 13 | ), 14 | ) 15 | ) 16 | -------------------------------------------------------------------------------- /backend/django_core/apps/user/migrations/0011_remove_customuser_created_at.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1 on 2024-09-01 13:03 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('user', '0010_customuser_created_at_alter_customuser_username'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='customuser', 15 | name='created_at', 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /tracker/frontend/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { dirname } from "path"; 2 | import { fileURLToPath } from "url"; 3 | import { FlatCompat } from "@eslint/eslintrc"; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | const __dirname = dirname(__filename); 7 | 8 | const compat = new FlatCompat({ 9 | baseDirectory: __dirname, 10 | }); 11 | 12 | const eslintConfig = [ 13 | ...compat.extends("next/core-web-vitals", "next/typescript"), 14 | ]; 15 | 16 | export default eslintConfig; 17 | -------------------------------------------------------------------------------- /backend/django_core/apps/episodes/migrations/0007_remove_episodetimestampmodel_episode.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.4 on 2023-08-31 01:56 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("episodes", "0006_alter_episodecommentmodel_options_and_more"), 9 | ] 10 | 11 | operations = [ 12 | migrations.RemoveField( 13 | model_name="episodetimestampmodel", 14 | name="episode", 15 | ), 16 | ] 17 | -------------------------------------------------------------------------------- /tracker/frontend/src/types/api.ts: -------------------------------------------------------------------------------- 1 | export interface BackendData { 2 | quart_version: string; 3 | redis_version: { 4 | client: string; 5 | server: string; 6 | }; 7 | python_version: string; 8 | redis_data: { 9 | [infoHash: string]: { 10 | [peerAddress: string]: string; 11 | }; 12 | }; 13 | } 14 | 15 | export interface RedisData { 16 | info_hash: string; 17 | type: "http" | "udp"; 18 | peer_id: string; 19 | peer_ip: string; 20 | port: number; 21 | left: number | null; 22 | } 23 | -------------------------------------------------------------------------------- /backend/django_core/apps/comments/migrations/0002_alter_commentmodel_options.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0rc1 on 2023-12-05 13:22 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("comments", "0001_initial"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterModelOptions( 13 | name="commentmodel", 14 | options={"verbose_name": "Comment", "verbose_name_plural": "Comments"}, 15 | ), 16 | ] 17 | -------------------------------------------------------------------------------- /backend/django_core/apps/producers/migrations/0003_remove_producermodel_default_title.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-19 09:43 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("producers", "0002_producermodel_created_at_producermodel_updated_at"), 9 | ] 10 | 11 | operations = [ 12 | migrations.RemoveField( 13 | model_name="producermodel", 14 | name="default_title", 15 | ), 16 | ] 17 | -------------------------------------------------------------------------------- /backend/django_core/core/storages.py: -------------------------------------------------------------------------------- 1 | from django.core.files.storage import FileSystemStorage 2 | 3 | 4 | class OverwriteStorage(FileSystemStorage): 5 | # https://stackoverflow.com/questions/9522759/imagefield-overwrite-image-file-with-same-name 6 | def get_available_name( 7 | self, 8 | name: str, 9 | max_length: int | None = None, 10 | ) -> str: 11 | self.delete(name) 12 | return super().get_available_name( 13 | name, 14 | max_length, 15 | ) 16 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/permissions.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | from apps.api.http import HttpRequest 3 | 4 | SAFE_METHODS = ("GET", "HEAD", "OPTIONS") 5 | 6 | 7 | class IsSuperUser: 8 | def __init__(self, request: HttpRequest, user: User) -> None: 9 | self.request = request 10 | self.user = user 11 | 12 | def has_permissions(self) -> bool: 13 | if self.user.is_superuser and self.request.method not in SAFE_METHODS: 14 | return True 15 | return False 16 | -------------------------------------------------------------------------------- /backend/django_core/apps/staffs/migrations/0005_alter_staffmodel_is_locked.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-25 19:40 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("staffs", "0004_staffmodel_is_locked"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="staffmodel", 14 | name="is_locked", 15 | field=models.BooleanField(default=False), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0002_alter_animemodel_rating.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.7 on 2023-03-10 10:32 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("anime", "0001_initial"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="animemodel", 14 | name="rating", 15 | field=models.CharField(blank=True, default="", max_length=50), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/views/user/logout.py: -------------------------------------------------------------------------------- 1 | from apps.api.models import Token 2 | from django.http import HttpRequest, HttpResponse 3 | from ninja import Router 4 | from http import HTTPStatus 5 | from ...auth import AuthBearer 6 | 7 | router = Router() 8 | 9 | 10 | @router.delete("/logout", auth=AuthBearer()) 11 | def post_user_logout_info(request: HttpRequest) -> HttpResponse: 12 | token: Token = Token.objects.get(user=request.auth) 13 | token.delete() 14 | return HttpResponse("Successful", status=HTTPStatus.ACCEPTED) 15 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0008_rename_updated_animemodel_updated_at.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-13 14:14 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("anime", "0007_animemodel_created_at_alter_animemodel_updated"), 9 | ] 10 | 11 | operations = [ 12 | migrations.RenameField( 13 | model_name="animemodel", 14 | old_name="updated", 15 | new_name="updated_at", 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0009_animegenremodel_description.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-19 13:20 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("anime", "0008_rename_updated_animemodel_updated_at"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="animegenremodel", 14 | name="description", 15 | field=models.TextField(null=True), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /backend/django_core/apps/producers/migrations/0006_alter_producermodel_is_locked.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-25 19:40 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("producers", "0005_producermodel_is_locked"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="producermodel", 14 | name="is_locked", 15 | field=models.BooleanField(default=False), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /tracker/frontend/src/constants/url.ts: -------------------------------------------------------------------------------- 1 | export const BACKEND_HOST = 2 | process.env.NEXT_PUBLIC_BACKEND_HOST || "localhost:5000"; 3 | 4 | export const HTTP_ENDPOINT = `http://${BACKEND_HOST}`; 5 | export const WS_ENDPOINT = `ws://${BACKEND_HOST}/`; 6 | 7 | // Websocket 8 | export const WS_TRACKER_ENDPOINT = `${WS_ENDPOINT}/announce`; 9 | 10 | // HTTP 11 | export const HTTP_ANNOUNCE_ENDPOINT = `${HTTP_ENDPOINT}/announce`; 12 | export const API_URL = `${HTTP_ENDPOINT}/api`; 13 | export const HTTP_TRACKER_ENDPOINT = `${HTTP_ENDPOINT}/http`; 14 | -------------------------------------------------------------------------------- /backend/django_core/apps/characters/migrations/0004_alter_charactermodel_is_locked.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-25 19:40 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("characters", "0003_charactermodel_is_locked"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="charactermodel", 14 | name="is_locked", 15 | field=models.BooleanField(default=False), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /backend/django_core/apps/comments/migrations/0006_commentmodel_deleted.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0.2 on 2024-02-23 15:09 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("comments", "0005_remove_commentmodel_dislikes_and_more"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="commentmodel", 15 | name="deleted", 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/django_core/apps/staffs/migrations/0009_alter_staffmodel_is_locked.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0.2 on 2024-02-21 03:40 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("staffs", "0008_alter_staffmodel_is_locked"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="staffmodel", 15 | name="is_locked", 16 | field=models.BooleanField(db_default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/django_core/apps/staffs/migrations/0008_alter_staffmodel_is_locked.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0b1 on 2023-10-29 09:54 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("staffs", "0007_alter_staffmodel_created_at"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="staffmodel", 14 | name="is_locked", 15 | field=models.BooleanField(db_default=models.Value(False)), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /tracker/frontend/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "", 8 | "css": "src/app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } -------------------------------------------------------------------------------- /backend/django_core/apps/api/schemas/characters/__init__.py: -------------------------------------------------------------------------------- 1 | from ninja import ModelSchema 2 | 3 | from ....characters.models import CharacterModel 4 | 5 | 6 | class CharacterGETSchema(ModelSchema): 7 | class Config: 8 | model = CharacterModel 9 | model_fields = "__all__" 10 | 11 | 12 | class CharacterPOSTSchema(ModelSchema): 13 | class Config: 14 | model = CharacterModel 15 | model_exclude = [ 16 | "id", 17 | "created_at", 18 | "updated_at", 19 | "is_locked", 20 | ] 21 | -------------------------------------------------------------------------------- /backend/django_core/apps/producers/migrations/0004_rename_japanese_title_producermodel_name_japanese.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-19 11:24 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("producers", "0003_remove_producermodel_default_title"), 9 | ] 10 | 11 | operations = [ 12 | migrations.RenameField( 13 | model_name="producermodel", 14 | old_name="japanese_title", 15 | new_name="name_japanese", 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /backend/django_core/apps/producers/migrations/0009_alter_producermodel_is_locked.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0.2 on 2024-02-21 03:40 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("producers", "0008_alter_producermodel_is_locked"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="producermodel", 15 | name="is_locked", 16 | field=models.BooleanField(db_default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/django_core/apps/staffs/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import StaffAlternateNameModel, StaffModel 4 | 5 | # Register your models here. 6 | 7 | 8 | @admin.register(StaffModel) 9 | class StaffModelAdmin(admin.ModelAdmin[StaffModel]): 10 | search_fields = [ 11 | "name", 12 | "given_name", 13 | "family_name", 14 | "alternate_names__name", 15 | ] 16 | 17 | 18 | @admin.register(StaffAlternateNameModel) 19 | class StaffAlternateNameAdmin(admin.ModelAdmin[StaffAlternateNameModel]): 20 | pass 21 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0010_animemodel_is_locked.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-24 07:00 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("anime", "0009_animegenremodel_description"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="animemodel", 14 | name="is_locked", 15 | field=models.BooleanField(default=False), 16 | preserve_default=False, 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0013_animethememodel_description.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-24 15:43 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("anime", "0012_animethememodel_created_at_animethememodel_is_locked_and_more"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="animethememodel", 14 | name="description", 15 | field=models.TextField(null=True), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /backend/django_core/apps/characters/migrations/0007_alter_charactermodel_is_locked.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0.2 on 2024-02-21 03:40 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("characters", "0006_alter_charactermodel_is_locked"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="charactermodel", 15 | name="is_locked", 16 | field=models.BooleanField(db_default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/django_core/apps/producers/migrations/0008_alter_producermodel_is_locked.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0b1 on 2023-10-29 09:54 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("producers", "0007_alter_producermodel_created_at"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="producermodel", 14 | name="is_locked", 15 | field=models.BooleanField(db_default=models.Value(False)), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /backend/django_core/apps/characters/migrations/0006_alter_charactermodel_is_locked.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0b1 on 2023-10-29 09:54 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("characters", "0005_alter_charactermodel_created_at"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="charactermodel", 14 | name="is_locked", 15 | field=models.BooleanField(db_default=models.Value(False)), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/views/user/username_validity.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpRequest, HttpResponse 2 | from ninja import Form, Router 3 | 4 | from ...schemas.user.username_validity import UsernameValiditySchema 5 | 6 | router = Router() 7 | 8 | 9 | @router.post( 10 | "", 11 | response={ 12 | 302: UsernameValiditySchema, 13 | 404: UsernameValiditySchema, 14 | 406: UsernameValiditySchema, 15 | }, 16 | ) 17 | def post_username_validity_info( 18 | request: HttpRequest, 19 | username: str = Form(...), 20 | ) -> HttpResponse: 21 | pass 22 | -------------------------------------------------------------------------------- /backend/django_core/apps/staffs/migrations/0004_staffmodel_is_locked.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-24 07:00 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("staffs", "0003_staffmodel_created_at_staffmodel_updated_at"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="staffmodel", 14 | name="is_locked", 15 | field=models.BooleanField(default=False), 16 | preserve_default=False, 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/schemas/anime/anime_genre.py: -------------------------------------------------------------------------------- 1 | from apps.anime.models.anime_genre import AnimeGenreModel 2 | from ninja import ModelSchema 3 | 4 | 5 | class AnimeGenreGETSchema(ModelSchema): 6 | class Config: 7 | model = AnimeGenreModel 8 | model_fields = "__all__" 9 | 10 | 11 | class AnimeGenrePOSTSchema(ModelSchema): 12 | class Config: 13 | model = AnimeGenreModel 14 | model_exclude = [ 15 | "id", 16 | "type", 17 | "created_at", 18 | "updated_at", 19 | "is_locked", 20 | ] 21 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/schemas/anime/anime_theme.py: -------------------------------------------------------------------------------- 1 | from apps.anime.models.anime_theme import AnimeThemeModel 2 | from ninja import ModelSchema 3 | 4 | 5 | class AnimeThemeGETSchema(ModelSchema): 6 | class Config: 7 | model = AnimeThemeModel 8 | model_fields = "__all__" 9 | 10 | 11 | class AnimeThemePOSTSchema(ModelSchema): 12 | class Config: 13 | model = AnimeThemeModel 14 | model_exclude = [ 15 | "id", 16 | "type", 17 | "created_at", 18 | "updated_at", 19 | "is_locked", 20 | ] 21 | -------------------------------------------------------------------------------- /tracker/frontend/src/hooks/useBackendData.ts: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import useSWR from "swr"; 3 | import { API_URL } from "@/constants/url"; 4 | import { BackendData } from "@/types/api"; 5 | 6 | const fetcher = async (url: URL) => { 7 | const res = await fetch(url); 8 | 9 | const data = await res.json(); 10 | 11 | return data; 12 | }; 13 | 14 | export function useBackendData() { 15 | const { data, error, isLoading } = useSWR(API_URL, fetcher); 16 | 17 | return { 18 | data: data ?? null, 19 | isLoading, 20 | isError: error, 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/parser.py: -------------------------------------------------------------------------------- 1 | from apps.api.http import HttpRequest 2 | from ninja.parser import Parser 3 | from ninja.types import DictStrAny 4 | 5 | try: 6 | import orjson 7 | 8 | HAS_ORJSON = True 9 | except ImportError: 10 | HAS_ORJSON = False 11 | 12 | 13 | class CustomParser(Parser): 14 | def __init__(self) -> None: 15 | # Override method only if `orjson`` exists 16 | if HAS_ORJSON: 17 | self.parse_body = self._parse_body 18 | 19 | def _parse_body(self, request: HttpRequest) -> DictStrAny: 20 | return orjson.loads(request.body) 21 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/schemas/producers/__init__.py: -------------------------------------------------------------------------------- 1 | from ninja import ModelSchema 2 | 3 | from ....producers.models import ProducerModel 4 | 5 | 6 | class ProducerGETSchema(ModelSchema): 7 | class Config: 8 | model = ProducerModel 9 | model_fields = "__all__" 10 | 11 | 12 | class ProducerPOSTSchema(ModelSchema): 13 | kitsu_id: int | None = None 14 | 15 | class Config: 16 | model = ProducerModel 17 | model_exclude = [ 18 | "id", 19 | "created_at", 20 | "updated_at", 21 | "is_locked", 22 | ] 23 | -------------------------------------------------------------------------------- /tracker/backend/tests_primitive/websocket_client.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | import websockets 4 | 5 | 6 | async def websocket_client(): 7 | uri = "ws://localhost:5000" # Change this to your WebSocket server URL 8 | 9 | while True: 10 | async with websockets.connect(uri) as websocket: 11 | ask = input() 12 | await websocket.send(ask) 13 | print(f"Sent: {ask}") 14 | 15 | response = await websocket.recv() 16 | print(f"Received: {response}") 17 | 18 | 19 | if __name__ == "__main__": 20 | asyncio.run(websocket_client()) 21 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/schemas/trackers/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from ninja import Schema 4 | 5 | 6 | class TrackerSchema(Schema): 7 | status: Literal[ 8 | "watching", 9 | "completed", 10 | "on_hold", 11 | "dropped", 12 | "plan_to_watch", 13 | ] 14 | is_rewatching: bool 15 | score: int 16 | num_watched_episodes: int 17 | priority: int 18 | num_times_rewatched: int 19 | rewatch_value: int 20 | tags: str 21 | comments: str 22 | 23 | 24 | class TrackerDeleteSchema(Schema): 25 | anime_id: int 26 | -------------------------------------------------------------------------------- /discord/settings.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | # Goes to the directory where `server.py` is present 4 | BASE_DIR = Path(__file__).resolve().parent 5 | TEMPLATE_DIRS = str( 6 | BASE_DIR.joinpath("templates"), 7 | ) 8 | 9 | # Goes to the directory where pipfile is present 10 | DJANGO_DIR = BASE_DIR.parent.joinpath("django_core") 11 | DJANGO_MEDIA_DIR = str(DJANGO_DIR.joinpath("media")) 12 | 13 | # Postgres 14 | POSTGRES = { 15 | "NAME": "discord-py", 16 | "USER": "postgres", 17 | "PASSWORD": "supersecretpassword", 18 | "HOST": "", 19 | "PORT": "", 20 | } 21 | -------------------------------------------------------------------------------- /backend/django_core/apps/producers/migrations/0005_producermodel_is_locked.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-24 07:00 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("producers", "0004_rename_japanese_title_producermodel_name_japanese"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="producermodel", 14 | name="is_locked", 15 | field=models.BooleanField(default=False), 16 | preserve_default=False, 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/functions/array.py: -------------------------------------------------------------------------------- 1 | import random 2 | from typing import Iterable 3 | 4 | __all__ = ["get_n_random_items"] 5 | 6 | 7 | async def get_n_random_items[T](array: Iterable[T], n: int) -> list[T]: 8 | """Given a set of array elements, returns n random elements from array""" 9 | # Convert to list if it's not already one 10 | if not isinstance(array, list): 11 | array = list(array) 12 | ret: list[T] = [] 13 | 14 | try: 15 | ret = random.sample(array, n) 16 | except ValueError: 17 | ret = array 18 | finally: 19 | return ret 20 | -------------------------------------------------------------------------------- /backend/django_core/apps/characters/migrations/0003_charactermodel_is_locked.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-24 07:00 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("characters", "0002_charactermodel_created_at_charactermodel_updated_at"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="charactermodel", 14 | name="is_locked", 15 | field=models.BooleanField(default=False), 16 | preserve_default=False, 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/django_core/apps/episodes/migrations/0002_episodemodel_episode_type.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.7 on 2023-03-21 14:24 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("episodes", "0001_initial"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="episodemodel", 14 | name="episode_type", 15 | field=models.CharField( 16 | blank=True, choices=[("sub", "sub"), ("dub", "dub")], max_length=3 17 | ), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /backend/django_core/apps/episodes/models/episode_timestamp.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import get_user_model 2 | from django.db import models 3 | from mixins.models.created_at import CreatedAtMixin 4 | 5 | # Create your models here. 6 | 7 | 8 | class EpisodeTimestampModel(CreatedAtMixin): 9 | timestamp = models.IntegerField(default=0) 10 | 11 | user = models.ForeignKey( 12 | get_user_model(), 13 | on_delete=models.CASCADE, 14 | ) 15 | 16 | def __str__(self) -> str: 17 | return f"{self.user} | {self.timestamp} seconds" 18 | 19 | class Meta: 20 | verbose_name = "Episode Timestamp" 21 | -------------------------------------------------------------------------------- /backend/django_core/templates/errors/base.html: -------------------------------------------------------------------------------- 1 | {% extends 'tailwind_base.html' %} 2 | {% block title %}CoreProject | {{ error_status_code }}{% endblock %} 3 | {% block body %} 4 |
5 | 8 |
9 |

{{ error_status_code }} - {{ error_status }}

10 |

{{ error_text|safe }}

11 |
12 |
13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /backend/django_core/apps/episodes/migrations/0008_alter_episodecommentmodel_options.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.4 on 2023-08-31 01:58 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("episodes", "0007_remove_episodetimestampmodel_episode"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterModelOptions( 13 | name="episodecommentmodel", 14 | options={ 15 | "verbose_name": "Episode Comment", 16 | "verbose_name_plural": "Episode Comments", 17 | }, 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /tracker/frontend/next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | // images: { unoptimized: true }, 5 | output: "standalone", 6 | productionBrowserSourceMaps: true, 7 | reactCompiler:true, 8 | turbopack: { 9 | rules: { 10 | "*.svg": { 11 | as: "*.ts", 12 | loaders: ["@svgr/webpack"], 13 | }, 14 | }, 15 | }, 16 | webpack(config) { 17 | config.module.rules.push({ 18 | test: /\.svg$/i, 19 | use: ["@svgr/webpack"], 20 | }); 21 | return config; 22 | }, 23 | }; 24 | 25 | export default nextConfig; 26 | -------------------------------------------------------------------------------- /backend/django_core/apps/staffs/migrations/0002_alter_staffalternatenamemodel_options.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.7 on 2023-03-12 16:03 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("staffs", "0001_initial"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterModelOptions( 13 | name="staffalternatenamemodel", 14 | options={ 15 | "verbose_name": "Staff | People ( Alternate Name )", 16 | "verbose_name_plural": "Staff | People ( Alternate Names )", 17 | }, 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /backend/django_core/templates/user/_layout.html: -------------------------------------------------------------------------------- 1 | {% extends 'tailwind_base.html' %} 2 | {% load static %} 3 | 4 | {% block body %} 5 |
6 | 10 | 11 |
12 | {% block sidebody %}{% endblock %} 13 |
14 |
15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0020_alter_animegenremodel_type.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.4 on 2023-08-29 06:35 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("anime", "0019_animemodel_anime_name_name_japanese_idx"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="animegenremodel", 14 | name="type", 15 | field=models.CharField( 16 | choices=[("Anime", "anime"), ("Manga", "manga")], default="", max_length=50 17 | ), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /discord/alembic/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | ${imports if imports else ""} 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = ${repr(up_revision)} 14 | down_revision = ${repr(down_revision)} 15 | branch_labels = ${repr(branch_labels)} 16 | depends_on = ${repr(depends_on)} 17 | 18 | 19 | def upgrade() -> None: 20 | ${upgrades if upgrades else "pass"} 21 | 22 | 23 | def downgrade() -> None: 24 | ${downgrades if downgrades else "pass"} 25 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/validators/connection.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Optional 2 | 3 | from attrs import Attribute 4 | 5 | from coreproject_tracker.constants import CONNECTION_ID 6 | from coreproject_tracker.functions import from_uint64 7 | 8 | __all__ = ["validate_connection_id"] 9 | 10 | 11 | def validate_connection_id(inst: Any, attr: Attribute, value: Optional[bytes]) -> None: 12 | if value: 13 | connection_id_unpacked = from_uint64(value) 14 | if connection_id_unpacked != CONNECTION_ID: 15 | raise ValueError( 16 | f"`{attr}` of `{inst}` is {value} which not {CONNECTION_ID}" 17 | ) 18 | -------------------------------------------------------------------------------- /backend/django_core/apps/user/migrations/0006_customuser_created_at.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-13 14:18 2 | 3 | import django.utils.timezone 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("user", "0005_remove_customuser_ip"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="customuser", 15 | name="created_at", 16 | field=models.DateTimeField( 17 | auto_now_add=True, default=django.utils.timezone.now 18 | ), 19 | preserve_default=False, 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /backend/django_core/apps/producers/migrations/0007_alter_producermodel_created_at.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0b1 on 2023-10-29 05:51 2 | 3 | import django.db.models.functions.datetime 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("producers", "0006_alter_producermodel_is_locked"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="producermodel", 15 | name="created_at", 16 | field=models.DateTimeField( 17 | db_default=django.db.models.functions.datetime.Now() 18 | ), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /backend/django_core/apps/characters/migrations/0005_alter_charactermodel_created_at.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0b1 on 2023-10-29 05:51 2 | 3 | import django.db.models.functions.datetime 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("characters", "0004_alter_charactermodel_is_locked"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="charactermodel", 15 | name="created_at", 16 | field=models.DateTimeField( 17 | db_default=django.db.models.functions.datetime.Now() 18 | ), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/constants/__init__.py: -------------------------------------------------------------------------------- 1 | from .interval import ( 2 | ANNOUNCE_INTERVAL as ANNOUNCE_INTERVAL, 3 | ) 4 | from .peers import ( 5 | DEFAULT_ANNOUNCE_PEERS as DEFAULT_ANNOUNCE_PEERS, 6 | MAX_ANNOUNCE_PEERS as MAX_ANNOUNCE_PEERS, 7 | ) 8 | from .redis import ( 9 | HASH_EXPIRE_TIME as HASH_EXPIRE_TIME, 10 | REDIS_SERVER_VERSION as REDIS_SERVER_VERSION, 11 | ) 12 | from .ttl import ( 13 | CONNECTION_TTL as CONNECTION_TTL, 14 | PEER_TTL as PEER_TTL, 15 | ) 16 | from .udp import CONNECTION_ID as CONNECTION_ID 17 | from .websocket import ( 18 | WEBSOCKET_INTERVAL as WEBSOCKET_INTERVAL, 19 | WEBSOCKET_PEER_TTL as WEBSOCKET_PEER_TTL, 20 | ) 21 | -------------------------------------------------------------------------------- /backend/django_core/apps/staffs/migrations/0007_alter_staffmodel_created_at.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0b1 on 2023-10-29 05:51 2 | 3 | import django.db.models.functions.datetime 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("staffs", "0006_staffalternatenamemodel_staff_alternate_idx_and_more"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="staffmodel", 15 | name="created_at", 16 | field=models.DateTimeField( 17 | db_default=django.db.models.functions.datetime.Now() 18 | ), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/views/anime/anime_staff.py: -------------------------------------------------------------------------------- 1 | from apps.anime.models import AnimeModel 2 | from apps.producers.models import ProducerModel 3 | from apps.api.http import HttpRequest 4 | from django.shortcuts import get_list_or_404, get_object_or_404 5 | from ninja import Router 6 | 7 | from ...schemas.staffs import StaffGETSchema 8 | 9 | router = Router() 10 | 11 | 12 | @router.get("/{int:anime_id}/staffs", response=list[StaffGETSchema]) 13 | def get_individual_anime_staff_info( 14 | request: HttpRequest, 15 | anime_id: int, 16 | ) -> list[ProducerModel]: 17 | query = get_list_or_404( 18 | get_object_or_404(AnimeModel, pk=anime_id).staffs, 19 | ) 20 | return query 21 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0005_alter_animeendingmodel_unique_together_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.7 on 2023-03-22 15:47 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("anime", "0004_alter_animeendingmodel_options_and_more"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterUniqueTogether( 13 | name="animeendingmodel", 14 | unique_together={("entry", "name", "url")}, 15 | ), 16 | migrations.AlterUniqueTogether( 17 | name="animeopeningmodel", 18 | unique_together={("entry", "name", "url")}, 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/schemas/episodes/episode_timestamp.py: -------------------------------------------------------------------------------- 1 | from apps.episodes.models.episode_timestamp import EpisodeTimestampModel 2 | from ninja import Field, ModelSchema, Schema 3 | 4 | 5 | class EpisodeTimestampTotalTimestampSchema(Schema): 6 | total_watchtime: int 7 | 8 | 9 | class EpisodeTimestampGETSchema(ModelSchema): 10 | user: str | None = Field(..., alias="user.username") 11 | 12 | class Config: 13 | model = EpisodeTimestampModel 14 | model_exclude = ["episode", "id"] 15 | 16 | 17 | class EpisodeTimestampPOSTSchema(ModelSchema): 18 | class Config: 19 | model = EpisodeTimestampModel 20 | model_exclude = ["episode", "id", "user", "created_at"] 21 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/schemas/user/__init__.py: -------------------------------------------------------------------------------- 1 | from apps.user.models import CustomUser 2 | from django.contrib.auth import get_user_model 3 | from django.urls import reverse_lazy 4 | from ninja import ModelSchema 5 | 6 | 7 | class UserSchema(ModelSchema): 8 | avatar: str 9 | 10 | class Config: 11 | model = get_user_model() 12 | model_exclude = [ 13 | "password", 14 | "avatar", 15 | ] 16 | 17 | @staticmethod 18 | def resolve_avatar(obj: CustomUser) -> str: 19 | url = reverse_lazy( 20 | "avatar_view", 21 | kwargs={ 22 | "user_id": obj.id, 23 | }, 24 | ) 25 | return f"{url}" 26 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/views/anime/anime_producer.py: -------------------------------------------------------------------------------- 1 | from apps.anime.models import AnimeModel 2 | from apps.producers.models import ProducerModel 3 | from apps.api.http import HttpRequest 4 | from django.shortcuts import get_list_or_404, get_object_or_404 5 | from ninja import Router 6 | 7 | from ...schemas.producers import ProducerGETSchema 8 | 9 | router = Router() 10 | 11 | 12 | @router.get("/{int:anime_id}/producers", response=list[ProducerGETSchema]) 13 | def get_individual_anime_producer_info( 14 | request: HttpRequest, 15 | anime_id: int, 16 | ) -> list[ProducerModel]: 17 | query = get_list_or_404( 18 | get_object_or_404(AnimeModel, pk=anime_id).producers, 19 | ) 20 | return query 21 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/views/anime/anime_studio.py: -------------------------------------------------------------------------------- 1 | from apps.anime.models import AnimeModel 2 | from apps.api.http import HttpRequest 3 | from django.shortcuts import get_list_or_404, get_object_or_404 4 | from ninja import Router 5 | 6 | from ....producers.models import ProducerModel 7 | from ...schemas.producers import ProducerGETSchema 8 | 9 | router = Router() 10 | 11 | 12 | @router.get("/{int:anime_id}/studios", response=list[ProducerGETSchema]) 13 | def get_individual_anime_studio_info( 14 | request: HttpRequest, 15 | anime_id: int, 16 | ) -> list[ProducerModel]: 17 | query = get_list_or_404( 18 | get_object_or_404(AnimeModel, pk=anime_id).studios, 19 | ) 20 | 21 | return query 22 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/filters/anime.py: -------------------------------------------------------------------------------- 1 | from ninja import Schema 2 | 3 | 4 | class AnimeInfoFilters(Schema): 5 | mal_id: int | None = None 6 | kitsu_id: int | None = None 7 | anilist_id: int | None = None 8 | 9 | # Relation based on 10 | # * name 11 | # * name_japanese 12 | # * name_synonyms__name 13 | name: str | None = None 14 | 15 | # Map as closely to model fields as possible. 16 | # So we can do something like 17 | # genres__name__icontains 18 | genres: str | None = None 19 | themes: str | None = None 20 | studios: str | None = None 21 | producers: str | None = None 22 | characters: str | None = None 23 | staffs: str | None = None 24 | -------------------------------------------------------------------------------- /tracker/frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | .env* 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | next-env.d.ts 42 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/views/anime/anime_genre.py: -------------------------------------------------------------------------------- 1 | from apps.anime.models import AnimeModel 2 | from apps.anime.models.anime_genre import AnimeGenreModel 3 | from apps.api.http import HttpRequest 4 | from django.shortcuts import get_list_or_404, get_object_or_404 5 | from ninja import Router 6 | 7 | from ...schemas.anime.anime_genre import AnimeGenreGETSchema 8 | 9 | router = Router() 10 | 11 | 12 | @router.get("/{int:anime_id}/genres", response=list[AnimeGenreGETSchema]) 13 | def get_individual_anime_genre_info( 14 | request: HttpRequest, 15 | anime_id: int, 16 | ) -> list[AnimeGenreModel]: 17 | query = get_list_or_404( 18 | get_object_or_404(AnimeModel, pk=anime_id).genres, 19 | ) 20 | return query 21 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/views/anime/anime_theme.py: -------------------------------------------------------------------------------- 1 | from apps.anime.models import AnimeModel 2 | from apps.anime.models.anime_theme import AnimeThemeModel 3 | from apps.api.http import HttpRequest 4 | from django.shortcuts import get_list_or_404, get_object_or_404 5 | from ninja import Router 6 | 7 | from ...schemas.anime.anime_theme import AnimeThemeGETSchema 8 | 9 | router = Router() 10 | 11 | 12 | @router.get("/{int:anime_id}/themes", response=list[AnimeThemeGETSchema]) 13 | def get_individual_anime_theme_info( 14 | request: HttpRequest, 15 | anime_id: int, 16 | ) -> list[AnimeThemeModel]: 17 | query = get_list_or_404( 18 | get_object_or_404(AnimeModel, pk=anime_id).themes, 19 | ) 20 | return query 21 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0025_alter_animemodel_comments_delete_animecommentmodel.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0rc1 on 2023-12-05 13:05 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("anime", "0024_animecommentmodel_animemodel_comments_and_more"), 9 | ("comments", "0001_initial"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="animemodel", 15 | name="comments", 16 | field=models.ManyToManyField(blank=True, to="comments.commentmodel"), 17 | ), 18 | migrations.DeleteModel( 19 | name="AnimeCommentModel", 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /tracker/backend/.dockerignore: -------------------------------------------------------------------------------- 1 | **/*.egg-info/ 2 | tests_primitive/ 3 | 4 | 5 | # Exclude version control and IDE files 6 | .git/ 7 | .gitignore 8 | .idea/ 9 | .vscode/ 10 | 11 | # Ignore local virtual environments 12 | .venv/ 13 | venv/ 14 | env/ 15 | 16 | # Exclude build, cache, and test artifacts 17 | __pycache__/ 18 | **.py[cod] 19 | .pytest_cache/ 20 | .coverage 21 | dist/ 22 | build/ 23 | 24 | # Logs and local environment files 25 | *.log 26 | logs/ 27 | .env 28 | .env.local 29 | 30 | # Documentation and project files 31 | docs/ 32 | README.md 33 | LICENSE 34 | 35 | # Exclude Docker-related files to prevent them from being copied into the image 36 | Dockerfile 37 | .dockerignore 38 | docker-compose.yml 39 | 40 | # OS-specific files 41 | .DS_Store -------------------------------------------------------------------------------- /backend/django_core/apps/episodes/migrations/0010_alter_episodemodel_episode_comments_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0rc1 on 2023-12-05 13:07 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("comments", "0001_initial"), 9 | ("episodes", "0009_alter_episodecommentmodel_created_at_and_more"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="episodemodel", 15 | name="episode_comments", 16 | field=models.ManyToManyField(blank=True, to="comments.commentmodel"), 17 | ), 18 | migrations.DeleteModel( 19 | name="EpisodeCommentModel", 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0019_animemodel_anime_name_name_japanese_idx.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.4 on 2023-08-29 04:12 2 | 3 | import django.contrib.postgres.indexes 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("anime", "0018_alter_animenamesynonymmodel_name_and_more"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddIndex( 14 | model_name="animemodel", 15 | index=django.contrib.postgres.indexes.GinIndex( 16 | fields=["name", "name_japanese"], 17 | name="anime_name|name_japanese_idx", 18 | opclasses=["gin_trgm_ops", "gin_trgm_ops"], 19 | ), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /backend/django_core/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | 4 | import os 5 | import sys 6 | 7 | 8 | def main() -> None: 9 | """Run administrative tasks.""" 10 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings") 11 | try: 12 | from django.core.management import execute_from_command_line 13 | except ImportError as exc: 14 | raise ImportError( 15 | "Couldn't import Django. Are you sure it's installed and " 16 | "available on your PYTHONPATH environment variable? Did you " 17 | "forget to activate a virtual environment?" 18 | ) from exc 19 | execute_from_command_line(sys.argv) 20 | 21 | 22 | if __name__ == "__main__": 23 | main() 24 | -------------------------------------------------------------------------------- /discord/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "discord" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["baseplate-admin <61817579+baseplate-admin@users.noreply.github.com>"] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.11" 10 | discord-py = "^2.5.2" 11 | sqlalchemy = {extras = ["postgresql-asyncpg"], version = "^2.0.40"} 12 | aiohttp-jinja2 = "^1.6" 13 | alembic = "^1.15.2" 14 | 15 | 16 | [tool.poetry.group.dev.dependencies] 17 | black = "^25.1.0" 18 | aiohttp-devtools = "^1.1" 19 | poethepoet = "^0.33.1" 20 | 21 | [tool.poe.tasks] 22 | dev = 'adev runserver ./web/main.py --app-factory=aiohttp_app' 23 | 24 | [build-system] 25 | build-backend = "poetry.core.masonry.api" 26 | requires = [ 27 | "poetry-core", 28 | ] 29 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/views/anime/anime_endings.py: -------------------------------------------------------------------------------- 1 | from apps.anime.models import AnimeModel 2 | from apps.anime.models.anime_openings_and_endings import AnimeOpeningModel 3 | from apps.api.http import HttpRequest 4 | from django.shortcuts import get_list_or_404, get_object_or_404 5 | from ninja import Router 6 | 7 | from ...schemas.anime.anime_opening_and_ending import AnimeOpeningAndEndingGETSchema 8 | 9 | router = Router() 10 | 11 | 12 | @router.get("/{int:anime_id}/endings", response=list[AnimeOpeningAndEndingGETSchema]) 13 | def get_individual_anime_endings_info( 14 | request: HttpRequest, 15 | anime_id: int, 16 | ) -> list[AnimeOpeningModel]: 17 | query = get_list_or_404( 18 | get_object_or_404(AnimeModel, pk=anime_id).endings, 19 | ) 20 | return query 21 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/views/anime/anime_openings.py: -------------------------------------------------------------------------------- 1 | from apps.anime.models import AnimeModel 2 | from apps.anime.models.anime_openings_and_endings import AnimeOpeningModel 3 | from apps.api.http import HttpRequest 4 | from django.shortcuts import get_list_or_404, get_object_or_404 5 | from ninja import Router 6 | 7 | from ...schemas.anime.anime_opening_and_ending import AnimeOpeningAndEndingGETSchema 8 | 9 | router = Router() 10 | 11 | 12 | @router.get("/{int:anime_id}/openings", response=list[AnimeOpeningAndEndingGETSchema]) 13 | def get_individual_anime_opening_info( 14 | request: HttpRequest, 15 | anime_id: int, 16 | ) -> list[AnimeOpeningModel]: 17 | query = get_list_or_404( 18 | get_object_or_404(AnimeModel, pk=anime_id).openings, 19 | ) 20 | return query 21 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0002_animemodel_staffs_alter_animemodel_rating.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.7 on 2023-03-10 10:47 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("staffs", "0001_initial"), 9 | ("anime", "0001_initial"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="animemodel", 15 | name="staffs", 16 | field=models.ManyToManyField(blank=True, to="staffs.staffmodel"), 17 | ), 18 | migrations.AlterField( 19 | model_name="animemodel", 20 | name="rating", 21 | field=models.CharField(blank=True, default="", max_length=50), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /backend/django_core/apps/user/migrations/0004_alter_customuser_avatar.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.7 on 2023-03-21 08:52 2 | 3 | import dynamic_filenames 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("user", "0003_delete_token"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="customuser", 15 | name="avatar", 16 | field=models.ImageField( 17 | blank=True, 18 | default=None, 19 | null=True, 20 | upload_to=dynamic_filenames.FilePattern( 21 | filename_pattern="avatar/{uuid:s}{ext}" 22 | ), 23 | ), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/views/user/signup.py: -------------------------------------------------------------------------------- 1 | from apps.user.models import CustomUser 2 | from django.conf import settings 3 | from apps.api.http import HttpRequest 4 | from ninja import Form, Router 5 | from pydantic import EmailStr 6 | 7 | from ...schemas.user import UserSchema 8 | 9 | router = Router() 10 | 11 | 12 | @router.post("/signup", response=UserSchema) 13 | def post_user_signup_info( 14 | request: HttpRequest, 15 | username: str = Form( 16 | ..., 17 | regex=rf"^[a-zA-Z0-9_-]+#[0-9]{{{settings.DISCRIMINATOR_LENGTH}}}$", 18 | ), 19 | password: str = Form(...), 20 | email: EmailStr = Form(...), 21 | ) -> CustomUser: 22 | user = CustomUser.objects.create_user( 23 | email=email, 24 | password=password, 25 | username=username, 26 | ) 27 | 28 | return user 29 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/functions/bytes.py: -------------------------------------------------------------------------------- 1 | import struct 2 | 3 | __all__ = ["to_uint32", "from_uint16", "from_uint32", "from_uint64"] 4 | 5 | 6 | async def to_uint32(value: int) -> bytes: 7 | """Convert an integer to a 4-byte unsigned integer in network byte order.""" 8 | return struct.pack(">I", value) 9 | 10 | 11 | async def from_uint16(data: bytes) -> int: 12 | """Convert an 2-byte `unsigned short` buffer into an integer.""" 13 | return struct.unpack(">H", data)[0] 14 | 15 | 16 | async def from_uint32(data: bytes) -> int: 17 | """Convert an 2-byte `unsigned int` buffer into an integer.""" 18 | return struct.unpack(">I", data)[0] 19 | 20 | 21 | def from_uint64(data: bytes) -> int: 22 | """Convert an 8-byte `unsigned long long` buffer into an integer.""" 23 | return struct.unpack(">Q", data)[0] 24 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/transaction/rollback.py: -------------------------------------------------------------------------------- 1 | import copy 2 | from contextlib import contextmanager 3 | from typing import TYPE_CHECKING, Any, Iterator 4 | 5 | if TYPE_CHECKING: 6 | from coreproject_tracker.datastructures import MutableBox 7 | 8 | __all__ = ["rollback_on_exception"] 9 | 10 | 11 | @contextmanager 12 | def rollback_on_exception(*boxes: "MutableBox[Any]") -> Iterator[None]: 13 | """ 14 | Context manager that restores Box values if an exception is raised inside the block. 15 | """ 16 | # Take a deep copy snapshot of each box's value 17 | old_values = [copy.deepcopy(b.value) for b in boxes] 18 | try: 19 | yield 20 | except Exception: 21 | # Roll back each box to its original value 22 | for b, old in zip(boxes, old_values): 23 | b.value = old 24 | raise 25 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/schemas/staffs/__init__.py: -------------------------------------------------------------------------------- 1 | from ninja import ModelSchema 2 | 3 | from ....staffs.models import StaffAlternateNameModel, StaffModel 4 | 5 | 6 | class StaffAlternateNameSchema(ModelSchema): 7 | class Config: 8 | model = StaffAlternateNameModel 9 | model_fields = ["name"] 10 | 11 | 12 | class StaffGETSchema(ModelSchema): 13 | alternate_names: list[StaffAlternateNameSchema] = [] 14 | 15 | class Config: 16 | model = StaffModel 17 | model_fields = "__all__" 18 | 19 | 20 | class StaffPOSTSchema(ModelSchema): 21 | alternate_names: list[StaffAlternateNameSchema] = [] 22 | 23 | class Config: 24 | model = StaffModel 25 | model_exclude = [ 26 | "id", 27 | "created_at", 28 | "updated_at", 29 | "is_locked", 30 | ] 31 | -------------------------------------------------------------------------------- /backend/django_core/apps/comments/migrations/0007_alter_commentmodel_user.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0.2 on 2024-02-23 15:12 2 | 3 | import django.db.models.deletion 4 | from django.conf import settings 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ("comments", "0006_commentmodel_deleted"), 12 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name="commentmodel", 18 | name="user", 19 | field=models.ForeignKey( 20 | blank=True, 21 | null=True, 22 | on_delete=django.db.models.deletion.CASCADE, 23 | to=settings.AUTH_USER_MODEL, 24 | ), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /discord/database.py: -------------------------------------------------------------------------------- 1 | from settings import POSTGRES 2 | from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine 3 | from sqlalchemy.orm import declarative_base, sessionmaker 4 | 5 | NAME = POSTGRES["NAME"] 6 | USER = POSTGRES["USER"] 7 | PASSWORD = POSTGRES["PASSWORD"] 8 | HOST = POSTGRES.get("HOST") or "localhost" 9 | PORT = POSTGRES.get("PORT") or "5432" 10 | 11 | engine = create_async_engine( 12 | f"postgresql+asyncpg://{USER}:{PASSWORD}@{HOST}:{PORT}/{NAME}", echo=True 13 | ) 14 | SessionLocal = sessionmaker(autocommit=False, autoflush=False, class_=AsyncSession) 15 | 16 | django_engine = create_async_engine( 17 | f"postgresql+asyncpg://{USER}:{PASSWORD}@{HOST}:{PORT}/django", echo=True 18 | ) 19 | DjangoSessionLocal = sessionmaker(autocommit=False, autoflush=False, class_=AsyncSession) 20 | 21 | BASE = declarative_base() 22 | -------------------------------------------------------------------------------- /backend/django_core/media/anime/.gitignore: -------------------------------------------------------------------------------- 1 | # JPEG 2 | *.jpg 3 | *.jpeg 4 | *.jpe 5 | *.jif 6 | *.jfif 7 | *.jfi 8 | 9 | # JPEG 2000 10 | *.jp2 11 | *.j2k 12 | *.jpf 13 | *.jpx 14 | *.jpm 15 | *.mj2 16 | 17 | # JPEG XR 18 | *.jxr 19 | *.hdp 20 | *.wdp 21 | 22 | # Graphics Interchange Format 23 | *.gif 24 | 25 | # RAW 26 | *.raw 27 | 28 | # Web P 29 | *.webp 30 | 31 | # Portable Network Graphics 32 | *.png 33 | 34 | # Animated Portable Network Graphics 35 | *.apng 36 | 37 | # Multiple-image Network Graphics 38 | *.mng 39 | 40 | # Tagged Image File Format 41 | *.tiff 42 | *.tif 43 | 44 | # Scalable Vector Graphics 45 | *.svg 46 | *.svgz 47 | 48 | # Portable Document Format 49 | *.pdf 50 | 51 | # X BitMap 52 | *.xbm 53 | 54 | # BMP 55 | *.bmp 56 | *.dib 57 | 58 | # ICO 59 | *.ico 60 | 61 | # 3D Images 62 | *.3dm 63 | *.max 64 | 65 | # AV1 Image File Format 66 | *.avif 67 | -------------------------------------------------------------------------------- /backend/django_core/media/avatars/.gitignore: -------------------------------------------------------------------------------- 1 | # JPEG 2 | *.jpg 3 | *.jpeg 4 | *.jpe 5 | *.jif 6 | *.jfif 7 | *.jfi 8 | 9 | # JPEG 2000 10 | *.jp2 11 | *.j2k 12 | *.jpf 13 | *.jpx 14 | *.jpm 15 | *.mj2 16 | 17 | # JPEG XR 18 | *.jxr 19 | *.hdp 20 | *.wdp 21 | 22 | # Graphics Interchange Format 23 | *.gif 24 | 25 | # RAW 26 | *.raw 27 | 28 | # Web P 29 | *.webp 30 | 31 | # Portable Network Graphics 32 | *.png 33 | 34 | # Animated Portable Network Graphics 35 | *.apng 36 | 37 | # Multiple-image Network Graphics 38 | *.mng 39 | 40 | # Tagged Image File Format 41 | *.tiff 42 | *.tif 43 | 44 | # Scalable Vector Graphics 45 | *.svg 46 | *.svgz 47 | 48 | # Portable Document Format 49 | *.pdf 50 | 51 | # X BitMap 52 | *.xbm 53 | 54 | # BMP 55 | *.bmp 56 | *.dib 57 | 58 | # ICO 59 | *.ico 60 | 61 | # 3D Images 62 | *.3dm 63 | *.max 64 | 65 | # AV1 Image File Format 66 | *.avif 67 | -------------------------------------------------------------------------------- /backend/django_core/media/banner/.gitignore: -------------------------------------------------------------------------------- 1 | # JPEG 2 | *.jpg 3 | *.jpeg 4 | *.jpe 5 | *.jif 6 | *.jfif 7 | *.jfi 8 | 9 | # JPEG 2000 10 | *.jp2 11 | *.j2k 12 | *.jpf 13 | *.jpx 14 | *.jpm 15 | *.mj2 16 | 17 | # JPEG XR 18 | *.jxr 19 | *.hdp 20 | *.wdp 21 | 22 | # Graphics Interchange Format 23 | *.gif 24 | 25 | # RAW 26 | *.raw 27 | 28 | # Web P 29 | *.webp 30 | 31 | # Portable Network Graphics 32 | *.png 33 | 34 | # Animated Portable Network Graphics 35 | *.apng 36 | 37 | # Multiple-image Network Graphics 38 | *.mng 39 | 40 | # Tagged Image File Format 41 | *.tiff 42 | *.tif 43 | 44 | # Scalable Vector Graphics 45 | *.svg 46 | *.svgz 47 | 48 | # Portable Document Format 49 | *.pdf 50 | 51 | # X BitMap 52 | *.xbm 53 | 54 | # BMP 55 | *.bmp 56 | *.dib 57 | 58 | # ICO 59 | *.ico 60 | 61 | # 3D Images 62 | *.3dm 63 | *.max 64 | 65 | # AV1 Image File Format 66 | *.avif 67 | -------------------------------------------------------------------------------- /backend/django_core/media/cover/.gitignore: -------------------------------------------------------------------------------- 1 | # JPEG 2 | *.jpg 3 | *.jpeg 4 | *.jpe 5 | *.jif 6 | *.jfif 7 | *.jfi 8 | 9 | # JPEG 2000 10 | *.jp2 11 | *.j2k 12 | *.jpf 13 | *.jpx 14 | *.jpm 15 | *.mj2 16 | 17 | # JPEG XR 18 | *.jxr 19 | *.hdp 20 | *.wdp 21 | 22 | # Graphics Interchange Format 23 | *.gif 24 | 25 | # RAW 26 | *.raw 27 | 28 | # Web P 29 | *.webp 30 | 31 | # Portable Network Graphics 32 | *.png 33 | 34 | # Animated Portable Network Graphics 35 | *.apng 36 | 37 | # Multiple-image Network Graphics 38 | *.mng 39 | 40 | # Tagged Image File Format 41 | *.tiff 42 | *.tif 43 | 44 | # Scalable Vector Graphics 45 | *.svg 46 | *.svgz 47 | 48 | # Portable Document Format 49 | *.pdf 50 | 51 | # X BitMap 52 | *.xbm 53 | 54 | # BMP 55 | *.bmp 56 | *.dib 57 | 58 | # ICO 59 | *.ico 60 | 61 | # 3D Images 62 | *.3dm 63 | *.max 64 | 65 | # AV1 Image File Format 66 | *.avif 67 | -------------------------------------------------------------------------------- /backend/django_core/media/ending/.gitignore: -------------------------------------------------------------------------------- 1 | # JPEG 2 | *.jpg 3 | *.jpeg 4 | *.jpe 5 | *.jif 6 | *.jfif 7 | *.jfi 8 | 9 | # JPEG 2000 10 | *.jp2 11 | *.j2k 12 | *.jpf 13 | *.jpx 14 | *.jpm 15 | *.mj2 16 | 17 | # JPEG XR 18 | *.jxr 19 | *.hdp 20 | *.wdp 21 | 22 | # Graphics Interchange Format 23 | *.gif 24 | 25 | # RAW 26 | *.raw 27 | 28 | # Web P 29 | *.webp 30 | 31 | # Portable Network Graphics 32 | *.png 33 | 34 | # Animated Portable Network Graphics 35 | *.apng 36 | 37 | # Multiple-image Network Graphics 38 | *.mng 39 | 40 | # Tagged Image File Format 41 | *.tiff 42 | *.tif 43 | 44 | # Scalable Vector Graphics 45 | *.svg 46 | *.svgz 47 | 48 | # Portable Document Format 49 | *.pdf 50 | 51 | # X BitMap 52 | *.xbm 53 | 54 | # BMP 55 | *.bmp 56 | *.dib 57 | 58 | # ICO 59 | *.ico 60 | 61 | # 3D Images 62 | *.3dm 63 | *.max 64 | 65 | # AV1 Image File Format 66 | *.avif 67 | -------------------------------------------------------------------------------- /backend/django_core/media/opening/.gitignore: -------------------------------------------------------------------------------- 1 | # JPEG 2 | *.jpg 3 | *.jpeg 4 | *.jpe 5 | *.jif 6 | *.jfif 7 | *.jfi 8 | 9 | # JPEG 2000 10 | *.jp2 11 | *.j2k 12 | *.jpf 13 | *.jpx 14 | *.jpm 15 | *.mj2 16 | 17 | # JPEG XR 18 | *.jxr 19 | *.hdp 20 | *.wdp 21 | 22 | # Graphics Interchange Format 23 | *.gif 24 | 25 | # RAW 26 | *.raw 27 | 28 | # Web P 29 | *.webp 30 | 31 | # Portable Network Graphics 32 | *.png 33 | 34 | # Animated Portable Network Graphics 35 | *.apng 36 | 37 | # Multiple-image Network Graphics 38 | *.mng 39 | 40 | # Tagged Image File Format 41 | *.tiff 42 | *.tif 43 | 44 | # Scalable Vector Graphics 45 | *.svg 46 | *.svgz 47 | 48 | # Portable Document Format 49 | *.pdf 50 | 51 | # X BitMap 52 | *.xbm 53 | 54 | # BMP 55 | *.bmp 56 | *.dib 57 | 58 | # ICO 59 | *.ico 60 | 61 | # 3D Images 62 | *.3dm 63 | *.max 64 | 65 | # AV1 Image File Format 66 | *.avif 67 | -------------------------------------------------------------------------------- /backend/django_core/media/staffs/.gitignore: -------------------------------------------------------------------------------- 1 | # JPEG 2 | *.jpg 3 | *.jpeg 4 | *.jpe 5 | *.jif 6 | *.jfif 7 | *.jfi 8 | 9 | # JPEG 2000 10 | *.jp2 11 | *.j2k 12 | *.jpf 13 | *.jpx 14 | *.jpm 15 | *.mj2 16 | 17 | # JPEG XR 18 | *.jxr 19 | *.hdp 20 | *.wdp 21 | 22 | # Graphics Interchange Format 23 | *.gif 24 | 25 | # RAW 26 | *.raw 27 | 28 | # Web P 29 | *.webp 30 | 31 | # Portable Network Graphics 32 | *.png 33 | 34 | # Animated Portable Network Graphics 35 | *.apng 36 | 37 | # Multiple-image Network Graphics 38 | *.mng 39 | 40 | # Tagged Image File Format 41 | *.tiff 42 | *.tif 43 | 44 | # Scalable Vector Graphics 45 | *.svg 46 | *.svgz 47 | 48 | # Portable Document Format 49 | *.pdf 50 | 51 | # X BitMap 52 | *.xbm 53 | 54 | # BMP 55 | *.bmp 56 | *.dib 57 | 58 | # ICO 59 | *.ico 60 | 61 | # 3D Images 62 | *.3dm 63 | *.max 64 | 65 | # AV1 Image File Format 66 | *.avif 67 | -------------------------------------------------------------------------------- /tracker/backend/tests_primitive/udp_client.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | # Define server address and port 4 | SERVER_ADDRESS = "127.0.0.1" # Change to the server's IP if needed 5 | SERVER_PORT = 5000 # Change to the appropriate port 6 | 7 | # Create a UDP socket 8 | client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 9 | 10 | try: 11 | while True: 12 | message = input("Enter message to send (or 'exit' to quit): ") 13 | if message.lower() == "exit": 14 | break 15 | 16 | # Send message to server 17 | client_socket.sendto(message.encode(), (SERVER_ADDRESS, SERVER_PORT)) 18 | 19 | # Receive response from server 20 | data, server = client_socket.recvfrom(1024) 21 | print(f"Received from server: {data.decode()}") 22 | 23 | finally: 24 | client_socket.close() 25 | print("Connection closed.") 26 | -------------------------------------------------------------------------------- /backend/django_core/media/characters/.gitignore: -------------------------------------------------------------------------------- 1 | # JPEG 2 | *.jpg 3 | *.jpeg 4 | *.jpe 5 | *.jif 6 | *.jfif 7 | *.jfi 8 | 9 | # JPEG 2000 10 | *.jp2 11 | *.j2k 12 | *.jpf 13 | *.jpx 14 | *.jpm 15 | *.mj2 16 | 17 | # JPEG XR 18 | *.jxr 19 | *.hdp 20 | *.wdp 21 | 22 | # Graphics Interchange Format 23 | *.gif 24 | 25 | # RAW 26 | *.raw 27 | 28 | # Web P 29 | *.webp 30 | 31 | # Portable Network Graphics 32 | *.png 33 | 34 | # Animated Portable Network Graphics 35 | *.apng 36 | 37 | # Multiple-image Network Graphics 38 | *.mng 39 | 40 | # Tagged Image File Format 41 | *.tiff 42 | *.tif 43 | 44 | # Scalable Vector Graphics 45 | *.svg 46 | *.svgz 47 | 48 | # Portable Document Format 49 | *.pdf 50 | 51 | # X BitMap 52 | *.xbm 53 | 54 | # BMP 55 | *.bmp 56 | *.dib 57 | 58 | # ICO 59 | *.ico 60 | 61 | # 3D Images 62 | *.3dm 63 | *.max 64 | 65 | # AV1 Image File Format 66 | *.avif 67 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0007_animemodel_created_at_alter_animemodel_updated.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-13 14:13 2 | 3 | import django.utils.timezone 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("anime", "0006_merge_20230326_1819"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="animemodel", 15 | name="created_at", 16 | field=models.DateTimeField( 17 | auto_now_add=True, default=django.utils.timezone.now 18 | ), 19 | preserve_default=False, 20 | ), 21 | migrations.AlterField( 22 | model_name="animemodel", 23 | name="updated", 24 | field=models.DateTimeField(auto_now=True), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /backend/django_core/media/episode_cover/.gitignore: -------------------------------------------------------------------------------- 1 | # JPEG 2 | *.jpg 3 | *.jpeg 4 | *.jpe 5 | *.jif 6 | *.jfif 7 | *.jfi 8 | 9 | # JPEG 2000 10 | *.jp2 11 | *.j2k 12 | *.jpf 13 | *.jpx 14 | *.jpm 15 | *.mj2 16 | 17 | # JPEG XR 18 | *.jxr 19 | *.hdp 20 | *.wdp 21 | 22 | # Graphics Interchange Format 23 | *.gif 24 | 25 | # RAW 26 | *.raw 27 | 28 | # Web P 29 | *.webp 30 | 31 | # Portable Network Graphics 32 | *.png 33 | 34 | # Animated Portable Network Graphics 35 | *.apng 36 | 37 | # Multiple-image Network Graphics 38 | *.mng 39 | 40 | # Tagged Image File Format 41 | *.tiff 42 | *.tif 43 | 44 | # Scalable Vector Graphics 45 | *.svg 46 | *.svgz 47 | 48 | # Portable Document Format 49 | *.pdf 50 | 51 | # X BitMap 52 | *.xbm 53 | 54 | # BMP 55 | *.bmp 56 | *.dib 57 | 58 | # ICO 59 | *.ico 60 | 61 | # 3D Images 62 | *.3dm 63 | *.max 64 | 65 | # AV1 Image File Format 66 | *.avif 67 | -------------------------------------------------------------------------------- /backend/django_core/apps/producers/migrations/0002_producermodel_created_at_producermodel_updated_at.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-13 14:16 2 | 3 | import django.utils.timezone 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("producers", "0001_initial"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="producermodel", 15 | name="created_at", 16 | field=models.DateTimeField( 17 | auto_now_add=True, default=django.utils.timezone.now 18 | ), 19 | preserve_default=False, 20 | ), 21 | migrations.AddField( 22 | model_name="producermodel", 23 | name="updated_at", 24 | field=models.DateTimeField(auto_now=True), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /backend/django_core/apps/characters/migrations/0002_charactermodel_created_at_charactermodel_updated_at.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-13 14:13 2 | 3 | import django.utils.timezone 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("characters", "0001_initial"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="charactermodel", 15 | name="created_at", 16 | field=models.DateTimeField( 17 | auto_now_add=True, default=django.utils.timezone.now 18 | ), 19 | preserve_default=False, 20 | ), 21 | migrations.AddField( 22 | model_name="charactermodel", 23 | name="updated_at", 24 | field=models.DateTimeField(auto_now=True), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /backend/django_core/apps/staffs/migrations/0003_staffmodel_created_at_staffmodel_updated_at.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-13 14:16 2 | 3 | import django.utils.timezone 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("staffs", "0002_alter_staffalternatenamemodel_options"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="staffmodel", 15 | name="created_at", 16 | field=models.DateTimeField( 17 | auto_now_add=True, default=django.utils.timezone.now 18 | ), 19 | preserve_default=False, 20 | ), 21 | migrations.AddField( 22 | model_name="staffmodel", 23 | name="updated_at", 24 | field=models.DateTimeField(auto_now=True), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /tracker/frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"] 23 | } 24 | }, 25 | "include": [ 26 | "svgr.d.ts", 27 | "next-env.d.ts", 28 | "**/*.ts", 29 | "**/*.tsx", 30 | ".next/types/**/*.ts", 31 | "src/app/test/page.tsx", 32 | "src/app/torrent/page.jsx" 33 | ], 34 | "exclude": ["node_modules"] 35 | } 36 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0018_alter_animenamesynonymmodel_name_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.4 on 2023-08-29 04:12 2 | 3 | import django.contrib.postgres.indexes 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("anime", "0017_remove_animemodel_anime_name_name_japanese_idx_and_more"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="animenamesynonymmodel", 15 | name="name", 16 | field=models.CharField(max_length=100, unique=True), 17 | ), 18 | migrations.AddIndex( 19 | model_name="animenamesynonymmodel", 20 | index=django.contrib.postgres.indexes.GinIndex( 21 | fields=["name"], name="anime_name_synonym_idx", opclasses=["gin_trgm_ops"] 22 | ), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /backend/django_core/apps/episodes/migrations/0004_episodemodel_created_at_episodemodel_updated_at.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-13 14:16 2 | 3 | import django.utils.timezone 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("episodes", "0003_alter_episodecommentmodel_options_and_more"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="episodemodel", 15 | name="created_at", 16 | field=models.DateTimeField( 17 | auto_now_add=True, default=django.utils.timezone.now 18 | ), 19 | preserve_default=False, 20 | ), 21 | migrations.AddField( 22 | model_name="episodemodel", 23 | name="updated_at", 24 | field=models.DateTimeField(auto_now=True), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0014_alter_animegenremodel_is_locked_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-25 19:40 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("anime", "0013_animethememodel_description"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="animegenremodel", 14 | name="is_locked", 15 | field=models.BooleanField(default=False), 16 | ), 17 | migrations.AlterField( 18 | model_name="animemodel", 19 | name="is_locked", 20 | field=models.BooleanField(default=False), 21 | ), 22 | migrations.AlterField( 23 | model_name="animethememodel", 24 | name="is_locked", 25 | field=models.BooleanField(default=False), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/models.py: -------------------------------------------------------------------------------- 1 | from functools import partial 2 | 3 | from apps.user.models import CustomUser 4 | from django.db import models 5 | from django.utils.crypto import get_random_string 6 | from django.utils.translation import gettext_lazy as _ 7 | 8 | 9 | class Token(models.Model): 10 | token = models.CharField( 11 | default=partial( 12 | get_random_string, 13 | 16, 14 | ), 15 | unique=True, 16 | max_length=16, 17 | editable=False, 18 | ) 19 | user = models.OneToOneField(CustomUser, on_delete=models.CASCADE, related_name="user") 20 | 21 | def __str__(self) -> str: 22 | return f"User : {self.user.username} | Token : {self.token}" 23 | 24 | class Meta: 25 | indexes = [ 26 | models.Index(fields=["token"]), 27 | ] 28 | verbose_name = _("token") 29 | verbose_name_plural = _("tokens") 30 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0015_animemodel_anime_anime_name_301f1e_gin_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.2 on 2023-06-17 14:01 2 | 3 | import django.contrib.postgres.indexes 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("anime", "0014_alter_animegenremodel_is_locked_and_more"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddIndex( 14 | model_name="animemodel", 15 | index=django.contrib.postgres.indexes.GinIndex( 16 | fields=["name", "name_japanese"], name="anime_anime_name_301f1e_gin" 17 | ), 18 | ), 19 | migrations.AddIndex( 20 | model_name="animenamesynonymmodel", 21 | index=django.contrib.postgres.indexes.GinIndex( 22 | fields=["name"], name="anime_anime_name_81fa03_gin" 23 | ), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/views/user/login.py: -------------------------------------------------------------------------------- 1 | from apps.api.models import Token 2 | from apps.user.backends import EmailOrUsernameModelBackend 3 | from django.http import Http404, HttpRequest 4 | from django_ratelimit.decorators import ratelimit 5 | from ninja import Form, Router 6 | from pydantic import EmailStr 7 | 8 | from ...schemas.user.login import LoginSchema 9 | 10 | router = Router() 11 | 12 | 13 | @router.post("/login", response=LoginSchema) 14 | @ratelimit(key="ip", rate="1/s") 15 | def post_user_login_info( 16 | request: HttpRequest, 17 | username: str | EmailStr = Form(...), 18 | password: str = Form(...), 19 | ) -> Token: 20 | user = EmailOrUsernameModelBackend.get_user_given_username_and_password( 21 | username, 22 | password, 23 | ) 24 | 25 | if not user: 26 | raise Http404("No such user exists") 27 | 28 | token, _ = Token.objects.get_or_create(user=user) 29 | return token 30 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0026_alter_animegenremodel_is_locked_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0.2 on 2024-02-21 03:40 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("anime", "0025_alter_animemodel_comments_delete_animecommentmodel"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="animegenremodel", 15 | name="is_locked", 16 | field=models.BooleanField(db_default=False), 17 | ), 18 | migrations.AlterField( 19 | model_name="animemodel", 20 | name="is_locked", 21 | field=models.BooleanField(db_default=False), 22 | ), 23 | migrations.AlterField( 24 | model_name="animethememodel", 25 | name="is_locked", 26 | field=models.BooleanField(db_default=False), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/models/anime_genre.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from mixins.models.created_at import CreatedAtMixin 3 | from mixins.models.is_locked import IsLockedMixin 4 | from mixins.models.updated_at import UpdatedAtMixin 5 | 6 | # Create your models here. 7 | 8 | TYPE_CHOICES = [ 9 | ("Anime", "anime"), 10 | ("Manga", "manga"), 11 | ] 12 | 13 | 14 | class AnimeGenreModel(UpdatedAtMixin, CreatedAtMixin, IsLockedMixin): 15 | mal_id = models.IntegerField(unique=True, blank=False, null=False) 16 | name = models.CharField(unique=True, max_length=50, default="", null=False, blank=False) 17 | type = models.CharField( 18 | max_length=50, choices=TYPE_CHOICES, default="", null=False, blank=False 19 | ) 20 | description = models.TextField(null=True, blank=False) 21 | 22 | def __str__(self) -> str: 23 | return f"{self.pk}. {self.name} ({self.type})" 24 | 25 | class Meta: 26 | verbose_name = "Anime Genre" 27 | -------------------------------------------------------------------------------- /backend/django_core/templates/user/user_does_not_exist.php: -------------------------------------------------------------------------------- 1 | 12 | 13 | User not found {{ user_id }} 14 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0023_alter_animegenremodel_is_locked_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0b1 on 2023-10-29 09:54 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("anime", "0022_alter_animegenremodel_created_at_and_more"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="animegenremodel", 14 | name="is_locked", 15 | field=models.BooleanField(db_default=models.Value(False)), 16 | ), 17 | migrations.AlterField( 18 | model_name="animemodel", 19 | name="is_locked", 20 | field=models.BooleanField(db_default=models.Value(False)), 21 | ), 22 | migrations.AlterField( 23 | model_name="animethememodel", 24 | name="is_locked", 25 | field=models.BooleanField(db_default=models.Value(False)), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/models/anime_theme.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from mixins.models.created_at import CreatedAtMixin 3 | from mixins.models.is_locked import IsLockedMixin 4 | from mixins.models.updated_at import UpdatedAtMixin 5 | 6 | # Create your models here. 7 | 8 | TYPE_CHOICES = [ 9 | ("Anime", "anime"), 10 | ("Manga", "manga"), 11 | ] 12 | 13 | 14 | class AnimeThemeModel(UpdatedAtMixin, CreatedAtMixin, IsLockedMixin): 15 | mal_id = models.IntegerField(unique=True, blank=False, null=False) 16 | name = models.CharField(unique=True, max_length=50, default="", null=False, blank=False) 17 | type = models.CharField( 18 | max_length=50, 19 | choices=TYPE_CHOICES, 20 | default="", 21 | null=False, 22 | blank=False, 23 | ) 24 | description = models.TextField(null=True, blank=False) 25 | 26 | def __str__(self) -> str: 27 | return f"{self.pk}. {self.name} ({self.type})" 28 | 29 | class Meta: 30 | verbose_name = "Anime Theme" 31 | -------------------------------------------------------------------------------- /backend/django_core/apps/comments/migrations/0003_commentmodel_dislikes_commentmodel_likes.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0rc1 on 2023-12-05 18:45 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("comments", "0002_alter_commentmodel_options"), 10 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name="commentmodel", 16 | name="dislikes", 17 | field=models.ManyToManyField( 18 | blank=True, default=[], related_name="dislikes", to=settings.AUTH_USER_MODEL 19 | ), 20 | ), 21 | migrations.AddField( 22 | model_name="commentmodel", 23 | name="likes", 24 | field=models.ManyToManyField( 25 | blank=True, default=[], related_name="likes", to=settings.AUTH_USER_MODEL 26 | ), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /backend/django_core/apps/comments/migrations/0004_alter_commentmodel_dislikes_alter_commentmodel_likes.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0 on 2023-12-06 03:56 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("comments", "0003_commentmodel_dislikes_commentmodel_likes"), 10 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name="commentmodel", 16 | name="dislikes", 17 | field=models.ManyToManyField( 18 | blank=True, related_name="dislikes", to=settings.AUTH_USER_MODEL 19 | ), 20 | ), 21 | migrations.AlterField( 22 | model_name="commentmodel", 23 | name="likes", 24 | field=models.ManyToManyField( 25 | blank=True, related_name="likes", to=settings.AUTH_USER_MODEL 26 | ), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /backend/django_core/apps/staffs/migrations/0006_staffalternatenamemodel_staff_alternate_idx_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.4 on 2023-08-29 04:05 2 | 3 | import django.contrib.postgres.indexes 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("staffs", "0005_alter_staffmodel_is_locked"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddIndex( 14 | model_name="staffalternatenamemodel", 15 | index=django.contrib.postgres.indexes.GinIndex( 16 | fields=["name"], name="staff_alternate_idx", opclasses=["gin_trgm_ops"] 17 | ), 18 | ), 19 | migrations.AddIndex( 20 | model_name="staffmodel", 21 | index=django.contrib.postgres.indexes.GinIndex( 22 | fields=["name", "given_name", "family_name"], 23 | name="staff_name_idx", 24 | opclasses=["gin_trgm_ops", "gin_trgm_ops", "gin_trgm_ops"], 25 | ), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /backend/django_core/utilities/format.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | KOKORO_WORD_COLOR_MAP = { 4 | "-": "white", 5 | "a": "white", 6 | "c": "white", 7 | "h": "white", 8 | "k": "white", 9 | "n": "white", 10 | "o": "yellow", 11 | "r": "white", 12 | } 13 | 14 | TAILWIND_COLOR_MAP = { 15 | "white": "text-white", 16 | "yellow": "text-warning", 17 | } 18 | 19 | WORD = "kokoro-chan" 20 | 21 | 22 | def format_kokoro_color(input: str) -> str: 23 | KOKORO_REGEX = re.compile(rf"{WORD}", re.M) 24 | 25 | color_formated_string = "" 26 | 27 | # This variable has ['kokoro-chan'] 28 | re.findall(KOKORO_REGEX, input) 29 | 30 | # Parse each letter in the word 31 | for letter in WORD: 32 | color = KOKORO_WORD_COLOR_MAP[letter] 33 | color_class = TAILWIND_COLOR_MAP[color] 34 | color_formated_string += f"{letter}" 35 | 36 | # Color the font. 37 | text = re.sub(KOKORO_REGEX, color_formated_string, input) 38 | # Hyperlink the home 39 | return text 40 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/signals.py: -------------------------------------------------------------------------------- 1 | from typing import TypedDict, Unpack 2 | 3 | from apps.anime.tasks import set_field_color 4 | from django.db.models.signals import post_save 5 | from django.dispatch import receiver 6 | 7 | from .models import AnimeModel 8 | 9 | 10 | class DjangoInstance(TypedDict): 11 | instance: AnimeModel 12 | 13 | 14 | @receiver(post_save, sender=AnimeModel) 15 | def banner_background_color_handler( 16 | **kwargs: Unpack[DjangoInstance], 17 | ) -> None: 18 | instance = kwargs["instance"] 19 | 20 | # Set Background Banner Image Color 21 | if (hasattr(instance, "banner")) and not instance.banner_background_color: 22 | set_field_color.delay( 23 | instance.pk, 24 | "banner_background_color", 25 | "banner", 26 | ) 27 | 28 | # Set Background Cover Image Color 29 | if (hasattr(instance, "cover")) and not instance.cover_background_color: 30 | set_field_color.delay( 31 | instance.pk, 32 | "cover_background_color", 33 | "cover", 34 | ) 35 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/app.py: -------------------------------------------------------------------------------- 1 | from quart import Quart 2 | from quart_cors import cors 3 | 4 | try: 5 | from flask_orjson import OrjsonProvider # type: ignore[import] 6 | 7 | HAS_FLASK_ORJSON = True 8 | except ImportError: 9 | HAS_FLASK_ORJSON = False 10 | 11 | from coreproject_tracker.envs import REDIS_URI 12 | from coreproject_tracker.servers import http_blueprint, ws_blueprint 13 | 14 | 15 | def make_app() -> Quart: 16 | app = Quart(__name__) 17 | app = cors(app, allow_origin="*") 18 | 19 | if HAS_FLASK_ORJSON: 20 | app.json = OrjsonProvider(app) # type: ignore 21 | 22 | from coreproject_tracker.singletons.redis import RedisHandler 23 | 24 | redis_manager = RedisHandler(REDIS_URI) 25 | 26 | @app.before_serving 27 | async def before_serving(): 28 | await redis_manager.init_redis() 29 | 30 | @app.after_serving 31 | async def after_serving(): 32 | await redis_manager.close_redis() 33 | 34 | app.register_blueprint(http_blueprint) 35 | app.register_blueprint(ws_blueprint) 36 | 37 | return app 38 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/functions/__init__.py: -------------------------------------------------------------------------------- 1 | from .array import get_n_random_items as get_n_random_items 2 | from .bytes import ( 3 | from_uint16 as from_uint16, 4 | from_uint32 as from_uint32, 5 | from_uint64 as from_uint64, 6 | to_uint32 as to_uint32, 7 | ) 8 | from .convertion import ( 9 | bytes_to_bin_str as bytes_to_bin_str, 10 | hex_str_to_bin_str as hex_str_to_bin_str, 11 | ) 12 | from .dictionary import decode_dictionary as decode_dictionary 13 | from .events import ( 14 | convert_event_id_to_event_enum as convert_event_id_to_event_enum, 15 | convert_event_name_to_event_enum as convert_event_name_to_event_enum, 16 | ) 17 | from .ip import ( 18 | addr_to_ip_port as addr_to_ip_port, 19 | addrs_to_compact as addrs_to_compact, 20 | check_ip_type as check_ip_type, 21 | convert_ipv4_coded_ipv6_to_ipv4 as convert_ipv4_coded_ipv6_to_ipv4, 22 | convert_str_to_ip_object as convert_str_to_ip_object, 23 | ) 24 | from .redis import ( 25 | get_all_hash_keys as get_all_hash_keys, 26 | hdel as hdel, 27 | hget as hget, 28 | hset as hset, 29 | ) 30 | -------------------------------------------------------------------------------- /discord/web/main.py: -------------------------------------------------------------------------------- 1 | from collections.abc import AsyncGenerator 2 | 3 | import aiohttp_jinja2 4 | import jinja2 5 | from aiohttp import web 6 | from database import DjangoSessionLocal, SessionLocal, engine 7 | from settings import TEMPLATE_DIRS 8 | 9 | routes = web.RouteTableDef() 10 | 11 | 12 | async def db_context( 13 | app: web.Application, 14 | ) -> AsyncGenerator[None, SessionLocal]: 15 | app["db"] = SessionLocal(bind=engine) 16 | yield 17 | await engine.dispose() 18 | 19 | 20 | async def django_db_context( 21 | app: web.Application, 22 | ) -> AsyncGenerator[None, SessionLocal]: 23 | app["django_db"] = DjangoSessionLocal(bind=engine) 24 | yield 25 | await engine.dispose() 26 | 27 | 28 | async def aiohttp_app() -> web.Application: 29 | app = web.Application() 30 | aiohttp_jinja2.setup(app, loader=jinja2.FileSystemLoader(TEMPLATE_DIRS)) 31 | app.cleanup_ctx.append(db_context) 32 | app.cleanup_ctx.append(django_db_context) 33 | 34 | return app 35 | 36 | 37 | if __name__ == "__main__": 38 | web.run_app(aiohttp_app()) 39 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0011_animegenremodel_created_at_animegenremodel_is_locked_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-24 14:44 2 | 3 | import django.utils.timezone 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("anime", "0010_animemodel_is_locked"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="animegenremodel", 15 | name="created_at", 16 | field=models.DateTimeField( 17 | auto_now_add=True, default=django.utils.timezone.now 18 | ), 19 | preserve_default=False, 20 | ), 21 | migrations.AddField( 22 | model_name="animegenremodel", 23 | name="is_locked", 24 | field=models.BooleanField(default=False), 25 | preserve_default=False, 26 | ), 27 | migrations.AddField( 28 | model_name="animegenremodel", 29 | name="updated_at", 30 | field=models.DateTimeField(auto_now=True), 31 | ), 32 | ] 33 | -------------------------------------------------------------------------------- /backend/django_core/apps/producers/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from mixins.models.created_at import CreatedAtMixin 3 | from mixins.models.is_locked import IsLockedMixin 4 | from mixins.models.updated_at import UpdatedAtMixin 5 | 6 | # Create your models here. 7 | 8 | 9 | class ProducerModel(CreatedAtMixin, UpdatedAtMixin, IsLockedMixin): 10 | mal_id = models.IntegerField(unique=True, blank=True, null=True) 11 | kitsu_id = models.IntegerField(unique=True, blank=True, null=True) 12 | name = models.CharField( 13 | max_length=128, 14 | default="", 15 | null=False, 16 | blank=False, 17 | ) 18 | # Titles 19 | name_japanese = models.CharField( 20 | max_length=128, 21 | default="", 22 | null=True, 23 | blank=True, 24 | ) 25 | established = models.DateTimeField(null=True, blank=True) 26 | about = models.TextField(null=True, blank=True) 27 | 28 | def __str__(self) -> str: 29 | return f"{self.pk}. {self.name} | Mal_id : {self.mal_id}" 30 | 31 | class Meta: 32 | verbose_name = "Producer" 33 | verbose_name_plural = "Producers" 34 | -------------------------------------------------------------------------------- /backend/django_core/apps/user/migrations/0008_alter_customuser_username.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.3 on 2023-07-15 01:53 2 | 3 | import apps.user.validators.username 4 | import django.core.validators 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [ 10 | ("user", "0007_alter_customuser_unique_together_and_more"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name="customuser", 16 | name="username", 17 | field=models.CharField( 18 | error_messages={"unique": "A user with that username already exists."}, 19 | help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.", 20 | max_length=150, 21 | unique=True, 22 | validators=[ 23 | apps.user.validators.username.username_validator, 24 | django.core.validators.RegexValidator("^[a-zA-Z0-9_-]+#[0-9]{4}$"), 25 | ], 26 | verbose_name="username", 27 | ), 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0012_animethememodel_created_at_animethememodel_is_locked_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-24 15:06 2 | 3 | import django.utils.timezone 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("anime", "0011_animegenremodel_created_at_animegenremodel_is_locked_and_more"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="animethememodel", 15 | name="created_at", 16 | field=models.DateTimeField( 17 | auto_now_add=True, default=django.utils.timezone.now 18 | ), 19 | preserve_default=False, 20 | ), 21 | migrations.AddField( 22 | model_name="animethememodel", 23 | name="is_locked", 24 | field=models.BooleanField(default=False), 25 | preserve_default=False, 26 | ), 27 | migrations.AddField( 28 | model_name="animethememodel", 29 | name="updated_at", 30 | field=models.DateTimeField(auto_now=True), 31 | ), 32 | ] 33 | -------------------------------------------------------------------------------- /.github/workflows/tracker.yml: -------------------------------------------------------------------------------- 1 | name: tracker 2 | 3 | on: 4 | push: 5 | branches: ['master'] 6 | paths: ['tracker/**'] 7 | pull_request: 8 | branches: ['master'] 9 | paths: ['tracker/**'] 10 | 11 | jobs: 12 | build-frontend: 13 | runs-on: ${{ matrix.os }} 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | os: 18 | - ubuntu-latest 19 | - macos-latest 20 | - windows-latest 21 | name: Build on ${{ matrix.os }} 22 | steps: 23 | - name: Checkout repository 24 | uses: actions/checkout@v6 25 | 26 | - name: Setup Node.js 27 | uses: actions/setup-node@v6 28 | with: 29 | cache: 'npm' 30 | cache-dependency-path: tracker/frontend/package-lock.json 31 | 32 | - name: Install frontend dependencies 33 | run: npm i 34 | working-directory: tracker/frontend 35 | 36 | - name: Build frontend 37 | run: npm run build 38 | working-directory: tracker/frontend 39 | -------------------------------------------------------------------------------- /tracker/frontend/src/hooks/useHttpData.ts: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import useSWR from "swr"; 4 | import { HTTP_ANNOUNCE_ENDPOINT } from "@/constants/url"; 5 | import { URL } from "node:url"; 6 | // Fake url: http://localhost:5000/?info_hash=%234筗%E1%83{r%DD^%1a%0a%F2%03%D0%D9l%DBj&peer_id=-qB5040-6CdLzJpk(Zrn&port=52629&uploaded=0&downloaded=0&left=0&corrupt=0&key=A7E1171F&event=started&numwant=200&compact=1&no_peer_id=1&supportcrypto=1&redundant=0 7 | const fetcher = async (url: URL) => { 8 | const res = await fetch(url); 9 | 10 | const text = await res.text(); 11 | 12 | return { 13 | status: res.status, 14 | body: text, 15 | }; 16 | }; 17 | 18 | export function useHttpData() { 19 | const URL = `${HTTP_ANNOUNCE_ENDPOINT}?info_hash=%234筗%E1%83{r%DD^%1a%0a%F2%03%D0%D9l%DBj&peer_id=-qB5040-6CdLzJpk(Zrn&port=52629&uploaded=0&downloaded=0&left=0&corrupt=0&key=A7E1171F&event=started&numwant=200&compact=1&no_peer_id=1&supportcrypto=1&redundant=0`; 20 | const { data, error, isLoading } = useSWR(URL, fetcher); 21 | 22 | return { 23 | data: data?.body, 24 | status: data?.status, 25 | isLoading: isLoading, 26 | isError: error, 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /backend/django_core/apps/episodes/migrations/0005_remove_episodecommentmodel_comment_added_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-13 14:48 2 | 3 | import django.utils.timezone 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("episodes", "0004_episodemodel_created_at_episodemodel_updated_at"), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name="episodecommentmodel", 15 | name="comment_added", 16 | ), 17 | migrations.AddField( 18 | model_name="episodecommentmodel", 19 | name="created_at", 20 | field=models.DateTimeField( 21 | auto_now_add=True, default=django.utils.timezone.now 22 | ), 23 | preserve_default=False, 24 | ), 25 | migrations.AddField( 26 | model_name="episodetimestampmodel", 27 | name="created_at", 28 | field=models.DateTimeField( 29 | auto_now_add=True, default=django.utils.timezone.now 30 | ), 31 | preserve_default=False, 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0021_alter_animemodel_banner_background_color_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.5 on 2023-09-09 04:19 2 | 3 | import colorfield.fields 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("anime", "0020_alter_animegenremodel_type"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="animemodel", 15 | name="banner_background_color", 16 | field=colorfield.fields.ColorField( 17 | blank=True, 18 | default=None, 19 | image_field=None, 20 | max_length=25, 21 | null=True, 22 | samples=None, 23 | ), 24 | ), 25 | migrations.AlterField( 26 | model_name="animemodel", 27 | name="cover_background_color", 28 | field=colorfield.fields.ColorField( 29 | blank=True, 30 | default=None, 31 | image_field=None, 32 | max_length=25, 33 | null=True, 34 | samples=None, 35 | ), 36 | ), 37 | ] 38 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0022_alter_animegenremodel_created_at_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0b1 on 2023-10-29 05:51 2 | 3 | import django.db.models.functions.datetime 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("anime", "0021_alter_animemodel_banner_background_color_and_more"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="animegenremodel", 15 | name="created_at", 16 | field=models.DateTimeField( 17 | db_default=django.db.models.functions.datetime.Now() 18 | ), 19 | ), 20 | migrations.AlterField( 21 | model_name="animemodel", 22 | name="created_at", 23 | field=models.DateTimeField( 24 | db_default=django.db.models.functions.datetime.Now() 25 | ), 26 | ), 27 | migrations.AlterField( 28 | model_name="animethememodel", 29 | name="created_at", 30 | field=models.DateTimeField( 31 | db_default=django.db.models.functions.datetime.Now() 32 | ), 33 | ), 34 | ] 35 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0004_alter_animeendingmodel_options_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.7 on 2023-03-22 15:45 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("anime", "0003_remove_animemodel_theme_endings_and_more"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterModelOptions( 13 | name="animeendingmodel", 14 | options={ 15 | "verbose_name": "Anime Ending", 16 | "verbose_name_plural": "Anime Endings", 17 | }, 18 | ), 19 | migrations.AlterModelOptions( 20 | name="animeopeningmodel", 21 | options={ 22 | "verbose_name": "Anime Opening", 23 | "verbose_name_plural": "Anime Openings", 24 | }, 25 | ), 26 | migrations.AddField( 27 | model_name="animeendingmodel", 28 | name="url", 29 | field=models.URLField(null=True), 30 | ), 31 | migrations.AddField( 32 | model_name="animeopeningmodel", 33 | name="url", 34 | field=models.URLField(null=True), 35 | ), 36 | ] 37 | -------------------------------------------------------------------------------- /backend/django_core/apps/characters/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from dynamic_filenames import FilePattern 3 | from mixins.models.created_at import CreatedAtMixin 4 | from mixins.models.is_locked import IsLockedMixin 5 | from mixins.models.updated_at import UpdatedAtMixin 6 | 7 | anime_charaters_pattern = FilePattern(filename_pattern="characters/{uuid:s}{ext}") 8 | 9 | 10 | # Create your models here. 11 | 12 | 13 | class CharacterModel(CreatedAtMixin, UpdatedAtMixin, IsLockedMixin): 14 | mal_id = models.IntegerField(unique=True, null=True) 15 | kitsu_id = models.IntegerField(unique=True, null=True) 16 | anilist_id = models.IntegerField(unique=True, null=True) 17 | 18 | name = models.CharField(max_length=1024) 19 | name_kanji = models.CharField(max_length=1024, null=True, blank=True) 20 | character_image = models.ImageField( 21 | upload_to=anime_charaters_pattern, 22 | default=None, 23 | blank=True, 24 | null=True, 25 | ) 26 | about = models.TextField(null=True, blank=True) 27 | 28 | def __str__(self) -> str: 29 | return f"{self.pk}. {self.name}" 30 | 31 | class Meta: 32 | verbose_name = "Character" 33 | verbose_name_plural = "Characters" 34 | -------------------------------------------------------------------------------- /backend/django_core/apps/episodes/migrations/0009_alter_episodecommentmodel_created_at_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0b1 on 2023-10-29 05:51 2 | 3 | import django.db.models.functions.datetime 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("episodes", "0008_alter_episodecommentmodel_options"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="episodecommentmodel", 15 | name="created_at", 16 | field=models.DateTimeField( 17 | db_default=django.db.models.functions.datetime.Now() 18 | ), 19 | ), 20 | migrations.AlterField( 21 | model_name="episodemodel", 22 | name="created_at", 23 | field=models.DateTimeField( 24 | db_default=django.db.models.functions.datetime.Now() 25 | ), 26 | ), 27 | migrations.AlterField( 28 | model_name="episodetimestampmodel", 29 | name="created_at", 30 | field=models.DateTimeField( 31 | db_default=django.db.models.functions.datetime.Now() 32 | ), 33 | ), 34 | ] 35 | -------------------------------------------------------------------------------- /tracker/backend/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "tracker" 3 | version = "0.1.0" 4 | description = "Add your description here" 5 | readme = "README.md" 6 | requires-python = ">=3.13" 7 | dependencies = [ 8 | "anyio>=4.8.0", 9 | "attrs>=25.1.0", 10 | "bencode-py>=4.0.0", 11 | "flask-orjson>=2.0.0; platform.python_implementation == 'CPython'", 12 | "hypercorn>=0.17.3", 13 | "quart>=0.20.0", 14 | "quart-cors>=0.8.0", 15 | "redis[hiredis]>=5.2.1", 16 | "uvloop>=0.21.0; sys_platform == 'linux'", 17 | ] 18 | 19 | [tool.setuptools] 20 | packages = ["coreproject_tracker"] 21 | 22 | [project.scripts] 23 | coreproject_tracker = "coreproject_tracker.__main__:main" 24 | 25 | [dependency-groups] 26 | dev = [ 27 | "py-spy>=0.4.0", 28 | ] 29 | lint = [ 30 | "ruff>=0.9.7", 31 | 32 | ] 33 | 34 | [tool.uv] 35 | default-groups = ["dev", "lint"] 36 | package = true 37 | 38 | [tool.ruff.lint] 39 | extend-select = ["I"] # Enables isort rules 40 | 41 | [tool.ruff.format] 42 | skip-magic-trailing-comma = false # Avoids conflict with isort.split-on-trailing-comma 43 | 44 | [tool.ruff.lint.isort] 45 | split-on-trailing-comma = true # Keep this aligned with format.skip-magic-trailing-comma 46 | combine-as-imports = true 47 | -------------------------------------------------------------------------------- /backend/django_core/apps/user/migrations/0010_customuser_created_at_alter_customuser_username.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0.4 on 2024-04-30 12:54 2 | 3 | import apps.user.validators.username 4 | import django.core.validators 5 | import django.db.models.functions.datetime 6 | from django.db import migrations, models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('user', '0009_merge_20240317_1131'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name='customuser', 18 | name='created_at', 19 | field=models.DateTimeField(db_default=django.db.models.functions.datetime.Now()), 20 | ), 21 | migrations.AlterField( 22 | model_name='customuser', 23 | name='username', 24 | field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[apps.user.validators.username.username_validator, django.core.validators.RegexValidator('^[a-zA-Z0-9_-]+#[0-9]{4}$', message='Username is not valid for this regex `^[a-zA-Z0-9_-]+#[0-9]{4}$`')], verbose_name='username'), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /backend/django_core/apps/comments/migrations/0005_remove_commentmodel_dislikes_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0 on 2023-12-07 16:37 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("comments", "0004_alter_commentmodel_dislikes_alter_commentmodel_likes"), 10 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name="commentmodel", 16 | name="dislikes", 17 | ), 18 | migrations.RemoveField( 19 | model_name="commentmodel", 20 | name="likes", 21 | ), 22 | migrations.AddField( 23 | model_name="commentmodel", 24 | name="downvotes", 25 | field=models.ManyToManyField( 26 | blank=True, related_name="downvotes", to=settings.AUTH_USER_MODEL 27 | ), 28 | ), 29 | migrations.AddField( 30 | model_name="commentmodel", 31 | name="upvotes", 32 | field=models.ManyToManyField( 33 | blank=True, related_name="upvotes", to=settings.AUTH_USER_MODEL 34 | ), 35 | ), 36 | ] 37 | -------------------------------------------------------------------------------- /backend/django_core/apps/comments/models.py: -------------------------------------------------------------------------------- 1 | from apps.user.models import CustomUser 2 | from django.contrib.postgres import indexes as idx 3 | from django.db import models 4 | from django_ltree.models import TreeModel 5 | from mixins.models.created_at import CreatedAtMixin 6 | 7 | 8 | # Create your models here. 9 | 10 | 11 | class CommentModel(CreatedAtMixin, TreeModel): 12 | user = models.ForeignKey(CustomUser, on_delete=models.CASCADE, null=True, blank=True) 13 | text = models.TextField() 14 | 15 | upvotes = models.ManyToManyField(CustomUser, blank=True, related_name="upvotes") 16 | downvotes = models.ManyToManyField(CustomUser, blank=True, related_name="downvotes") 17 | 18 | deleted = models.BooleanField(default=False) 19 | 20 | @property 21 | def ratio(self) -> int: 22 | # Formula : - 23 | return self.upvotes.count() - self.downvotes.count() 24 | 25 | @property 26 | def childrens(self) -> int: 27 | return self.children().count() 28 | 29 | def __str__(self) -> str: 30 | return f"{self.user} | {self.text}" 31 | 32 | class Meta: 33 | # https://youtu.be/u8F7bTJVe_4?t=1051 34 | indexes = [idx.GistIndex(fields=["path"])] 35 | verbose_name = "Comment" 36 | verbose_name_plural = "Comments" 37 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/functions/events.py: -------------------------------------------------------------------------------- 1 | from coreproject_tracker.enums import EVENT_NAMES 2 | 3 | 4 | async def convert_event_id_to_event_enum(event_id: int) -> EVENT_NAMES: 5 | match event_id: 6 | case 0: 7 | return EVENT_NAMES.UPDATE 8 | case 1: 9 | return EVENT_NAMES.COMPLETE 10 | case 2: 11 | return EVENT_NAMES.START 12 | case 3: 13 | return EVENT_NAMES.STOP 14 | case 4: 15 | return EVENT_NAMES.PAUSE 16 | case _: 17 | raise ValueError("`event_id` is not supported") 18 | 19 | 20 | async def convert_event_name_to_event_enum(event_name: str | None) -> EVENT_NAMES: 21 | if event_name: 22 | event_name = event_name.lower() 23 | 24 | match event_name: 25 | case "update": 26 | return EVENT_NAMES.UPDATE 27 | case "completed": 28 | return EVENT_NAMES.COMPLETE 29 | case "started": 30 | return EVENT_NAMES.START 31 | case "stopped": 32 | return EVENT_NAMES.STOP 33 | case "paused": 34 | return EVENT_NAMES.PAUSE 35 | case None: 36 | raise ValueError("`event_name` is None") 37 | case _: 38 | raise ValueError("`event_name` not supported") 39 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/views/user/__init__.py: -------------------------------------------------------------------------------- 1 | from apps.user.models import CustomUser 2 | from apps.api.http import HttpRequest 3 | from django.shortcuts import get_object_or_404 4 | from ninja import File, Form, Router, UploadedFile 5 | from pydantic import AnyUrl, EmailStr 6 | 7 | from ...auth import AuthBearer 8 | from ...schemas.user import UserSchema 9 | 10 | router = Router() 11 | 12 | 13 | @router.get("/", response=UserSchema, auth=AuthBearer()) 14 | def get_user_info(request: HttpRequest) -> CustomUser: 15 | user = CustomUser.objects.get(pk=request.auth.id) 16 | return user 17 | 18 | 19 | @router.patch("/", response=UserSchema, auth=AuthBearer()) 20 | def patch_individual_user_info( 21 | request: HttpRequest, 22 | username: str = Form(...), 23 | first_name: str = Form(...), 24 | last_name: str = Form(...), 25 | password: str = Form(...), 26 | email: EmailStr = Form(...), 27 | avatar_provider: AnyUrl = Form(...), 28 | avatar: UploadedFile = File(...), 29 | ) -> None: 30 | pass 31 | 32 | 33 | @router.get("/{str:username}/", response=UserSchema) 34 | def get_individual_user_info( 35 | request: HttpRequest, 36 | username: str, 37 | ) -> CustomUser: 38 | user = get_object_or_404( 39 | CustomUser, 40 | username=username, 41 | ) 42 | return user 43 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0017_remove_animemodel_anime_name_name_japanese_idx_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.4 on 2023-08-29 04:11 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("anime", "0016_remove_animemodel_anime_anime_name_301f1e_gin_and_more"), 9 | ] 10 | 11 | operations = [ 12 | migrations.RemoveIndex( 13 | model_name="animemodel", 14 | name="anime_name|name_japanese_idx", 15 | ), 16 | migrations.RemoveIndex( 17 | model_name="animenamesynonymmodel", 18 | name="anime_name_synonym_idx", 19 | ), 20 | migrations.AlterField( 21 | model_name="animemodel", 22 | name="name", 23 | field=models.CharField(db_index=True, max_length=1024, unique=True), 24 | ), 25 | migrations.AlterField( 26 | model_name="animemodel", 27 | name="name_japanese", 28 | field=models.CharField(blank=True, db_index=True, default="", max_length=1024), 29 | ), 30 | migrations.AlterField( 31 | model_name="animenamesynonymmodel", 32 | name="name", 33 | field=models.CharField(db_index=True, max_length=100, unique=True), 34 | ), 35 | ] 36 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0016_remove_animemodel_anime_anime_name_301f1e_gin_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.2 on 2023-06-17 14:08 2 | 3 | import django.contrib.postgres.indexes 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("anime", "0015_animemodel_anime_anime_name_301f1e_gin_and_more"), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveIndex( 14 | model_name="animemodel", 15 | name="anime_anime_name_301f1e_gin", 16 | ), 17 | migrations.RemoveIndex( 18 | model_name="animenamesynonymmodel", 19 | name="anime_anime_name_81fa03_gin", 20 | ), 21 | migrations.AddIndex( 22 | model_name="animemodel", 23 | index=django.contrib.postgres.indexes.GinIndex( 24 | fields=["name", "name_japanese"], 25 | name="anime_name|name_japanese_idx", 26 | opclasses=["gin_trgm_ops", "gin_trgm_ops"], 27 | ), 28 | ), 29 | migrations.AddIndex( 30 | model_name="animenamesynonymmodel", 31 | index=django.contrib.postgres.indexes.GinIndex( 32 | fields=["name"], name="anime_name_synonym_idx", opclasses=["gin_trgm_ops"] 33 | ), 34 | ), 35 | ] 36 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/datastructures/immutable/http.py: -------------------------------------------------------------------------------- 1 | from attrs import define, field, validators 2 | 3 | from coreproject_tracker.constants import DEFAULT_ANNOUNCE_PEERS, MAX_ANNOUNCE_PEERS 4 | from coreproject_tracker.converters import ( 5 | convert_ip, 6 | convert_to_url_bytes, 7 | ) 8 | from coreproject_tracker.enums import EVENT_NAMES 9 | from coreproject_tracker.validators import ( 10 | validate_20_length, 11 | validate_ip, 12 | validate_port, 13 | ) 14 | 15 | __all__ = ["HttpDatastructure"] 16 | 17 | 18 | @define 19 | class HttpDatastructure: 20 | info_hash_raw: bytes = field( 21 | converter=convert_to_url_bytes, 22 | validator=[validate_20_length], 23 | ) 24 | port: int = field(converter=int, validator=[validate_port]) 25 | left: int = field(converter=int) 26 | numwant: int = field(converter=int) 27 | peer_id: str = field(validator=validators.instance_of(str)) 28 | peer_ip: str = field(converter=convert_ip, validator=[validate_ip]) 29 | 30 | event_name: EVENT_NAMES = field(default=None) 31 | 32 | # Derived 33 | info_hash: str = field(init=False) 34 | 35 | def __attrs_post_init__(self) -> None: 36 | self.numwant = min(self.numwant or DEFAULT_ANNOUNCE_PEERS, MAX_ANNOUNCE_PEERS) 37 | 38 | # Derived Data 39 | self.info_hash = self.info_hash_raw.hex() 40 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/auth.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Any 3 | 4 | from apps.user.models import CustomUser 5 | from django.conf import settings 6 | from django.contrib.auth.models import AnonymousUser 7 | from django.http import HttpRequest 8 | from ninja.security import HttpBearer 9 | 10 | from .models import Token 11 | 12 | logger = logging.getLogger("django") 13 | 14 | 15 | class AuthBearer(HttpBearer): 16 | def authenticate( 17 | self, 18 | request: HttpRequest, 19 | token: str, 20 | ) -> CustomUser | AnonymousUser: 21 | try: 22 | token_data = Token.objects.get(token=token) 23 | return token_data.user 24 | 25 | except Token.DoesNotExist: 26 | return AnonymousUser 27 | 28 | 29 | class OptionalAuthBearer(AuthBearer): 30 | def __call__(self, request: HttpRequest) -> Any | None: 31 | auth_value = request.headers.get(self.header) 32 | if not auth_value: 33 | return AnonymousUser() # if there is no key, we return AnonymousUser object 34 | parts = auth_value.split(" ") 35 | 36 | if parts[0].lower() != self.openapi_scheme: 37 | if settings.DEBUG: 38 | logger.error(f"Unexpected auth - '{auth_value}'") 39 | return None 40 | token = " ".join(parts[1:]) 41 | return self.authenticate(request, token) 42 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/decorator.py: -------------------------------------------------------------------------------- 1 | from collections.abc import Callable 2 | from functools import wraps 3 | from http import HTTPStatus 4 | from typing import TYPE_CHECKING, Any 5 | 6 | from apps.user.models import CustomUser 7 | from django.contrib.auth.models import AnonymousUser 8 | from apps.api.http import HttpRequest 9 | from ninja.errors import HttpError 10 | 11 | if TYPE_CHECKING: 12 | from .permissions import IsSuperUser 13 | 14 | 15 | def permission_required( 16 | permissions: list[Callable[[HttpRequest, CustomUser], "IsSuperUser"]], 17 | key: str | None = "auth", # To get request.auth 18 | ) -> Callable[[Callable[..., Any]], Callable[..., Any]]: 19 | def decorator(func: Callable[..., Any]) -> Callable[..., Any]: 20 | @wraps(func) 21 | def wrapper(request: HttpRequest, *args: Any, **kwargs: Any) -> Any: 22 | user = getattr(request, key, AnonymousUser) 23 | 24 | permission_granted = any( 25 | [permission(request, user).has_permissions() for permission in permissions] 26 | ) 27 | if not permission_granted: 28 | raise HttpError( 29 | HTTPStatus.UNAUTHORIZED, 30 | "Superuser is required for this operation", 31 | ) 32 | return func(request, *args, **kwargs) 33 | 34 | return wrapper 35 | 36 | return decorator 37 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/views/anime/genres.py: -------------------------------------------------------------------------------- 1 | from apps.anime.models.anime_genre import AnimeGenreModel 2 | from apps.api.auth import AuthBearer 3 | from apps.api.decorator import permission_required 4 | from apps.api.permissions import IsSuperUser 5 | from apps.api.http import HttpRequest 6 | from ninja import Query, Router 7 | 8 | from ...filters.genres import GenreFilter 9 | from ...schemas.anime.anime_genre import AnimeGenreGETSchema, AnimeGenrePOSTSchema 10 | 11 | router = Router() 12 | 13 | 14 | @router.get("/genres", response=list[AnimeGenreGETSchema]) 15 | def get_anime_genre_info( 16 | request: HttpRequest, 17 | filters: GenreFilter = Query(...), 18 | ) -> list[AnimeGenreModel]: 19 | query = AnimeGenreModel.objects.filter( 20 | type__icontains="anime", **filters.dict(exclude_none=True) 21 | ) 22 | 23 | return query 24 | 25 | 26 | @router.post("/genres", response=list[AnimeGenreGETSchema], auth=AuthBearer()) 27 | @permission_required([IsSuperUser]) 28 | def post_anime_genre_info( 29 | request: HttpRequest, 30 | payload: list[AnimeGenrePOSTSchema], 31 | ) -> list[AnimeGenreModel]: 32 | instance_objects = [] 33 | for object in payload: 34 | instance_objects.append( 35 | AnimeGenreModel( 36 | type="anime", 37 | **object.dict(exclude_none=True), 38 | ) 39 | ) 40 | 41 | query = AnimeGenreModel.objects.bulk_create(instance_objects) 42 | return query 43 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/views/anime/themes.py: -------------------------------------------------------------------------------- 1 | from apps.anime.models.anime_theme import AnimeThemeModel 2 | from apps.api.auth import AuthBearer 3 | from apps.api.decorator import permission_required 4 | from apps.api.permissions import IsSuperUser 5 | from apps.api.http import HttpRequest 6 | from ninja import Query, Router 7 | 8 | from ...filters.themes import ThemeFilter 9 | from ...schemas.anime.anime_theme import AnimeThemeGETSchema, AnimeThemePOSTSchema 10 | 11 | router = Router() 12 | 13 | 14 | @router.get("/themes", response=list[AnimeThemeGETSchema]) 15 | def get_anime_theme_info( 16 | request: HttpRequest, 17 | filters: ThemeFilter = Query(...), 18 | ) -> list[AnimeThemeModel]: 19 | query = AnimeThemeModel.objects.filter( 20 | type__icontains="anime", 21 | **filters.dict(exclude_none=True), 22 | ) 23 | return query 24 | 25 | 26 | @router.post("/themes", response=list[AnimeThemeGETSchema], auth=AuthBearer()) 27 | @permission_required([IsSuperUser]) 28 | def post_anime_theme_info( 29 | request: HttpRequest, 30 | payload: list[AnimeThemePOSTSchema], 31 | ) -> list[AnimeThemeModel]: 32 | instance_objects = [] 33 | for object in payload: 34 | instance_objects.append( 35 | AnimeThemeModel( 36 | type="anime", 37 | **object.dict(exclude_none=True), 38 | ) 39 | ) 40 | 41 | query = AnimeThemeModel.objects.bulk_create(instance_objects) 42 | return query 43 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | enable-beta-ecosystems: true 6 | version: 2 7 | updates: 8 | # Backend 9 | # ======= 10 | - package-ecosystem: pip 11 | directory: /backend/ 12 | schedule: 13 | interval: weekly 14 | open-pull-requests-limit: 100 15 | 16 | # Tracker 17 | # ======= 18 | - package-ecosystem: uv 19 | directory: /tracker/backend/ 20 | schedule: 21 | interval: weekly 22 | open-pull-requests-limit: 100 23 | 24 | - package-ecosystem: npm 25 | directory: /tracker/frontend/ 26 | schedule: 27 | interval: weekly 28 | open-pull-requests-limit: 100 29 | 30 | # Bots 31 | # ===== 32 | - package-ecosystem: pip 33 | directory: /discord/ 34 | schedule: 35 | interval: weekly 36 | open-pull-requests-limit: 100 37 | 38 | #Frontend 39 | #- package-ecosystem: 'npm' 40 | # directory: '/frontend' 41 | # schedule: 42 | # interval: 'weekly' 43 | 44 | # Actions 45 | - package-ecosystem: 'github-actions' 46 | directory: '/' 47 | schedule: 48 | interval: 'weekly' 49 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/views/anime/endings.py: -------------------------------------------------------------------------------- 1 | from apps.anime.models.anime_openings_and_endings import AnimeEndingModel 2 | from apps.api.auth import AuthBearer 3 | from apps.api.decorator import permission_required 4 | from apps.api.permissions import IsSuperUser 5 | from apps.api.http import HttpRequest 6 | from ninja import File, Form, Query, Router 7 | from ninja.files import UploadedFile 8 | from pydantic import AnyUrl 9 | 10 | from ...filters.openings_and_endings import OpeningAndEndingFilter 11 | from ...schemas.anime.anime_opening_and_ending import AnimeOpeningAndEndingGETSchema 12 | 13 | router = Router() 14 | 15 | 16 | @router.get("/endings", response=list[AnimeOpeningAndEndingGETSchema]) 17 | def get_anime_ending_info( 18 | request: HttpRequest, 19 | filters: OpeningAndEndingFilter = Query(...), 20 | ) -> list[AnimeEndingModel]: 21 | query = AnimeEndingModel.objects.filter(**filters.dict(exclude_none=True)) 22 | 23 | return query 24 | 25 | 26 | @router.post("/endings", response=AnimeOpeningAndEndingGETSchema, auth=AuthBearer()) 27 | @permission_required([IsSuperUser]) 28 | def post_anime_ending_info( 29 | request: HttpRequest, 30 | entry: int | None = Form(...), 31 | name: str | None = Form(...), 32 | url: AnyUrl = Form(...), 33 | thumbnail: UploadedFile | None = File(...), 34 | ) -> AnimeEndingModel: 35 | query = AnimeEndingModel.objects.create( 36 | entry=entry, 37 | name=name, 38 | url=url, 39 | thumbnail=thumbnail, 40 | ) 41 | return query 42 | -------------------------------------------------------------------------------- /backend/django_core/apps/episodes/admin/episode_timestamp.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.db.models.query import QuerySet 3 | from apps.api.http import HttpRequest 4 | 5 | from ..models.episode_timestamp import EpisodeTimestampModel 6 | 7 | # Register your models here. 8 | 9 | 10 | @admin.register(EpisodeTimestampModel) 11 | class EpisodeTimestampAdmin(admin.ModelAdmin[EpisodeTimestampModel]): 12 | autocomplete_fields = ["user"] 13 | list_filter = ["user"] 14 | search_fields = ["user__username"] 15 | 16 | def get_search_results( 17 | self, 18 | request: HttpRequest, 19 | queryset: QuerySet[EpisodeTimestampModel], 20 | search_term: str, 21 | ) -> tuple[QuerySet[EpisodeTimestampModel], bool]: 22 | queryset, may_have_duplicates = super().get_search_results( 23 | request, 24 | queryset, 25 | search_term, 26 | ) 27 | if "#" in search_term: 28 | queryset = self.model.objects.filter( 29 | user__username__in=[ 30 | # Remove trailing whitespace 31 | # We might have something like 32 | # user = ['baseplate-admin ', ' baseplate-foot'] 33 | # make it 34 | # user = ['baseplate-admin','baseplate-foot'] 35 | item.strip() 36 | for item in search_term.split(",") 37 | ] 38 | ).distinct() 39 | 40 | return queryset, may_have_duplicates 41 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/views/anime/openings.py: -------------------------------------------------------------------------------- 1 | from apps.anime.models.anime_openings_and_endings import AnimeOpeningModel 2 | from apps.api.auth import AuthBearer 3 | from apps.api.decorator import permission_required 4 | from apps.api.permissions import IsSuperUser 5 | from apps.api.http import HttpRequest 6 | from ninja import File, Form, Query, Router 7 | from ninja.files import UploadedFile 8 | from pydantic import AnyUrl 9 | 10 | from ...filters.openings_and_endings import OpeningAndEndingFilter 11 | from ...schemas.anime.anime_opening_and_ending import AnimeOpeningAndEndingGETSchema 12 | 13 | router = Router() 14 | 15 | 16 | @router.get("/openings", response=list[AnimeOpeningAndEndingGETSchema]) 17 | def get_anime_opening_info( 18 | request: HttpRequest, 19 | filters: OpeningAndEndingFilter = Query(...), 20 | ) -> list[AnimeOpeningModel]: 21 | query = AnimeOpeningModel.objects.filter(**filters.dict(exclude_none=True)) 22 | 23 | return query 24 | 25 | 26 | @router.post("/openings", response=AnimeOpeningAndEndingGETSchema, auth=AuthBearer()) 27 | @permission_required([IsSuperUser]) 28 | def post_anime_opening_info( 29 | request: HttpRequest, 30 | entry: int | None = Form(...), 31 | name: str | None = Form(...), 32 | url: AnyUrl = Form(...), 33 | thumbnail: UploadedFile | None = File(...), 34 | ) -> AnimeOpeningModel: 35 | query = AnimeOpeningModel.objects.create( 36 | entry=entry, 37 | name=name, 38 | url=url, 39 | thumbnail=thumbnail, 40 | ) 41 | return query 42 | -------------------------------------------------------------------------------- /tracker/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "next build", 7 | "dev": "cross-env NODE_OPTIONS='--inspect' next dev --turbopack", 8 | "lint": "next lint", 9 | "start": "next start" 10 | }, 11 | "browserslist": [ 12 | "IE 11", 13 | "last 2 versions", 14 | "> 0.2%", 15 | "not dead" 16 | ], 17 | "dependencies": { 18 | "@radix-ui/react-dropdown-menu": "^2.1.16", 19 | "@radix-ui/react-slot": "^1.2.4", 20 | "class-variance-authority": "^0.7.1", 21 | "clsx": "^2.1.1", 22 | "next": "16.0.10", 23 | "react": "^19.2.1", 24 | "react-dom": "^19.2.1" 25 | }, 26 | "devDependencies": { 27 | "@eslint/eslintrc": "^3", 28 | "@svgr/webpack": "^8.1.0", 29 | "@tailwindcss/postcss": "^4", 30 | "@types/bencode": "^2.0.4", 31 | "@types/node": "^24", 32 | "@types/react": "^19", 33 | "@types/react-dom": "^19", 34 | "@types/webtorrent": "^0.110.1", 35 | "babel-plugin-react-compiler": "^19.1.0-rc.3", 36 | "bencode": "^4.0.0", 37 | "cross-env": "^10.1.0", 38 | "eslint": "^9", 39 | "eslint-config-next": "16.0.8", 40 | "lucide-react": "^0.556.0", 41 | "next-themes": "^0.4.6", 42 | "prettier": "^3.7.4", 43 | "prettier-plugin-packagejson": "^2.5.20", 44 | "prettier-plugin-tailwindcss": "^0.7.2", 45 | "swr": "^2.3.7", 46 | "tailwind-merge": "^3.4.0", 47 | "tailwindcss": "^4", 48 | "tw-animate-css": "^1.4.0", 49 | "typescript": "^5" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tracker/nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | events { 2 | worker_connections 10240; 3 | } 4 | 5 | http { 6 | upstream backend_pool { 7 | server backend:5000; 8 | server backend:5001; 9 | server backend:5002; 10 | server backend:5003; 11 | } 12 | 13 | 14 | server { 15 | listen 80; 16 | server_name _; 17 | 18 | # Combined API & WebSocket endpoints 19 | location ~ ^/(api|announce)(/.*)?$ { 20 | proxy_pass http://backend_pool; 21 | proxy_http_version 1.1; # Insecure HTTP/2 22 | 23 | # Forward headers 24 | proxy_set_header X-Real-IP $remote_addr; 25 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 26 | proxy_set_header X-Forwarded-Proto $scheme; 27 | 28 | # Upgrade websockets 29 | proxy_set_header Upgrade $http_upgrade; 30 | proxy_set_header Connection "Upgrade"; 31 | } 32 | 33 | # Combined frontend routes (health check + general routing) 34 | location / { 35 | proxy_pass http://frontend:3000; 36 | proxy_set_header Host $host; 37 | proxy_set_header X-Real-IP $remote_addr; 38 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 39 | proxy_set_header X-Forwarded-Proto $scheme; 40 | } 41 | } 42 | } 43 | 44 | stream { 45 | server { 46 | listen 5000 udp; 47 | proxy_pass backend:5000; 48 | proxy_timeout 1s; 49 | proxy_responses 1; 50 | proxy_bind $remote_addr transparent; 51 | } 52 | } -------------------------------------------------------------------------------- /tracker/frontend/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. 37 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/views/anime/anime_character.py: -------------------------------------------------------------------------------- 1 | from apps.anime.models import AnimeModel 2 | from apps.api.auth import AuthBearer 3 | from apps.api.decorator import permission_required 4 | from apps.api.permissions import IsSuperUser 5 | from apps.characters.models import CharacterModel 6 | from apps.api.http import HttpRequest 7 | from django.shortcuts import get_list_or_404, get_object_or_404 8 | from ninja import Router 9 | 10 | from ...schemas.characters import CharacterGETSchema 11 | 12 | router = Router() 13 | 14 | 15 | @router.get("/{int:anime_id}/character", response=list[CharacterGETSchema]) 16 | def get_individual_anime_character_info( 17 | request: HttpRequest, 18 | anime_id: int, 19 | ) -> list[CharacterModel]: 20 | query = get_list_or_404( 21 | get_object_or_404(AnimeModel, pk=anime_id).characters, 22 | ) 23 | return query 24 | 25 | 26 | @router.post( 27 | "/{int:anime_id}/character", 28 | response=list[CharacterGETSchema], 29 | auth=AuthBearer(), 30 | ) 31 | @permission_required([IsSuperUser]) 32 | def post_individual_anime_character_info( 33 | request: HttpRequest, 34 | anime_id: int, 35 | payload: CharacterGETSchema, 36 | ) -> CharacterModel: 37 | # Set this at top 38 | # Because if there is no anime_info_model with corresponding query 39 | # theres no point in continuing 40 | anime_info_model = get_object_or_404(AnimeModel, pk=anime_id) 41 | 42 | query = CharacterModel.objects.get_or_create( 43 | **payload.dict(), 44 | ) 45 | instance: CharacterModel = query[0] 46 | anime_info_model.characters.add(instance) 47 | 48 | return instance 49 | -------------------------------------------------------------------------------- /backend/django_core/apps/producers/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.7 on 2023-03-08 10:21 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | initial = True 8 | 9 | dependencies = [] 10 | 11 | operations = [ 12 | migrations.CreateModel( 13 | name="ProducerModel", 14 | fields=[ 15 | ( 16 | "id", 17 | models.BigAutoField( 18 | auto_created=True, 19 | primary_key=True, 20 | serialize=False, 21 | verbose_name="ID", 22 | ), 23 | ), 24 | ("mal_id", models.IntegerField(blank=True, null=True, unique=True)), 25 | ("kitsu_id", models.IntegerField(blank=True, null=True, unique=True)), 26 | ("name", models.CharField(default="", max_length=128)), 27 | ( 28 | "default_title", 29 | models.CharField(blank=True, default="", max_length=128, null=True), 30 | ), 31 | ( 32 | "japanese_title", 33 | models.CharField(blank=True, default="", max_length=128, null=True), 34 | ), 35 | ("established", models.DateTimeField(blank=True, null=True)), 36 | ("about", models.TextField(blank=True, null=True)), 37 | ], 38 | options={ 39 | "verbose_name": "Producer", 40 | "verbose_name_plural": "Producers", 41 | }, 42 | ), 43 | ] 44 | -------------------------------------------------------------------------------- /tracker/backend/Dockerfile: -------------------------------------------------------------------------------- 1 | # First, build the application in the `/app` directory. 2 | # See `Dockerfile` for details. 3 | FROM ghcr.io/astral-sh/uv:python3.13-alpine AS builder 4 | ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy 5 | 6 | # Disable Python downloads, because we want to use the system interpreter 7 | # across both images. If using a managed Python version, it needs to be 8 | # copied from the build image into the final image; see `standalone.Dockerfile` 9 | # for an example. 10 | ENV UV_PYTHON_DOWNLOADS=0 11 | 12 | WORKDIR /app 13 | RUN --mount=type=cache,target=/root/.cache/uv \ 14 | --mount=type=bind,source=uv.lock,target=uv.lock \ 15 | --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ 16 | uv sync --frozen --no-install-project --no-default-groups 17 | COPY . /app 18 | RUN --mount=type=cache,target=/root/.cache/uv \ 19 | uv sync --frozen --no-default-groups 20 | 21 | # Remove the .egg-info file that are created from setuptools 22 | RUN find /app -type d -name '*.egg-info' -exec rm -rf {} + 23 | 24 | # Then, use a final image without uv 25 | FROM python:3.13-alpine 26 | # It is important to use the image that matches the builder, as the path to the 27 | # Python executable must be the same, e.g., using `python:3.11-slim-bookworm` 28 | # will fail. 29 | 30 | # Copy the application from the builder 31 | COPY --from=builder --chown=app:app /app /app 32 | 33 | # Place executables in the environment at the front of the path 34 | ENV PATH="/app/.venv/bin:$PATH" 35 | 36 | ENV HOST=127.0.0.1 37 | ENV PORT=5000 38 | EXPOSE 5000/udp 5000/tcp 39 | 40 | # Run the FastAPI application by default 41 | CMD ["sh", "-c", "coreproject_tracker --host ${HOST} --port ${PORT}"] 42 | -------------------------------------------------------------------------------- /tracker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | redis: 3 | image: redis:7.4.3-alpine 4 | container_name: redis 5 | restart: unless-stopped 6 | ports: 7 | - "16379:16379" 8 | volumes: 9 | - redis_data:/data 10 | command: redis-server --port 16379 11 | healthcheck: 12 | test: [ "CMD", "redis-cli", "-p", "16379", "ping" ] 13 | interval: 10s 14 | timeout: 5s 15 | retries: 5 16 | 17 | backend: 18 | build: 19 | context: ./backend 20 | dockerfile: Dockerfile 21 | container_name: backend 22 | restart: unless-stopped 23 | depends_on: 24 | redis: 25 | condition: service_healthy 26 | environment: 27 | REDIS_HOST: redis 28 | REDIS_PORT: 16379 29 | HOST: "0.0.0.0" 30 | PORT: 5000 31 | WORKERS_COUNT: 4 32 | ports: 33 | - "5000:5000" 34 | 35 | frontend: 36 | build: 37 | context: ./frontend 38 | dockerfile: Dockerfile 39 | container_name: frontend 40 | restart: unless-stopped 41 | environment: 42 | NEXT_PUBLIC_BACKEND_URL: http://localhost:5000 43 | HOST: "0.0.0.0" 44 | PORT: 3000 45 | NODE_ENV: production 46 | ports: 47 | - "3000:3000" 48 | depends_on: 49 | - backend 50 | 51 | nginx: 52 | # build instead of pull 53 | build: 54 | context: ./nginx 55 | dockerfile: Dockerfile 56 | 57 | container_name: nginx 58 | restart: unless-stopped 59 | 60 | ports: 61 | - "80:80" 62 | - "5000:5000/udp" 63 | 64 | depends_on: 65 | - backend 66 | - frontend 67 | 68 | volumes: 69 | redis_data: 70 | -------------------------------------------------------------------------------- /backend/django_core/apps/user/migrations/0007_alter_customuser_unique_together_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.3 on 2023-07-21 13:13 2 | 3 | import apps.user.validators.username 4 | import django.core.validators 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [ 10 | ("user", "0006_customuser_created_at"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterUniqueTogether( 15 | name="customuser", 16 | unique_together=set(), 17 | ), 18 | migrations.AlterField( 19 | model_name="customuser", 20 | name="username", 21 | field=models.CharField( 22 | error_messages={"unique": "A user with that username already exists."}, 23 | help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.", 24 | max_length=150, 25 | unique=True, 26 | validators=[ 27 | apps.user.validators.username.username_validator, 28 | django.core.validators.RegexValidator( 29 | "^[a-zA-Z0-9_-]+#[0-9]{4}$", 30 | message="Username is not valid for this regex `^[a-zA-Z0-9_-]+#[0-9]{4}$`", 31 | ), 32 | ], 33 | verbose_name="username", 34 | ), 35 | ), 36 | migrations.AlterUniqueTogether( 37 | name="customuser", 38 | unique_together={("username", "email")}, 39 | ), 40 | migrations.RemoveField( 41 | model_name="customuser", 42 | name="discriminator", 43 | ), 44 | ] 45 | -------------------------------------------------------------------------------- /backend/django_core/apps/user/backends.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | from typing import Any, cast 3 | 4 | from apps.user.models import CustomUser 5 | from django.contrib.auth.backends import ModelBackend 6 | from django.contrib.auth.models import AbstractBaseUser 7 | from django.db.models import Q 8 | from apps.api.http import HttpRequest 9 | 10 | 11 | class EmailOrUsernameModelBackend(ModelBackend): 12 | """ 13 | Authentication backend which allows users to authenticate using either their 14 | username or email address 15 | 16 | Source: https://stackoverflow.com/a/35836674/59984 17 | """ 18 | 19 | @staticmethod 20 | def get_user_given_username_and_password( 21 | username_or_email: str | None, 22 | password: str | None, 23 | ) -> CustomUser | None: 24 | if not username_or_email and not password: 25 | return None 26 | query = None 27 | 28 | # Supress user doesn't exist 29 | with contextlib.suppress(CustomUser.DoesNotExist): 30 | user_model = CustomUser.objects.get( 31 | Q(username=username_or_email) | Q(email__iexact=username_or_email) 32 | ) 33 | 34 | # If password matches then return user 35 | if user_model.check_password(password) and password: 36 | query = user_model 37 | 38 | return query 39 | 40 | def authenticate( 41 | self, 42 | request: HttpRequest | None, 43 | username: str | None = None, 44 | password: str | None = None, 45 | **kwargs: Any, 46 | ) -> AbstractBaseUser | None: 47 | user_model = self.get_user_given_username_and_password(username, password) 48 | return cast(AbstractBaseUser, user_model) 49 | -------------------------------------------------------------------------------- /backend/django_core/core/celery.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from celery import Celery 4 | from celery.schedules import crontab 5 | 6 | # Set the default Django settings module for the 'celery' program. 7 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings") 8 | 9 | app = Celery("core") 10 | 11 | ## Get the base REDIS URL, default to redis' default 12 | BASE_REDIS_URL = os.environ.get("REDIS_URL", "redis://localhost:6379") 13 | 14 | # Using a string here means the worker doesn't have to serialize 15 | # the configuration object to child processes. 16 | # - namespace='CELERY' means all celery-related configuration keys 17 | # should have a `CELERY_` prefix. 18 | app.config_from_object("django.conf:settings", namespace="CELERY") 19 | 20 | # Load task modules from all registered Django apps. 21 | app.autodiscover_tasks() 22 | 23 | # Celery beat 24 | app.conf.beat_schedule = { 25 | # Characters 26 | # ========== 27 | # Executes every Friday night at 12:00 a.m. 28 | "get-periodic-characters-every-friday-morning": { 29 | "task": "apps.characters.tasks.get_periodic_character", 30 | "schedule": crontab(hour=0, minute=00, day_of_week=5), 31 | }, 32 | # Staffs / People 33 | # ========== 34 | # Executes every Friday night at 12:00 a.m. 35 | "get-periodic-staffs-every-friday-morning": { 36 | "task": "apps.staffs.tasks.get_periodic_staff", 37 | "schedule": crontab(hour=0, minute=00, day_of_week=5), 38 | }, 39 | # Anime 40 | # ====== 41 | # Genre 42 | "get-periodic-genres-every-friday-night": { 43 | "task": "apps.anime.tasks.get_periodic_anime_genres", 44 | "schedule": crontab(hour=0, minute=00, day_of_week=5), 45 | }, 46 | } 47 | 48 | app.conf.timezone = "UTC" 49 | 50 | app.conf.broker_url = BASE_REDIS_URL 51 | -------------------------------------------------------------------------------- /backend/django_core/apps/user/managers.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Any 2 | 3 | from django.contrib.auth.base_user import BaseUserManager 4 | from django.utils.translation import gettext_lazy as _ 5 | 6 | if TYPE_CHECKING: 7 | from .models import CustomUser 8 | 9 | 10 | class UserManager( 11 | BaseUserManager["CustomUser"], 12 | ): 13 | """ 14 | Custom user model manager where email is the unique identifiers 15 | for authentication instead of usernames. 16 | """ 17 | 18 | def create_user( 19 | self, 20 | email: str, 21 | password: str, 22 | **extra_fields: dict[str, dict[str, Any]], 23 | ) -> "CustomUser": 24 | """Create and save a User with the given email and password.""" 25 | 26 | if not email: 27 | raise ValueError(_("The Email must be set")) 28 | email = self.normalize_email(email) 29 | user: CustomUser = self.model(email=email, **extra_fields) 30 | user.set_password(password) 31 | user.save() 32 | return user 33 | 34 | def create_superuser( 35 | self, 36 | email: str, 37 | password: str, 38 | **extra_fields: Any, 39 | ) -> "CustomUser": 40 | """Create and save a SuperUser with the given email and password.""" 41 | 42 | extra_fields.setdefault("is_staff", True) 43 | extra_fields.setdefault("is_superuser", True) 44 | extra_fields.setdefault("is_active", True) 45 | 46 | # Sanity Check 47 | if extra_fields.get("is_staff") is not True: 48 | raise ValueError(_("Superuser must have is_staff=True.")) 49 | if extra_fields.get("is_superuser") is not True: 50 | raise ValueError(_("Superuser must have is_superuser=True.")) 51 | 52 | return self.create_user(email, password, **extra_fields) 53 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/models/anime_openings_and_endings.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from dynamic_filenames import FilePattern 3 | 4 | opening_upload_pattern = FilePattern(filename_pattern="opening/{uuid:s}{ext}") 5 | ending_upload_pattern = FilePattern(filename_pattern="ending/{uuid:s}{ext}") 6 | 7 | 8 | # Create your models here. 9 | 10 | 11 | class AbstractBaseOpeningAndEndingModel(models.Model): 12 | # Either opening number/closing number 13 | entry = models.BigIntegerField(null=False, blank=False) 14 | # Opening/closing theme name 15 | name = models.CharField(max_length=512, blank=False, null=False) 16 | # Canonical URL 17 | url = models.URLField(blank=False, null=True) 18 | 19 | class Meta: 20 | abstract = True 21 | 22 | 23 | class AnimeOpeningModel(AbstractBaseOpeningAndEndingModel): 24 | thumbnail = models.ImageField( 25 | upload_to=opening_upload_pattern, 26 | default=None, 27 | blank=True, 28 | null=True, 29 | ) 30 | 31 | def __str__(self) -> str: 32 | return f"{self.entry}. {self.name}" 33 | 34 | class Meta: 35 | unique_together = [ 36 | ("entry", "name", "url"), 37 | ] 38 | verbose_name = "Anime Opening" 39 | verbose_name_plural = "Anime Openings" 40 | 41 | 42 | class AnimeEndingModel(AbstractBaseOpeningAndEndingModel): 43 | thumbnail = models.ImageField( 44 | upload_to=ending_upload_pattern, 45 | default=None, 46 | blank=True, 47 | null=True, 48 | ) 49 | 50 | def __str__(self) -> str: 51 | return f"{self.entry}. {self.name}" 52 | 53 | class Meta: 54 | unique_together = [ 55 | ("entry", "name", "url"), 56 | ] 57 | verbose_name = "Anime Ending" 58 | verbose_name_plural = "Anime Endings" 59 | -------------------------------------------------------------------------------- /backend/django_core/apps/characters/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.7 on 2023-03-08 10:21 2 | 3 | import dynamic_filenames 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | initial = True 9 | 10 | dependencies = [] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name="CharacterModel", 15 | fields=[ 16 | ( 17 | "id", 18 | models.BigAutoField( 19 | auto_created=True, 20 | primary_key=True, 21 | serialize=False, 22 | verbose_name="ID", 23 | ), 24 | ), 25 | ("mal_id", models.IntegerField(null=True, unique=True)), 26 | ("kitsu_id", models.IntegerField(null=True, unique=True)), 27 | ("anilist_id", models.IntegerField(null=True, unique=True)), 28 | ("name", models.CharField(max_length=1024)), 29 | ("name_kanji", models.CharField(blank=True, max_length=1024, null=True)), 30 | ( 31 | "character_image", 32 | models.ImageField( 33 | blank=True, 34 | default=None, 35 | null=True, 36 | upload_to=dynamic_filenames.FilePattern( 37 | filename_pattern="characters/{uuid:s}{ext}" 38 | ), 39 | ), 40 | ), 41 | ("about", models.TextField(blank=True, null=True)), 42 | ], 43 | options={ 44 | "verbose_name": "Character", 45 | "verbose_name_plural": "Characters", 46 | }, 47 | ), 48 | ] 49 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/datastructures/immutable/redis.py: -------------------------------------------------------------------------------- 1 | from attrs import asdict, define, field, validators 2 | from quart import json 3 | 4 | from coreproject_tracker.constants import PEER_TTL, WEBSOCKET_PEER_TTL 5 | from coreproject_tracker.converters import ( 6 | convert_str_int_to_float, 7 | ) 8 | from coreproject_tracker.enums import REDIS_NAMESPACE_ENUM 9 | from coreproject_tracker.functions import ( 10 | hset, 11 | ) 12 | from coreproject_tracker.validators import ( 13 | validate_ip, 14 | validate_port, 15 | ) 16 | 17 | 18 | @define 19 | class RedisDatastructure: 20 | info_hash: str = field( 21 | validator=validators.instance_of(str), metadata={"asdict": False} 22 | ) 23 | type: str = field(validator=validators.instance_of(str)) 24 | peer_id: str = field(validator=validators.instance_of(str)) 25 | peer_ip: str = field(validator=[validate_ip]) 26 | port: int = field(converter=int, validator=[validate_port]) 27 | left: float | None = field(converter=convert_str_int_to_float) 28 | 29 | async def save(self) -> None: 30 | """ 31 | Save the object to Redis. 32 | """ 33 | 34 | # CONSTANT 35 | match self.type: 36 | case "websocket": 37 | expire_time = WEBSOCKET_PEER_TTL 38 | redis_namespace = REDIS_NAMESPACE_ENUM.WEBSOCKET 39 | case "http" | "udp": 40 | expire_time = PEER_TTL 41 | redis_namespace = REDIS_NAMESPACE_ENUM.HTTP_UDP 42 | case _: 43 | raise ValueError(f"{self.type} is not a valid type") 44 | 45 | await hset( 46 | self.info_hash, 47 | f"{self.peer_ip}:{self.port}", 48 | json.dumps(asdict(self, recurse=True)), 49 | expire_time=expire_time, 50 | namespace=redis_namespace, 51 | ) 52 | -------------------------------------------------------------------------------- /FAQ.md: -------------------------------------------------------------------------------- 1 |
2 | How do you plan to fund this site? 3 |

4 | => I don't know, for now at least. But I have made the site to be as cost effective as possible. 5 | I plan on funding through patreon at some point. 6 |

7 |
8 | 9 |
10 | Where do you get the contents from? 11 |

=> Mostly from nyaa

12 |
13 | 14 |
15 | What will you do to a DMCA takedown? 16 |

17 | => I am not storing much for this site. But in reality our site will eventually be DMCA striked. 18 | Thats why this site will have an import export feature. Since the code is 19 | open source, anyone can host the site and the user can just import the data. 21 |

22 |
23 | 24 |
25 | When can we expect a working site ? 26 |

27 | => I don't know actually. With my current financial status, it's really hard for me to host this 28 | site (We need around 25$ - 30$ to host this, hopefully ! ). 29 |

30 |
31 | 32 |
33 | Whats the gurantee that an anime will stay forever? 34 |

35 | => Theres no gurantee. But we have 2 cloud provider, an offline hard disk backup. Unless we get 36 | DMCA striked at both CDN, my house catches on fire, all the content uploaded on the site should 37 | stay for lifetime 38 |

39 |
40 | 41 |
42 | But the x,y,z site does this better. 43 |

44 | => I am sorry I couldn't meet your expectations. So maybe you can make 45 | this 46 | site look even better? 47 |

48 |
49 | -------------------------------------------------------------------------------- /tracker/frontend/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import "./globals.css"; 3 | import { ThemeProvider } from "@/components/theme-provider"; 4 | import { Navbar } from "@/components/navbar"; 5 | import localFont from "next/font/local"; 6 | export const metadata: Metadata = { 7 | title: "Create Next App", 8 | description: "Generated by create next app", 9 | }; 10 | 11 | const KokoroFont = localFont({ 12 | src: [ 13 | // Regular 14 | { 15 | path: "../fonts/Kokoro/Kokoro-Regular.woff2", 16 | weight: "400", 17 | style: "normal", 18 | }, 19 | { 20 | path: "../fonts/Kokoro/Kokoro-SemiBold.woff2", 21 | weight: "500", 22 | style: "italic", 23 | }, 24 | { 25 | path: "../fonts/Kokoro/Kokoro-Bold.woff2", 26 | weight: "700", 27 | style: "normal", 28 | }, 29 | 30 | // Italic 31 | { 32 | path: "../fonts/Kokoro/Kokoro-Italic.woff2", 33 | weight: "400", 34 | style: "italic", 35 | }, 36 | { 37 | path: "../fonts/Kokoro/Kokoro-SemiBoldItalic.woff2", 38 | weight: "500", 39 | style: "italic", 40 | }, 41 | 42 | { 43 | path: "../fonts/Kokoro/Kokoro-BoldItalic.woff2", 44 | weight: "700", 45 | style: "italic", 46 | }, 47 | ], 48 | }); 49 | 50 | export default function RootLayout({ 51 | children, 52 | }: Readonly<{ 53 | children: React.ReactNode; 54 | }>) { 55 | return ( 56 | 57 | 58 | 64 | 65 | {children} 66 | 67 | 68 | 69 | ); 70 | } 71 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/datastructures/immutable/udp.py: -------------------------------------------------------------------------------- 1 | from attrs import define, field, validators 2 | 3 | from coreproject_tracker.constants import ( 4 | ANNOUNCE_INTERVAL, 5 | DEFAULT_ANNOUNCE_PEERS, 6 | MAX_ANNOUNCE_PEERS, 7 | ) 8 | from coreproject_tracker.enums import EVENT_NAMES 9 | from coreproject_tracker.validators import ( 10 | validate_20_length, 11 | validate_connection_id, 12 | validate_ip, 13 | validate_port, 14 | ) 15 | 16 | __all__ = ["UdpDatastructure"] 17 | 18 | 19 | @define 20 | class UdpDatastructure: 21 | # CONSTANT 22 | interval: int = field( 23 | init=False, default=ANNOUNCE_INTERVAL, validator=validators.instance_of(int) 24 | ) 25 | 26 | connection_id: bytes = field( 27 | validator=[validators.instance_of(bytes), validate_connection_id] 28 | ) 29 | action: int = field(validator=validators.instance_of(int)) 30 | transaction_id: int = field(validator=validators.instance_of(int)) 31 | 32 | # Only available on ANNOUNCE 33 | info_hash: bytes = field(default=None, validator=[validate_20_length]) 34 | peer_id: str = field(default=None) 35 | downloaded: int = field(default=None) 36 | left: int = field(default=None) 37 | uploaded: int = field(default=None) 38 | key: int = field(default=None) 39 | numwant: int = field(default=DEFAULT_ANNOUNCE_PEERS) 40 | 41 | # Set from function call 42 | peers: bytes = field(default=None) # TODO: make a log_2 based validator here 43 | incomplete: int = field(default=0) 44 | complete: int = field(default=0) 45 | 46 | # Might not need these 47 | ip: str = field(default=None, validator=[validate_ip]) 48 | port: int = field(default=None, validator=[validate_port]) 49 | 50 | # Derived 51 | event_name: EVENT_NAMES = field(default=None) 52 | 53 | def __attrs_post_init__(self): 54 | self.numwant = min(self.numwant, MAX_ANNOUNCE_PEERS) 55 | --------------------------------------------------------------------------------