diff --git a/Dockerfile b/Dockerfile
index 75145b2..f2450f5 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,19 +1,18 @@
-FROM python:3.10.0
+FROM python:3.10.1
# Addding requirements
COPY requirements.txt requirements.txt
RUN pip install -U pip && pip install -r requirements.txt --no-cache-dir
# Setting working directory
WORKDIR /home/sholex
+RUN mkdir static
+RUN mkdir state
+RUN mkdir healthchecks
# Healthcheks
COPY healthchecks/gunicorn.py healthchecks/gunicorn.py
-# Adding Static Directory
-RUN mkdir static
# Copying source
COPY LilSholex LilSholex
COPY templates templates
COPY manage.py manage.py
COPY persianmeme persianmeme
-# Setting Volumes
-VOLUME /home/sholex/persianmeme/migrations
# Running
-CMD gunicorn --workers=2 --bind=0.0.0.0:80 --access-logfile /dev/null --error-logfile /dev/stderr LilSholex.wsgi
\ No newline at end of file
+CMD gunicorn --workers=2 --bind=0.0.0.0:80 --error-logfile /dev/stderr -t 15 LilSholex.wsgi
\ No newline at end of file
diff --git a/LilSholex/__init__.py b/LilSholex/__init__.py
index e69de29..c9f18d7 100755
--- a/LilSholex/__init__.py
+++ b/LilSholex/__init__.py
@@ -0,0 +1,3 @@
+from .celery import celery_app
+
+__all__ = ('celery_app',)
diff --git a/LilSholex/celery.py b/LilSholex/celery.py
new file mode 100644
index 0000000..bb0f790
--- /dev/null
+++ b/LilSholex/celery.py
@@ -0,0 +1,7 @@
+import os
+from celery import Celery
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'LilSholex.settings')
+celery_app = Celery('LilSholex')
+celery_app.config_from_object('django.conf:settings', namespace='CELERY')
+celery_app.autodiscover_tasks()
diff --git a/LilSholex/decorators.py b/LilSholex/decorators.py
index 9fa349d..249ad49 100755
--- a/LilSholex/decorators.py
+++ b/LilSholex/decorators.py
@@ -1,6 +1,7 @@
from aiohttp import ClientError
from requests import RequestException
-from asyncio import sleep
+from asyncio import sleep as async_sleep
+from time import sleep as sync_sleep
from .exceptions import TooManyRequests
@@ -10,7 +11,7 @@ async def check_exception(*args, **kwargs):
try:
return await func(*args, **kwargs)
except ClientError:
- await sleep(0.4)
+ await async_sleep(0.4)
return check_exception
@@ -21,7 +22,7 @@ def check_exception(*args, **kwargs):
try:
return func(*args, **kwargs)
except TooManyRequests as e:
- sleep(e.retry_after)
+ sync_sleep(e.retry_after)
except RequestException:
continue
diff --git a/LilSholex/settings.py b/LilSholex/settings.py
index 68c5ded..ddbb403 100755
--- a/LilSholex/settings.py
+++ b/LilSholex/settings.py
@@ -35,8 +35,7 @@
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
- 'background_task',
- 'persianmeme.apps.PersianmemeConfig'
+ 'persianmeme.apps.PersianmemeConfig',
]
MIDDLEWARE = [
@@ -122,8 +121,6 @@
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'static'
MAX_ATTEMPTS = 2
-BACKGROUND_TASK_RUN_ASYNC = True
-BACKGROUND_TASK_ASYNC_THREADS = 4
# Pagination Limit Broadcast
PAGINATION_LIMIT = 1500
@@ -146,5 +143,12 @@
SPAM_TIME = 5
SPAM_PENALTY = 1800
VIOLATION_REPORT_LIMIT = 5
-VIDEO_DURATION_LIMIT = 180
-VIDEO_SIZE_LIMIT = 15728640
+VIDEO_DURATION_LIMIT = 240
+VIDEO_SIZE_LIMIT = 20971520
+# Celery
+CELERY_BROKER_URL = 'amqp://guest:guest@rabbitmq:5672/'
+CELERY_WORKER_STATE_DB = str(BASE_DIR / 'state' / 'celery_state')
+REVOKE_REVIEW_COUNTDOWN = 3600
+CHECK_MEME_COUNTDOWN = 21600
+# CSRF
+CSRF_TRUSTED_ORIGINS = [f'https://{ALLOWED_HOSTS[0]}']
diff --git a/LilSholex/urls.py b/LilSholex/urls.py
index c61660a..ade1a6f 100755
--- a/LilSholex/urls.py
+++ b/LilSholex/urls.py
@@ -2,6 +2,6 @@
from django.urls import path, include
urlpatterns = [
- path('adminpanel/', admin.site.urls),
+ path('admin/', admin.site.urls),
path('persianmeme/', include('persianmeme.urls'))
]
diff --git a/Nginx b/Nginx
index 677a18b..5d379d8 100644
--- a/Nginx
+++ b/Nginx
@@ -1,6 +1,6 @@
FROM ubuntu:impish
-ARG NGINX_VERSION=nginx-1.21.4
-ARG OPENSSL_VERSION=openssl-3.0.0
+ARG NGINX_VERSION=nginx-1.21.5
+ARG OPENSSL_VERSION=openssl-3.0.1
# Compiling
RUN apt update && DEBIAN_FRONTEND="noninteractive" apt install -y build-essential \
libpcre3 libpcre3-dev zlib1g zlib1g-dev libssl-dev python3.9 python3-pip wget \
diff --git a/conf/my.cnf b/conf/my.cnf
index 448e578..0d159ca 100644
--- a/conf/my.cnf
+++ b/conf/my.cnf
@@ -1,5 +1,5 @@
[mysqld]
-innodb_buffer_pool_chunk_size=536870912
-innodb_buffer_pool_instances=4
-innodb_buffer_pool_size=2147483648
-key_buffer_size=1073741824
\ No newline at end of file
+innodb_buffer_pool_chunk_size=335544320
+innodb_buffer_pool_instances=8
+innodb_buffer_pool_size=2684354560
+key_buffer_size=2684354560
\ No newline at end of file
diff --git a/conf/nginx.conf b/conf/nginx.conf
index 57f2225..b5ae6d4 100644
--- a/conf/nginx.conf
+++ b/conf/nginx.conf
@@ -7,13 +7,6 @@ events {
}
http {
- server {
- access_log off;
- listen 80;
- server_name localhost;
- return 200 'I\'m Up and Running !';
- }
-
# Buffering
client_max_body_size 500k;
client_body_buffer_size 100k;
@@ -24,6 +17,7 @@ http {
send_timeout 3s;
tcp_nopush on;
sendfile on;
+ sendfile_max_chunk 256m;
# Includes
include mime.types;
@@ -34,9 +28,9 @@ http {
gzip_types text/javascript text/css;
# SSL
- ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
+ ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
+ ssl_conf_command Options KTLS;
ssl_prefer_server_ciphers on;
- ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:ECDHE-RSA-AES128-GCM-SHA256:AES256+EECDH:DHE-RSA-AES128-GCM-SHA256:AES256+EDH:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
ssl_session_cache shared:SSL:10m;
ssl_session_tickets on;
ssl_session_timeout 10m;
@@ -45,8 +39,24 @@ http {
# Proxy
proxy_set_header Host $host;
- proxy_buffer_size 4k;
- proxy_buffers 4 4k;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_buffers 4 2m;
+
+ # Servers
+ server_tokens off;
+
+ server {
+ access_log off;
+ listen 80;
+ server_name localhost;
+ return 200 'I\'m Up and Running !';
+ }
+
+ server {
+ listen 443 ssl default_server;
+ access_log /var/log/nginx/rejected.log;
+ ssl_reject_handshake on;
+ }
server {
listen 443 ssl http2;
@@ -74,6 +84,14 @@ http {
proxy_pass http://gunicorn;
}
+ location /flower/ {
+ expires 7d;
+ etag on;
+ add_header Cache-Control public;
+ access_log /var/log/nginx/flower.log;
+ proxy_pass http://celery_flower:5555;
+ }
+
location /persianmeme/{persianmeme_token}/ {
access_log off;
keepalive_requests 100;
@@ -86,7 +104,7 @@ http {
access_log off;
expires 7d;
etag on;
- root /root;
+ root /root/lilsholex;
try_files $uri @not_found;
}
diff --git a/conf/rabbitmq.conf b/conf/rabbitmq.conf
new file mode 100644
index 0000000..67987d3
--- /dev/null
+++ b/conf/rabbitmq.conf
@@ -0,0 +1 @@
+consumer_timeout = 28800000
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
index 381bc4c..7451546 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,7 +1,7 @@
version: "3.8"
services:
db:
- image: mysql:8.0.26
+ image: mysql:8.0.27
command: --default-authentication-plugin=mysql_native_password
environment:
MYSQL_ROOT_PASSWORD_FILE: "/run/secrets/db_password"
@@ -37,15 +37,57 @@ services:
delay: 5s
failure_action: pause
monitor: 10s
- order: start-first
+ order: stop-first
networks:
- db_django
volumes:
- - type: volume
- source: db
- target: /var/lib/mysql
+ - type: volume
+ source: db
+ target: /var/lib/mysql
+ configs:
+ - source: mysql
+ target: /etc/mysql/conf.d/mysql.cnf
+ rabbitmq:
+ image: rabbitmq:3.9.12
+ healthcheck:
+ test: "rabbitmq-diagnostics -q ping"
+ interval: 30s
+ timeout: 5s
+ start_period: 5m
+ retries: 3
+ networks:
+ - tasks_django
+ deploy:
+ replicas: 1
+ placement:
+ constraints:
+ - node.labels.task_master==true
+ restart_policy:
+ condition: any
+ max_attempts: 3
+ window: 20s
+ delay: 10s
+ update_config:
+ parallelism: 1
+ delay: 2s
+ failure_action: rollback
+ monitor: 10s
+ order: stop-first
+ rollback_config:
+ parallelism: 1
+ delay: 5s
+ failure_action: pause
+ monitor: 10s
+ order: stop-first
+ volumes:
+ - type: volume
+ source: rabbitmq_dump
+ target: /var/lib/rabbitmq
+ configs:
+ - source: rabbitmq
+ target: /etc/rabbitmq/rabbitmq.conf
nginx:
- image: ghcr.io/sholex-team/nginx:3.7
+ image: ghcr.io/sholex-team/nginx:3.8
networks:
- nginx_lilsholex
- internet
@@ -59,7 +101,7 @@ services:
- gunicorn
- daphne
volumes:
- - ./static:/root/static:ro
+ - ./lilsholex-dev/static:/root/lilsholex/static:ro
- type: volume
source: nginx
target: /var/log/nginx
@@ -92,46 +134,51 @@ services:
- ssl_key
- dhparam
ports:
- - "443:443/tcp"
- - "80:80/tcp"
- gunicorn:
- image: ghcr.io/sholex-team/lilsholex:5.2
+ - "443:443/tcp"
+ - "80:80/tcp"
+ lilsholex:
+ image: ghcr.io/sholex-team/lilsholex:5.3
networks:
- db_django
- nginx_lilsholex
- - internet
- cache_django
+ - internet
+ - tasks_django
secrets:
- db_password
- persianmeme_channel
- - persianmeme_help_messages
+ - persianmeme_reports
+ - source: persianmeme_help_messages
+ target: persianmeme_help_messages
- persianmeme_token
- secret_key
- persianmeme_logs
- persianmeme_messages
- - persianmeme_reports
+ - domain
depends_on:
- db
- migrations
healthcheck:
test: python healthchecks/gunicorn.py
- interval: 30s
- timeout: 5s
- start_period: 1m
+ interval: 20s
+ timeout: 20s
+ start_period: 30s
retries: 3
deploy:
- replicas: 2
+ replicas: 6
placement:
+ preferences:
+ - spread: node.labels.zone
constraints:
- node.labels.webserver==true
update_config:
- parallelism: 1
+ parallelism: 3
delay: 5s
failure_action: rollback
- monitor: 10s
+ monitor: 5s
order: start-first
rollback_config:
- parallelism: 1
+ parallelism: 2
delay: 5s
failure_action: pause
monitor: 10s
@@ -141,31 +188,37 @@ services:
max_attempts: 3
window: 10s
delay: 5s
- volumes:
- - type: volume
- source: persianmeme
- target: /home/sholex/persianmeme/migrations
- tasks:
- image: ghcr.io/sholex-team/lilsholex:5.2
- command: bash -c "python manage.py clear_tasks && python manage.py process_tasks --sleep 15"
+ celery: &celery_base
+ image: ghcr.io/sholex-team/lilsholex-dev:6.1.5
+ command: "celery -A LilSholex worker -Q celery -l info -c 2 -E"
networks:
- db_django
- internet
+ - tasks_django
secrets:
- db_password
- secret_key
- persianmeme_token
- persianmeme_channel
- - persianmeme_help_messages
+ - persianmeme_reports
+ - source: persianmeme_help_messages
+ target: persianmeme_help_messages
- persianmeme_logs
- persianmeme_messages
- - persianmeme_reports
+ - domain
+ volumes:
+ - type: volume
+ source: state
+ target: /home/sholex/state
depends_on:
- db
- migrations
+ - rabbitmq
deploy:
replicas: 1
placement:
+ preferences:
+ - spread: node.labels.zone
constraints:
- node.labels.task_master==true
update_config:
@@ -179,18 +232,21 @@ services:
delay: 5s
failure_action: pause
monitor: 10s
- order: start-first
+ order: stop-first
restart_policy:
condition: any
max_attempts: 3
window: 10s
delay: 5s
- volumes:
- - type: volume
- source: persianmeme
- target: /home/sholex/persianmeme/migrations
- broadcasts:
- image: ghcr.io/sholex-team/lilsholex:5.2
+ celery_flower:
+ <<: *celery_base
+ command: "celery -A LilSholex flower --url_prefix=flower"
+ depends_on:
+ - celery
+ environment:
+ FLOWER_BASIC_AUTH: "sholex:flower_password"
+ lilsholex_broadcasts:
+ image: ghcr.io/sholex-team/lilsholex:5.3
command: "python manage.py process_broadcasts"
networks:
- db_django
@@ -200,10 +256,12 @@ services:
- secret_key
- persianmeme_token
- persianmeme_channel
- - persianmeme_help_messages
+ - persianmeme_reports
+ - source: persianmeme_help_messages
+ target: persianmeme_help_messages
- persianmeme_logs
- persianmeme_messages
- - persianmeme_reports
+ - domain
depends_on:
- db
- migrations
@@ -223,16 +281,12 @@ services:
delay: 5s
failure_action: pause
monitor: 10s
- order: start-first
+ order: stop-first
restart_policy:
condition: any
max_attempts: 3
window: 10s
delay: 5s
- volumes:
- - type: volume
- source: persianmeme
- target: /home/sholex/persianmeme/migrations
memcached:
image: memcached:1.6.12
command: "memcached -m 256"
@@ -262,25 +316,30 @@ services:
failure_action: pause
monitor: 10s
order: start-first
- migrations:
- image: ghcr.io/sholex-team/lilsholex:5.2
- command: "bash -c 'yes | python manage.py makemigrations persianmeme background_task && \
+ lilsholex_migrations:
+ image: ghcr.io/sholex-team/lilsholex:5.3
+ command: "bash -c 'yes | python manage.py makemigrations persianmeme && \
yes | python manage.py migrate && python manage.py collectstatic --noinput'"
secrets:
- db_password
- secret_key
- persianmeme_token
- persianmeme_channel
- - persianmeme_help_messages
+ - persianmeme_reports
+ - source: persianmeme_help_messages
+ target: persianmeme_help_messages
- persianmeme_logs
- persianmeme_messages
- - persianmeme_reports
+ - domain
depends_on:
- db
networks:
- db_django
deploy:
replicas: 1
+ placement:
+ constraints:
+ - node.labels.db==true
update_config:
parallelism: 1
delay: 5s
@@ -292,7 +351,7 @@ services:
delay: 5s
failure_action: pause
monitor: 10s
- order: start-first
+ order: stop-first
restart_policy:
condition: on-failure
max_attempts: 3
@@ -311,12 +370,16 @@ networks:
driver: "overlay"
name: "nginx_lilsholex"
internal: true
+ cache_django:
+ driver: "overlay"
+ name: "cache_django"
+ internal: true
internet:
driver: "overlay"
name: "internet"
- cache_django:
+ tasks_django:
driver: "overlay"
- name: "cache_django"
+ name: "tasks_django"
internal: true
volumes:
db:
@@ -325,6 +388,10 @@ volumes:
name: "persianmeme"
nginx:
name: "nginx"
+ state:
+ name: "state"
+ rabbitmq_dump:
+ name: "rabbitmq_dump"
secrets:
db_password:
external: true
@@ -336,10 +403,10 @@ secrets:
external: true
persianmeme_messages:
external: true
- persianmeme_help_messages:
- file: secrets/help_messages.json
persianmeme_reports:
external: true
+ persianmeme_help_messages:
+ file: lilsholex-dev/secrets/help_messages.json # Replace file_id of animations.
secret_key:
external: true
ssl_certificate:
@@ -350,3 +417,8 @@ secrets:
file: ./ssl/dhparam.pem
domain:
external: true
+configs:
+ mysql:
+ file: conf/my.cnf # Change MySQL configuration based on your host.
+ rabbitmq:
+ file: conf/rabbitmq.conf
diff --git a/persianmeme/admin.py b/persianmeme/admin.py
index 72dc87a..de5dcff 100755
--- a/persianmeme/admin.py
+++ b/persianmeme/admin.py
@@ -221,7 +221,8 @@ def recover_memes(self, request: HttpRequest, queryset):
'deny_vote',
'usage_count',
'reviewed',
- 'previous_status'
+ 'previous_status',
+ 'task_id'
)})
)
@@ -282,7 +283,7 @@ class Message(admin.ModelAdmin):
@admin.register(models.MemeTag)
-class MemeTa(admin.ModelAdmin):
+class MemeTag(admin.ModelAdmin):
list_display = ('tag',)
search_fields = ('tag',)
list_per_page = 30
@@ -319,7 +320,7 @@ class Report(admin.ModelAdmin):
'meme__id',
'meme__name',
'meme__file_id',
- 'meme__unique_file_id'
+ 'meme__file_unique_id'
)
raw_id_fields = ('meme', 'reporters')
fieldsets = (('Information', {'fields': ('meme', 'reporters')}), ('Status', {'fields': ('status',)}))
diff --git a/persianmeme/classes.py b/persianmeme/classes.py
index c3059af..65bc419 100755
--- a/persianmeme/classes.py
+++ b/persianmeme/classes.py
@@ -64,7 +64,7 @@ def get_user(self):
self.__ads = models.Ad.objects.exclude(seen=user)
return user
- def delete_current_voice(self):
+ def delete_current_meme(self):
if self.database.current_meme:
self.database.current_meme.delete(admin=self.database, log=True)
self.send_message(translations.admin_messages['deleted'])
@@ -78,18 +78,9 @@ def delete_meme(self, file_unique_id, meme_type: models.MemeType):
status=models.Meme.Status.ACTIVE,
type=meme_type
)).exists():
- result.first().delete(admin=self.database, log=True)
-
- @sync_fix
- def __delete_voting(self, message_id: int):
- with self.session.get(
- f'{self._BASE_URL}deleteMessage',
- params={'chat_id': settings.MEME_CHANNEL, 'message_id': message_id},
- timeout=settings.REQUESTS_TIMEOUT
- ) as response:
- if response.status_code != 429:
- return
- raise TooManyRequests(response.json()['parameters']['retry_after'])
+ target_meme = result.first()
+ target_meme.assigned_admin = self.database
+ target_meme.delete(admin=self.database, log=True)
def cancel_voting(self, meme_type: models.MemeType):
if not (pending_memes := models.Meme.objects.filter(
@@ -100,7 +91,7 @@ def cancel_voting(self, meme_type: models.MemeType):
))
return
for pending_meme in pending_memes:
- self.__delete_voting(pending_meme.message_id)
+ pending_meme.delete_vote()
pending_meme.delete()
self.send_message(translations.user_messages['voting_canceled'].format(
translations.user_messages['voice' if meme_type == models.MemeType.VOICE else 'video']
@@ -508,7 +499,7 @@ def translate(self, key: str, *formatting_args):
self.database.menu_mode == self.database.MenuMode.USER else \
translations.admin_messages[key].format(*formatting_args)
- def __check_voice_tags(self, tags: str):
+ def __check_meme_tags(self, tags: str):
if 'tags:' in tags:
raise InvalidMemeTag()
if len(tags) >= len(punctuation):
@@ -530,7 +521,7 @@ def process_meme_tags(self, tags: str):
self.send_message(self.translate('send_meme_tags', self.temp_meme_translation))
return False
try:
- self.__check_voice_tags(tags)
+ self.__check_meme_tags(tags)
except ValueError as e:
self.send_message(self.translate(str(e), self.temp_meme_translation))
return False
@@ -570,7 +561,7 @@ def add_meme(self, message: dict, status: models.Meme.Status):
def validate_meme_name(self, message: dict, text: str, meme_type: models.MemeType or int):
if not text or \
- message.get('entities') or len(text) > 50 or text.startswith('tags:') or \
+ message.get('entities') or len(text) > 80 or text.startswith('tags:') or \
text.startswith('names:'):
self.send_message(
self.translate('invalid_meme_name', self.translate(
@@ -696,7 +687,7 @@ def assign_meme(self):
self.database.current_meme = new_meme.first()
self.database.current_meme.assigned_admin = self.database
self.database.current_meme.save()
- revoke_review(self.database.current_meme.id)
+ revoke_review.apply_async((self.database.current_meme.id,), countdown=settings.REVOKE_REVIEW_COUNTDOWN)
else:
self.send_message(translations.admin_messages['no_meme_to_review'])
return False
diff --git a/persianmeme/functions.py b/persianmeme/functions.py
index 663b146..13125f9 100755
--- a/persianmeme/functions.py
+++ b/persianmeme/functions.py
@@ -294,3 +294,4 @@ def fake_deny_vote(queryset):
meme.deny_vote.count() < (random_fake := randint(fake_min, fake_max)):
faked_count += 1
meme.deny_vote.set(models.User.objects.all()[:random_fake])
+ return faked_count
diff --git a/persianmeme/handlers/callback_query/handlers.py b/persianmeme/handlers/callback_query/handlers.py
index 9179d0a..5b337d6 100644
--- a/persianmeme/handlers/callback_query/handlers.py
+++ b/persianmeme/handlers/callback_query/handlers.py
@@ -15,6 +15,9 @@
delete_logs,
reports
)
+from persianmeme.translations import admin_messages
+from persianmeme.keyboards import processed
+from django.conf import settings
def handler(request, callback_query, user_chat_id):
@@ -64,6 +67,10 @@ def handler(request, callback_query, user_chat_id):
try:
report = Report.objects.get(meme__id=meme_id, status=Report.Status.PENDING)
except Report.DoesNotExist:
+ answer_query(query_id, admin_messages['meme_already_processed'], False)
+ functions.edit_message_reply_markup(
+ settings.MEME_REPORTS_CHANNEL, processed, message_id, session=inliner.session
+ )
inliner.database.save()
raise RequestInterruption()
reports.handler(command, query_id, message_id, answer_query, report, inliner)
diff --git a/persianmeme/handlers/callback_query/menus/reports.py b/persianmeme/handlers/callback_query/menus/reports.py
index fb7ee41..497cc15 100644
--- a/persianmeme/handlers/callback_query/menus/reports.py
+++ b/persianmeme/handlers/callback_query/menus/reports.py
@@ -34,7 +34,7 @@ def handler(command: str, query_id: str, message_id: int, answer_query, report:
)
answer_query(query_id, translations.admin_messages['deleted'], True)
edit_message_reply_markup(
- settings.MEME_REPORTS_CHANNEL, deleted, message_id=message_id, session=inliner.session
+ settings.MEME_REPORTS_CHANNEL, deleted, message_id, session=inliner.session
)
else:
if report.meme.status == Meme.Status.REPORTED:
diff --git a/persianmeme/handlers/message/menus/admin/menus/meme_review.py b/persianmeme/handlers/message/menus/admin/menus/meme_review.py
index cfd0078..5d02f7a 100644
--- a/persianmeme/handlers/message/menus/admin/menus/meme_review.py
+++ b/persianmeme/handlers/message/menus/admin/menus/meme_review.py
@@ -26,7 +26,7 @@ def handler(text: str, message_id: int, user: UserClass):
admin_messages['edit_meme_description'].format(user.current_meme_translation), en_back
)
case 'Delete 🗑':
- user.delete_current_voice()
+ user.delete_current_meme()
if not user.assign_meme():
user.go_back()
case 'Check the Meme':
diff --git a/persianmeme/keyboards.py b/persianmeme/keyboards.py
index 31fa68b..f11bcfd 100755
--- a/persianmeme/keyboards.py
+++ b/persianmeme/keyboards.py
@@ -81,6 +81,7 @@
deleted = {'inline_keyboard': [[{'text': 'Deleted 🗑', 'callback_data': 'none'}]]}
recovered = {'inline_keyboard': [[{'text': 'Recovered ♻', 'callback_data': 'none'}]]}
dismissed = {'inline_keyboard': [[{'text': 'Dismissed ✔', 'callback_data': 'none'}]]}
+processed = {'inline_keyboard': [[{'text': 'Processed ✔', 'callback_data': 'none'}]]}
def suggestion_vote(meme_id: int):
diff --git a/persianmeme/models.py b/persianmeme/models.py
index f6a4e1e..010b87c 100755
--- a/persianmeme/models.py
+++ b/persianmeme/models.py
@@ -9,6 +9,7 @@
from . import translations
from functools import cached_property
from LilSholex.exceptions import TooManyRequests
+from LilSholex.celery import celery_app
class MemeType(models.IntegerChoices):
@@ -120,7 +121,7 @@ class Menu(models.IntegerChoices):
status = models.CharField(max_length=1, choices=Status.choices, default=Status.ACTIVE)
rank = models.CharField(max_length=1, choices=Rank.choices, default=Rank.USER)
username = models.CharField(max_length=35, null=True, blank=True)
- temp_meme_name = models.CharField(max_length=50, null=True, verbose_name='Temporary Meme Name', blank=True)
+ temp_meme_name = models.CharField(max_length=80, null=True, verbose_name='Temporary Meme Name', blank=True)
temp_user_id = models.BigIntegerField(null=True, verbose_name='Temporary User ID', blank=True)
temp_meme_tags = models.ManyToManyField(
MemeTag, 'user_voice_tags', blank=True, verbose_name='Temporary Voice Tags'
@@ -199,6 +200,7 @@ class Visibility(models.TextChoices):
reviewed = models.BooleanField('Is Reviewed', default=False)
type = models.PositiveSmallIntegerField('Meme Type', choices=MemeType.choices, default=MemeType.VOICE)
description = models.CharField(max_length=120, blank=True, null=True)
+ task_id = models.CharField(max_length=36, blank=True, null=True)
class Meta:
db_table = 'persianmeme_memes'
@@ -281,7 +283,7 @@ def delete(self, *args, **kwargs):
meme_recovery(self.id),
translations.admin_messages['deleted_by_admins'].format(
translations.admin_messages[self.type_string],
- kwargs.pop("admin") if kwargs.get('admin') else '',
+ kwargs.pop('admin'),
self.file_id
)
)
@@ -291,15 +293,15 @@ def delete(self, *args, **kwargs):
@sync_fix
def delete_vote(self, session: requests.Session = requests.Session()):
- from background_task.models import Task
-
- Task.objects.filter(task_name='persianmeme.tasks.check_meme', task_params=f'[[{self.id}], ''{}]').delete()
+ celery_app.control.revoke(self.task_id)
with session.get(
f'https://api.telegram.org/bot{settings.MEME}/deleteMessage',
params={'chat_id': settings.MEME_CHANNEL, 'message_id': self.message_id},
timeout=settings.REQUESTS_TIMEOUT
- ):
- return
+ ) as response:
+ if response.status_code != 429:
+ return
+ raise TooManyRequests(response.json()['parameters']['retry_after'])
def send_vote(self, session: requests.Session = requests.Session()):
from persianmeme.tasks import check_meme
@@ -310,7 +312,8 @@ def send_vote(self, session: requests.Session = requests.Session()):
suggestion_vote(self.id)
)
self.save()
- check_meme(self.id)
+ self.task_id = check_meme.apply_async((self.id,), countdown=settings.CHECK_MEME_COUNTDOWN)
+ self.save()
class Ad(models.Model):
diff --git a/persianmeme/tasks.py b/persianmeme/tasks.py
index 69fa949..85bb778 100755
--- a/persianmeme/tasks.py
+++ b/persianmeme/tasks.py
@@ -1,11 +1,11 @@
from .models import Meme
-from background_task import background
from zoneinfo import ZoneInfo
from datetime import datetime
-from background_task.models import CompletedTask
+from LilSholex import celery_app
+from django.conf import settings
-@background(schedule=3600)
+@celery_app.task
def revoke_review(meme_id: int):
try:
target_meme = Meme.objects.get(id=meme_id, reviewed=False, status=Meme.Status.ACTIVE)
@@ -15,19 +15,21 @@ def revoke_review(meme_id: int):
target_meme.save()
-@background(schedule=21600)
+@celery_app.task
def check_meme(meme_id: int):
- CompletedTask.objects.all().delete()
try:
meme = Meme.objects.get(id=meme_id, status=Meme.Status.PENDING)
except Meme.DoesNotExist:
return
if datetime.now(ZoneInfo('Asia/Tehran')).hour < 8:
- return check_meme(meme_id)
+ meme.task_id = check_meme.apply_async((meme_id,), countdown=settings.CHECK_MEME_COUNTDOWN)
+ meme.save()
+ return
accept_count = meme.accept_vote.count()
deny_count = meme.deny_vote.count()
if accept_count == deny_count == 0:
- return check_meme(meme_id)
+ meme.task_id = check_meme.apply_async((meme_id,), countdown=settings.CHECK_MEME_COUNTDOWN)
+ meme.save()
else:
meme.delete_vote()
if accept_count >= deny_count:
diff --git a/persianmeme/translations.py b/persianmeme/translations.py
index 12ff484..2001286 100644
--- a/persianmeme/translations.py
+++ b/persianmeme/translations.py
@@ -96,7 +96,8 @@
'unknown_meme': 'Meme was not found !',
'new_delete_request': 'New delete request 🗑',
'report_dismissed': 'Report has been dismissed ✔',
- 'description': 'Description: {0}
\n\n'
+ 'description': 'Description: {0}
\n\n',
+ 'meme_already_processed': 'Meme was already processed ✔'
}
user_messages = {
'back': 'شما به منوی اصلی بازگشتید 🔙',
@@ -105,19 +106,6 @@
'vote_before': 'شما قبلا به این {0} رای داده اید ⚠️\nنتایج هر ۳ دقیقه به روزرسانی می شوند 🔄',
'voted': 'رای شما ثبت شد ✔️',
'donate': '''برای حمایت مالی از ما می توانید از روش های زیر استفاده کنید 👇
-
- PayPing : https://payping.ir/RezFD
-
- IDPay : https://idpay.ir/persianmeme
-
- Bitcoin : `12wL8ggGqNA52JKUGtAP9TrNNxKUw5E7tT`
-
- Ether : `0x15ce953E6dd57b64f4360DE14a2DE00f87d7be06`
-
- Tether : `0x15ce953E6dd57b64f4360DE14a2DE00f87d7be06`
-
- Litecoin: `Lc7rPW4vgbeKwEYQw7gt7kmJ1grY9vWvoR`
-
از حمایت های شما مچکریم 🙏''',
'managing_playlist': 'مدیریت پلی لیست ⚙️',
'manage_meme': 'مدیریت {0} ⚙️',
@@ -152,7 +140,7 @@
'@Persian_Meme_Bot {1}\n\n از این {0} استفاده کنید 😁',
'meme_not_found': 'نتونستم این {0} رو پیدا کنم ☹',
'message_sent': 'پیام شما به مدیریت ارسال شد ✔',
- 'invalid_meme_name': 'نام {0} معتبر نیست ❌\nنام باید متن ساده و حداکثر ۵۰ کارکتر باشد !',
+ 'invalid_meme_name': 'نام {0} معتبر نیست ❌\nنام باید متن ساده و حداکثر ۸۰ کارکتر باشد !',
'meme_added': '{0} شما برای تایید به کانال رای گیری ارسال شد 👇\n🆔 @PersianMemeVoting',
'meme_already_exists': 'این {0} در ربات موجود میباشد ⚠',
'meme_deleted': '{0} شما با موفقیت حذف شد 🗑',
@@ -225,5 +213,5 @@
'search_item_videos_and_voices': 'جستجو در بین ویس ها و ویدئو ها انجام خواهد شد ✔',
'select_search_items': 'جستجو در بین کدام یک از آیتم های زیر انجام شود ؟',
'send_a_video': 'لطفا ویدئو مورد نظر را ارسال کنید.'
- '\n\n🔴 ویدئو باید در تلگرام قابل پخش، حداکثر ۳ دقیقه و کوچک تر از ۱۵ مگابایت باشد ⚠'
+ '\n\n🔴 ویدئو باید در تلگرام قابل پخش، حداکثر ۴ دقیقه و کوچک تر از ۲۰ مگابایت باشد ⚠'
}
diff --git a/requirements.txt b/requirements.txt
index 076927b..0b6563c 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,49 +1,63 @@
aiodns==3.0.0
-aiohttp==3.7.4.post0
+aiohttp==3.8.1
+aiosignal==1.2.0
+amqp==5.0.9
asgiref==3.4.1
asn1crypto==1.4.0
-astroid==2.8.0
-async-timeout==3.0.1
-attrs==21.2.0
-autobahn==21.3.1
+astroid==2.9.3
+async-timeout==4.0.2
+attrs==21.4.0
+autobahn==21.11.1
Automat==20.2.0
+billiard==3.6.4.0
brotlipy==0.7.0
cchardet==2.1.7
-certifi==2021.5.30
-cffi==1.14.6
+celery==5.2.3
+certifi==2021.10.8
+cffi==1.15.0
chardet==4.0.0
-charset-normalizer==2.0.5
+charset-normalizer==2.0.10
+click==8.0.3
+click-didyoumean==0.3.0
+click-plugins==1.1.1
+click-repl==0.2.0
constantly==15.1.0
-cryptography==3.4.8
-Django==3.2.7
-django-background-tasks==1.2.5
-django-compat==1.0.15
+cryptography==36.0.1
+Django==4.0.1
+flower==1.0.0
+frozenlist==1.2.0
gunicorn==20.1.0
+humanize==3.13.1
hyperlink==21.0.0
-idna==2.10
+idna==3.3
incremental==21.3.0
-isort==5.9.3
-lazy-object-proxy==1.6.0
+isort==5.10.1
+kombu==5.2.3
+lazy-object-proxy==1.7.1
mccabe==0.6.1
-multidict==5.1.0
-mysqlclient==2.0.3
+multidict==5.2.0
+mysqlclient==2.1.0
+prometheus-client==0.12.0
+prompt-toolkit==3.0.24
pyasn1==0.4.8
pyasn1-modules==0.2.8
-pycares==4.0.0
-pycparser==2.20
-PyHamcrest==2.0.2
+pycares==4.1.2
+pycparser==2.21
+PyHamcrest==2.0.3
pymemcache==3.5.0
-pyOpenSSL==20.0.1
-pytz==2021.1
-requests==2.25.1
-sentry-sdk==1.3.1
+pyOpenSSL==21.0.0
+pytz==2021.3
+requests==2.27.1
+sentry-sdk==1.5.2
service-identity==21.1.0
six==1.16.0
sqlparse==0.4.2
toml==0.10.2
+tornado==6.1
txaio==21.2.1
-typing-extensions==3.10.0.2
-urllib3==1.26.6
-wrapt==1.12.1
-yarl==1.6.3
+urllib3==1.26.8
+vine==5.0.0
+wcwidth==0.2.5
+wrapt==1.13.3
+yarl==1.7.2
zope.interface==5.4.0
diff --git a/secrets/help_messages.json b/secrets/help_messages.json
new file mode 100644
index 0000000..cef127c
--- /dev/null
+++ b/secrets/help_messages.json
@@ -0,0 +1,26 @@
+{
+ "جستجو با نام": {
+ "animation": "CgACAgQAAxkBAAFZLulgZtrVVZ_JySfT11ISeu8o6YwTHgACIwoAAuZbIFNBhpI1BkXZwR4E",
+ "caption": "راهنمای جستجو ویس با استفاده از نام \uD83D\uDC46"
+ },
+ "جستجو با تگ": {
+ "animation": "CgACAgQAAxkBAAFZLvxgZtsgqYrRFxNphpJ0bCVvTb7ZdgACGwoAAuZbIFPmydlQTWchxR4E",
+ "caption": "راهنمای جستجو ویس با استفاده تگ ها \uD83D\uDC46"
+ },
+ "جستجوی عمومی": {
+ "animation": "CgACAgQAAxkBAAFZLxNgZtttHJ0QNelkWA83A1z3QbgLDwACBQgAAgYtuFK12Zuzheg3Lh4E",
+ "caption": "راهنمای جستجوی عمومی ویس ها \uD83D\uDC46"
+ },
+ "پلی لیست ها": {
+ "animation": "CgACAgQAAxkBAAFZLxxgZtunnHA8-VQEIdDmaNiDFgfCWgACrggAA3HRURHVLtAwpklyHgQ",
+ "caption": "آموزش ساخت و استفاده از پلی لیست ها \uD83D\uDC46"
+ },
+ "افزودن ویس عمومی": {
+ "animation": "CgACAgQAAxkBAROpXGHNKYlqTtOM_neWenIFB-4IlCBjAALJDAACKi-4UKkOJQI3G3bxIwQ",
+ "caption": "آموزش اضافه کردن ویس عمومی به ربات \uD83D\uDC46"
+ },
+ "افزودن ویدئو عمومی": {
+ "animation": "CgACAgQAAxkBAROpUGHNKLZNkH0jemgSzsrLD_FfbCB1AAIDDQACKi-4UJ6ZuNXt5uZeIwQ",
+ "caption": "آموزش اضافه کردن ویدئو عمومی به ربات \uD83D\uDC46"
+ }
+}