├── .dockerignore ├── LICENSE ├── README.md ├── TODO ├── docker-compose.yml ├── dockerfiles ├── nginx │ └── Dockerfile ├── postgres │ └── Dockerfile ├── telegram │ └── Dockerfile └── web │ └── Dockerfile ├── nginx ├── nginx.conf └── static │ ├── css │ ├── authorization_form.css │ ├── base.css │ ├── chessboards.css │ ├── error_page.css │ ├── fonts.css │ ├── reset.css │ └── success_authorization.css │ ├── favicon.ico │ ├── fonts │ ├── Inter-VariableFont_slnt,wght.ttf │ └── PlacidArmor-Medium.ttf │ ├── images │ ├── 1x1.png │ ├── 45.gif │ ├── 45_1.gif │ ├── 45_3.gif │ ├── 45_reversed.gif │ ├── Avatar.png │ ├── Icon LeftGive up.png │ ├── Icon LeftRevenge.png │ ├── Icon Leftdraw.png │ ├── US_menu_icon_flag.png │ ├── bb.png │ ├── bk.png │ ├── bn.png │ ├── bp.png │ ├── bq.png │ ├── br.png │ ├── burgermenu_icon.png │ ├── error.png │ ├── success_authorization.png │ ├── unknown_user.png │ ├── wb.png │ ├── wk.png │ ├── wn.png │ ├── wp.png │ ├── wq.png │ └── wr.png │ └── js │ ├── authorization_response.js │ ├── chessboard.js │ ├── chessboard_spectator.js │ ├── config.js │ ├── core.js │ ├── jquery-1.7.1.min.js │ ├── jquery.chess.js │ ├── jquery.chess.spectator.js │ ├── jquery.ui.core.js │ ├── jquery.ui.draggable.js │ ├── jquery.ui.droppable.js │ ├── jquery.ui.mouse.js │ ├── jquery.ui.touch-punch.min.js │ ├── jquery.ui.widget.js │ ├── player_timers.js │ ├── prepare_time.js │ ├── websocket.js │ └── websocket_spectator.js ├── previews ├── achievement_1 ├── game └── game_ready ├── telegram ├── QueueClass.py ├── __init__.py ├── authorization_core.py ├── config │ ├── ConfigValues.py │ └── config.ini ├── decorators.py ├── main.py ├── requirements.txt ├── telegram_admin.py ├── telegram_authorize.py ├── telegram_dashboard.py ├── telegram_help.py ├── telegram_log │ └── log.py ├── telegram_profile.py ├── telegram_queue.py └── telegram_start.py └── web_django ├── .env ├── __init__.py ├── api ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ └── __init__.py ├── models.py ├── responses.py ├── serializers.py ├── tests.py ├── urls.py ├── utils.py └── views.py ├── authorization ├── __init__.py ├── admin.py ├── apps.py ├── models.py ├── templates │ └── authorization │ │ └── success_authorization.html ├── tests.py ├── urls.py └── views.py ├── base └── base.html ├── blacklist ├── __init__.py ├── admin.py ├── apps.py ├── models.py ├── tests.py ├── urls.py └── views.py ├── chessboards ├── __init__.py ├── admin.py ├── apps.py ├── chess_core │ ├── Game.py │ ├── Timer.py │ ├── User.py │ ├── __init__.py │ └── core.py ├── consumers.py ├── games_management.py ├── migrations │ └── __init__.py ├── models.py ├── routing.py ├── templates │ └── chessboards │ │ ├── game.html │ │ └── game_spectator.html ├── tests.py ├── urls.py └── views.py ├── django_log └── log.py ├── error_page ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ └── __init__.py ├── models.py ├── templates │ └── error_page │ │ └── index.html ├── tests.py └── views.py ├── manage.py ├── requirements.txt └── web_django ├── __init__.py ├── asgi.py ├── settings.py ├── urls.py └── wsgi.py /.dockerignore: -------------------------------------------------------------------------------- 1 | venv 2 | __pycache__ 3 | .git 4 | .idea 5 | .gitignore 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Justiks 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

♟ CHESSBOT - БОТ ДЛЯ ШАХМАТ ♟

2 | 3 | Шамхматный бот - Это не просто очередная реализация шахмат. Вам не нужно скачивать никаких приложений, для того чтобы играть достаточно иметь аккаунт Telegram и выход в интернет, конечно ;). Это удобно при проведении шахматных турниров в ваших Telegram каналах. 4 | 5 | ![Image alt](https://github.com/Justiks1337/ChessBot/raw/main/previews/game) 6 | ![Image alt](https://github.com/Justiks1337/ChessBot/raw/main/previews/game_ready) 7 | 8 | ------------------------------------------ 9 | ### Releases 10 | * ##### 29.09.23 - 27.10.23 - Первый релиз для студентов Кода Будущего от 1С, поток 2022-2023 11 | * ##### 15.07.24 - 29.07.24 - Второй релиз для студентов Кода Будущего от 1С, поток 2023-2024 12 | 13 | --------- 14 | ### Ahievements 15 | * ##### Проект упомянут в презентации-отчете о проектах учеников Кода Будущего 16 | ![Image alt](https://github.com/Justiks1337/ChessBot/raw/main/previews/achievement_1) 17 | 18 | ### Dependencies 19 | * ##### [requirements.txt](https://github.com/Justiks1337/ChessBot/blob/main/requirements.txt) 20 | * ##### https://github.com/furf/jquery-ui-touch-punch 21 | * ##### https://github.com/serbanghita/jQuery-Chess?ysclid=lm7zyb365a478152142 22 | 23 | --------- 24 | ### Installation 25 | 26 | Понадобится установленный docker и git 27 | 28 | ##### windows/mac/linux 29 | ``` 30 | git clone https://github.com/Justiks1337/ChessBot/ 31 | cd ChessBot 32 | docker compose up --build 33 | ``` 34 | 35 | Made by Maksim Dubkov with aiogram, django and a lot of ❤️. 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | 2 | 2. Isolate telegram bot and web site containers 3 | 4 | 5. isolate configs 5 | django has special env lib! 6 | 7 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | # docker-compose.yml 2 | version: '3' 3 | 4 | services: 5 | 6 | nginx: 7 | build: 8 | context: . 9 | dockerfile: ./dockerfiles/nginx/Dockerfile 10 | depends_on: 11 | - daphne 12 | ports: 13 | - 80:80 14 | volumes: 15 | - avatars:/static/avatars/ 16 | 17 | database: 18 | build: 19 | context: . 20 | dockerfile: ./dockerfiles/postgres/Dockerfile 21 | environment: 22 | POSTGRES_DB: "chess_database" 23 | POSTGRES_USER: "chess" 24 | POSTGRES_PASSWORD: "12345678" 25 | expose: 26 | - 5432 27 | 28 | daphne: 29 | build: 30 | context: . 31 | dockerfile: ./dockerfiles/web/Dockerfile 32 | tty: true 33 | command: python3 -u -m daphne web_django.asgi:application -b daphne -p 8000 34 | depends_on: 35 | - database 36 | restart: always 37 | expose: 38 | - 8000 39 | volumes: 40 | - avatars:/static/avatars/ 41 | 42 | 43 | telegram: 44 | build: 45 | context: . 46 | dockerfile: ./dockerfiles/telegram/Dockerfile 47 | command: python3 -u telegram/main.py 48 | depends_on: 49 | - database 50 | tty: true 51 | environment: 52 | - PYTHONPATH=. 53 | 54 | networks: 55 | database_network: 56 | driver: bridge 57 | 58 | volumes: 59 | avatars: 60 | external: true -------------------------------------------------------------------------------- /dockerfiles/nginx/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:latest 2 | COPY ./nginx/nginx.conf /etc/nginx/ 3 | COPY ./nginx/static static 4 | -------------------------------------------------------------------------------- /dockerfiles/postgres/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM postgres:latest -------------------------------------------------------------------------------- /dockerfiles/telegram/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10-slim 2 | 3 | RUN mkdir /telegram 4 | COPY telegram/requirements.txt /telegram 5 | 6 | RUN python3 -m pip install --no-cache-dir --no-warn-script-location --upgrade pip \ 7 | && python3 -m pip install --no-cache-dir --no-warn-script-location --user -r /telegram/requirements.txt 8 | 9 | COPY telegram /telegram 10 | 11 | -------------------------------------------------------------------------------- /dockerfiles/web/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10-slim 2 | 3 | COPY web_django/requirements.txt . 4 | 5 | RUN python3 -m pip install --no-cache-dir --no-warn-script-location --upgrade pip \ 6 | && python3 -m pip install --no-cache-dir --no-warn-script-location --user -r requirements.txt 7 | 8 | COPY web_django/ . 9 | 10 | 11 | -------------------------------------------------------------------------------- /nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | user root; 2 | worker_processes auto; 3 | pid /run/nginx.pid; 4 | include /etc/nginx/modules-enabled/*.conf; 5 | 6 | 7 | events { 8 | worker_connections 768; 9 | } 10 | 11 | http { 12 | 13 | include /etc/nginx/mime.types; 14 | sendfile on; 15 | 16 | upstream chess_backend { 17 | server daphne:8000; 18 | } 19 | 20 | 21 | server { 22 | listen 80; 23 | server_name 127.0.0.1; 24 | 25 | 26 | location /static { 27 | alias /static/; 28 | autoindex on; 29 | } 30 | 31 | location / { 32 | proxy_pass http://chess_backend; 33 | proxy_http_version 1.1; 34 | proxy_set_header Upgrade $http_upgrade; 35 | proxy_set_header Connection "Upgrade"; 36 | proxy_set_header Host $http_host; 37 | } 38 | 39 | location /favicon.ico { 40 | alias ./web_django/static/favicon.ico; 41 | } 42 | 43 | proxy_set_header Host $http_host; 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /nginx/static/css/authorization_form.css: -------------------------------------------------------------------------------- 1 | body {font-size: 30px; background:#E6E6FA; margin: 0 !important; min-width: 320px; text-align: center; display: grid !important;} 2 | .popup .into {padding: 40px; position: relative;max-width: 613px; border-radius: 20px; flex: 0 1 auto; border: 1px solid rgba(0, 0, 0, 0); background: linear-gradient(138deg, #F4F3F0 0%, #F2F0E9 100%); margin-left: auto; margin-right: auto;} 3 | .popup .into .close {position: absolute; top: 15px; right: 15px; cursor: pointer;} 4 | input {box-sizing: border-box;} 5 | label {font-size: 20px; margin-left: auto} 6 | 7 | @media (max-width: 613px){ 8 | .into{ 9 | max-width: 100% !important; 10 | } 11 | } 12 | 13 | input { 14 | display: block; 15 | width: 100%; 16 | height: calc(2.25rem + 2px); 17 | padding: 0.375rem 0.75rem; 18 | font-family: inherit; 19 | font-size: 1rem; 20 | font-weight: 400; 21 | line-height: 1.5; 22 | color: #212529; 23 | background-color: #fff; 24 | background-clip: padding-box; 25 | border: 1px solid #bdbdbd; 26 | border-radius: 0.25rem; 27 | transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; 28 | } 29 | 30 | input::placeholder { 31 | color: #212529; 32 | opacity: 0.4; 33 | } 34 | 35 | button { 36 | min-width: 100px; 37 | font-family: inherit; 38 | appearance: none; 39 | border: 0; 40 | border-radius: 5px; 41 | background: #656964; 42 | color: #fff; 43 | padding: 8px 16px; 44 | font-size: 1rem; 45 | cursor: pointer; 46 | } 47 | 48 | button:hover { 49 | background: #5c5e5c; 50 | } 51 | 52 | button:focus { 53 | outline: none; 54 | box-shadow: 0 0 0 4px #cbd6ee; 55 | } 56 | 57 | @media (max-height ) -------------------------------------------------------------------------------- /nginx/static/css/base.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700&display=swap'); 2 | 3 | html{ 4 | height: 100% !important} 5 | 6 | body{ 7 | margin: 0 !important; 8 | display: flex !important; 9 | flex-direction: column !important; 10 | min-height: 100% !important; 11 | height: 100% !important; 12 | font-family: Montserrat; 13 | } 14 | 15 | 16 | footer { 17 | font-family: Montserrat !important; 18 | background: #dedede; 19 | position: relative !important; 20 | margin-top: 50px !important; 21 | } 22 | 23 | .footer-col{ 24 | height: 100% !important; 25 | display: inline-block !important; 26 | width: 32%; 27 | } 28 | 29 | @media (max-width: 1000px){ 30 | #contacts { 31 | width: 49%; 32 | } 33 | 34 | #help { 35 | width: 49%; 36 | } 37 | 38 | #project_resources{ 39 | width: 100%; 40 | } 41 | } 42 | 43 | @media (max-width: 450px){ 44 | .footer-container{ 45 | display: flex; 46 | flex-direction: column; 47 | justify-content: space-around; 48 | } 49 | 50 | .footer-col{ 51 | width: 100% !important; 52 | } 53 | } 54 | 55 | .footer_list_heading{ 56 | text-align: center !important; 57 | font-size: 25px !important;} 58 | 59 | .footer_section{ 60 | display: flex !important; 61 | position: relative !important; 62 | align-items: center !important; 63 | justify-content: center !important; 64 | } 65 | 66 | .footer_list{padding: 0 !important} 67 | 68 | .footer_item{ 69 | text-align: center !important; 70 | font-size: 20px !important; 71 | list-style: none !important; 72 | } 73 | 74 | a{text-decoration: none; color: #b08989} 75 | 76 | a:hover{ 77 | color: #7d4343; 78 | } 79 | 80 | .magic_div{ 81 | display: none 82 | } 83 | 84 | -------------------------------------------------------------------------------- /nginx/static/css/chessboards.css: -------------------------------------------------------------------------------- 1 | *{ 2 | padding: 0; 3 | box-sizing: border-box; 4 | } 5 | 6 | /* Задаём первоначальные стили элементам */ 7 | :root{ 8 | /* Задаём стандартные цвета элементам */ 9 | --bg-header: #242424; 10 | --bg-button1: #FF1F44; 11 | --bg-button2: #C22A2A; 12 | --bg-main: #121212; 13 | --bg-footer: #242424; 14 | --bg-player-content: rgb(18,66,43); 15 | --gradient-player-content: linear-gradient(90deg, rgba(18,66,43,1) 0%, rgba(36,36,36,1) 100%); 16 | --bg-table-content: rgba(255, 31, 68, 0.2); 17 | --bg-table: #242424; 18 | --border-table: 1px solid #313131; 19 | --color-text-header: #fff; 20 | --color-text-wrap: #A4A4A4; 21 | --color-link-footer: #818181; 22 | --bg-modal-button: #383838; 23 | 24 | /* Задаём стандартные размеры элементов */ 25 | --body-width: 1728px; 26 | --navbar-width: 100%; 27 | --navbar-height: 72px; 28 | --navbar-sec-width: 50%; 29 | --navbar-sec-wrap-height: 40px; 30 | --index-header: 3; 31 | --index-main: 2; 32 | --padding-main: 70px 88px; 33 | --padding-header: 0px 88px; 34 | --main-iframe-size: 800px; 35 | --chessboard-bg-size: 200px; 36 | --chessboard-square-size: 100px; 37 | --main-inner_iframe-size: 800px; 38 | --table-size-number: 56px; 39 | --modal-width: 580px; 40 | } 41 | 42 | 43 | /* Выдаём стили главным элементам */ 44 | html{ 45 | background: var(--bg-main); 46 | } 47 | 48 | body{ 49 | max-width: 100%; 50 | min-height: 100%; 51 | margin: 0 auto; 52 | } 53 | 54 | header{ 55 | background: var(--bg-header); 56 | z-index: var(--index-header); 57 | transition: 0.3s; 58 | } 59 | 60 | footer{ 61 | background: var(--bg-footer); 62 | } 63 | 64 | h1 { 65 | color: var(--color-text-header); 66 | } 67 | 68 | /* Стили для панели навигации (
) */ 69 | 70 | .navbar{ 71 | display: flex; 72 | justify-content: center; 73 | width: var(--navbar-width); 74 | min-height: var(--navbar-height); 75 | position: fixed; 76 | left: 0; 77 | top: 0; 78 | margin: 0 auto; 79 | width: 100%; 80 | } 81 | 82 | .navbar .wrap{ 83 | display: flex; 84 | flex-direction: row; 85 | max-width: var(--body-width); 86 | min-height: var(--navbar-height); 87 | position: relative; 88 | align-items: center; 89 | justify-content: space-between; 90 | width: 100%; 91 | } 92 | 93 | @media (max-width: 1816px) { 94 | .navbar .wrap, .navbar{ 95 | width: 100%; 96 | } 97 | .navbar .wrap{ 98 | padding: var(--padding-header); 99 | } 100 | } 101 | 102 | .navbar .navsec{ 103 | display: flex; 104 | /* width: calc(var(--body-width) / 2); */ 105 | min-height: var(--navbar-height); 106 | align-items: center; 107 | justify-content: flex-start; 108 | transition: 0.3s; 109 | user-select: none; 110 | } 111 | 112 | .navbar .navsec:last-child{ 113 | justify-content: flex-end; 114 | } 115 | 116 | .navsec .wrap{ 117 | display: flex; 118 | height: var(--navbar-sec-wrap-height); 119 | font-family: 'Inter'; 120 | font-weight: 400; 121 | font-size: 16px; 122 | line-height: 24px; 123 | text-align: center; 124 | color: var(--color-text-wrap); 125 | align-items: center; 126 | max-width: 100%; 127 | } 128 | 129 | .navsec .wrap.logo{ 130 | justify-content: flex-start; 131 | padding-left: 0; 132 | padding: 0; 133 | } 134 | 135 | .navsec .wrap.logo a{ 136 | display: inline-flex; 137 | text-decoration: none; 138 | font-size: 24px; 139 | font-weight: 500; 140 | line-height: 29.05px; 141 | color: var(--color-text-wrap); 142 | } 143 | 144 | .burgermenu{ 145 | display: flex; 146 | width: 40px; 147 | height: 40px; 148 | justify-content: center; 149 | align-items: center; 150 | flex-direction: column; 151 | cursor: pointer; 152 | user-select: none; 153 | } 154 | 155 | .burgermenu img{ 156 | pointer-events: none; 157 | } 158 | 159 | .navsec nav{ 160 | display: flex; 161 | flex-direction: row; 162 | min-height: var(--navbar-sec-wrap-height); 163 | gap: 4px; 164 | } 165 | 166 | .navsec nav .inner, .navsec nav .inner .menu{ 167 | display: flex; 168 | flex-direction: row; 169 | align-items: center; 170 | color: var(--color-text-wrap); 171 | font-family: Inter; 172 | font-size: 16px; 173 | font-style: normal; 174 | font-weight: 400; 175 | line-height: 24px; /* 150% */ 176 | 177 | } 178 | 179 | .navsec nav .inner .menu{ 180 | cursor: pointer; 181 | } 182 | 183 | .navsec nav .inner .menu p { 184 | margin-left: 4px; 185 | margin-right: 4px; 186 | } 187 | 188 | .navsec nav .inner .menu::before{ 189 | content: ""; 190 | display: block; 191 | width: 24px; 192 | height: 16px; 193 | background-position: center; 194 | background-repeat: no-repeat; 195 | background-size: contain; 196 | } 197 | 198 | .navsec nav .inner .menu[lang="English"]::before{background-image: url(../images/US_menu_icon_flag.png);} 199 | 200 | .menu_icon{ 201 | width: 24px; 202 | height: 24px; 203 | } 204 | 205 | .navsec nav .inner .link{ 206 | display: inline-flex; 207 | color: var(--color-text-wrap); 208 | text-align: center; 209 | font-family: Inter; 210 | font-size: 16px; 211 | font-style: normal; 212 | font-weight: 400; 213 | line-height: 24px; /* 150% */ 214 | text-decoration: none; 215 | margin-left: 24px; 216 | } 217 | 218 | .navsec nav .inner .link:last-child{ 219 | margin-right: 24px; 220 | } 221 | 222 | .navsec nav .nav_btn.type1, .navsec nav .nav_btn{ 223 | padding: 8px 16px; 224 | justify-content: center; 225 | align-items: center; 226 | gap: 8px; 227 | background: none; 228 | border: 1px solid var(--bg-button1); 229 | color: var(--bg-button1); 230 | cursor: pointer; 231 | margin-right: 16px; 232 | } 233 | 234 | .navsec nav .nav_btn.type1:last-child, .navsec nav .nav_btn:last-child{ 235 | margin-right: 0; 236 | } 237 | 238 | 239 | .navsec nav .nav_btn.type2{ 240 | background: var(--bg-button1); 241 | color: var(--color-text-header); 242 | } 243 | 244 | /* Стили для тела */ 245 | 246 | #prepare_time_div{ 247 | height: 100%; 248 | background: #ff9c56ef; 249 | width: 100%; 250 | z-index: 10; 251 | } 252 | 253 | #prepare_title{ 254 | font-size: 3vh; 255 | text-align: center; 256 | } 257 | 258 | #prepare_counter{ 259 | font-size: 4vh; 260 | text-align: center; 261 | color: black !important; 262 | } 263 | 264 | #prepare_text{ 265 | display: flex; 266 | width: 100%; 267 | height: 100%; 268 | flex-direction: column; 269 | justify-content: center; 270 | } 271 | 272 | main{ 273 | display: flex; 274 | position: relative; 275 | top: var(--navbar-height); 276 | width: 100%; 277 | } 278 | 279 | main .wrap:first-child{ 280 | min-height: calc(100% - var(--navbar-height)); 281 | } 282 | 283 | main .wrap{ 284 | display: flex; 285 | padding: 70px 88px; 286 | flex-direction: column; 287 | align-items: center; 288 | gap: 64px; 289 | align-self: stretch; 290 | z-index: var(--index-main); 291 | width: 100%; 292 | } 293 | 294 | main .wrap nav { 295 | display: flex; 296 | flex-wrap: wrap; 297 | flex-direction: row; 298 | } 299 | 300 | .nav_btn { 301 | display: flex; 302 | padding: 16px 32px; 303 | justify-content: center; 304 | align-items: center; 305 | gap: 8px; 306 | background: var(--bg-button2); 307 | font-family: 'Inter'; 308 | font-size: 16px; 309 | font-style: normal; 310 | font-weight: 500; 311 | line-height: 24px; /* 150% */ 312 | text-transform: uppercase; 313 | color: var(--color-text-header); 314 | cursor: pointer; 315 | user-select: none; 316 | } 317 | 318 | .nav_btn::before{ 319 | content: ""; 320 | width: 24px; 321 | height: 24px; 322 | background-position: center; 323 | background-repeat: no-repeat; 324 | background-size: contain; 325 | display: none; 326 | } 327 | 328 | .nav_btn[icon="Draw"]::before{ 329 | background-image: url(../images/Icon\ Leftdraw.png); 330 | display: block; 331 | } 332 | 333 | .nav_btn[icon="Revenge"]::before{ 334 | background-image: url(../images/Icon\ LeftRevenge.png); 335 | display: block; 336 | } 337 | .nav_btn[icon="Give up"]::before{ 338 | background-image: url(../images/Icon\ LeftGive\ up.png); 339 | display: block; 340 | } 341 | 342 | main .wrap nav .nav_btn { 343 | width: 500px; 344 | margin-right: 16px; 345 | flex: 1 0 0; 346 | } 347 | 348 | main .wrap nav .nav_btn:last-child{ 349 | margin-right: 0; 350 | } 351 | 352 | /* Секция для шахматной доски и статистик */ 353 | 354 | main .wrap .sec{ 355 | display: flex; 356 | align-items: stretch; 357 | gap: 32px; 358 | align-self: stretch; 359 | width: 100%; 360 | min-height: 800px; 361 | } 362 | 363 | main .wrap .sec .inner { 364 | display: flex; 365 | flex-direction: column; 366 | height: 100%; 367 | margin-right: 32px; 368 | /* align-items: center; */ 369 | height: 100%; 370 | } 371 | 372 | main .wrap .sec .inner:last-child{ 373 | margin-right: 0; 374 | } 375 | 376 | main .wrap .sec .inner .sidebar{ 377 | height: 100%; 378 | } 379 | 380 | .table{ 381 | display: flex; 382 | flex-direction: column; 383 | height: 50%; 384 | } 385 | 386 | main .wrap .sec .inner.chess_inner{ 387 | justify-content: center; 388 | align-items: center; 389 | width: var(--main-inner_iframe-size); 390 | } 391 | 392 | main .wrap .sec .inner.chess_static{ 393 | width: calc(100% - var(--main-inner_iframe-size) - 32px); 394 | } 395 | 396 | .square img{ 397 | position: absolute; 398 | width: 100%; 399 | height: 100%; 400 | background-size: contain; 401 | background-position: center; 402 | user-select: none; 403 | } 404 | 405 | .square:hover{ 406 | background: #66820369; 407 | opacity: 35; 408 | } 409 | 410 | .last_move{ 411 | background: #9bc70069; 412 | } 413 | 414 | .squareNotation{ 415 | user-select: none; 416 | } 417 | 418 | .inner.chess_inner{ 419 | min-width: var(--main-iframe-size); 420 | min-height: var(--main-iframe-size); 421 | overflow: hidden; 422 | } 423 | 424 | table.iksweb{ 425 | display: flex; 426 | width: 100%; 427 | height: 100%; 428 | position: relative; 429 | border-collapse:collapse; 430 | border-spacing:0; 431 | height: auto; 432 | z-index: 3; 433 | flex-direction: column; 434 | 435 | } 436 | 437 | tbody.player::before{ 438 | content: ""; 439 | display: block; 440 | position: absolute; 441 | top: 0; 442 | left: 0; 443 | background: #12B76A; 444 | flex: 1 0 0; 445 | width: 135px; 446 | height: 4px; 447 | z-index: 4; 448 | } 449 | 450 | /* table.iksweb,table.iksweb td, table.iksweb th { 451 | border: 1px solid #595959; 452 | } */ 453 | table.iksweb{ 454 | 455 | } 456 | 457 | .table.history table tbody{ 458 | background: var(--bg-table); 459 | } 460 | 461 | 462 | table.iksweb td,table.iksweb th { 463 | display: flex; 464 | padding: 3px; 465 | width: 30px; 466 | height: 35px; 467 | align-items: center; 468 | font-family: 'Inter'; 469 | font-size: 20px; 470 | font-style: normal; 471 | font-weight: 500; 472 | line-height: normal; 473 | text-transform: uppercase; 474 | } 475 | table.iksweb th { 476 | background: #347c99; 477 | color: #fff; 478 | font-weight: normal; 479 | } 480 | 481 | 482 | table.iksweb tbody.player{ 483 | display: block; 484 | float: left; 485 | background: var(--gradient-player-content); 486 | height: 128px; 487 | width: 100%; 488 | gap: 8px; 489 | /* padding: 16px !important; */ 490 | margin-bottom: 16px; 491 | } 492 | 493 | table.iksweb tbody.player:last-child{ 494 | margin-bottom: 32px; 495 | position: relative; 496 | } 497 | 498 | table.iksweb tbody.player tr{ 499 | display: flex; 500 | flex-direction: row; 501 | width: 100%; 502 | } 503 | 504 | td{ 505 | height: 64px !important; 506 | width: 100%; 507 | padding: 16px !important; 508 | } 509 | 510 | td#player_avatar_wrap{ 511 | display: flex; 512 | width: 64px !important; 513 | height: 100%; 514 | justify-content: center; 515 | align-items: center; 516 | } 517 | td.player_time{ 518 | font-family: 'Placid Armor' !important; 519 | font-size: 32px !IMPORTANT; 520 | font-style: normal !important; 521 | font-weight: 500 !IMPORTANT; 522 | line-height: 40px !important; /* 125% */ 523 | letter-spacing: -0.8px !important; 524 | text-transform !important: uppercase; 525 | width: 100% !IMPORTANT; 526 | } 527 | 528 | #player_avatar{ 529 | width: 48px; 530 | height: 48px; 531 | border-radius: 50%; 532 | } 533 | 534 | td#player_name_wrap{ 535 | width: calc(100% - 64px); 536 | } 537 | 538 | table.iksweb td.header{ 539 | background: #343434; 540 | font-family: Inter; 541 | font-size: 14px; 542 | font-style: normal; 543 | font-weight: 400; 544 | line-height: 24px; /* 171.429% */ 545 | color: #FFF; 546 | /* width: calc((100% - var(--table-size-number)) / 2); */ 547 | } 548 | 549 | table.iksweb td.number, .table.iksweb td.center{ 550 | justify-content: center; 551 | } 552 | 553 | table.iksweb td.number{ 554 | width: var(--table-size-number); 555 | } 556 | 557 | table.iksweb td{ 558 | display: flex; 559 | align-items: center; 560 | width: calc((100% - var(--table-size-number)) / 2); 561 | text-align: center; 562 | font-family: 'Inter'; 563 | font-size: 14px; 564 | font-style: normal; 565 | font-weight: 400; 566 | line-height: 24px; /* 171.429% */ 567 | color: var(--color-text-wrap); 568 | } 569 | 570 | table.iksweb td.last{ 571 | background: linear-gradient(0deg, rgba(255, 31, 68, 0.20) 0%, rgba(255, 31, 68, 0.20) 100%), var(--bg-header); 572 | color: #FFF; 573 | font-weight: 600; 574 | line-height: 24px; /* 150% */ 575 | } 576 | 577 | table.iksweb tbody tr{ 578 | display: flex; 579 | /* padding: 16px; */ 580 | /* background: var(--bg-table); */ 581 | /* border: var(--border-table); */ 582 | flex-direction: row; 583 | justify-content: center; 584 | align-items: center; 585 | } 586 | 587 | .table.history table.iksweb tbody tr{ 588 | height: calc(100% / 6) !important; 589 | 590 | } 591 | 592 | /* Стили для модального окошечка */ 593 | .modal{ 594 | display: flex; 595 | position: fixed; 596 | top: 35%; 597 | left: calc(50% - var(--modal-width) / 2); 598 | z-index: 999; 599 | width: var(--modal-width); 600 | padding: 24px; 601 | flex-direction: column; 602 | align-items: center; 603 | gap: 24px; 604 | background: var(--bg-main); 605 | font-family: 'Inter'; 606 | font-style: normal; 607 | text-align: center; 608 | visibility: hidden; 609 | } 610 | 611 | .modal h1{ 612 | font-size: 20px; 613 | font-weight: 500; 614 | line-height: normal; 615 | text-transform: uppercase; 616 | } 617 | 618 | .modal p{ 619 | font-size: 16px; 620 | font-weight: 400; 621 | line-height: 24px; /* 150% */ 622 | color: var(--color-text-wrap); 623 | } 624 | 625 | .modal nav{ 626 | display: flex; 627 | flex-direction: row; 628 | width: 100%; 629 | align-items: center; 630 | justify-content: space-evenly; 631 | } 632 | 633 | .modal nav .nav_btn { 634 | display: flex; 635 | padding: 8px 16px; 636 | justify-content: center; 637 | align-items: center; 638 | gap: 8px; 639 | flex: 1 0 0; 640 | background: var(--bg-modal-button); 641 | margin-right: 8px; 642 | margin-left: 8px; 643 | color: var(--color-text-wrap); 644 | } 645 | 646 | .modal nav .nav_btn.type2{ 647 | background: var(--bg-button1); 648 | color: #FFF; 649 | } 650 | 651 | /* Адаптация под маленькие разрешения экрана */ 652 | @media (max-width: 1536px) { 653 | table.iksweb td.header, table.iksweb td{ 654 | font-size: 12px; 655 | } 656 | } 657 | 658 | @media (max-width: 800px){ 659 | .inner.chess_inner{ 660 | width: 100% !important; 661 | height: 100vw !important; 662 | min-height: 100vw !important; 663 | min-width: 100vw !important; 664 | } 665 | } 666 | 667 | @media (max-width: 1400px){ 668 | 669 | main .wrap .sec{ 670 | flex-direction: column; 671 | align-items: center; 672 | justify-content: c; 673 | } 674 | 675 | main .wrap .sec .inner.chess_static{ 676 | width: 100%; 677 | } 678 | 679 | table.iksweb td.header, table.iksweb td{ 680 | font-size: 16px; 681 | } 682 | 683 | main .wrap .sec .inner{ 684 | margin-right: 0; 685 | } 686 | } 687 | 688 | @media (max-width: 1152px){ 689 | .navsec nav .inner .link, .navsec nav .inner, .navsec nav .inner .menu{ 690 | font-size: 14px; 691 | } 692 | } 693 | 694 | @media (max-width: 982px){ 695 | /* Код адаптации для шахматной доски под маленькие устройства */ 696 | 697 | /* main .wrap .sec .inner.chess_inner{ 698 | width: calc(var(--main-inner_iframe-size) / 2); 699 | } 700 | 701 | #chess_frame{ 702 | min-width: calc(var(--main-iframe-size) /2); 703 | min-height: calc(var(--main-iframe-size) /2); 704 | } 705 | 706 | #chessBoard{ 707 | width: calc(var(--main-iframe-size) / 2) !important; 708 | height: calc(var(--main-iframe-size) / 2) !important; 709 | background-size: calc(var(--chessboard-bg-size) / 2) !important; 710 | } 711 | 712 | .square{ 713 | width: calc(var(--chessboard-square-size) / 2) !important; 714 | height: calc(var(--chessboard-square-size) / 2) !important; 715 | position: static !important; 716 | float: left; 717 | } */ 718 | 719 | main .wrap nav{ 720 | flex-direction: column; 721 | width: 100%; 722 | justify-content: center; 723 | align-items: center; 724 | } 725 | 726 | 727 | main .wrap nav .nav_btn{ 728 | margin-right: 0; 729 | margin-bottom: 16px; 730 | width: 100% !important; 731 | max-width: 500px; 732 | } 733 | 734 | main .wrap nav .nav_btn:last-child{ 735 | margin-bottom: 0; 736 | } 737 | } 738 | 739 | .legal_move{ 740 | width: 25%; 741 | height: 25%; 742 | margin-right: auto; 743 | margin-left: auto; 744 | opacity: .7; 745 | border-radius: 50%; 746 | background: green; 747 | position: relative; 748 | top: 50%; 749 | transform: translate(0, -50%); 750 | } 751 | 752 | @media (max-width: 640px){ 753 | #player_name{ 754 | font-size: 20px; 755 | } 756 | 757 | main .wrap{ 758 | padding: 70px 16px; 759 | } 760 | 761 | .modal{ 762 | width: 100%; 763 | left: 0; 764 | } 765 | 766 | .modal nav{ 767 | flex-direction: column; 768 | } 769 | 770 | .modal nav .nav_btn{ 771 | width: 100%; 772 | margin-bottom: 16px; 773 | margin-left: 0; 774 | margin-right: 0; 775 | } 776 | .modal nav .nav_btn:last-child{ 777 | margin-bottom: 0; 778 | } 779 | 780 | .navbar .wrap { 781 | padding: 0 20px; 782 | } 783 | .navsec .wrap.logo a { 784 | font-size: 18px; 785 | } 786 | } 787 | 788 | /* board */ 789 | #chessBoard { 790 | /* background: url("/static/images/45.gif") !important; */ 791 | background-size: 25% !important;; 792 | background-repeat: repeat !important; 793 | overflow: hidden; 794 | border:1px solid #D3A36A; 795 | width: var(--main-iframe-size); 796 | height: var(--main-iframe-size); 797 | } 798 | .squareNotation { padding-left:1px; z-index:1; } 799 | .square.oddX.evenY .squareNotation, 800 | .square.evenX.oddY .squareNotation { color:#D3A36A; } 801 | .square.evenX.evenY .squareNotation, 802 | .square.oddX.oddY .squareNotation { color:#EDC9A2; } 803 | 804 | /* pieces */ 805 | .draggablePiece { z-index: 2; cursor:pointer;} 806 | .wp { background: url("/static/images/wp.png") no-repeat; } 807 | .wr { background: url("/static/images/wr.png") no-repeat; } 808 | .wn { background: url("/static/images/wn.png") no-repeat; } 809 | .wb { background: url("/static/images/wb.png") no-repeat; } 810 | .wk { background: url("/static/images/wk.png") no-repeat; } 811 | .wq { background: url("/static/images/wq.png") no-repeat; } 812 | 813 | .bp { background: url("/static/images/bp.png") no-repeat; } 814 | .br { background: url("/static/images/br.png") no-repeat; } 815 | .bn { background: url("/static/images/bn.png") no-repeat; } 816 | .bb { background: url("/static/images/bb.png") no-repeat; } 817 | .bk { background: url("/static/images/bk.png") no-repeat; } 818 | .bq { background: url("/static/images/bq.png") no-repeat; } 819 | 820 | .static_file {display: none;} -------------------------------------------------------------------------------- /nginx/static/css/error_page.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700&display=swap'); 2 | 3 | body{ 4 | background: #FFFFFF; 5 | } 6 | 7 | h1{ 8 | font-family: Montserrat; 9 | color: #C0C0C0; 10 | text-align: center 11 | } 12 | 13 | #popup{ 14 | display: flex; 15 | flex-direction: column; 16 | height: 100%; 17 | margin: 0 auto; 18 | } 19 | 20 | @media (max-width: 830px){ 21 | #popup{ 22 | max-width: 100%; 23 | width: 100%; 24 | } 25 | 26 | .error_img{ 27 | max-width: 100%; 28 | width: 100%; 29 | } 30 | 31 | h1{font-size: 20px;} 32 | } 33 | -------------------------------------------------------------------------------- /nginx/static/css/fonts.css: -------------------------------------------------------------------------------- 1 | /* Inter */ 2 | @font-face{ 3 | font-family: 'Inter'; 4 | src: url(../fonts/Inter-VariableFont_slnt\,wght.ttf); 5 | } 6 | 7 | /* Placid Armor */ 8 | @font-face{ 9 | font-family: "Placid Armor"; 10 | src: url(../fonts/PlacidArmor-Medium.ttf); 11 | } -------------------------------------------------------------------------------- /nginx/static/css/reset.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) 4 | */ 5 | 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, header, hgroup, 17 | menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | font-size: 100%; 23 | font: inherit; 24 | vertical-align: baseline; 25 | } 26 | /* HTML5 display-role reset for older browsers */ 27 | article, aside, details, figcaption, figure, 28 | header, hgroup, menu, nav, section { 29 | display: block; 30 | } 31 | body { 32 | line-height: 1; 33 | } 34 | ol, ul { 35 | list-style: none; 36 | } 37 | blockquote, q { 38 | quotes: none; 39 | } 40 | blockquote:before, blockquote:after, 41 | q:before, q:after { 42 | content: ''; 43 | content: none; 44 | } 45 | table { 46 | border-collapse: collapse; 47 | border-spacing: 0; 48 | } -------------------------------------------------------------------------------- /nginx/static/css/success_authorization.css: -------------------------------------------------------------------------------- 1 | h1{color: #C0C0C0; 2 | text-align: center} 3 | 4 | .success_img{ 5 | display: block; 6 | margin: auto; 7 | } 8 | 9 | .container{ 10 | position: relative; 11 | left: 0; 12 | right: 0; 13 | margin: 0 auto; 14 | display: block; 15 | height: 100%; 16 | } 17 | 18 | @media (max-width: 830px){ 19 | .container{ 20 | max-width: 100%; 21 | width: 100%; 22 | } 23 | 24 | .success_img{ 25 | max-width: 100%; 26 | width: 100%; 27 | } 28 | 29 | h1{font-size: 20px;} 30 | } -------------------------------------------------------------------------------- /nginx/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/nginx/static/favicon.ico -------------------------------------------------------------------------------- /nginx/static/fonts/Inter-VariableFont_slnt,wght.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/nginx/static/fonts/Inter-VariableFont_slnt,wght.ttf -------------------------------------------------------------------------------- /nginx/static/fonts/PlacidArmor-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/nginx/static/fonts/PlacidArmor-Medium.ttf -------------------------------------------------------------------------------- /nginx/static/images/1x1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/nginx/static/images/1x1.png -------------------------------------------------------------------------------- /nginx/static/images/45.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/nginx/static/images/45.gif -------------------------------------------------------------------------------- /nginx/static/images/45_1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/nginx/static/images/45_1.gif -------------------------------------------------------------------------------- /nginx/static/images/45_3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/nginx/static/images/45_3.gif -------------------------------------------------------------------------------- /nginx/static/images/45_reversed.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/nginx/static/images/45_reversed.gif -------------------------------------------------------------------------------- /nginx/static/images/Avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/nginx/static/images/Avatar.png -------------------------------------------------------------------------------- /nginx/static/images/Icon LeftGive up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/nginx/static/images/Icon LeftGive up.png -------------------------------------------------------------------------------- /nginx/static/images/Icon LeftRevenge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/nginx/static/images/Icon LeftRevenge.png -------------------------------------------------------------------------------- /nginx/static/images/Icon Leftdraw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/nginx/static/images/Icon Leftdraw.png -------------------------------------------------------------------------------- /nginx/static/images/US_menu_icon_flag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/nginx/static/images/US_menu_icon_flag.png -------------------------------------------------------------------------------- /nginx/static/images/bb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/nginx/static/images/bb.png -------------------------------------------------------------------------------- /nginx/static/images/bk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/nginx/static/images/bk.png -------------------------------------------------------------------------------- /nginx/static/images/bn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/nginx/static/images/bn.png -------------------------------------------------------------------------------- /nginx/static/images/bp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/nginx/static/images/bp.png -------------------------------------------------------------------------------- /nginx/static/images/bq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/nginx/static/images/bq.png -------------------------------------------------------------------------------- /nginx/static/images/br.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/nginx/static/images/br.png -------------------------------------------------------------------------------- /nginx/static/images/burgermenu_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/nginx/static/images/burgermenu_icon.png -------------------------------------------------------------------------------- /nginx/static/images/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/nginx/static/images/error.png -------------------------------------------------------------------------------- /nginx/static/images/success_authorization.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/nginx/static/images/success_authorization.png -------------------------------------------------------------------------------- /nginx/static/images/unknown_user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/nginx/static/images/unknown_user.png -------------------------------------------------------------------------------- /nginx/static/images/wb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/nginx/static/images/wb.png -------------------------------------------------------------------------------- /nginx/static/images/wk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/nginx/static/images/wk.png -------------------------------------------------------------------------------- /nginx/static/images/wn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/nginx/static/images/wn.png -------------------------------------------------------------------------------- /nginx/static/images/wp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/nginx/static/images/wp.png -------------------------------------------------------------------------------- /nginx/static/images/wq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/nginx/static/images/wq.png -------------------------------------------------------------------------------- /nginx/static/images/wr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/nginx/static/images/wr.png -------------------------------------------------------------------------------- /nginx/static/js/authorization_response.js: -------------------------------------------------------------------------------- 1 | function check_token(){ 2 | 3 | var token = $("#token").val(); 4 | 5 | if (token.length < 35){ 6 | 7 | $("label").replaceWith(""); 8 | return false; 9 | 10 | } 11 | console.log(config) 12 | $.ajax( 13 | { 14 | type: 'POST', 15 | url: `${config.ssl_http}://${config.domain}/api/v1/authorize?token=${token}`, 16 | success: function(data){ 17 | var response = $.parseJSON(data); 18 | 19 | if (!response.success){ 20 | $("label").replaceWith(("")); 21 | return false; 22 | } 23 | location.reload(true); 24 | } 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /nginx/static/js/chessboard.js: -------------------------------------------------------------------------------- 1 | function closeModalWindow(modal){ 2 | modal.style = {'visibility': 'hidden'}; 3 | } 4 | 5 | function showGiveUpModal(){ 6 | $('#give_up_modal').css({'visibility': 'visible'}); 7 | } 8 | 9 | function showDrawOfferModal(){ 10 | socket_connection.send(JSON.stringify({"type": "draw_offer"})); 11 | $('#draw_offer_create_modal').css({"visibility": 'visible'}); 12 | draw_offer = true; 13 | } 14 | 15 | function giveUp(){ 16 | socket_connection.send(JSON.stringify({"type": "give_up"})); 17 | $('#give_up_modal').css({"visibility": "hidden"}); 18 | } 19 | 20 | function drawOfferAccept(){ 21 | socket_connection.send(JSON.stringify({"type": "draw_offer"})); 22 | $('#draw_offer_modal').css({"visibility": "hidden"}); 23 | } 24 | 25 | function transitionAcceptModal(url){ 26 | replaceText(document.getElementById('transition_message'), 'Ты уверен что хочешь перейти на страницу ' + url + '?'); 27 | $('#transition_modal').css({'visibility': 'visible'}); 28 | } 29 | 30 | function transitionUser(url){ 31 | window.location.href = url; 32 | } 33 | 34 | function onMoveSound(){ 35 | 36 | var on_move_sound = new Audio($("#on_move_sound").textContent); 37 | on_move_sound.play(); 38 | } 39 | 40 | function onReady(prepare_time, user_id, board_fen, first_player_time, second_player_time, draw_offer, color, turn){ 41 | 42 | updatePrepareTime(prepare_time); 43 | updateUserId(user_id); 44 | updateBoardFEN(board_fen); 45 | updateFirstPlayerTime(first_player_time); 46 | updateSecondPlayerTime(second_player_time); 47 | updateDrawOffer(draw_offer); 48 | updateColor(color); 49 | updateTurn(turn); 50 | 51 | if (window.prepare_time <= 0){ 52 | window.startTimers(); 53 | } 54 | } 55 | 56 | function updatePrepareTime(prepare_time_var){ 57 | var prepare_time = prepare_time_var; 58 | window.prepare_time = prepare_time; 59 | 60 | window.replaceText($("#prepare_counter")[0], prepare_time); 61 | } 62 | 63 | function updateUserId(user_id_var){ 64 | var user_id = user_id_var; 65 | window.user_id = user_id; 66 | } 67 | 68 | function updateBoardFEN(board_fen_var){ 69 | var board_fen = board_fen_var; 70 | window.board_fen = board_fen; 71 | } 72 | 73 | function updateDrawOffer(draw_offer_var){ 74 | var draw_offer = (draw_offer_var.toLowerCase() == "true"); 75 | window.draw_offer = draw_offer; 76 | } 77 | 78 | function updateColor(color_var){ 79 | 80 | var color = (color_var.toLowerCase() == "true"); 81 | var color_word; 82 | 83 | if (color) color_word = "w"; 84 | else color_word = "b"; 85 | 86 | window.color = color; 87 | window.color_word = color_word 88 | } 89 | 90 | function updateTurn(turn_var){ 91 | var turn = (turn_var.toLowerCase() == "true"); 92 | window.turn = turn; 93 | } 94 | 95 | function possibles_to_move(piece_type){ 96 | if (check_turn() && check_color(piece_type)) return true; 97 | } 98 | 99 | function check_turn(){ 100 | if (window.turn == window.color) return true; 101 | } 102 | 103 | function check_color(piece_type){ 104 | if (piece_type[0] == window.color_word) return true; 105 | } 106 | 107 | function showLegalMoves(legal_moves_array){ 108 | legal_moves_array.forEach(function(element){ 109 | $('#' + element)[0].offsetParent.append($('')[0]); 110 | }) 111 | } 112 | 113 | function hideLegalMoves(){ 114 | let legal_moves = [...document.getElementsByClassName("legal_move")]; 115 | for (i=0; i < legal_moves.length; i++) { 116 | legal_moves[i].remove(); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /nginx/static/js/chessboard_spectator.js: -------------------------------------------------------------------------------- 1 | function closeModalWindow(modal){ 2 | modal.style = {'visibility': 'hidden'}; 3 | } 4 | 5 | function transitionAcceptModal(url){ 6 | replaceText(document.getElementById('transition_message'), 'Ты уверен что хочешь перейти на страницу ' + url + '?'); 7 | $('#transition_modal').css({'visibility': 'visible'}); 8 | } 9 | 10 | function transitionUser(url){ 11 | window.location.href = url; 12 | } 13 | 14 | function onReady(prepare_time, board_fen, first_player_time, second_player_time, turn){ 15 | 16 | updatePrepareTime(prepare_time); 17 | updateBoardFEN(board_fen); 18 | updateFirstPlayerTime(first_player_time); 19 | updateSecondPlayerTime(second_player_time); 20 | updateTurn(turn); 21 | 22 | if (window.prepare_time <= 0){ 23 | window.startTimers(); 24 | } 25 | } 26 | 27 | function updatePrepareTime(prepare_time_var){ 28 | var prepare_time = prepare_time_var; 29 | window.prepare_time = prepare_time; 30 | 31 | window.replaceText($("#prepare_counter")[0], prepare_time); 32 | } 33 | 34 | function updateBoardFEN(board_fen_var){ 35 | var board_fen = board_fen_var; 36 | window.board_fen = board_fen; 37 | } 38 | 39 | function updateTurn(turn_var){ 40 | var turn = (turn_var.toLowerCase() == "true"); 41 | window.turn = turn; 42 | } 43 | 44 | -------------------------------------------------------------------------------- /nginx/static/js/config.js: -------------------------------------------------------------------------------- 1 | var config = { 2 | domain: "127.0.0.1", 3 | ssl_http: "http", 4 | ssl_ws: "ws" 5 | } -------------------------------------------------------------------------------- /nginx/static/js/core.js: -------------------------------------------------------------------------------- 1 | function replaceText(element, text){ 2 | 3 | if (element.innerText) { 4 | element.innerText = text; 5 | } 6 | 7 | else if (element.textContent) { 8 | element.textContent = text; 9 | } 10 | } -------------------------------------------------------------------------------- /nginx/static/js/jquery.chess.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | =========== 4 | 5 | Copyright (c) 2012 Serban Ghita 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a 8 | copy of this software and associated documentation files (the "Software"), 9 | to deal in the Software without restriction, including without limitation 10 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | and/or sell copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 20 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | 27 | (function($){ 28 | 29 | function Chess(){ 30 | 31 | var self = this, 32 | $piece, 33 | $pieces, 34 | $board, 35 | $boardInner, 36 | $settings; 37 | 38 | this.init = function(options, elem){ 39 | 40 | $settings = $.extend({ 41 | squareSize: 12.5, 42 | x : [1,2,3,4,5,6,7,8], 43 | y : [1,2,3,4,5,6,7,8], 44 | xLiteral : ['a','b','c','d','e','f','g','h'], 45 | appendTo: document.body, 46 | type: 'matrix', 47 | position: null 48 | }, options); 49 | 50 | if (window.color === false){ 51 | $settings.y = $settings.y.reverse(); 52 | let elem = document.getElementById("chessBoard"); 53 | elem.style.background = 'url("/static/images/45_reversed.gif")'; 54 | } 55 | 56 | else { 57 | let elem = document.getElementById("chessBoard"); 58 | elem.style.background = 'url("/static/images/45.gif")'; 59 | } 60 | 61 | var path_to_1x1 = $("#static_img").textContent; 62 | 63 | $piece = $('').attr({src: path_to_1x1, width: $settings.squareSize, height: $settings.squareSize}).addClass('draggablePiece'); 64 | 65 | $pieces = { 66 | wp: $piece.clone().addClass('wp'), 67 | wr: $piece.clone().addClass('wr'), 68 | wn: $piece.clone().addClass('wn'), 69 | wb: $piece.clone().addClass('wb'), 70 | wk: $piece.clone().addClass('wk'), 71 | wq: $piece.clone().addClass('wq'), 72 | 73 | bp: $piece.clone().addClass('bp'), 74 | br: $piece.clone().addClass('br'), 75 | bn: $piece.clone().addClass('bn'), 76 | bb: $piece.clone().addClass('bb'), 77 | bk: $piece.clone().addClass('bk'), 78 | bq: $piece.clone().addClass('bq') 79 | }; 80 | 81 | $board = $('.inner.chess_inner') 82 | .css({ 83 | position: 'relative', 84 | width: $settings.squareSize*8, 85 | height: $settings.squareSize*8 86 | }); 87 | 88 | $boardInner = $('
').attr({id:'chessBoardInner'}).addClass('chessBoardInner'); 89 | 90 | this.buildBoard(); 91 | this.buildPosition(false, $settings.type, $settings.position); 92 | this.drawBoard(); 93 | 94 | } 95 | 96 | this.buildBoard = function(){ 97 | 98 | // @todo - Reverse pieces here. 99 | $settings.newY = $settings.y.slice(); 100 | $settings.newY.reverse(); 101 | 102 | for(x in $settings.x){ 103 | for(y in $settings.newY){ 104 | 105 | var tmpSquare = $('
') 106 | .attr({id: 'pos'+$settings.x[x]+''+$settings.y[y]}) 107 | .css({ 108 | left: (($settings.x[x]-1)*$settings.squareSize) + '%', 109 | top: (($settings.newY[y]-1)*$settings.squareSize) + '%', 110 | position: 'absolute', 111 | width: $settings.squareSize + '%', 112 | height: $settings.squareSize + '%' 113 | }) 114 | .addClass('square '+(x%2 ? 'evenX' : 'oddX')+' '+(y%2 ? 'evenY' : 'oddY')); 115 | var tmpSquareNotation = $(`
`) 116 | .css({ 117 | position: 'absolute', 118 | bottom:0, 119 | left:0, 120 | fontSize:'56%' 121 | }) 122 | .addClass('squareNotation') 123 | .html($settings.xLiteral[$settings.x[x]-1]+''+(parseInt(y)+1)); 124 | tmpSquare.append(tmpSquareNotation); 125 | $boardInner.append(tmpSquare); 126 | 127 | } 128 | } 129 | 130 | } 131 | 132 | // Build position, including the start position. 133 | 134 | this.buildPosition = function(start, type, position){ 135 | 136 | if(start){ 137 | 138 | // Initial black pieces position. 139 | $boardInner.find('#pos17,#pos27,#pos37,#pos47,#pos57,#pos67,#pos77,#pos87').append($pieces.bp); 140 | $boardInner.find('#pos18,#pos88').append($pieces.br); 141 | $boardInner.find('#pos28,#pos78').append($pieces.bn); 142 | $boardInner.find('#pos38,#pos68').append($pieces.bb); 143 | $boardInner.find('#pos58').append($pieces.bk); 144 | $boardInner.find('#pos48').append($pieces.bq); 145 | 146 | // Initial white pieces position. 147 | $boardInner.find('#pos12,#pos22,#pos32,#pos42,#pos52,#pos62,#pos72,#pos82').append($pieces.wp); 148 | $boardInner.find('#pos11,#pos81').append($pieces.wr); 149 | $boardInner.find('#pos21,#pos71').append($pieces.wn); 150 | $boardInner.find('#pos31,#pos61').append($pieces.wb); 151 | $boardInner.find('#pos51').append($pieces.wk); 152 | $boardInner.find('#pos41').append($pieces.wq); 153 | 154 | } 155 | 156 | if(typeof type === 'string'){ 157 | switch(type){ 158 | case 'matrix': 159 | this._arrangeMatrixPosition(position); 160 | break; 161 | case 'fen': 162 | this._arrangeMatrixPosition(this._fenToPosition(position)); 163 | break; 164 | default: 165 | alert(1); 166 | } 167 | } 168 | 169 | // Make pieces draggable. 170 | this._createDraggable($boardInner.find('.draggablePiece')); 171 | 172 | // Make pieces droppable. 173 | this._createDroppable($boardInner.find('.square')); 174 | 175 | } 176 | 177 | this._fenToPosition = function(fen){ 178 | var position = []; 179 | var rows = fen.split('/'); 180 | for(var i=0; i 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a 8 | copy of this software and associated documentation files (the "Software"), 9 | to deal in the Software without restriction, including without limitation 10 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | and/or sell copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 20 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | 27 | (function($){ 28 | 29 | function Chess(){ 30 | 31 | var self = this, 32 | $piece, 33 | $pieces, 34 | $board, 35 | $boardInner, 36 | $settings; 37 | 38 | this.init = function(options, elem){ 39 | 40 | $settings = $.extend({ 41 | squareSize: 12.5, 42 | x : [1,2,3,4,5,6,7,8], 43 | y : [1,2,3,4,5,6,7,8], 44 | xLiteral : ['a','b','c','d','e','f','g','h'], 45 | appendTo: document.body, 46 | type: 'matrix', 47 | position: null 48 | }, options); 49 | 50 | var path_to_1x1 = $("#static_img").textContent; 51 | 52 | $piece = $('').attr({src: path_to_1x1, width: $settings.squareSize, height: $settings.squareSize}).addClass('draggablePiece'); 53 | 54 | $pieces = { 55 | wp: $piece.clone().addClass('wp'), 56 | wr: $piece.clone().addClass('wr'), 57 | wn: $piece.clone().addClass('wn'), 58 | wb: $piece.clone().addClass('wb'), 59 | wk: $piece.clone().addClass('wk'), 60 | wq: $piece.clone().addClass('wq'), 61 | 62 | bp: $piece.clone().addClass('bp'), 63 | br: $piece.clone().addClass('br'), 64 | bn: $piece.clone().addClass('bn'), 65 | bb: $piece.clone().addClass('bb'), 66 | bk: $piece.clone().addClass('bk'), 67 | bq: $piece.clone().addClass('bq') 68 | }; 69 | 70 | $board = $('.inner.chess_inner') 71 | .css({ 72 | position: 'relative', 73 | width: $settings.squareSize*8, 74 | height: $settings.squareSize*8 75 | }); 76 | 77 | $boardInner = $('
').attr({id:'chessBoardInner'}).addClass('chessBoardInner'); 78 | 79 | this.buildBoard(); 80 | this.buildPosition(false, $settings.type, $settings.position); 81 | this.drawBoard(); 82 | 83 | } 84 | 85 | this.buildBoard = function(){ 86 | 87 | // @todo - Reverse pieces here. 88 | $settings.newY = $settings.y.slice(); 89 | $settings.newY.reverse(); 90 | 91 | for(x in $settings.x){ 92 | for(y in $settings.newY){ 93 | 94 | var tmpSquare = $('
') 95 | .attr({id: 'pos'+$settings.x[x]+''+$settings.y[y]}) 96 | .css({ 97 | left: (($settings.x[x]-1)*$settings.squareSize) + '%', 98 | top: (($settings.newY[y]-1)*$settings.squareSize) + '%', 99 | position: 'absolute', 100 | width: $settings.squareSize + '%', 101 | height: $settings.squareSize + '%' 102 | }) 103 | .addClass('square '+(x%2 ? 'evenX' : 'oddX')+' '+(y%2 ? 'evenY' : 'oddY')); 104 | var tmpSquareNotation = $('
') 105 | .css({ 106 | position: 'absolute', 107 | bottom:0, 108 | left:0, 109 | fontSize:'56%' 110 | }) 111 | .addClass('squareNotation') 112 | .html($settings.xLiteral[$settings.x[x]-1]+''+$settings.y[y]); 113 | tmpSquareNotation.id = 'test' 114 | tmpSquare.append(tmpSquareNotation); 115 | $boardInner.append(tmpSquare); 116 | 117 | } 118 | } 119 | 120 | } 121 | 122 | // Build position, including the start position. 123 | 124 | this.buildPosition = function(start, type, position){ 125 | 126 | if(start){ 127 | 128 | // Initial black pieces position. 129 | $boardInner.find('#pos17,#pos27,#pos37,#pos47,#pos57,#pos67,#pos77,#pos87').append($pieces.bp); 130 | $boardInner.find('#pos18,#pos88').append($pieces.br); 131 | $boardInner.find('#pos28,#pos78').append($pieces.bn); 132 | $boardInner.find('#pos38,#pos68').append($pieces.bb); 133 | $boardInner.find('#pos58').append($pieces.bk); 134 | $boardInner.find('#pos48').append($pieces.bq); 135 | 136 | // Initial white pieces position. 137 | $boardInner.find('#pos12,#pos22,#pos32,#pos42,#pos52,#pos62,#pos72,#pos82').append($pieces.wp); 138 | $boardInner.find('#pos11,#pos81').append($pieces.wr); 139 | $boardInner.find('#pos21,#pos71').append($pieces.wn); 140 | $boardInner.find('#pos31,#pos61').append($pieces.wb); 141 | $boardInner.find('#pos51').append($pieces.wk); 142 | $boardInner.find('#pos41').append($pieces.wq); 143 | 144 | } 145 | 146 | if(typeof type === 'string'){ 147 | switch(type){ 148 | case 'matrix': 149 | this._arrangeMatrixPosition(position); 150 | break; 151 | case 'fen': 152 | this._arrangeMatrixPosition(this._fenToPosition(position)); 153 | break; 154 | default: 155 | alert(1); 156 | } 157 | } 158 | } 159 | 160 | this._fenToPosition = function(fen){ 161 | var position = []; 162 | var rows = fen.split('/'); 163 | for(var i=0; i
110 | value = parseInt( elem.css( "zIndex" ), 10 ); 111 | if ( !isNaN( value ) && value !== 0 ) { 112 | return value; 113 | } 114 | } 115 | elem = elem.parent(); 116 | } 117 | } 118 | 119 | return 0; 120 | }, 121 | 122 | disableSelection: function() { 123 | return this.bind( ( $.support.selectstart ? "selectstart" : "mousedown" ) + 124 | ".ui-disableSelection", function( event ) { 125 | event.preventDefault(); 126 | }); 127 | }, 128 | 129 | enableSelection: function() { 130 | return this.unbind( ".ui-disableSelection" ); 131 | } 132 | }); 133 | 134 | $.each( [ "Width", "Height" ], function( i, name ) { 135 | var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ], 136 | type = name.toLowerCase(), 137 | orig = { 138 | innerWidth: $.fn.innerWidth, 139 | innerHeight: $.fn.innerHeight, 140 | outerWidth: $.fn.outerWidth, 141 | outerHeight: $.fn.outerHeight 142 | }; 143 | 144 | function reduce( elem, size, border, margin ) { 145 | $.each( side, function() { 146 | size -= parseFloat( $.curCSS( elem, "padding" + this, true) ) || 0; 147 | if ( border ) { 148 | size -= parseFloat( $.curCSS( elem, "border" + this + "Width", true) ) || 0; 149 | } 150 | if ( margin ) { 151 | size -= parseFloat( $.curCSS( elem, "margin" + this, true) ) || 0; 152 | } 153 | }); 154 | return size; 155 | } 156 | 157 | $.fn[ "inner" + name ] = function( size ) { 158 | if ( size === undefined ) { 159 | return orig[ "inner" + name ].call( this ); 160 | } 161 | 162 | return this.each(function() { 163 | $( this ).css( type, reduce( this, size ) + "px" ); 164 | }); 165 | }; 166 | 167 | $.fn[ "outer" + name] = function( size, margin ) { 168 | if ( typeof size !== "number" ) { 169 | return orig[ "outer" + name ].call( this, size ); 170 | } 171 | 172 | return this.each(function() { 173 | $( this).css( type, reduce( this, size, true, margin ) + "px" ); 174 | }); 175 | }; 176 | }); 177 | 178 | // selectors 179 | function focusable( element, isTabIndexNotNaN ) { 180 | var nodeName = element.nodeName.toLowerCase(); 181 | if ( "area" === nodeName ) { 182 | var map = element.parentNode, 183 | mapName = map.name, 184 | img; 185 | if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) { 186 | return false; 187 | } 188 | img = $( "img[usemap=#" + mapName + "]" )[0]; 189 | return !!img && visible( img ); 190 | } 191 | return ( /input|select|textarea|button|object/.test( nodeName ) 192 | ? !element.disabled 193 | : "a" == nodeName 194 | ? element.href || isTabIndexNotNaN 195 | : isTabIndexNotNaN) 196 | // the element and all of its ancestors must be visible 197 | && visible( element ); 198 | } 199 | 200 | function visible( element ) { 201 | return !$( element ).parents().andSelf().filter(function() { 202 | return $.curCSS( this, "visibility" ) === "hidden" || 203 | $.expr.filters.hidden( this ); 204 | }).length; 205 | } 206 | 207 | $.extend( $.expr[ ":" ], { 208 | data: function( elem, i, match ) { 209 | return !!$.data( elem, match[ 3 ] ); 210 | }, 211 | 212 | focusable: function( element ) { 213 | return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) ); 214 | }, 215 | 216 | tabbable: function( element ) { 217 | var tabIndex = $.attr( element, "tabindex" ), 218 | isTabIndexNaN = isNaN( tabIndex ); 219 | return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN ); 220 | } 221 | }); 222 | 223 | // support 224 | $(function() { 225 | var body = document.body, 226 | div = body.appendChild( div = document.createElement( "div" ) ); 227 | 228 | // access offsetHeight before setting the style to prevent a layout bug 229 | // in IE 9 which causes the elemnt to continue to take up space even 230 | // after it is removed from the DOM (#8026) 231 | div.offsetHeight; 232 | 233 | $.extend( div.style, { 234 | minHeight: "100px", 235 | height: "auto", 236 | padding: 0, 237 | borderWidth: 0 238 | }); 239 | 240 | $.support.minHeight = div.offsetHeight === 100; 241 | $.support.selectstart = "onselectstart" in div; 242 | 243 | // set display to none to avoid a layout bug in IE 244 | // http://dev.jquery.com/ticket/4014 245 | body.removeChild( div ).style.display = "none"; 246 | }); 247 | 248 | 249 | 250 | 251 | 252 | // deprecated 253 | $.extend( $.ui, { 254 | // $.ui.plugin is deprecated. Use the proxy pattern instead. 255 | plugin: { 256 | add: function( module, option, set ) { 257 | var proto = $.ui[ module ].prototype; 258 | for ( var i in set ) { 259 | proto.plugins[ i ] = proto.plugins[ i ] || []; 260 | proto.plugins[ i ].push( [ option, set[ i ] ] ); 261 | } 262 | }, 263 | call: function( instance, name, args ) { 264 | var set = instance.plugins[ name ]; 265 | if ( !set || !instance.element[ 0 ].parentNode ) { 266 | return; 267 | } 268 | 269 | for ( var i = 0; i < set.length; i++ ) { 270 | if ( instance.options[ set[ i ][ 0 ] ] ) { 271 | set[ i ][ 1 ].apply( instance.element, args ); 272 | } 273 | } 274 | } 275 | }, 276 | 277 | // will be deprecated when we switch to jQuery 1.4 - use jQuery.contains() 278 | contains: function( a, b ) { 279 | return document.compareDocumentPosition ? 280 | a.compareDocumentPosition( b ) & 16 : 281 | a !== b && a.contains( b ); 282 | }, 283 | 284 | // only used by resizable 285 | hasScroll: function( el, a ) { 286 | 287 | //If overflow is hidden, the element might have extra content, but the user wants to hide it 288 | if ( $( el ).css( "overflow" ) === "hidden") { 289 | return false; 290 | } 291 | 292 | var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop", 293 | has = false; 294 | 295 | if ( el[ scroll ] > 0 ) { 296 | return true; 297 | } 298 | 299 | // TODO: determine which cases actually cause this to happen 300 | // if the element doesn't have the scroll set, see if it's possible to 301 | // set the scroll 302 | el[ scroll ] = 1; 303 | has = ( el[ scroll ] > 0 ); 304 | el[ scroll ] = 0; 305 | return has; 306 | }, 307 | 308 | // these are odd functions, fix the API or move into individual plugins 309 | isOverAxis: function( x, reference, size ) { 310 | //Determines when x coordinate is over "b" element axis 311 | return ( x > reference ) && ( x < ( reference + size ) ); 312 | }, 313 | isOver: function( y, x, top, left, height, width ) { 314 | //Determines when x, y coordinates is over "b" element 315 | return $.ui.isOverAxis( y, top, height ) && $.ui.isOverAxis( x, left, width ); 316 | } 317 | }); 318 | 319 | })( jQuery ); 320 | -------------------------------------------------------------------------------- /nginx/static/js/jquery.ui.droppable.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery UI Droppable @VERSION 3 | * 4 | * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) 5 | * Dual licensed under the MIT or GPL Version 2 licenses. 6 | * http://jquery.org/license 7 | * 8 | * http://docs.jquery.com/UI/Droppables 9 | * 10 | * Depends: 11 | * jquery.ui.core.js 12 | * jquery.ui.widget.js 13 | * jquery.ui.mouse.js 14 | * jquery.ui.draggable.js 15 | */ 16 | 17 | (function( $, undefined ) { 18 | 19 | $.widget("ui.droppable", { 20 | widgetEventPrefix: "drop", 21 | options: { 22 | accept: '*', 23 | activeClass: false, 24 | addClasses: true, 25 | greedy: false, 26 | hoverClass: false, 27 | scope: 'default', 28 | tolerance: 'intersect' 29 | }, 30 | _create: function() { 31 | 32 | var o = this.options, accept = o.accept; 33 | this.isover = 0; this.isout = 1; 34 | 35 | this.accept = $.isFunction(accept) ? accept : function(d) { 36 | return d.is(accept); 37 | }; 38 | 39 | //Store the droppable's proportions 40 | this.proportions = { width: this.element[0].offsetWidth, height: this.element[0].offsetHeight }; 41 | 42 | // Add the reference and positions to the manager 43 | $.ui.ddmanager.droppables[o.scope] = $.ui.ddmanager.droppables[o.scope] || []; 44 | $.ui.ddmanager.droppables[o.scope].push(this); 45 | 46 | (o.addClasses && this.element.addClass("ui-droppable")); 47 | 48 | }, 49 | 50 | destroy: function() { 51 | var drop = $.ui.ddmanager.droppables[this.options.scope]; 52 | for ( var i = 0; i < drop.length; i++ ) 53 | if ( drop[i] == this ) 54 | drop.splice(i, 1); 55 | 56 | this.element 57 | .removeClass("ui-droppable ui-droppable-disabled") 58 | .removeData("droppable") 59 | .unbind(".droppable"); 60 | 61 | return this; 62 | }, 63 | 64 | _setOption: function(key, value) { 65 | 66 | if(key == 'accept') { 67 | this.accept = $.isFunction(value) ? value : function(d) { 68 | return d.is(value); 69 | }; 70 | } 71 | $.Widget.prototype._setOption.apply(this, arguments); 72 | }, 73 | 74 | _activate: function(event) { 75 | 76 | var draggable = $.ui.ddmanager.current; 77 | if(this.options.activeClass) this.element.addClass(this.options.activeClass); 78 | (draggable && this._trigger('activate', event, this.ui(draggable))); 79 | }, 80 | 81 | _deactivate: function(event) { 82 | var draggable = $.ui.ddmanager.current; 83 | if(this.options.activeClass) this.element.removeClass(this.options.activeClass); 84 | (draggable && this._trigger('deactivate', event, this.ui(draggable))); 85 | }, 86 | 87 | _over: function(event) { 88 | 89 | var draggable = $.ui.ddmanager.current; 90 | if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return; // Bail if draggable and droppable are same element 91 | 92 | if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) { 93 | if(this.options.hoverClass) this.element.addClass(this.options.hoverClass); 94 | this._trigger('over', event, this.ui(draggable)); 95 | } 96 | 97 | }, 98 | 99 | _out: function(event) { 100 | 101 | var draggable = $.ui.ddmanager.current; 102 | if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return; // Bail if draggable and droppable are same element 103 | 104 | if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) { 105 | if(this.options.hoverClass) this.element.removeClass(this.options.hoverClass); 106 | this._trigger('out', event, this.ui(draggable)); 107 | } 108 | 109 | }, 110 | 111 | _drop: function(event,custom) { 112 | 113 | var draggable = custom || $.ui.ddmanager.current; 114 | if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) {return false;} // Bail if draggable and droppable are same element 115 | 116 | var end_cell = this.element.find(":data(droppable)").context.getElementsByClassName('squareNotation')[0].innerText; 117 | 118 | var childrenIntersection = false; 119 | this.element.find(":data(droppable)").not(".ui-draggable-dragging").each(function() { 120 | var inst = $.data(this, 'droppable'); 121 | if( 122 | inst.options.greedy 123 | && !inst.options.disabled 124 | && inst.options.scope == draggable.options.scope 125 | && inst.accept.call(inst.element[0], (draggable.currentItem || draggable.element)) 126 | && $.ui.intersect(draggable, $.extend(inst, { offset: inst.element.offset() }), inst.options.tolerance) 127 | ) { childrenIntersection = true; return false; } 128 | }); 129 | if(childrenIntersection) return false; 130 | 131 | if(this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) { 132 | if(this.options.activeClass) this.element.removeClass(this.options.activeClass); 133 | if(this.options.hoverClass) this.element.removeClass(this.options.hoverClass); 134 | this._trigger('drop', event, this.ui(draggable)); 135 | return this.element; 136 | } 137 | 138 | 139 | 140 | return false; 141 | 142 | }, 143 | 144 | ui: function(c) { 145 | return { 146 | draggable: (c.currentItem || c.element), 147 | helper: c.helper, 148 | position: c.position, 149 | offset: c.positionAbs 150 | }; 151 | } 152 | 153 | }); 154 | 155 | $.extend($.ui.droppable, { 156 | version: "@VERSION" 157 | }); 158 | 159 | $.ui.intersect = function(draggable, droppable, toleranceMode) { 160 | 161 | if (!droppable.offset) return false; 162 | 163 | var x1 = (draggable.positionAbs || draggable.position.absolute).left, x2 = x1 + draggable.helperProportions.width, 164 | y1 = (draggable.positionAbs || draggable.position.absolute).top, y2 = y1 + draggable.helperProportions.height; 165 | var l = droppable.offset.left, r = l + droppable.proportions.width, 166 | t = droppable.offset.top, b = t + droppable.proportions.height; 167 | 168 | switch (toleranceMode) { 169 | case 'fit': 170 | return (l <= x1 && x2 <= r 171 | && t <= y1 && y2 <= b); 172 | break; 173 | case 'intersect': 174 | return (l < x1 + (draggable.helperProportions.width / 2) // Right Half 175 | && x2 - (draggable.helperProportions.width / 2) < r // Left Half 176 | && t < y1 + (draggable.helperProportions.height / 2) // Bottom Half 177 | && y2 - (draggable.helperProportions.height / 2) < b ); // Top Half 178 | break; 179 | case 'pointer': 180 | var draggableLeft = ((draggable.positionAbs || draggable.position.absolute).left + (draggable.clickOffset || draggable.offset.click).left), 181 | draggableTop = ((draggable.positionAbs || draggable.position.absolute).top + (draggable.clickOffset || draggable.offset.click).top), 182 | isOver = $.ui.isOver(draggableTop, draggableLeft, t, l, droppable.proportions.height, droppable.proportions.width); 183 | return isOver; 184 | break; 185 | case 'touch': 186 | return ( 187 | (y1 >= t && y1 <= b) || // Top edge touching 188 | (y2 >= t && y2 <= b) || // Bottom edge touching 189 | (y1 < t && y2 > b) // Surrounded vertically 190 | ) && ( 191 | (x1 >= l && x1 <= r) || // Left edge touching 192 | (x2 >= l && x2 <= r) || // Right edge touching 193 | (x1 < l && x2 > r) // Surrounded horizontally 194 | ); 195 | break; 196 | default: 197 | return false; 198 | break; 199 | } 200 | 201 | }; 202 | 203 | /* 204 | This manager tracks offsets of draggables and droppables 205 | */ 206 | $.ui.ddmanager = { 207 | current: null, 208 | droppables: { 'default': [] }, 209 | prepareOffsets: function(t, event) { 210 | 211 | var m = $.ui.ddmanager.droppables[t.options.scope] || []; 212 | var type = event ? event.type : null; // workaround for #2317 213 | var list = (t.currentItem || t.element).find(":data(droppable)").andSelf(); 214 | 215 | droppablesLoop: for (var i = 0; i < m.length; i++) { 216 | 217 | if(m[i].options.disabled || (t && !m[i].accept.call(m[i].element[0],(t.currentItem || t.element)))) continue; //No disabled and non-accepted 218 | for (var j=0; j < list.length; j++) { if(list[j] == m[i].element[0]) { m[i].proportions.height = 0; continue droppablesLoop; } }; //Filter out elements in the current dragged item 219 | m[i].visible = m[i].element.css("display") != "none"; if(!m[i].visible) continue; //If the element is not visible, continue 220 | 221 | if(type == "mousedown") m[i]._activate.call(m[i], event); //Activate the droppable if used directly from draggables 222 | 223 | m[i].offset = m[i].element.offset(); 224 | m[i].proportions = { width: m[i].element[0].offsetWidth, height: m[i].element[0].offsetHeight }; 225 | 226 | } 227 | 228 | }, 229 | drop: function(draggable, event) { 230 | 231 | hideLegalMoves(); 232 | 233 | var dropped = false; 234 | $.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() { 235 | 236 | if(!this.options) return; 237 | if (!this.options.disabled && this.visible && $.ui.intersect(draggable, this, this.options.tolerance)) 238 | dropped = this._drop.call(this, event) || dropped; 239 | 240 | 241 | if (!this.options.disabled && this.visible && this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) { 242 | this.isout = 1; this.isover = 0; 243 | this._deactivate.call(this, event); 244 | } 245 | 246 | }); 247 | if (dropped){ 248 | let end_cell = dropped[0]; 249 | if (window.start_cell == end_cell){return false;} 250 | move(window.start_cell, end_cell); 251 | } 252 | 253 | return dropped; 254 | 255 | }, 256 | dragStart: function( draggable, event ) { 257 | 258 | window.start_cell = draggable.element.context.offsetParent; 259 | getLegalMoves(window.start_cell.getElementsByClassName("squareNotation")[0].innerText); 260 | //Listen for scrolling so that if the dragging causes scrolling the position of the droppables can be recalculated (see #5003) 261 | draggable.element.parents( ":not(body,html)" ).bind( "scroll.droppable", function() { 262 | if( !draggable.options.refreshPositions ) $.ui.ddmanager.prepareOffsets( draggable, event ); 263 | }); 264 | }, 265 | drag: function(draggable, event) { 266 | 267 | //If you have a highly dynamic page, you might try this option. It renders positions every time you move the mouse. 268 | if(draggable.options.refreshPositions) $.ui.ddmanager.prepareOffsets(draggable, event); 269 | 270 | //Run through all droppables and check their positions based on specific tolerance options 271 | $.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() { 272 | 273 | if(this.options.disabled || this.greedyChild || !this.visible) return; 274 | var intersects = $.ui.intersect(draggable, this, this.options.tolerance); 275 | 276 | var c = !intersects && this.isover == 1 ? 'isout' : (intersects && this.isover == 0 ? 'isover' : null); 277 | if(!c) return; 278 | 279 | var parentInstance; 280 | if (this.options.greedy) { 281 | var parent = this.element.parents(':data(droppable):eq(0)'); 282 | if (parent.length) { 283 | parentInstance = $.data(parent[0], 'droppable'); 284 | parentInstance.greedyChild = (c == 'isover' ? 1 : 0); 285 | } 286 | } 287 | 288 | // we just moved into a greedy child 289 | if (parentInstance && c == 'isover') { 290 | parentInstance['isover'] = 0; 291 | parentInstance['isout'] = 1; 292 | parentInstance._out.call(parentInstance, event); 293 | } 294 | 295 | this[c] = 1; this[c == 'isout' ? 'isover' : 'isout'] = 0; 296 | this[c == "isover" ? "_over" : "_out"].call(this, event); 297 | 298 | // we just moved out of a greedy child 299 | if (parentInstance && c == 'isout') { 300 | parentInstance['isout'] = 0; 301 | parentInstance['isover'] = 1; 302 | parentInstance._over.call(parentInstance, event); 303 | } 304 | }); 305 | 306 | }, 307 | dragStop: function( draggable, event ) { 308 | draggable.element.parents( ":not(body,html)" ).unbind( "scroll.droppable" ); 309 | //Call prepareOffsets one final time since IE does not fire return scroll events when overflow was caused by drag (see #5003) 310 | if( !draggable.options.refreshPositions ) $.ui.ddmanager.prepareOffsets( draggable, event ); 311 | } 312 | }; 313 | 314 | })(jQuery); 315 | -------------------------------------------------------------------------------- /nginx/static/js/jquery.ui.mouse.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Mouse 1.8.18 3 | * 4 | * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) 5 | * Dual licensed under the MIT or GPL Version 2 licenses. 6 | * http://jquery.org/license 7 | * 8 | * http://docs.jquery.com/UI/Mouse 9 | * 10 | * Depends: 11 | * jquery.ui.widget.js 12 | */ 13 | (function( $, undefined ) { 14 | 15 | var mouseHandled = false; 16 | $( document ).mouseup( function( e ) { 17 | mouseHandled = false; 18 | }); 19 | 20 | $.widget("ui.mouse", { 21 | options: { 22 | cancel: ':input,option', 23 | distance: 1, 24 | delay: 0 25 | }, 26 | _mouseInit: function() { 27 | 28 | var self = this; 29 | 30 | this.element 31 | .bind('mousedown.'+this.widgetName, function(event) { 32 | return self._mouseDown(event); 33 | }) 34 | .bind('click.'+this.widgetName, function(event) { 35 | if (true === $.data(event.target, self.widgetName + '.preventClickEvent')) { 36 | $.removeData(event.target, self.widgetName + '.preventClickEvent'); 37 | event.stopImmediatePropagation(); 38 | return false; 39 | } 40 | }); 41 | 42 | this.started = false; 43 | }, 44 | 45 | // TODO: make sure destroying one instance of mouse doesn't mess with 46 | // other instances of mouse 47 | _mouseDestroy: function() { 48 | this.element.unbind('.'+this.widgetName); 49 | }, 50 | 51 | _mouseDown: function(event) { 52 | if (!possibles_to_move(event.srcElement.classList[1])) return; 53 | 54 | // don't let more than one widget handle mouseStart 55 | if( mouseHandled ) { return }; 56 | 57 | // we may have missed mouseup (out of window) 58 | (this._mouseStarted && this._mouseUp(event)); 59 | 60 | this._mouseDownEvent = event; 61 | 62 | var self = this, 63 | btnIsLeft = (event.which == 1), 64 | // event.target.nodeName works around a bug in IE 8 with 65 | // disabled inputs (#7620) 66 | elIsCancel = (typeof this.options.cancel == "string" && event.target.nodeName ? $(event.target).closest(this.options.cancel).length : false); 67 | if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) { 68 | return true; 69 | } 70 | 71 | this.mouseDelayMet = !this.options.delay; 72 | if (!this.mouseDelayMet) { 73 | this._mouseDelayTimer = setTimeout(function() { 74 | self.mouseDelayMet = true; 75 | }, this.options.delay); 76 | } 77 | 78 | if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) { 79 | this._mouseStarted = (this._mouseStart(event) !== false); 80 | if (!this._mouseStarted) { 81 | event.preventDefault(); 82 | return true; 83 | } 84 | } 85 | 86 | // Click event may never have fired (Gecko & Opera) 87 | if (true === $.data(event.target, this.widgetName + '.preventClickEvent')) { 88 | $.removeData(event.target, this.widgetName + '.preventClickEvent'); 89 | } 90 | 91 | // these delegates are required to keep context 92 | this._mouseMoveDelegate = function(event) { 93 | return self._mouseMove(event); 94 | }; 95 | this._mouseUpDelegate = function(event) { 96 | return self._mouseUp(event); 97 | }; 98 | $(document) 99 | .bind('mousemove.'+this.widgetName, this._mouseMoveDelegate) 100 | .bind('mouseup.'+this.widgetName, this._mouseUpDelegate); 101 | 102 | event.preventDefault(); 103 | 104 | mouseHandled = true; 105 | return true; 106 | }, 107 | 108 | _mouseMove: function(event) { 109 | // IE mouseup check - mouseup happened when mouse was out of window 110 | if ($.browser.msie && !(document.documentMode >= 9) && !event.button) { 111 | return this._mouseUp(event); 112 | } 113 | 114 | if (this._mouseStarted) { 115 | this._mouseDrag(event); 116 | return event.preventDefault(); 117 | } 118 | 119 | if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) { 120 | this._mouseStarted = 121 | (this._mouseStart(this._mouseDownEvent, event) !== false); 122 | (this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event)); 123 | } 124 | 125 | return !this._mouseStarted; 126 | }, 127 | 128 | _mouseUp: function(event) { 129 | $(document) 130 | .unbind('mousemove.'+this.widgetName, this._mouseMoveDelegate) 131 | .unbind('mouseup.'+this.widgetName, this._mouseUpDelegate); 132 | 133 | if (this._mouseStarted) { 134 | this._mouseStarted = false; 135 | 136 | if (event.target == this._mouseDownEvent.target) { 137 | $.data(event.target, this.widgetName + '.preventClickEvent', true); 138 | } 139 | 140 | this._mouseStop(event); 141 | } 142 | 143 | return false; 144 | }, 145 | 146 | _mouseDistanceMet: function(event) { 147 | return (Math.max( 148 | Math.abs(this._mouseDownEvent.pageX - event.pageX), 149 | Math.abs(this._mouseDownEvent.pageY - event.pageY) 150 | ) >= this.options.distance 151 | ); 152 | }, 153 | 154 | _mouseDelayMet: function(event) { 155 | return this.mouseDelayMet; 156 | }, 157 | 158 | // These are placeholder methods, to be overriden by extending plugin 159 | _mouseStart: function(event) {}, 160 | _mouseDrag: function(event) {}, 161 | _mouseStop: function(event) {}, 162 | _mouseCapture: function(event) { return true; } 163 | }); 164 | 165 | })(jQuery); 166 | -------------------------------------------------------------------------------- /nginx/static/js/jquery.ui.touch-punch.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Touch Punch 0.2.3 3 | * 4 | * Copyright 2011–2014, Dave Furfero 5 | * Dual licensed under the MIT or GPL Version 2 licenses. 6 | * 7 | * Depends: 8 | * jquery.ui.widget.js 9 | * jquery.ui.mouse.js 10 | */ 11 | !function(a){function f(a,b){if(!(a.originalEvent.touches.length>1)){a.preventDefault();var c=a.originalEvent.changedTouches[0],d=document.createEvent("MouseEvents");d.initMouseEvent(b,!0,!0,window,1,c.screenX,c.screenY,c.clientX,c.clientY,!1,!1,!1,!1,0,null),a.target.dispatchEvent(d)}}if(a.support.touch="ontouchend"in document,a.support.touch){var e,b=a.ui.mouse.prototype,c=b._mouseInit,d=b._mouseDestroy;b._touchStart=function(a){var b=this;!e&&b._mouseCapture(a.originalEvent.changedTouches[0])&&(e=!0,b._touchMoved=!1,f(a,"mouseover"),f(a,"mousemove"),f(a,"mousedown"))},b._touchMove=function(a){e&&(this._touchMoved=!0,f(a,"mousemove"))},b._touchEnd=function(a){e&&(f(a,"mouseup"),f(a,"mouseout"),this._touchMoved||f(a,"click"),e=!1)},b._mouseInit=function(){var b=this;b.element.bind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),c.call(b)},b._mouseDestroy=function(){var b=this;b.element.unbind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),d.call(b)}}}(jQuery); -------------------------------------------------------------------------------- /nginx/static/js/jquery.ui.widget.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Widget 1.8.18 3 | * 4 | * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) 5 | * Dual licensed under the MIT or GPL Version 2 licenses. 6 | * http://jquery.org/license 7 | * 8 | * http://docs.jquery.com/UI/Widget 9 | */ 10 | (function( $, undefined ) { 11 | 12 | // jQuery 1.4+ 13 | if ( $.cleanData ) { 14 | var _cleanData = $.cleanData; 15 | $.cleanData = function( elems ) { 16 | for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { 17 | try { 18 | $( elem ).triggerHandler( "remove" ); 19 | // http://bugs.jquery.com/ticket/8235 20 | } catch( e ) {} 21 | } 22 | _cleanData( elems ); 23 | }; 24 | } else { 25 | var _remove = $.fn.remove; 26 | $.fn.remove = function( selector, keepData ) { 27 | return this.each(function() { 28 | if ( !keepData ) { 29 | if ( !selector || $.filter( selector, [ this ] ).length ) { 30 | $( "*", this ).add( [ this ] ).each(function() { 31 | try { 32 | $( this ).triggerHandler( "remove" ); 33 | // http://bugs.jquery.com/ticket/8235 34 | } catch( e ) {} 35 | }); 36 | } 37 | } 38 | return _remove.call( $(this), selector, keepData ); 39 | }); 40 | }; 41 | } 42 | 43 | $.widget = function( name, base, prototype ) { 44 | var namespace = name.split( "." )[ 0 ], 45 | fullName; 46 | name = name.split( "." )[ 1 ]; 47 | fullName = namespace + "-" + name; 48 | 49 | if ( !prototype ) { 50 | prototype = base; 51 | base = $.Widget; 52 | } 53 | 54 | // create selector for plugin 55 | $.expr[ ":" ][ fullName ] = function( elem ) { 56 | return !!$.data( elem, name ); 57 | }; 58 | 59 | $[ namespace ] = $[ namespace ] || {}; 60 | $[ namespace ][ name ] = function( options, element ) { 61 | // allow instantiation without initializing for simple inheritance 62 | if ( arguments.length ) { 63 | this._createWidget( options, element ); 64 | } 65 | }; 66 | 67 | var basePrototype = new base(); 68 | // we need to make the options hash a property directly on the new instance 69 | // otherwise we'll modify the options hash on the prototype that we're 70 | // inheriting from 71 | // $.each( basePrototype, function( key, val ) { 72 | // if ( $.isPlainObject(val) ) { 73 | // basePrototype[ key ] = $.extend( {}, val ); 74 | // } 75 | // }); 76 | basePrototype.options = $.extend( true, {}, basePrototype.options ); 77 | $[ namespace ][ name ].prototype = $.extend( true, basePrototype, { 78 | namespace: namespace, 79 | widgetName: name, 80 | widgetEventPrefix: $[ namespace ][ name ].prototype.widgetEventPrefix || name, 81 | widgetBaseClass: fullName 82 | }, prototype ); 83 | 84 | $.widget.bridge( name, $[ namespace ][ name ] ); 85 | }; 86 | 87 | $.widget.bridge = function( name, object ) { 88 | $.fn[ name ] = function( options ) { 89 | var isMethodCall = typeof options === "string", 90 | args = Array.prototype.slice.call( arguments, 1 ), 91 | returnValue = this; 92 | 93 | // allow multiple hashes to be passed on init 94 | options = !isMethodCall && args.length ? 95 | $.extend.apply( null, [ true, options ].concat(args) ) : 96 | options; 97 | 98 | // prevent calls to internal methods 99 | if ( isMethodCall && options.charAt( 0 ) === "_" ) { 100 | return returnValue; 101 | } 102 | 103 | if ( isMethodCall ) { 104 | this.each(function() { 105 | var instance = $.data( this, name ), 106 | methodValue = instance && $.isFunction( instance[options] ) ? 107 | instance[ options ].apply( instance, args ) : 108 | instance; 109 | // TODO: add this back in 1.9 and use $.error() (see #5972) 110 | // if ( !instance ) { 111 | // throw "cannot call methods on " + name + " prior to initialization; " + 112 | // "attempted to call method '" + options + "'"; 113 | // } 114 | // if ( !$.isFunction( instance[options] ) ) { 115 | // throw "no such method '" + options + "' for " + name + " widget instance"; 116 | // } 117 | // var methodValue = instance[ options ].apply( instance, args ); 118 | if ( methodValue !== instance && methodValue !== undefined ) { 119 | returnValue = methodValue; 120 | return false; 121 | } 122 | }); 123 | } else { 124 | this.each(function() { 125 | var instance = $.data( this, name ); 126 | if ( instance ) { 127 | instance.option( options || {} )._init(); 128 | } else { 129 | $.data( this, name, new object( options, this ) ); 130 | } 131 | }); 132 | } 133 | 134 | return returnValue; 135 | }; 136 | }; 137 | 138 | $.Widget = function( options, element ) { 139 | // allow instantiation without initializing for simple inheritance 140 | if ( arguments.length ) { 141 | this._createWidget( options, element ); 142 | } 143 | }; 144 | 145 | $.Widget.prototype = { 146 | widgetName: "widget", 147 | widgetEventPrefix: "", 148 | options: { 149 | disabled: false 150 | }, 151 | _createWidget: function( options, element ) { 152 | // $.widget.bridge stores the plugin instance, but we do it anyway 153 | // so that it's stored even before the _create function runs 154 | $.data( element, this.widgetName, this ); 155 | this.element = $( element ); 156 | this.options = $.extend( true, {}, 157 | this.options, 158 | this._getCreateOptions(), 159 | options ); 160 | 161 | var self = this; 162 | this.element.bind( "remove." + this.widgetName, function() { 163 | self.destroy(); 164 | }); 165 | 166 | this._create(); 167 | this._trigger( "create" ); 168 | this._init(); 169 | }, 170 | _getCreateOptions: function() { 171 | return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ]; 172 | }, 173 | _create: function() {}, 174 | _init: function() {}, 175 | 176 | destroy: function() { 177 | this.element 178 | .unbind( "." + this.widgetName ) 179 | .removeData( this.widgetName ); 180 | this.widget() 181 | .unbind( "." + this.widgetName ) 182 | .removeAttr( "aria-disabled" ) 183 | .removeClass( 184 | this.widgetBaseClass + "-disabled " + 185 | "ui-state-disabled" ); 186 | }, 187 | 188 | widget: function() { 189 | return this.element; 190 | }, 191 | 192 | option: function( key, value ) { 193 | var options = key; 194 | 195 | if ( arguments.length === 0 ) { 196 | // don't return a reference to the internal hash 197 | return $.extend( {}, this.options ); 198 | } 199 | 200 | if (typeof key === "string" ) { 201 | if ( value === undefined ) { 202 | return this.options[ key ]; 203 | } 204 | options = {}; 205 | options[ key ] = value; 206 | } 207 | 208 | this._setOptions( options ); 209 | 210 | return this; 211 | }, 212 | _setOptions: function( options ) { 213 | var self = this; 214 | $.each( options, function( key, value ) { 215 | self._setOption( key, value ); 216 | }); 217 | 218 | return this; 219 | }, 220 | _setOption: function( key, value ) { 221 | this.options[ key ] = value; 222 | 223 | if ( key === "disabled" ) { 224 | this.widget() 225 | [ value ? "addClass" : "removeClass"]( 226 | this.widgetBaseClass + "-disabled" + " " + 227 | "ui-state-disabled" ) 228 | .attr( "aria-disabled", value ); 229 | } 230 | 231 | return this; 232 | }, 233 | 234 | enable: function() { 235 | return this._setOption( "disabled", false ); 236 | }, 237 | disable: function() { 238 | return this._setOption( "disabled", true ); 239 | }, 240 | 241 | _trigger: function( type, event, data ) { 242 | var prop, orig, 243 | callback = this.options[ type ]; 244 | 245 | data = data || {}; 246 | event = $.Event( event ); 247 | event.type = ( type === this.widgetEventPrefix ? 248 | type : 249 | this.widgetEventPrefix + type ).toLowerCase(); 250 | // the original event may come from any element 251 | // so we need to reset the target on the new event 252 | event.target = this.element[ 0 ]; 253 | 254 | // copy original event properties over to the new event 255 | orig = event.originalEvent; 256 | if ( orig ) { 257 | for ( prop in orig ) { 258 | if ( !( prop in event ) ) { 259 | event[ prop ] = orig[ prop ]; 260 | } 261 | } 262 | } 263 | 264 | this.element.trigger( event, data ); 265 | 266 | return !( $.isFunction(callback) && 267 | callback.call( this.element[0], event, data ) === false || 268 | event.isDefaultPrevented() ); 269 | } 270 | }; 271 | 272 | })( jQuery ); 273 | -------------------------------------------------------------------------------- /nginx/static/js/player_timers.js: -------------------------------------------------------------------------------- 1 | var chessTimer; 2 | 3 | function updateFirstPlayerTime(first_player_time_var){ 4 | 5 | var first_player_time = first_player_time_var; 6 | window.first_player_time = first_player_time; 7 | updateTimer(first_player_time, "first_player_time"); 8 | } 9 | 10 | function updateSecondPlayerTime(second_player_time_var){ 11 | 12 | var second_player_time = second_player_time_var; 13 | window.second_player_time = second_player_time; 14 | updateTimer(second_player_time, "second_player_time"); 15 | 16 | if (first_player_time <= 0){ 17 | clearInterval(chessTimer); 18 | } 19 | } 20 | 21 | function startTimers(){ 22 | chessTimer = setInterval(function () { 23 | if (window.turn){ 24 | updateFirstPlayerTime(window.first_player_time - 1); 25 | return; 26 | } 27 | updateSecondPlayerTime(window.second_player_time - 1); 28 | }, 1000);} 29 | 30 | function updateTimer(time, player){ 31 | 32 | ifNegativeTime(time); 33 | 34 | watch = Math.floor(time / 60) + ":" + (time % 60); 35 | 36 | replaceText($('#' + player)[0].children[0], watch); 37 | 38 | } 39 | 40 | function UpdateTimerFunction(){ 41 | if (window.turn){ 42 | updateFirstPlayerTime(window.first_player_time - 1); 43 | return; 44 | } 45 | updateSecondPlayerTime(window.second_player_time - 1); 46 | } 47 | 48 | function ifNegativeTime(player_time){ 49 | if (player_time < 0) { 50 | 51 | string = window.location.pathname.slice(0, -1); 52 | 53 | tag = string.slice(string.lastIndexOf('/')+1); 54 | 55 | console.log(tag); 56 | 57 | $.ajax( 58 | { 59 | type: 'POST', 60 | url: `${config.ssl_http}://${config.domain} /api/v1/check_timer?tag=${tag}` 61 | }); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /nginx/static/js/prepare_time.js: -------------------------------------------------------------------------------- 1 | function startPrepareTimer(prepare_time_var){ 2 | 3 | if (prepare_time_var <= 0) { 4 | clearPrepareDiv(); 5 | return false;} 6 | 7 | var prepare_time_timer = window.setInterval(function(){ 8 | if (prepare_time <= 0) { 9 | window.clearInterval(prepare_time_timer); 10 | clearPrepareDiv(); 11 | startTimers(); 12 | return false;} 13 | 14 | updatePrepareTime(window.prepare_time - 1); 15 | }, 1000);} 16 | 17 | 18 | function clearPrepareDiv(){ 19 | document.getElementById('prepare_time_div').remove(); 20 | } 21 | 22 | startPrepareTimer(prepare_time) -------------------------------------------------------------------------------- /nginx/static/js/websocket.js: -------------------------------------------------------------------------------- 1 | function webSocketConnector(){ 2 | 3 | let game_tag = window.location.pathname.split('/').at(-2); 4 | 5 | let connect = new WebSocket(`${config.ssl_ws}://${config.domain}/websocket/games/` + game_tag + "/"); 6 | 7 | return connect; 8 | } 9 | 10 | 11 | function onMessage(event){ 12 | let response = $.parseJSON(event.data); 13 | 14 | console.log(response); 15 | 16 | switch(response["event"]){ 17 | 18 | case "draw_offer": 19 | if (Number(response["recipient"]) == user_id){ 20 | if(!draw_offer){ 21 | $('#draw_offer_modal').css({"visibility": "visible"})} 22 | } 23 | break; 24 | 25 | case "error": 26 | alert(response["message"] + "Если это не так, скинь скрин этой ошибки - @Justiks1337"); 27 | break; 28 | 29 | case "illegal_move_error": 30 | replaceText(document.getElementById('illegal_move_message'), response["message"]); 31 | $("#illegal_move_modal").css({"visibility": "visible"}) 32 | window.c.updateMatrixPosition(window.board_fen) 33 | break; 34 | 35 | case "end_game": 36 | replaceText(document.getElementById('end_game_message'), response["message"]); 37 | $("#end_game_modal").css({"visibility": "visible"}) 38 | window.clearInterval(window.chessTimer); 39 | break; 40 | 41 | case "on_check": 42 | if (response["recipient"] == user_id){ 43 | $('#on_check_modal').css({"visibility": "visible"}) 44 | } 45 | 46 | break; 47 | 48 | case "update_board": 49 | window.c.updateMatrixPosition(response["board"]); 50 | window.board_fen = response["board"]; 51 | 52 | window.onMoveSound(); 53 | 54 | if (window.turn){ 55 | updateTurn("false"); 56 | } 57 | else {updateTurn("true");} 58 | 59 | updateFirstPlayerTime(response["first_user_time"]); 60 | updateSecondPlayerTime(response["second_user_time"]); 61 | break; 62 | 63 | case "legal_moves": 64 | showLegalMoves(response["cells"]); 65 | break; 66 | } 67 | 68 | 69 | } 70 | 71 | var socket_connection = webSocketConnector(); 72 | 73 | function move(start_cell, end_cell){ 74 | 75 | socket_connection.send(JSON.stringify({ 76 | "type": "move", "start_cell": start_cell.getElementsByClassName('squareNotation')[0].innerText, 77 | "end_cell": end_cell.getElementsByClassName('squareNotation')[0].innerText})); 78 | } 79 | 80 | function getLegalMoves(figure_cell){ 81 | socket_connection.send(JSON.stringify({ 82 | "type": "get_legal_moves", "figure_cell": figure_cell})) 83 | } 84 | 85 | socket_connection.onmessage = onMessage; -------------------------------------------------------------------------------- /nginx/static/js/websocket_spectator.js: -------------------------------------------------------------------------------- 1 | function webSocketConnector(){ 2 | 3 | let game_tag = window.location.pathname.split('/').at(-2); 4 | 5 | let connect = new WebSocket(`${config.ssl_ws}://${config.domain}/websocket/games/` + game_tag + "/"); 6 | 7 | return connect; 8 | } 9 | 10 | 11 | function onMessage(event){ 12 | let response = $.parseJSON(event.data); 13 | 14 | switch(response["event"]){ 15 | 16 | case "end_game": 17 | replaceText(document.getElementById('end_game_message'), response["message"]); 18 | $("#end_game_modal").css({"visibility": "visible"}) 19 | window.clearInterval(window.chessTimer); 20 | break; 21 | 22 | case "update_board": 23 | window.c.updateMatrixPosition(response["board"]); 24 | window.board_fen = response["board"]; 25 | if (window.turn){ 26 | updateTurn("false"); 27 | } 28 | else {updateTurn("true");} 29 | 30 | updateFirstPlayerTime(response["first_user_time"]); 31 | updateSecondPlayerTime(response["second_user_time"]); 32 | break; 33 | } 34 | } 35 | 36 | var socket_connection = webSocketConnector(); 37 | 38 | function timeEnd(){ 39 | socket_connection.send(JSON.stringify({"type": "end_timer"})) 40 | } 41 | 42 | 43 | 44 | socket_connection.onmessage = onMessage; -------------------------------------------------------------------------------- /previews/achievement_1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/previews/achievement_1 -------------------------------------------------------------------------------- /previews/game: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/previews/game -------------------------------------------------------------------------------- /previews/game_ready: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/previews/game_ready -------------------------------------------------------------------------------- /telegram/QueueClass.py: -------------------------------------------------------------------------------- 1 | from asyncio import create_task 2 | from queue import Queue as PyQueue 3 | 4 | from aiohttp import ClientSession 5 | 6 | from config.ConfigValues import ConfigValues 7 | from decorators import send_message 8 | 9 | 10 | class Queue(PyQueue): 11 | """Класс - очередь (единственный экземпляр создаётся в __init__.py""" 12 | 13 | # rewriting implementation 14 | def __init__(self, maxsize): 15 | super().__init__(maxsize) 16 | self.queue: set = set() 17 | 18 | def _put(self, item): 19 | self.queue.add(item) 20 | 21 | def _get(self): 22 | return self.queue.pop() 23 | 24 | async def add_new_user(self, user_id: int): 25 | """Добавляет нового игрока в очередь""" 26 | 27 | try: 28 | await self.checks(user_id) 29 | except AssertionError as error: 30 | await send_message(user_id, error.args[0]) 31 | return 32 | 33 | self.put(user_id) 34 | 35 | send_task = create_task(send_message(user_id, ConfigValues.on_queue_join_message)) 36 | new_user_task = create_task(self.on_new_user()) 37 | 38 | await new_user_task 39 | await send_task 40 | 41 | def leave_from_queue(self, user_id: int): 42 | """Удаляет участника из очереди""" 43 | 44 | self.queue.remove(user_id) 45 | 46 | async def on_new_user(self): 47 | """Хандлер срабатывающий при попадании нового пользователя в очередь""" 48 | if self.full(): 49 | await self.start_game() 50 | 51 | async def start_game(self): 52 | """Начало игры """ 53 | 54 | users = [self.get(), self.get()] 55 | 56 | async with ClientSession() as session: 57 | async with session.post( 58 | f"{ConfigValues.server_http_protocol}://{ConfigValues.server_ip}/api/v1/start_game", 59 | headers={ 60 | "content-type": "application/json", 61 | "Authorization": ConfigValues.server_authkey}, 62 | json={"players": [users[0], users[1]]}) as response: 63 | json = await response.json() 64 | 65 | url = f"{ConfigValues.proxy_http_protocol}://{ConfigValues.proxy_ip}\ 66 | {ConfigValues.url_to_playground.replace('{rout}', json['uuid'])}" 67 | 68 | for user_id in users: 69 | await send_message(user_id, ConfigValues.on_find_enemy.replace('{url}', url)) 70 | 71 | async def checks(self, user_id): 72 | self.check_in_queue(user_id) 73 | await Queue.check_in_game(user_id) 74 | 75 | def check_in_queue(self, user_id): 76 | assert user_id not in self.queue, ConfigValues.if_in_queue 77 | 78 | @staticmethod 79 | async def check_in_game(user_id): 80 | async with ClientSession() as session: 81 | async with session.post( 82 | f"{ConfigValues.server_http_protocol}://{ConfigValues.server_ip}/api/v1/check_in_game", 83 | params={"user_id": user_id}, 84 | headers={ 85 | "content-type": "application/json", 86 | "Authorization": ConfigValues.server_authkey}) as response: 87 | json = await response.json() 88 | print(json) 89 | assert not json['in_game'], ConfigValues.in_game_error 90 | 91 | 92 | main_queue = Queue(2) 93 | -------------------------------------------------------------------------------- /telegram/__init__.py: -------------------------------------------------------------------------------- 1 | from aiogram import Dispatcher, Bot 2 | 3 | from config.ConfigValues import ConfigValues 4 | 5 | bot: Bot = Bot(token=ConfigValues.telegram_token) 6 | dp = Dispatcher() 7 | -------------------------------------------------------------------------------- /telegram/authorization_core.py: -------------------------------------------------------------------------------- 1 | import os 2 | import aiohttp 3 | from aiogram.types import Message, InlineKeyboardButton, InlineKeyboardMarkup 4 | from aiohttp.client import ClientSession 5 | 6 | from config.ConfigValues import ConfigValues 7 | from decorators import send_message 8 | from telegram import bot 9 | 10 | 11 | async def download_user_avatar(user_id: int): 12 | """Download user profile photo 13 | 14 | :arg user_id - user id 15 | """ 16 | 17 | async with aiohttp.ClientSession() as session: 18 | async with session.get(f"{ConfigValues.server_http_protocol}://{ConfigValues.server_ip}/api/v1/in_database", 19 | params={"user_id": user_id}, 20 | headers={"Content-type": "application/json", 21 | "Authorization": ConfigValues.server_authkey}) as response: 22 | json = await response.json() 23 | if json.get("in_database"): 24 | return 25 | 26 | user_profile_photo = await bot.get_user_profile_photos(user_id) 27 | 28 | if len(user_profile_photo.photos) > 0: 29 | file = await bot.get_file(user_profile_photo.photos[0][0].file_id) 30 | file_destination = get_destination(user_id, get_file_name(file.file_path)) 31 | await bot.download_file(file.file_path, file_destination) 32 | 33 | with open(file_destination, "rb") as destination: 34 | async with aiohttp.ClientSession() as session: 35 | async with session.post( 36 | f"{ConfigValues.server_http_protocol}://{ConfigValues.server_ip}/api/v1/add_avatar?file_name={file_destination}", 37 | data={"file": destination}, 38 | headers={"Authorization": ConfigValues.server_authkey}): 39 | os.remove(file_destination) 40 | 41 | 42 | def get_file_name(file_path: str) -> str: 43 | return file_path[-file_path[::-1].index('/')] 44 | 45 | 46 | def get_file_format(file_name: str) -> str: 47 | try: 48 | return file_name[file_name.index('.'):] 49 | except ValueError: 50 | return ".png" 51 | 52 | 53 | def get_destination(user_id: int, file_name) -> str: 54 | file_format = get_file_format(file_name) 55 | 56 | file_destination = str(user_id) + file_format 57 | 58 | return file_destination 59 | 60 | 61 | async def send_auth_message(message: Message): 62 | user_id = message.from_user.id 63 | 64 | username = message.from_user.username 65 | nickname = message.from_user.full_name 66 | 67 | if not username: 68 | username = nickname 69 | 70 | async with ClientSession() as session: 71 | async with session.post( 72 | f"{ConfigValues.server_http_protocol}://{ConfigValues.server_ip}/api/v1/new_token", 73 | params={"user_id": user_id, 74 | "username": username, 75 | "nickname": nickname}, 76 | headers={"content-type": "application/json", 77 | "Authorization": ConfigValues.server_authkey}) as response: 78 | json = dict(await response.json()) 79 | if json['success']: 80 | 81 | button = InlineKeyboardButton( 82 | text="Авторизуйся!", 83 | url=f"{ConfigValues.proxy_http_protocol}://{ConfigValues.proxy_ip}/?token={json['token']}") 84 | markup = InlineKeyboardMarkup(inline_keyboard=[[button]]) 85 | 86 | await message.reply(ConfigValues.authorization_message, reply_markup=markup) 87 | return 88 | 89 | await send_message(message.chat.id, ConfigValues.on_authorization_error) 90 | 91 | -------------------------------------------------------------------------------- /telegram/config/ConfigValues.py: -------------------------------------------------------------------------------- 1 | from configparser import ConfigParser as _ConfigParser 2 | import os 3 | 4 | 5 | class ConfigValues: 6 | """Get values from config""" 7 | 8 | __config_file = _ConfigParser() 9 | __config_file.read(os.path.join(os.path.dirname(__file__), 'config.ini'), encoding='utf-8-sig') 10 | 11 | # database section 12 | database_user = __config_file.get('database', 'database_user') 13 | database_password = __config_file.get('database', 'database_password') 14 | database_name = __config_file.get('database', 'database_name') 15 | database_host = __config_file.get('database', 'database_host') 16 | database_port = int(__config_file.get('database', 'database_port')) 17 | 18 | # Telegram section 19 | telegram_token = __config_file.get('Telegram', 'telegram_token') 20 | admin_ids: list = __config_file.get('Telegram', 'admin_ids').split(', ') 21 | recharge_time = int(__config_file.get('Telegram', 'recharge_time')) 22 | authorization_tokens_live_time = int(__config_file.get('Telegram', 'authorization_tokens_live_time')) 23 | 24 | # Messages 25 | authorization_instructions = __config_file.get('Messages', 'authorization_instructions').replace('\\n', '\n') 26 | game_instructions = __config_file.get('Messages', 'game_instructions').replace('\\n', '\n') 27 | manual = __config_file.get('Messages', 'manual').replace('\\n', '\n') 28 | on_queue_join_message = __config_file.get('Messages', 'on_queue_join_message').replace('\\n', '\n') 29 | on_queue_leave_message = __config_file.get('Messages', 'on_queue_leave_message').replace('\\n', '\n') 30 | only_in_dm_message = __config_file.get('Messages', 'only_in_dm_message').replace('\\n', '\n') 31 | if_in_queue = __config_file.get('Messages', 'if_in_queue').replace('\\n', '\n') 32 | if_not_in_queue = __config_file.get('Messages', 'if_not_in_queue').replace('\\n', '\n') 33 | if_games_not_enough = __config_file.get('Messages', 'if_games_not_enough').replace('\\n', '\n') 34 | in_game_error = __config_file.get('Messages', 'in_game_error').replace('\\n', '\n') 35 | profile_message = __config_file.get('Messages', 'profile_message').replace('\\n', '\n') 36 | dashboard_title = __config_file.get('Messages', 'dashboard_title').replace('\\n', '\n') 37 | dashboard_object = __config_file.get('Messages', 'dashboard_object').replace('\\n', '\n') 38 | dashboard_on_range_error = __config_file.get('Messages', 'dashboard_on_range_error').replace('\\n', '\n') 39 | illegal_move_error = __config_file.get('Messages', 'illegal_move_error').replace('\\n', '\n') 40 | on_mate_message = __config_file.get('Messages', 'on_mate_message').replace('\\n', '\n') 41 | on_stalemate_message = __config_file.get('Messages', 'on_stalemate_message').replace('\\n', '\n') 42 | on_draw_message = __config_file.get('Messages', 'on_draw_message').replace('\\n', '\n') 43 | on_end_time = __config_file.get('Messages', 'on_end_time').replace('\\n', '\n') 44 | on_someone_move = __config_file.get('Messages', 'on_someone_move').replace('\\n', '\n') 45 | on_resign = __config_file.get('Messages', 'on_resign').replace('\\n', '\n') 46 | authorization_message = __config_file.get('Messages', 'authorization_message').replace('\\n', '\n') 47 | success_authorization_message = __config_file.get('Messages', 'success_authorization_message').replace('\\n', '\n') 48 | on_authorization_error = __config_file.get('Messages', 'on_authorization_error').replace('\\n', '\n') 49 | on_delete_authorization_code = __config_file.get('Messages', 'on_delete_authorization_code').replace('\\n', '\n') 50 | on_unsuccessful_authorization_message = __config_file.get('Messages', 'on_unsuccessful_authorization_message').replace('\\n', '\n') 51 | unauthorized_message = __config_file.get('Messages', 'unauthorized_message').replace('\\n', '\n') 52 | on_blacklist_message = __config_file.get('Messages', 'on_blacklist_message').replace('\\n', '\n') 53 | on_is_not_admin = __config_file.get('Messages', 'on_is_not_admin').replace('\\n', '\n') 54 | successful_add_to_blacklist = __config_file.get('Messages', 'successful_add_to_blacklist').replace('\\n', '\n') 55 | successful_remove_from_blacklist = __config_file.get('Messages', 'successful_remove_from_blacklist').replace('\\n', '\n') 56 | on_invalid_args = __config_file.get('Messages', 'on_invalid_args').replace('\\n', '\n') 57 | on_find_enemy = __config_file.get('Messages', 'on_find_enemy').replace('\\n', '\n') 58 | in_recharge = __config_file.get('Messages', 'in_recharge').replace('\\n', '\n') 59 | on_illegal_action_error = __config_file.get('Messages', 'on_illegal_action_error').replace('\\n', '\n') 60 | 61 | # Game 62 | 63 | prepare_time = int(__config_file.get('Game', 'prepare_time')) 64 | game_time = int(__config_file.get('Game', 'game_time')) 65 | 66 | # Web 67 | 68 | path_to_avatars = __config_file.get('Web', 'path_to_avatars') 69 | url_to_playground = __config_file.get('Web', 'url_to_playground').replace('\\n', '\n') 70 | bot_websocket = __config_file.get('Web', 'bot_websocket').replace('\\n', '\n') 71 | server_ip = __config_file.get('Web', 'server_ip').replace('\\n', '\n') 72 | proxy_ip = __config_file.get('Web', 'proxy_ip').replace('\\n', '\n') 73 | server_port = int(__config_file.get('Web', 'server_port')) 74 | server_http_protocol = __config_file.get('Web', 'server_http_protocol') 75 | proxy_http_protocol = __config_file.get('Web', 'proxy_http_protocol') 76 | websocket_protocol = __config_file.get('Web', 'websocket_protocol') 77 | server_authkey = __config_file.get('Web', 'server_authkey') 78 | -------------------------------------------------------------------------------- /telegram/config/config.ini: -------------------------------------------------------------------------------- 1 | [database] 2 | database_user=chess 3 | database_password=12345678 4 | database_name=chess_database 5 | database_host=database 6 | database_port=5432 7 | 8 | [Telegram] 9 | recharge_time = 5 10 | telegram_token = 11 | admin_ids = 408895166, 2121622736 12 | authorization_tokens_live_time = 300 13 | 14 | [Messages] 15 | authorization_instructions = Для начала игры авторизуйся! \n\nИспользуй команду /authorization и следуй иструкциям в появившемся сообщении 16 | game_instructions = Для регистрации на турнире используй команду /authorization, а после следуй инструкциям в появившемся сообщении. \n\nЕсли ты ввел все правильно, ты увидишь на экране сообщение об этом, твой телеграм аккаунт свяжется с сайтом и ты сможешь играть! По остальным вопросам - /help 17 | manual = После авторизации на сайте ты можешь начинать играть. \n\nДля этого отправь боту команду /play и наберись терпения, бот начнет искать тебе противника. \n\nКак только противник будет найден ты получишь оповещение в чате и ссылку на игру. После этого у тебя будет 30 секунд на то, что бы перейти по ссылке и приготовиться к игре. Далее действуй как желаешь нужным. \n \n По вопросам: @Elvira1C @Justiks1337 @Danya_501 18 | on_queue_join_message = Ожидание противника. Когда противник будет найден, бот оповестит тебя о начале игры. \n\nДля выхода из очереди используй команду /leave 19 | on_queue_leave_message = Ты покинул очередь, можешь перестать волноваться за свой рейтинг. 20 | only_in_dm_message = Эта команда доступна только в личных сообщениях бота! 21 | if_in_queue = Ты уже находишься в очереди, ожидай противника! 22 | if_not_in_queue = Ты не находишься в очереди. 23 | if_games_not_enough = У тебя недостаточно игр (0). Дождись следующего тура что бы продолжить играть! Если ты конечно прошел в него... 24 | in_game_error = Ты уже находишься в игре! 25 | profile_message = Твой профиль 📊📈: \n\n Очков: {points_amount} 💠 26 | dashboard_title = 🏆⭐️ Топ {amount} по победам в шахматах: \n\n 27 | dashboard_object = {position}. {player_name}: {points_amount} очков \n 28 | dashboard_on_range_error = Ты указал неверный диапазон участников! (Всего {amount} участников) 29 | illegal_move_error = Такой ход невозможен! 30 | on_mate_message = Мат! Победил игрок играющий за {color}ый цвет! 31 | on_stalemate_message = Пат! Ничья, победила дружба! 32 | on_draw_message = Игра окончена! Ничья, победила дружба! 33 | on_end_time = У игрока играющего за {color}ый цвет не осталось времени. Победа игрока играющего за противоположный цвет! 34 | on_someone_move = Сейчас ход другого игрока! Играем в порядке живой очереди! 35 | on_resign = Победа игрока за {color}ый цвет, ведь его противник сдался! 36 | authorization_message = Для авторизации нажми на кнопку под сообщением! 37 | success_authorization_message = Ты успешно авторизовался, теперь можешь идти играть! 38 | on_authorization_error = Ошибка авторизации! Возможные причины: \n\n 1). У вас уже есть токен авторизации \n\n 2). Вас нету в базе данных. 39 | on_delete_authorization_code = Вы не успели авторизоваться за 5 минут. Ваш токен авторизации был удалён, попробуй снова! 40 | on_unsuccessful_authorization_message = Такого кода авторизации не существует! 41 | unauthorized_message = Для использования этой команды авторизуйтесь! /start 42 | on_blacklist_message = Ты находишься в чёрном списке и не можешь использовать какие либо команды. За разблокировкой пиши - @Elvira1C 43 | on_is_not_admin = Для того что бы использовать эту команду ты должен находиться в списке администраторов! 44 | successful_add_to_blacklist = Пользователь @{username} успешно добавлен в чёрный список! 45 | successful_remove_from_blacklist = Пользователь успешно изъят из чёрного списка! 46 | on_invalid_args = Указан недопустимый аргумент/Недостаточно аргументов. Уточните синтаксис команды перед использованием. 47 | on_find_enemy = Противник найден! Ссылка на игру: \n{url} 48 | in_recharge = Не спешите с вводом команд! Подождите некоторое время 49 | on_illegal_action_error = Не пытайся сломать систему! 50 | 51 | [Game] 52 | prepare_time = 30 53 | game_time = 900 54 | games_amount = 5 55 | 56 | [Web] 57 | path_to_avatars = ../web_django/static/avatars/ 58 | url_to_playground = /playgrounds/games/{rout}/ 59 | bot_websocket = websocket/bot_consumer 60 | server_ip = daphne:8000 61 | proxy_ip = 127.0.0.1 62 | server_port = 8000 63 | server_http_protocol = http 64 | proxy_http_protocol = http 65 | websocket_protocol = ws 66 | server_authkey = 419a4997-0107-490e-bb83-2538890d8bb7 -------------------------------------------------------------------------------- /telegram/decorators.py: -------------------------------------------------------------------------------- 1 | from asyncio import sleep, create_task 2 | 3 | import aiogram 4 | import aiohttp 5 | 6 | from config.ConfigValues import ConfigValues 7 | from telegram import dp, bot 8 | 9 | 10 | def in_blacklist(func): 11 | """Check user in blacklist""" 12 | 13 | async def wrapped(message: aiogram.types.Message): 14 | async with aiohttp.ClientSession() as session: 15 | async with session.get( 16 | f"{ConfigValues.server_http_protocol}://{ConfigValues.server_ip}/blacklist/in_blacklist", 17 | params={"user_id": message.from_user.id}, 18 | headers={"Content-type": "application/json", 19 | "Authorization": ConfigValues.server_authkey}) as response: 20 | jsn = await response.json() 21 | if jsn.get("in_blacklist"): 22 | return await func(message) 23 | return await send_message(message.chat.id, ConfigValues.on_blacklist_message) 24 | return wrapped 25 | 26 | 27 | def authorize(func): 28 | async def wrapper(message: aiogram.types.Message): 29 | async with aiohttp.ClientSession() as session: 30 | async with session.get( 31 | f"{ConfigValues.server_http_protocol}://{ConfigValues.server_ip}/api/v1/in_database", 32 | params={"user_id": message.from_user.id}, 33 | headers={"Content-type": "application/json", 34 | "Authorization": ConfigValues.server_authkey}) as response: 35 | jsn = await response.json() 36 | if jsn.get("in_database"): 37 | return await func(message) 38 | await send_message(message.chat.id, ConfigValues.unauthorized_message) 39 | return wrapper 40 | 41 | 42 | def in_admins(func): 43 | """Check user in admins""" 44 | async def wrapped(message): 45 | if str(message.from_user.id) in ConfigValues.admin_ids: 46 | return await func(message) 47 | 48 | return await send_message(message.chat.id, ConfigValues.on_is_not_admin) 49 | 50 | return wrapped 51 | 52 | 53 | def recharge(func): 54 | async def wrapper(message): 55 | if message.from_user.id in users_in_recharge: 56 | return await send_message(message.chat.id, ConfigValues.in_recharge) 57 | 58 | users_in_recharge.append(message.from_user.id) 59 | sleep_task = create_task(clear_recharge(message.from_user.id)) 60 | func_task = create_task(func(message)) 61 | await sleep_task 62 | await func_task 63 | 64 | return wrapper 65 | 66 | 67 | def only_in_dm(coro): 68 | async def wrapper(message: aiogram.types.Message): 69 | if message.from_user.id != message.chat.id: 70 | return await send_message(message.chat.id, ConfigValues.only_in_dm_message) 71 | 72 | await coro(message) 73 | 74 | return wrapper 75 | 76 | 77 | def command_handler(command): 78 | def decorator(coro): 79 | @dp.message(command) 80 | @only_in_dm 81 | @recharge 82 | @authorize 83 | @in_blacklist 84 | async def wrapper(message: aiogram.types.Message): 85 | await coro(message) 86 | 87 | return wrapper 88 | return decorator 89 | 90 | 91 | async def clear_recharge(user_id: int): 92 | await sleep(ConfigValues.recharge_time) 93 | users_in_recharge.remove(user_id) 94 | 95 | 96 | async def send_message(chat_id: int, text: str, *args, **kwargs): 97 | await bot.send_message(chat_id, text, *args, **kwargs) 98 | 99 | 100 | users_in_recharge = [] 101 | -------------------------------------------------------------------------------- /telegram/main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from telegram_log.log import log 4 | from telegram import dp, bot 5 | 6 | import telegram_admin 7 | import telegram_authorize 8 | import telegram_dashboard 9 | import telegram_help 10 | import telegram_profile 11 | import telegram_queue 12 | import telegram_start 13 | 14 | 15 | log.info('bot successful started!') 16 | 17 | 18 | async def main(): 19 | 20 | await dp.start_polling(bot) 21 | 22 | 23 | if __name__ == '__main__': 24 | loop = asyncio.get_event_loop() 25 | loop.run_until_complete(main()) 26 | -------------------------------------------------------------------------------- /telegram/requirements.txt: -------------------------------------------------------------------------------- 1 | aiofiles==24.1.0 2 | aiogram==3.20.0.post0 3 | aiohappyeyeballs==2.6.1 4 | aiohttp==3.11.18 5 | aiosignal==1.3.2 6 | annotated-types==0.7.0 7 | attrs==25.3.0 8 | certifi==2025.4.26 9 | frozenlist==1.6.0 10 | idna==3.10 11 | magic-filter==1.0.12 12 | multidict==6.4.4 13 | propcache==0.3.1 14 | pydantic==2.11.5 15 | pydantic_core==2.33.2 16 | typing-inspection==0.4.1 17 | typing_extensions==4.13.2 18 | yarl==1.20.0 19 | -------------------------------------------------------------------------------- /telegram/telegram_admin.py: -------------------------------------------------------------------------------- 1 | import aiohttp 2 | from aiogram.types import Message 3 | from aiogram.filters import Command 4 | 5 | from config.ConfigValues import ConfigValues 6 | from decorators import in_admins, command_handler, send_message 7 | 8 | 9 | @command_handler(Command('add_on_blacklist')) 10 | @in_admins 11 | async def add_on_blacklist(message: Message): 12 | user_id = int(message.get_args()) 13 | async with aiohttp.ClientSession() as session: 14 | async with session.post( 15 | f"{ConfigValues.server_http_protocol}://{ConfigValues.server_ip}/blacklist/add_to_blacklist", 16 | params={"user_id": user_id}, 17 | headers={ 18 | "content-type": "application/json", 19 | "Authorization": ConfigValues.server_authkey}): 20 | await send_message(message.chat.id, ConfigValues.successful_add_to_blacklist.replace('{username}', user_id)) 21 | 22 | 23 | @command_handler(Command('remove_from_blacklist')) 24 | @in_admins 25 | async def remove_from_blacklist(message: Message): 26 | user_id = int(message.get_args()) 27 | 28 | async with aiohttp.ClientSession() as session: 29 | async with session.post( 30 | f"{ConfigValues.server_http_protocol}://{ConfigValues.server_ip}/blacklist/remove_from_blacklist", 31 | params={"user_id": user_id}, 32 | headers={"Content-type": "application/json", 33 | "Authorization": ConfigValues.server_authkey}): 34 | await send_message(message.chat.id, ConfigValues.successful_remove_from_blacklist) 35 | 36 | -------------------------------------------------------------------------------- /telegram/telegram_authorize.py: -------------------------------------------------------------------------------- 1 | from aiogram.filters import Command 2 | 3 | from decorators import recharge, in_blacklist, only_in_dm 4 | from authorization_core import send_auth_message 5 | from telegram import dp 6 | 7 | 8 | @dp.message(Command("authorization")) 9 | @recharge 10 | @only_in_dm 11 | @in_blacklist 12 | async def new_token(message): 13 | await send_auth_message(message) 14 | 15 | -------------------------------------------------------------------------------- /telegram/telegram_dashboard.py: -------------------------------------------------------------------------------- 1 | import aiohttp 2 | from aiogram.types import Message 3 | from aiogram.exceptions import TelegramForbiddenError 4 | from aiogram.filters import Command 5 | 6 | from config.ConfigValues import ConfigValues 7 | from decorators import command_handler, send_message 8 | 9 | 10 | @command_handler(Command('top')) 11 | async def get_top(message: Message): 12 | """send message with dashboard""" 13 | 14 | count = message.text.split()[-1] 15 | 16 | amount = 0 17 | 18 | if not count: 19 | amount = 10 20 | elif count.isdigit(): 21 | amount = int(count) 22 | elif count == "all": 23 | amount = 0 24 | 25 | async with aiohttp.ClientSession() as session: 26 | async with session.get( 27 | f"{ConfigValues.server_http_protocol}://{ConfigValues.server_ip}/api/v1/dashboard", 28 | params={"count": amount}, 29 | headers={"Content-type": "application/json", 30 | "Authorization": ConfigValues.server_authkey}) as response: 31 | top = await response.json() 32 | 33 | msg: str = ConfigValues.dashboard_title.replace('{amount}', str(amount)) 34 | 35 | position = 0 36 | for player in top: 37 | try: 38 | 39 | player_name = player.get("nickname") 40 | 41 | position += 1 42 | 43 | msg = msg + ConfigValues.dashboard_object.replace( 44 | '{position}', str(position)).replace( 45 | '{player_name}', f'{player_name}').replace( 46 | '{points_amount}', str(player.get("points"))) 47 | 48 | except TelegramForbiddenError: 49 | continue 50 | 51 | await send_message(message.chat.id, msg, parse_mode="html") 52 | 53 | -------------------------------------------------------------------------------- /telegram/telegram_help.py: -------------------------------------------------------------------------------- 1 | from aiogram.types import Message 2 | from aiogram.filters import Command 3 | 4 | from decorators import recharge, send_message 5 | from config.ConfigValues import ConfigValues 6 | from telegram import dp 7 | 8 | 9 | @dp.message(Command('help')) 10 | @recharge 11 | async def send_manual(message: Message): 12 | await send_message(message.chat.id, ConfigValues.manual) 13 | -------------------------------------------------------------------------------- /telegram/telegram_log/log.py: -------------------------------------------------------------------------------- 1 | from logging import getLogger, FileHandler, Formatter 2 | from os import path 3 | 4 | log = getLogger('telegram_log') 5 | log.setLevel('DEBUG') 6 | 7 | log_handler = FileHandler(path.join(path.dirname(__file__), 'main.log'), 'a+') 8 | log_handler.setFormatter(Formatter("%(name)s %(asctime)s %(levelname)s %(message)s")) 9 | 10 | 11 | log.addHandler(log_handler) 12 | 13 | -------------------------------------------------------------------------------- /telegram/telegram_profile.py: -------------------------------------------------------------------------------- 1 | import aiohttp 2 | from aiogram.types import Message 3 | from aiogram.filters import Command 4 | 5 | from decorators import command_handler, send_message 6 | from config.ConfigValues import ConfigValues 7 | 8 | 9 | @command_handler(Command('profile')) 10 | async def profile(message: Message): 11 | """Отправляет в чат статистику пользователя""" 12 | 13 | async with aiohttp.ClientSession() as session: 14 | async with session.get( 15 | f"{ConfigValues.server_http_protocol}://{ConfigValues.server_ip}/api/v1/profile/{message.from_user.id}/", 16 | headers={"Content-type": "application/json", 17 | "Authorization": ConfigValues.server_authkey}) as response: 18 | stats_values = await response.json() 19 | 20 | await send_message( 21 | message.chat.id, 22 | ConfigValues.profile_message.replace('{points_amount}', str(stats_values["points"]))) 23 | -------------------------------------------------------------------------------- /telegram/telegram_queue.py: -------------------------------------------------------------------------------- 1 | from aiogram.types import Message 2 | from aiogram.filters import Command 3 | 4 | from QueueClass import main_queue as queue 5 | from config.ConfigValues import ConfigValues 6 | from decorators import command_handler, send_message, authorize 7 | 8 | 9 | @command_handler(Command('play')) 10 | @authorize 11 | async def queue_join(message: Message): 12 | """Добавляет пользователя в очередь""" 13 | 14 | await queue.add_new_user(message.from_user.id) 15 | 16 | 17 | @command_handler(Command('leave')) 18 | @authorize 19 | async def queue_leave(message: Message): 20 | """Удаляет пользователя из очереди""" 21 | 22 | try: 23 | queue.leave_from_queue(message.from_user.id) 24 | except KeyError: 25 | await send_message(message.chat.id, ConfigValues.if_not_in_queue) 26 | return 27 | 28 | await send_message(message.chat.id, ConfigValues.on_queue_leave_message) 29 | 30 | 31 | async def send_url_to_playground(user_id: int, uuid): 32 | """send url to game""" 33 | 34 | await send_message( 35 | user_id, 36 | ConfigValues.on_find_enemy.replace('{url}', ConfigValues.url_to_playground.replace('{rout}', uuid))) 37 | -------------------------------------------------------------------------------- /telegram/telegram_start.py: -------------------------------------------------------------------------------- 1 | from aiogram.types import Message 2 | from aiogram.filters import Command 3 | 4 | from decorators import in_blacklist, recharge, only_in_dm 5 | from authorization_core import download_user_avatar, send_auth_message 6 | from telegram import dp 7 | 8 | 9 | @dp.message(Command('start')) 10 | @recharge 11 | @only_in_dm 12 | @in_blacklist 13 | async def start(message: Message): 14 | """send start message""" 15 | 16 | await send_auth_message(message) 17 | await download_user_avatar(message.from_user.id) 18 | -------------------------------------------------------------------------------- /web_django/.env: -------------------------------------------------------------------------------- 1 | DATABASE_USER=chess 2 | DATABASE_PASSWORD=12345678 3 | DATABASE_NAME=chess_database 4 | DATABASE_HOST=database 5 | DATABASE_PORT=5432 6 | 7 | ILLEGAL_MOVE_ERROR="Такой ход невозможен!" 8 | ON_MATE_MESSAGE="Мат! Победил игрок играющий за {color}ый цвет!" 9 | ON_STALEMATE_MESSAGE="Пат! Ничья, победила дружба!" 10 | ON_DRAW_MESSAGE="Игра окончена! Ничья, победила дружба!" 11 | ON_END_TIME="У игрока играющего за {color}ый цвет не осталось времени. Победа игрока играющего за противоположный цвет!" 12 | ON_SOMEONE_MOVE="Сейчас ход другого игрока! Играем в порядке живой очереди!" 13 | ON_RESIGN="Победа игрока за {color}ый цвет, ведь его противник сдался!" 14 | ON_UNSUCCESSFUL_AUTHORIZATION_MESSAGE="Такого кода авторизации не существует!" 15 | ON_ILLEGAL_ACTION_ERROR="Не пытайся сломать систему!" 16 | 17 | PREPARE_TIME=30 18 | GAME_TIME=900 19 | GAMES_AMOUNT=5 20 | 21 | PATH_TO_AVATARS=static/avatars/ 22 | URL_TO_PLAYGROUND=/playgrounds/games/{rout}/ 23 | BOT_WEBSOCKET=websocket/bot_consumer 24 | SERVER_IP=daphne:8000 25 | PROXY_IP=127.0.0.1 26 | SERVER_PORT=8080 27 | SERVER_HTTP_PROTOCOL=http 28 | PROXY_HTTP_PROTOCOL=http 29 | WEBSOCKET_PROTOCOL=ws 30 | SERVER_AUTHKEY=419a4997-0107-490e-bb83-2538890d8bb7 31 | JWT_LIVETIME=300 -------------------------------------------------------------------------------- /web_django/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/web_django/__init__.py -------------------------------------------------------------------------------- /web_django/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/web_django/api/__init__.py -------------------------------------------------------------------------------- /web_django/api/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /web_django/api/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ApiConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'api' 7 | -------------------------------------------------------------------------------- /web_django/api/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/web_django/api/migrations/__init__.py -------------------------------------------------------------------------------- /web_django/api/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /web_django/api/responses.py: -------------------------------------------------------------------------------- 1 | class StartGameResponse: 2 | def __init__(self, uuid: str): 3 | self.uuid = uuid 4 | 5 | 6 | class CheckInGameResponse: 7 | def __init__(self, in_game: bool): 8 | self.in_game = in_game 9 | 10 | 11 | class NewAuthorizeTokenResponse: 12 | def __init__(self, success: bool, token: str): 13 | self.success = success 14 | self.token = token 15 | 16 | 17 | class DeleteAuthorizeTokenResponse: 18 | def __init__(self, success): 19 | self.success = success 20 | 21 | 22 | class AuthorizeAttemptResponse: 23 | def __init__( 24 | self, 25 | success: bool, 26 | message: str = "no message"): 27 | 28 | self.success = success 29 | self.message = message 30 | 31 | 32 | class DashboardResponse: 33 | def __init__(self, 34 | user_id, points, username, nickname): 35 | self.user_id = user_id 36 | self.points = points 37 | self.username = username 38 | self.nickname = nickname 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /web_django/api/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework.serializers import Serializer, CharField, BooleanField, FileField, ModelSerializer 2 | from chessboards.models import UserModel 3 | 4 | 5 | class StartGameSerializer(Serializer): 6 | uuid = CharField(max_length=38) 7 | 8 | 9 | class CheckInGameSerializer(Serializer): 10 | in_game = BooleanField() 11 | 12 | 13 | class NewAuthorizeTokenSerializer(Serializer): 14 | success = BooleanField() 15 | token = CharField(max_length=38) 16 | 17 | 18 | class DeleteAuthorizeTokenSerializer(Serializer): 19 | success = BooleanField() 20 | 21 | 22 | class AuthorizeAttemptSerializer(Serializer): 23 | success = BooleanField() 24 | 25 | 26 | class DashboardSerializer(ModelSerializer): 27 | class Meta: 28 | model = UserModel 29 | fields = ["user_id", "points", "username", "nickname"] 30 | -------------------------------------------------------------------------------- /web_django/api/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /web_django/api/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from api.views import ( 4 | start_game, 5 | check_in_game, 6 | new_authorize_token, 7 | authorization_attempt, 8 | check_timer, 9 | download_avatar, 10 | in_database_api, 11 | dashboard, 12 | UserInfo) 13 | 14 | urlpatterns = [ 15 | path('check_in_game', check_in_game), 16 | path('new_token', new_authorize_token), 17 | path('authorize', authorization_attempt), 18 | path('start_game', start_game), 19 | path('check_timer', check_timer), 20 | path('add_avatar', download_avatar), 21 | path('in_database', in_database_api), 22 | path('dashboard', dashboard), 23 | path('profile//', UserInfo.as_view()) 24 | ] 25 | -------------------------------------------------------------------------------- /web_django/api/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from rest_framework.response import Response 4 | from rest_framework.request import Request 5 | 6 | from chessboards.models import UserModel 7 | 8 | 9 | async def in_database(user_id: int) -> bool: 10 | 11 | try: 12 | await UserModel.objects.aget(user_id=user_id) 13 | return True 14 | 15 | except UserModel.DoesNotExist: 16 | return False 17 | 18 | 19 | def authorization(func): 20 | async def wrapper(request: Request): 21 | try: 22 | if request.META.get("HTTP_AUTHORIZATION") == os.getenv("SERVER_AUTHKEY"): 23 | return await func(request) 24 | 25 | return Response({'Неверный ключ авторизации!'}) 26 | 27 | except KeyError: 28 | 29 | return Response({'Неверный ключ авторизации!'}) 30 | return wrapper 31 | -------------------------------------------------------------------------------- /web_django/api/views.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import os 3 | from asyncio import get_running_loop 4 | from time import time 5 | 6 | from rest_framework.response import Response 7 | from rest_framework.request import Request 8 | from rest_framework.renderers import JSONRenderer 9 | from rest_framework.generics import RetrieveAPIView 10 | from rest_framework.serializers import ModelSerializer 11 | from adrf.decorators import api_view 12 | from api.utils import in_database 13 | from asgiref import sync 14 | import jwt 15 | 16 | from .serializers import ( 17 | StartGameSerializer, 18 | CheckInGameSerializer, 19 | NewAuthorizeTokenSerializer, 20 | AuthorizeAttemptSerializer) 21 | 22 | from .responses import ( 23 | StartGameResponse, 24 | CheckInGameResponse, 25 | NewAuthorizeTokenResponse, 26 | AuthorizeAttemptResponse) 27 | 28 | from api.utils import authorization 29 | 30 | from chessboards.chess_core.core import get 31 | from chessboards.chess_core.Game import games, Game 32 | from chessboards.models import UserModel 33 | 34 | 35 | @api_view(['POST']) 36 | @authorization 37 | async def start_game(request: Request): 38 | first_user_id, second_user_id = map(int, await sync.sync_to_async(request.data.get)('players')) 39 | 40 | game = Game((first_user_id, second_user_id)) 41 | 42 | get_running_loop().create_task(game.start_timers_game()) 43 | 44 | return Response(StartGameSerializer(StartGameResponse(game.tag)).data) 45 | 46 | 47 | @api_view(['POST']) 48 | async def check_in_game(request: Request): 49 | user_id = int(request.query_params.get('user_id')) 50 | 51 | user = await get(games, 'players', user_id=user_id) 52 | 53 | if user: 54 | return Response(CheckInGameSerializer(CheckInGameResponse(True)).data) 55 | 56 | return Response(CheckInGameSerializer(CheckInGameResponse(False)).data) 57 | 58 | 59 | @api_view(['POST']) 60 | @authorization 61 | async def new_authorize_token(request: Request): 62 | user_id = await sync.sync_to_async(request.query_params.get)("user_id") 63 | username = await sync.sync_to_async(request.query_params.get)("username") 64 | nickname = await sync.sync_to_async(request.query_params.get)("nickname") 65 | 66 | token = jwt.encode({"user_id": user_id, 67 | "username": username, 68 | "nickname": nickname, 69 | "exp": time() + int(os.getenv("JWT_LIVETIME"))}, 70 | os.getenv("SERVER_AUTHKEY")) 71 | 72 | return Response(NewAuthorizeTokenSerializer(NewAuthorizeTokenResponse(True, token)).data) 73 | 74 | 75 | @api_view(['POST']) 76 | async def authorization_attempt(request: Request): 77 | token = request.query_params.get('token') 78 | 79 | decoded_token = jwt.decode(token, os.getenv("SERVER_AUTHKEY"), algorithms=["HS256"]) 80 | 81 | if not decoded_token.get("user_id"): 82 | return Response(JSONRenderer().render(AuthorizeAttemptSerializer(AuthorizeAttemptResponse(False, message="Invalid token")).data)) 83 | 84 | if decoded_token.get("created_at") + 300 < time(): 85 | return Response(JSONRenderer().render(AuthorizeAttemptSerializer(AuthorizeAttemptResponse(False, message="Token expired")).data)) 86 | 87 | user_id = decoded_token["user_id"] 88 | username = decoded_token["username"] 89 | nickname = decoded_token["nickname"] 90 | 91 | request.session["user_id"] = user_id 92 | 93 | if not await in_database(user_id): 94 | user = await UserModel.objects.acreate(user_id=user_id, points=0, username=username, nickname=nickname) 95 | await user.asave() 96 | 97 | return Response(JSONRenderer().render(AuthorizeAttemptSerializer(AuthorizeAttemptResponse(True)).data)) 98 | 99 | 100 | @api_view(['POST']) 101 | async def check_timer(request: Request): 102 | game_tag = request.query_params.get("tag") 103 | 104 | game: Game = await get(games, '', tag=game_tag) 105 | 106 | if not game: 107 | return Response({"status": 200}) 108 | 109 | player = game.get_turn_player() 110 | 111 | if time() - player.timer.last_flip > player.timer.time: 112 | await game.on_end_timer(player) 113 | 114 | return Response({'status': 200}) 115 | 116 | 117 | @api_view(['POST']) 118 | @authorization 119 | async def download_avatar(request: Request): 120 | file = request.FILES["file"] 121 | file_name = request.query_params.get("file_name") 122 | with open(os.path.join(os.getenv("PATH_TO_AVATARS"), file_name), "wb") as destination: 123 | for chunk in file.chunks(): 124 | destination.write(chunk) 125 | 126 | return Response({"status": 200}) 127 | 128 | 129 | @api_view(['GET']) 130 | @authorization 131 | async def in_database_api(request: Request): 132 | user_id = request.query_params.get("user_id") 133 | 134 | if await in_database(user_id): 135 | return Response({"in_database": True}) 136 | 137 | return Response({"in_database": False}) 138 | 139 | 140 | @api_view(['GET']) 141 | @authorization 142 | async def dashboard(request: Request): 143 | 144 | count = int(request.query_params.get("count")) 145 | if not count: 146 | users = UserModel.objects.all().order_by("-points") 147 | else: 148 | users = UserModel.objects.all().order_by("-points")[:count] 149 | 150 | json = [{"user_id": user.user_id, "points": user.points, "nickname": user.nickname} async for user in users] 151 | 152 | return Response(json) 153 | 154 | 155 | class UserInfo(RetrieveAPIView): 156 | class Serializer(ModelSerializer): 157 | class Meta: 158 | model = UserModel 159 | fields = ["points"] 160 | 161 | queryset = UserModel.objects.all() 162 | serializer_class = Serializer 163 | lookup_field = "user_id" 164 | -------------------------------------------------------------------------------- /web_django/authorization/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/web_django/authorization/__init__.py -------------------------------------------------------------------------------- /web_django/authorization/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /web_django/authorization/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class AuthorizationConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'authorization' 7 | -------------------------------------------------------------------------------- /web_django/authorization/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /web_django/authorization/templates/authorization/success_authorization.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | 4 | {% block css_load %} 5 | 6 | {% load static %} 7 | 8 | 9 | {% endblock %} 10 | 11 | {% block title %}Успешная авторизация {% endblock %} 12 | 13 | {% block body %} 14 |
15 | 16 | {%load static%} 17 | 18 | 19 |

ВЫ АВТОРИЗОВАНЫ. МОЖЕТЕ ЗАКРЫВАТЬ ЭТУ СТРАНИЦУ.

20 | 21 |
22 | 23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /web_django/authorization/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /web_django/authorization/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from authorization.views import index 3 | 4 | 5 | urlpatterns = [ 6 | path('', index), 7 | ] -------------------------------------------------------------------------------- /web_django/authorization/views.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from django.shortcuts import render 4 | from asgiref.sync import sync_to_async 5 | import jwt 6 | 7 | from api.utils import in_database 8 | from chessboards.models import UserModel 9 | 10 | 11 | async def index(request): 12 | token = request.GET.get('token') 13 | 14 | try: 15 | decoded_token = jwt.decode(token, os.getenv("SERVER_AUTHKEY"), algorithms=["HS256"]) 16 | 17 | except (jwt.ExpiredSignatureError, jwt.DecodeError, jwt.InvalidTokenError): 18 | return render(request, 'error_page/index.html', {"error": "Токен не валидный или истек", "error_code": "401"}) 19 | 20 | user_id = decoded_token["user_id"] 21 | username = decoded_token["username"] 22 | nickname = decoded_token["nickname"] 23 | 24 | await sync_to_async(request.session.update)({"user_id": user_id}) 25 | 26 | if not await in_database(user_id): 27 | user = await UserModel.objects.acreate(user_id=user_id, points=0, username=username, nickname=nickname) 28 | await user.asave() 29 | 30 | return render(request, "authorization/success_authorization.html") 31 | -------------------------------------------------------------------------------- /web_django/base/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | {% load static %} 10 | 11 | {% block css_load %} 12 | {% endblock %} 13 | 14 | {% block title %} {% endblock %} 15 | 16 | 17 | 18 | 19 | 20 | {% block body %} 21 | {% endblock %} 22 | 60 | {% block scripts %} 61 | {% endblock %} 62 | 63 | -------------------------------------------------------------------------------- /web_django/blacklist/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/web_django/blacklist/__init__.py -------------------------------------------------------------------------------- /web_django/blacklist/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /web_django/blacklist/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class BlacklistConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'blacklist' 7 | -------------------------------------------------------------------------------- /web_django/blacklist/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | from chessboards.models import UserModel 4 | 5 | 6 | class BlacklistModel(models.Model): 7 | blacklist_user_id = models.ForeignKey(UserModel, models.CASCADE, related_name="blacklist_user_id") 8 | -------------------------------------------------------------------------------- /web_django/blacklist/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /web_django/blacklist/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from blacklist.views import add_to_blacklist, in_blacklist, remove_from_blacklist 4 | 5 | 6 | urlpatterns = [ 7 | path("in_blacklist", in_blacklist), 8 | path("add_to_blacklist", add_to_blacklist), 9 | path("remove_from_blacklist", remove_from_blacklist) 10 | ] -------------------------------------------------------------------------------- /web_django/blacklist/views.py: -------------------------------------------------------------------------------- 1 | from adrf.decorators import api_view 2 | from django.db import models 3 | from rest_framework.request import Request 4 | from rest_framework.response import Response 5 | from asgiref.sync import sync_to_async 6 | 7 | from blacklist.models import BlacklistModel 8 | from chessboards.models import UserModel 9 | from api.utils import authorization 10 | 11 | 12 | @api_view(["GET"]) 13 | @authorization 14 | async def in_blacklist(request: Request): 15 | try: 16 | user_id = await (sync_to_async(request.query_params.get)("user_id")) 17 | 18 | await BlacklistModel.objects.aget(blacklist_user_id=await UserModel.objects.aget(user_id=user_id)) 19 | 20 | except (UserModel.DoesNotExist, BlacklistModel.DoesNotExist): 21 | return Response({"in_blacklist": True}) 22 | 23 | 24 | @api_view(["POST"]) 25 | @authorization 26 | async def add_to_blacklist(request: Request): 27 | try: 28 | user_id = await (sync_to_async(request.query_params.get)("user_id")) 29 | 30 | user = BlacklistModel(blacklist_user_id=await UserModel.objects.aget(user_id=user_id)) 31 | await user.asave() 32 | 33 | return Response({"status": 200}) 34 | except (UserModel.DoesNotExist, BlacklistModel.DoesNotExist): 35 | return Response({"status": 404}) 36 | 37 | 38 | @api_view(["POST"]) 39 | @authorization 40 | async def remove_from_blacklist(request: Request): 41 | try: 42 | user_id = await (sync_to_async(request.query_params.get)("user_id")) 43 | 44 | user = await BlacklistModel.objects.all().afilter( 45 | blacklist_user_id=await UserModel.objects.aget(user_id=user_id)) 46 | await user.adelete() 47 | 48 | return Response({"status": 200}) 49 | 50 | except (UserModel.DoesNotExist, BlacklistModel.DoesNotExist): 51 | return Response({"status": 404}) 52 | 53 | -------------------------------------------------------------------------------- /web_django/chessboards/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/web_django/chessboards/__init__.py -------------------------------------------------------------------------------- /web_django/chessboards/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /web_django/chessboards/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ChessboardsConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'chessboards' 7 | -------------------------------------------------------------------------------- /web_django/chessboards/chess_core/Game.py: -------------------------------------------------------------------------------- 1 | import os 2 | import asyncio 3 | from uuid import uuid4 4 | from time import time 5 | from typing import Optional 6 | 7 | import chess 8 | from channels.consumer import get_channel_layer 9 | 10 | from chessboards.chess_core.User import User 11 | from chessboards.models import GamesModel 12 | 13 | 14 | class Game: 15 | """Класс отвечающий за игру""" 16 | 17 | def __init__(self, users_ids: tuple): 18 | self.board: chess.Board = chess.Board() 19 | self.players: list = [] 20 | self.player_1: User = User(users_ids[0], True, self) 21 | self.player_2: User = User(users_ids[1], False, self) 22 | self._started_at = time() 23 | self.tag = str(uuid4()) 24 | self.game_end: bool = False 25 | 26 | games.append(self) 27 | 28 | @property 29 | def started_at(self): 30 | return round(self._started_at) 31 | 32 | async def move(self, text_move): 33 | """:raise BaseError если на доске ситуация приводящая к концу игры либо противоречащая её продолжению""" 34 | 35 | turn_player = self.get_turn_player() 36 | 37 | try: 38 | 39 | self.board.push_san(text_move) 40 | except chess.IllegalMoveError as error: 41 | if "missing promotion piece type" in error.args[0]: 42 | self.board.push_san(f"{text_move}q") 43 | else: 44 | raise chess.IllegalMoveError() 45 | 46 | asyncio.get_running_loop().create_task(self.get_turn_player().start_timer(), name=self.tag) 47 | 48 | await turn_player.timer.flip_the_timer() 49 | 50 | await channel_layer.group_send( 51 | self.tag, 52 | { 53 | "type": "update_board", 54 | "board": self.board.board_fen(), 55 | "first_user_time": self.player_1.timer.time, 56 | "second_user_time": self.player_2.timer.time 57 | } 58 | ) 59 | 60 | await self.move_checks() 61 | 62 | return self.board.board_fen() 63 | 64 | async def checkmate(self): 65 | """:raise MateError если есть на доске мат""" 66 | 67 | if self.board.is_checkmate(): 68 | winner = self.get_winner() 69 | await self.on_win(winner, os.getenv("ON_MATE_MESSAGE").replace('{color}', winner.color_text)) 70 | 71 | async def check_stalemate(self): 72 | """:raise DrawError если на доске пат""" 73 | 74 | if self.board.is_stalemate(): 75 | await self.on_end_game(os.getenv("ON_STALEMATE_MESSAGE")) 76 | 77 | async def on_check(self): 78 | if self.board.is_check(): 79 | await channel_layer.group_send( 80 | self.tag, 81 | { 82 | 'type': 'on_check', 83 | 'recipient': self.get_turn_player().user_id 84 | } 85 | ) 86 | 87 | async def move_checks(self): 88 | await self.checkmate() 89 | await self.check_stalemate() 90 | await self.on_check() 91 | 92 | def get_winner(self) -> User: 93 | """:return User - object""" 94 | 95 | return self.player_1 if not self.board.turn else self.player_2 96 | 97 | def get_turn_player(self): 98 | """:return User object""" 99 | 100 | return self.player_1 if self.player_1.color is self.board.turn else self.player_2 101 | 102 | async def start_timers_game(self): 103 | """actions before start game""" 104 | 105 | for player in self.players: 106 | asyncio.get_running_loop().create_task(player.fill_attributes()) 107 | 108 | await asyncio.sleep(int(os.getenv("PREPARE_TIME"))) 109 | 110 | asyncio.get_running_loop().create_task(self.get_turn_player().start_timer(), name=self.tag) 111 | 112 | async def on_end_game(self, message): 113 | """Коро хендлер срабатывающий после окончания игры""" 114 | 115 | if self.game_end: 116 | return 117 | 118 | self.game_end = True 119 | 120 | self.delete_timers() 121 | 122 | await channel_layer.group_send( 123 | self.tag, 124 | { 125 | 'type': "end_game_event", 126 | 'message': message 127 | }) 128 | 129 | games.remove(self) 130 | del self 131 | 132 | def delete_timers(self): 133 | for task in asyncio.all_tasks(asyncio.get_running_loop()): 134 | if task.get_name() == self.tag: 135 | task.cancel() 136 | 137 | async def on_win(self, winner: User, message: str): 138 | await self.on_end_game(message) 139 | await winner.give_points() 140 | await self.add_to_game_registry(winner) 141 | 142 | async def on_draw(self): 143 | await self.add_to_game_registry(None) 144 | 145 | async def add_to_game_registry(self, winner: Optional[User]): 146 | 147 | game = GamesModel( 148 | first_player=self.player_1.model_user, 149 | second_player=self.player_2.model_user, 150 | winner=winner.model_user 151 | ) 152 | await game.asave() 153 | 154 | async def on_draw_offer(self): 155 | for player in self.players: 156 | if not player.draw_offer: 157 | 158 | await channel_layer.group_send( 159 | self.tag, 160 | { 161 | 'type': "draw_offer_event", 162 | 'recipient': player.user_id 163 | } 164 | ) 165 | return 166 | 167 | await self.on_end_game(os.getenv("ON_DRAW_MESSAGE")) 168 | 169 | async def on_give_up(self, user_id: int): 170 | for player in self.players: 171 | if player.user_id != user_id: 172 | await self.on_win(player, os.getenv("ON_RESIGN").replace('{color}', player.color_text)) 173 | 174 | async def on_end_timer(self, loser: User): 175 | 176 | if self.game_end: 177 | return 178 | 179 | # noinspection PyTypeChecker 180 | 181 | await self.on_win((lambda player: self.player_1 if player == self.player_2 else self.player_2)(loser), os.getenv("ON_END_TIME").replace('{color}', loser.color_text)) 182 | 183 | def get_legal_moves(self, cell: str) -> list: 184 | 185 | """:return legal moves for piece on cell""" 186 | 187 | return [str(move)[2:] for move in self.board.legal_moves if str(move)[:2] == cell] 188 | 189 | 190 | channel_layer = get_channel_layer("default") 191 | games = [] 192 | -------------------------------------------------------------------------------- /web_django/chessboards/chess_core/Timer.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import Optional 3 | from time import time as _time 4 | import asyncio 5 | 6 | 7 | class Timer: 8 | """class Timer""" 9 | 10 | def __init__( 11 | self, 12 | own_object, 13 | time: int = int(os.getenv("GAME_TIME")), 14 | ): 15 | self.__event = asyncio.Event() 16 | 17 | self.last_flip = None 18 | self.own_object = own_object 19 | self._time = time 20 | 21 | @property 22 | def time(self): 23 | return round(self._time) 24 | 25 | async def __wait_move(self): 26 | """wait move""" 27 | 28 | self.last_flip = _time() 29 | 30 | waiter_task = asyncio.get_running_loop().create_task(self.__event.wait()) 31 | await waiter_task 32 | 33 | self.__event.clear() 34 | 35 | async def start_timer(self): 36 | """Start the timer""" 37 | 38 | loop = asyncio.get_running_loop() 39 | 40 | try: 41 | await Timer.wait_for(self.__wait_move(), timeout=self._time, loop=loop) 42 | except asyncio.TimeoutError: 43 | loop.create_task(self.own_object.own_object.on_end_timer(self.own_object)) 44 | 45 | async def update_timer(self): 46 | 47 | if _time() - self.own_object.own_object.started_at < int(os.getenv("PREPARE_TIME")): 48 | return 49 | 50 | if not self.last_flip: 51 | self.last_flip = _time() 52 | 53 | self._time = self._time - (_time() - self.last_flip) 54 | 55 | if self._time <= 0: 56 | await self.own_object.own_object.on_end_timer(self.own_object) 57 | 58 | self.last_flip = _time() 59 | 60 | async def flip_the_timer(self): 61 | """Меняет положение таймера (активный/деактивный)""" 62 | 63 | self.__event.set() 64 | 65 | await self.update_timer() 66 | 67 | return self._time 68 | 69 | @staticmethod 70 | async def wait_for(task, timeout=0, loop: Optional[asyncio.AbstractEventLoop] = None): 71 | 72 | task = loop.create_task(task) 73 | 74 | await asyncio.sleep(timeout) 75 | 76 | if not task.done(): 77 | raise asyncio.TimeoutError() 78 | -------------------------------------------------------------------------------- /web_django/chessboards/chess_core/User.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import Optional 3 | import glob 4 | 5 | from chessboards.chess_core.Timer import Timer 6 | from chessboards.models import UserModel 7 | 8 | 9 | class User: 10 | """User class""" 11 | 12 | def __init__(self, user_id: int, color: bool, own_object): 13 | self.user_id = int(user_id) 14 | self.color: bool = color 15 | self.timer: Timer = Timer(self) 16 | self.color_text = (lambda x: "бел" if x else "чёрн")(self.color) 17 | self.draw_offer = False 18 | self.own_object = own_object 19 | self.avatar_path = None 20 | self.model_user = None 21 | 22 | self.points: Optional[int] = None 23 | self.nickname: Optional[str] = None 24 | self.username: Optional[str] = None 25 | self.session_id: Optional[str] = None 26 | self.avatar_path: Optional[str] = None 27 | 28 | self.own_object.players.append(self) 29 | 30 | async def fill_attributes(self): 31 | """fill attributes from database""" 32 | self.model_user = await UserModel.objects.aget(user_id=self.user_id) 33 | 34 | self.points = self.model_user.points 35 | self.nickname = self.model_user.nickname 36 | self.username = self.model_user.username 37 | 38 | file_name = glob.glob(f"{os.getenv('PATH_TO_AVATARS')}{self.user_id}.*") 39 | 40 | if len(file_name): 41 | self.avatar_path = file_name[0][file_name[0].index('avatars/'):] 42 | return 43 | 44 | self.avatar_path = "images/unknown_user.png" 45 | 46 | async def move(self, start_cell: str, end_cell: str): 47 | """move in board and flip timer""" 48 | 49 | await self.timer.flip_the_timer() 50 | await self.own_object.move("".join([start_cell, end_cell])) 51 | 52 | async def draw(self): 53 | self.draw_offer = True 54 | await self.own_object.on_draw_offer() 55 | 56 | async def give_up(self): 57 | await self.own_object.on_give_up(self.user_id) 58 | 59 | async def start_timer(self): 60 | """timer starter""" 61 | 62 | await self.timer.start_timer() 63 | 64 | async def give_points(self): 65 | """add points count after end game""" 66 | 67 | self.model_user.points += 1 68 | await self.model_user.asave() 69 | -------------------------------------------------------------------------------- /web_django/chessboards/chess_core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/web_django/chessboards/chess_core/__init__.py -------------------------------------------------------------------------------- /web_django/chessboards/chess_core/core.py: -------------------------------------------------------------------------------- 1 | from asgiref.sync import sync_to_async 2 | 3 | 4 | @sync_to_async() 5 | def get(from_list: list, path: str, **kwargs): 6 | """The method allows you to get an object according to the condition set using **kwargs and path 7 | 8 | :arg: from_list - The list in which the search is performed 9 | :arg: path - path the path from the class attributes to the class to get (str) 10 | :arg: **kwarg - Give one argument with the name corresponding to the argument by which the comparison will be made, and put the value itself that you are looking for in the value 11 | 12 | :return: class which you want get 13 | 14 | Ex.: 15 | 16 | print(get(games, "players.timer", time=30)) 17 | # Output: Timer object 18 | 19 | (#GOVNOCODE #YANDERECODING #SHITPOST) 20 | 21 | """ 22 | 23 | positions = path.split('.') 24 | value_name = list(kwargs.keys())[0] 25 | 26 | if path == '': 27 | positions = [] 28 | 29 | for obj in from_list: 30 | 31 | for position in positions: 32 | 33 | try: 34 | obj = obj.__dict__[position] 35 | except TypeError: 36 | for i in obj: 37 | obj = i.__dict__[position] 38 | if obj.__dict__[value_name] == kwargs[value_name]: 39 | return obj 40 | 41 | try: 42 | if obj.__dict__[value_name] == kwargs[value_name]: 43 | return obj 44 | except AttributeError: 45 | for i in obj: 46 | if i.__dict__[value_name] == kwargs[value_name]: 47 | return i 48 | -------------------------------------------------------------------------------- /web_django/chessboards/consumers.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from channels.generic.websocket import AsyncJsonWebsocketConsumer 4 | from chess import IllegalMoveError 5 | from autobahn.exception import Disconnected 6 | from asgiref.sync import sync_to_async 7 | 8 | from chessboards.chess_core.core import get 9 | from chessboards.chess_core.Game import games 10 | from django_log.log import log 11 | 12 | 13 | class UserWebsocket(AsyncJsonWebsocketConsumer): 14 | 15 | async def connect(self): 16 | 17 | self.board_tag = self.scope['url_route']['kwargs']['tag'] 18 | self.user = await get( 19 | games, 20 | 'players', 21 | user_id=await sync_to_async(int)(await sync_to_async(self.scope["session"].get)("user_id"))) 22 | 23 | await self.channel_layer.group_add( 24 | self.board_tag, 25 | self.channel_name 26 | ) 27 | 28 | await self.accept() 29 | 30 | async def disconnect(self, code): 31 | await self.channel_layer.group_discard( 32 | self.board_tag, 33 | self.channel_name 34 | ) 35 | 36 | await self.close() 37 | 38 | async def receive_json(self, content, **kwargs): 39 | 40 | # switch case construction does not exit in python 3.9 so: 41 | 42 | if content["type"] == "draw_offer": 43 | await self.draw_offer() 44 | elif content["type"] == "give_up": 45 | await self.give_up() 46 | elif content["type"] == "move": 47 | await self.move(content["start_cell"], content["end_cell"]) 48 | elif content["type"] == "get_legal_moves": 49 | await self.get_legal_moves(content["figure_cell"]) 50 | 51 | async def send_json(self, content, close=False): 52 | try: 53 | await super().send_json(content, close=close) 54 | except Disconnected: 55 | await sync_to_async(log.info)("autobahn.exception.Disconnection error. ") 56 | 57 | async def end_game_event(self, event): 58 | await self.send_json({"event": 'end_game', "message": event['message']}) 59 | await self.disconnect(200) 60 | 61 | async def draw_offer_event(self, event): 62 | await self.send_json({"event": "draw_offer", "recipient": event['recipient']}) 63 | 64 | async def on_check(self, event): 65 | await self.send_json({"event": "on_check", "recipient": event['recipient']}) 66 | 67 | async def update_board(self, event): 68 | await self.send_json({ 69 | "event": "update_board", 70 | "board": event["board"], 71 | "first_user_time": round(event["first_user_time"]), 72 | "second_user_time": round(event["second_user_time"])}) 73 | 74 | async def illegal_move_error(self, message): 75 | await self.send_json({"event": "illegal_move_error", "message": message}) 76 | 77 | async def error(self, message): 78 | await self.send_json({"event": "error", "message": message}) 79 | 80 | async def move(self, start_cell: str, end_cell: str): 81 | 82 | if not self.user: 83 | return 84 | 85 | if self.user.own_object.board.turn is not self.user.color: 86 | await self.error(await (sync_to_async(os.getenv)("ON_ILLEGAL_ACTION_ERROR"))) 87 | return 88 | 89 | try: 90 | await self.user.move(start_cell, end_cell) 91 | except IllegalMoveError: 92 | await self.illegal_move_error(await (sync_to_async(os.getenv)("ILLEGAL_MOVE_ERROR"))) 93 | 94 | async def get_legal_moves(self, figure_cell: str): 95 | 96 | if not self.user: 97 | return 98 | 99 | cells = await sync_to_async(self.user.own_object.get_legal_moves)(figure_cell) 100 | 101 | await self.send_json({"event": "legal_moves", "cells": cells}) 102 | 103 | async def draw_offer(self): 104 | 105 | if not self.user: 106 | return 107 | 108 | await self.user.draw() 109 | 110 | async def give_up(self): 111 | 112 | if not self.user: 113 | return 114 | 115 | await self.user.give_up() 116 | -------------------------------------------------------------------------------- /web_django/chessboards/games_management.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /web_django/chessboards/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/web_django/chessboards/migrations/__init__.py -------------------------------------------------------------------------------- /web_django/chessboards/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class UserModel(models.Model): 5 | 6 | user_id = models.BigIntegerField(primary_key=True) 7 | points = models.IntegerField() 8 | nickname = models.TextField() 9 | username = models.TextField() 10 | 11 | class Meta: 12 | db_table = "users" 13 | 14 | 15 | class GamesModel(models.Model): 16 | first_player = models.ForeignKey(UserModel, models.CASCADE, related_name="first_player_id") 17 | second_player = models.ForeignKey(UserModel, models.CASCADE, related_name="second_player_id") 18 | winner = models.ForeignKey(UserModel, models.CASCADE, related_name="winner_id") 19 | -------------------------------------------------------------------------------- /web_django/chessboards/routing.py: -------------------------------------------------------------------------------- 1 | from chessboards.consumers import UserWebsocket 2 | from django.urls import path, re_path, utils 3 | 4 | 5 | websocket_urlpatterns = [ 6 | path('websocket/games//', UserWebsocket.as_asgi()) 7 | ] 8 | -------------------------------------------------------------------------------- /web_django/chessboards/templates/chessboards/game.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block title%} App | Chess {% endblock %} 4 | 5 | {% block css_load %} 6 | 7 | {% load static %} 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | {% endblock %} 26 | 27 | {% block body %} 28 | 45 |
46 |
47 | 51 |
52 |
53 |
54 |
55 |

Игра ещё не началась, подождите ещё

56 |

30

57 |
58 |
59 |
60 |
61 | 103 |
104 |
105 |
106 |
107 | 108 | 116 | 117 | 124 | 125 | 133 | 134 | 141 | 142 | 149 | 150 | 157 | 158 | 166 | 167 | {% load static %} 168 |
{% static 'images/1x1.png' %}
169 |
{% static 'move_sound.mp3' %}
170 | 171 | {% endblock %} 172 | 173 | {% block scripts %} 174 | 175 | 178 | 179 | {% load static %} 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 200 | 201 | {% load static %} 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 221 | 222 | {% endblock %} 223 | -------------------------------------------------------------------------------- /web_django/chessboards/templates/chessboards/game_spectator.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block title%} App | Chess {% endblock %} 4 | 5 | {% block css_load %} 6 | 7 | {% load static %} 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | {% endblock %} 26 | 27 | {% block body %} 28 | 46 |
47 |
48 | 61 |
62 |
63 |
64 |
65 |

Игра ещё не началась, подождите ещё

66 |

30

67 |
68 |
69 |
70 |
71 | 113 |
114 |
115 |
116 |
117 | 118 | 125 | 126 | 134 | 135 | {% load static %} 136 |
{% static 'images/1x1.png' %}
137 | 138 | {% endblock %} 139 | 140 | {% block scripts %} 141 | 142 | 145 | 146 | {% load static %} 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 164 | 165 | {% load static %} 166 | 167 | 168 | 169 | 170 | 171 | 172 | 181 | 182 | {% endblock %} 183 | -------------------------------------------------------------------------------- /web_django/chessboards/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /web_django/chessboards/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from chessboards.views import game_view 3 | 4 | urlpatterns = [ 5 | path('games//', game_view, name="chess_game") 6 | ] 7 | -------------------------------------------------------------------------------- /web_django/chessboards/views.py: -------------------------------------------------------------------------------- 1 | import os 2 | from time import time 3 | 4 | from django.http import HttpRequest 5 | from django.shortcuts import render 6 | from asgiref.sync import sync_to_async 7 | 8 | from chessboards.chess_core.core import get 9 | from chessboards.chess_core.Game import games 10 | 11 | 12 | async def game_view(request: HttpRequest, **kwargs): 13 | 14 | game_object = await get(games, '', tag=kwargs['tag']) 15 | user_id = await sync_to_async(request.session.get)("user_id") 16 | 17 | if not game_object or not user_id: 18 | return await sync_to_async(render)(request, 'error_page/index.html', {'error_code': '404', "error": "Игры с таким uuid не существует"}) 19 | 20 | user_id = int(user_id) 21 | 22 | user = await get(game_object.players, '', user_id=user_id) 23 | 24 | kwargs["game"] = game_object 25 | kwargs["user"] = user 26 | 27 | await game_object.get_turn_player().timer.update_timer() 28 | 29 | if user: 30 | return await game_player_mode(request, **kwargs) 31 | 32 | return await game_spectator_mode(request, **kwargs) 33 | 34 | 35 | @sync_to_async() 36 | def game_player_mode(request: HttpRequest, **kwargs): 37 | 38 | game = kwargs["game"] 39 | user = kwargs["user"] 40 | 41 | return render(request, 'chessboards/game.html', { 42 | 'prepare_time': int(os.getenv("PREPARE_TIME")) - round(time() - game.started_at), 43 | 'board_tag': kwargs['tag'], 44 | 'user_id': user.user_id, 45 | 'board_fen': game.board.board_fen(), 46 | 'first_player_nickname': game.player_1.nickname, 47 | 'second_player_nickname': game.player_2.nickname, 48 | 'first_player_avatar': game.player_1.avatar_path, 49 | 'second_player_avatar': game.player_2.avatar_path, 50 | 'first_player_time': game.player_1.timer.time, 51 | 'second_player_time': game.player_2.timer.time, 52 | 'draw_offer': user.draw_offer, 53 | 'color': user.color, 54 | 'turn': game.board.turn}) 55 | 56 | 57 | @sync_to_async() 58 | def game_spectator_mode(request: HttpRequest, **kwargs): 59 | 60 | game = kwargs["game"] 61 | 62 | return render(request, 'chessboards/game_spectator.html', { 63 | 'prepare_time': int(os.getenv('PREPARE_TIME')) - round(time() - game.started_at), 64 | 'board_tag': kwargs['tag'], 65 | 'board_fen': game.board.board_fen(), 66 | 'first_player_nickname': game.player_1.nickname, 67 | 'second_player_nickname': game.player_2.nickname, 68 | 'first_player_avatar': game.player_1.avatar_path, 69 | 'second_player_avatar': game.player_2.avatar_path, 70 | 'first_player_time': game.player_1.timer.time, 71 | 'second_player_time': game.player_2.timer.time, 72 | 'turn': game.board.turn}) 73 | -------------------------------------------------------------------------------- /web_django/django_log/log.py: -------------------------------------------------------------------------------- 1 | import os 2 | from logging import getLogger, FileHandler, Formatter, basicConfig 3 | 4 | basicConfig(level='DEBUG') 5 | 6 | log_handler = FileHandler(os.path.join(os.path.dirname(__file__), 'main.log'), 'a+') 7 | log_handler.setFormatter(Formatter("%(name)s %(asctime)s %(levelname)s %(message)s %(exc_info)s %(lineno)s")) 8 | 9 | log = getLogger('django_log') 10 | log.addHandler(log_handler) 11 | log.setLevel('DEBUG') 12 | 13 | asyncio_log = getLogger('asyncio') 14 | asyncio_log.setLevel('DEBUG') 15 | asyncio_log.addHandler(log_handler) 16 | 17 | log.info('logging successful started') 18 | -------------------------------------------------------------------------------- /web_django/error_page/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/web_django/error_page/__init__.py -------------------------------------------------------------------------------- /web_django/error_page/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /web_django/error_page/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ErrorPageConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'error_page' 7 | -------------------------------------------------------------------------------- /web_django/error_page/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Justiks1337/ChessBot/930b08e5f11bb77708c9fb99ff735816c84635ae/web_django/error_page/migrations/__init__.py -------------------------------------------------------------------------------- /web_django/error_page/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /web_django/error_page/templates/error_page/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block css_load %} 4 | 5 | {% load static %} 6 | 7 | 8 | {% endblock%} 9 | 10 | {% block title %} Ошибка {% endblock %} 11 | 12 | {% block body %} 13 | 19 | 20 | {% endblock %} -------------------------------------------------------------------------------- /web_django/error_page/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /web_django/error_page/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | from django.http import HttpRequest 3 | 4 | 5 | def handler404(request: HttpRequest, *args, **kwargs): 6 | return render(request, 'error_page/index.html', {'error_code': "404", "error": "СТРАНИЦА НЕ НАЙДЕНА"}) 7 | 8 | -------------------------------------------------------------------------------- /web_django/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'web_django.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | 24 | -------------------------------------------------------------------------------- /web_django/requirements.txt: -------------------------------------------------------------------------------- 1 | adrf==0.1.9 2 | asgiref==3.8.1 3 | async-property==0.2.2 4 | attrs==25.3.0 5 | autobahn==24.4.2 6 | Automat==25.4.16 7 | certifi==2025.4.26 8 | cffi==1.17.1 9 | channels==4.2.2 10 | charset-normalizer==3.4.2 11 | chess==1.11.2 12 | constantly==23.10.4 13 | cryptography==45.0.3 14 | daphne==4.2.0 15 | Django==5.2.1 16 | django-channels==0.7.0 17 | django-cors-headers==4.7.0 18 | django-ipware==7.0.1 19 | djangorestframework==3.16.0 20 | hyperlink==21.0.0 21 | idna==3.10 22 | incremental==24.7.2 23 | oauthlib==3.2.2 24 | psycopg2-binary==2.9.10 25 | pyasn1==0.6.1 26 | pyasn1_modules==0.4.2 27 | pycparser==2.22 28 | PyJWT==2.10.1 29 | pyOpenSSL==25.1.0 30 | python-dotenv==1.1.0 31 | python-ipware==3.0.0 32 | requests==2.32.3 33 | requests-oauthlib==2.0.0 34 | service-identity==24.2.0 35 | setuptools==80.8.0 36 | six==1.17.0 37 | sqlparse==0.5.3 38 | Twisted==24.11.0 39 | txaio==23.1.1 40 | typing_extensions==4.13.2 41 | urllib3==2.4.0 42 | zope.interface==7.2 43 | -------------------------------------------------------------------------------- /web_django/web_django/__init__.py: -------------------------------------------------------------------------------- 1 | from asyncio import get_event_loop 2 | 3 | 4 | main_loop = get_event_loop() 5 | -------------------------------------------------------------------------------- /web_django/web_django/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for web_django 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/4.2/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django import setup 13 | from django.core.management import call_command 14 | from django.core.asgi import get_asgi_application 15 | from channels.routing import ProtocolTypeRouter, URLRouter 16 | from channels.security.websocket import AllowedHostsOriginValidator 17 | from channels.sessions import SessionMiddlewareStack 18 | 19 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'web_django.settings') 20 | setup() 21 | 22 | 23 | asgi_application = get_asgi_application() 24 | 25 | from chessboards.routing import websocket_urlpatterns as chessboards_websocket_url_patterns 26 | from django_log.log import log 27 | 28 | os.environ.setdefault('PYTHONASYNCIODEBUG', '1') 29 | os.environ.setdefault('SERVER_GATEWAY_INTERFACE', 'ASGI') 30 | 31 | call_command('makemigrations') 32 | call_command('migrate') 33 | 34 | log.info(f'server successful started. Server gateway interface: ASGI') 35 | 36 | application = ProtocolTypeRouter({ 37 | "http": asgi_application, 38 | "websocket": AllowedHostsOriginValidator( 39 | SessionMiddlewareStack(URLRouter(chessboards_websocket_url_patterns))) 40 | }) 41 | -------------------------------------------------------------------------------- /web_django/web_django/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for web_django project. 3 | 4 | Generated by 'django-admin startproject' using Django 4.2.2. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.2/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/4.2/ref/settings/ 11 | """ 12 | 13 | import os 14 | from pathlib import Path 15 | import dotenv 16 | 17 | 18 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 19 | BASE_DIR = Path(__file__).resolve().parent.parent 20 | 21 | # Read environment variables 22 | dotenv.load_dotenv(os.path.join(BASE_DIR, ".env")) 23 | 24 | # Quick-start development settings - unsuitable for production 25 | # See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/ 26 | 27 | # SECURITY WARNING: keep the secret key used in production secret! 28 | SECRET_KEY = os.getenv("SERVER_AUTHKEY") 29 | 30 | # SECURITY WARNING: don't run with debug turned on in production! 31 | DEBUG = False 32 | 33 | ALLOWED_HOSTS = ["127.0.0.1", 34 | "192.168.1.60", 35 | "45.142.122.143", 36 | "chess.kazoe.ru", 37 | "chess-kb.ru", 38 | "beta.chess-kb.ru", 39 | "daphne"] 40 | 41 | 42 | # Application definition 43 | INSTALLED_APPS = [ 44 | 'django.contrib.admin', 45 | 'django.contrib.auth', 46 | 'django.contrib.contenttypes', 47 | 'django.contrib.sessions', 48 | 'django.db.models', 49 | 'django.contrib.messages', 50 | 'daphne', 51 | 'django.contrib.staticfiles', 52 | 'rest_framework', 53 | 'corsheaders', 54 | 'adrf', 55 | 'channels', 56 | 'chessboards', 57 | 'blacklist' 58 | ] 59 | 60 | MIDDLEWARE = [ 61 | 'django.middleware.security.SecurityMiddleware', 62 | 'django.contrib.sessions.middleware.SessionMiddleware', 63 | 'django.middleware.common.CommonMiddleware', 64 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 65 | 'django.contrib.messages.middleware.MessageMiddleware', 66 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 67 | 'django.middleware.common.CommonMiddleware' 68 | ] 69 | 70 | CORS_ORIGIN_ALLOW_ALL = True 71 | 72 | ROOT_URLCONF = 'web_django.urls' 73 | 74 | CHANNEL_LAYERS = { 75 | "default": { 76 | "BACKEND": "channels.layers.InMemoryChannelLayer" 77 | } 78 | } 79 | 80 | TEMPLATES = [ 81 | { 82 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 83 | 'DIRS': [ 84 | 'authorization/templates', 85 | 'error_page/templates', 86 | 'chessboards/templates', 87 | 'base' 88 | ], 89 | 'APP_DIRS': True, 90 | 'OPTIONS': { 91 | 'context_processors': [ 92 | 'django.template.context_processors.debug', 93 | 'django.template.context_processors.request', 94 | 'django.contrib.auth.context_processors.auth', 95 | 'django.contrib.messages.context_processors.messages', 96 | ], 97 | }, 98 | }, 99 | ] 100 | 101 | ASGI_APPLICATION = 'asgi.application' 102 | 103 | 104 | # Database 105 | # https://docs.djangoproject.com/en/4.2/ref/settings/#databases 106 | 107 | DATABASES = { 108 | 'default': { 109 | 'ENGINE': 'django.db.backends.postgresql', 110 | 'NAME': os.getenv("DATABASE_NAME"), 111 | 'USER': os.getenv("DATABASE_USER"), 112 | 'PASSWORD': os.getenv("DATABASE_PASSWORD"), 113 | 'HOST': os.getenv("DATABASE_HOST"), 114 | 'PORT': int(os.getenv("DATABASE_PORT")) 115 | } 116 | } 117 | 118 | 119 | # Password validation 120 | # https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators 121 | 122 | AUTH_PASSWORD_VALIDATORS = [ 123 | { 124 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 125 | }, 126 | { 127 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 128 | }, 129 | { 130 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 131 | }, 132 | { 133 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 134 | }, 135 | ] 136 | 137 | # Internationalization 138 | # https://docs.djangoproject.com/en/4.2/topics/i18n/ 139 | 140 | LANGUAGE_CODE = 'en-us' 141 | 142 | TIME_ZONE = 'UTC' 143 | 144 | USE_I18N = True 145 | 146 | USE_TZ = True 147 | 148 | 149 | # Static files (CSS, JavaScript, Images) 150 | # https://docs.djangoproject.com/en/4.2/howto/static-files/ 151 | 152 | STATIC_URL = '/static/' 153 | 154 | STATICFILES_DIRS = [ 155 | BASE_DIR / "static", 156 | ] 157 | 158 | # Default primary key field type 159 | # https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field 160 | 161 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' 162 | 163 | X_FRAME_OPTIONS = 'SAMEORIGIN' 164 | 165 | LOGGING = { 166 | 'version': 1, 167 | 'disable_existing_loggers': False, 168 | 'handlers': { 169 | 'file': { 170 | 'level': 'DEBUG', 171 | 'class': 'logging.FileHandler', 172 | 'filename': 'django_log/main.log', 173 | }, 174 | }, 175 | 'loggers': { 176 | 'django': { 177 | 'handlers': ['file'], 178 | 'level': 'DEBUG', 179 | 'propagate': True, 180 | }, 181 | }, 182 | } 183 | 184 | 185 | SECURE_CROSS_ORIGIN_OPENER_POLICY = None 186 | SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') 187 | -------------------------------------------------------------------------------- /web_django/web_django/urls.py: -------------------------------------------------------------------------------- 1 | """ 2 | URL configuration for web_django project. 3 | 4 | The `urlpatterns` list routes URLs to views. For more information please see: 5 | https://docs.djangoproject.com/en/4.2/topics/http/urls/ 6 | Examples: 7 | Function views 8 | 1. Add an import: from my_app import views 9 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 10 | Class-based views 11 | 1. Add an import: from other_app.views import Home 12 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 13 | Including another URLconf 14 | 1. Import the include() function: from django.urls import include, path 15 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 16 | """ 17 | from django.contrib import admin 18 | from django.urls import path, include 19 | from django.conf import settings 20 | from django.conf.urls.static import static 21 | from error_page.views import handler404 22 | 23 | urlpatterns = [ 24 | path('', include('authorization.urls')), 25 | path('admin/', admin.site.urls), 26 | path('playgrounds/', include('chessboards.urls')), 27 | path('api/v1/', include('api.urls')), 28 | path('authorization/', include('authorization.urls')), 29 | path('blacklist/', include('blacklist.urls')) 30 | ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) 31 | 32 | handler404 = handler404 33 | -------------------------------------------------------------------------------- /web_django/web_django/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for web_django project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings') 13 | os.environ.setdefault('SERVER_GATEWAY_INTERFACE', 'WSGI') 14 | 15 | --------------------------------------------------------------------------------