BloodyInside commited on
Commit
947c08e
1 Parent(s): 57c9b03
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .dockerignore +8 -0
  2. .expo/README.md +15 -0
  3. .expo/settings.json +8 -0
  4. .gitattributes +3 -0
  5. .gitignore +57 -0
  6. Dockerfile +42 -0
  7. README.md +7 -9
  8. backend/__init__.py +0 -0
  9. backend/__pycache__/__init__.cpython-312.pyc +0 -0
  10. backend/__pycache__/admin.cpython-312.pyc +0 -0
  11. backend/__pycache__/apps.cpython-312.pyc +0 -0
  12. backend/__pycache__/socket_routing.cpython-312.pyc +0 -0
  13. backend/__pycache__/urls.cpython-312.pyc +0 -0
  14. backend/admin.py +3 -0
  15. backend/api/__init__.py +4 -0
  16. backend/api/__pycache__/__init__.cpython-311.pyc +0 -0
  17. backend/api/__pycache__/__init__.cpython-312.pyc +0 -0
  18. backend/api/__pycache__/admin.cpython-311.pyc +0 -0
  19. backend/api/__pycache__/admin.cpython-312.pyc +0 -0
  20. backend/api/__pycache__/borrow_book.cpython-311.pyc +0 -0
  21. backend/api/__pycache__/borrow_book.cpython-312.pyc +0 -0
  22. backend/api/__pycache__/cloudflare_turnstile.cpython-312.pyc +0 -0
  23. backend/api/__pycache__/get_join_library_action.cpython-311.pyc +0 -0
  24. backend/api/__pycache__/get_join_library_action.cpython-312.pyc +0 -0
  25. backend/api/__pycache__/get_media_feedback.cpython-311.pyc +0 -0
  26. backend/api/__pycache__/get_media_feedback.cpython-312.pyc +0 -0
  27. backend/api/__pycache__/join_library.cpython-311.pyc +0 -0
  28. backend/api/__pycache__/join_library.cpython-312.pyc +0 -0
  29. backend/api/__pycache__/queue.cpython-312.pyc +0 -0
  30. backend/api/__pycache__/security.cpython-311.pyc +0 -0
  31. backend/api/__pycache__/security.cpython-312.pyc +0 -0
  32. backend/api/__pycache__/stream_file.cpython-312.pyc +0 -0
  33. backend/api/__pycache__/test.cpython-312.pyc +0 -0
  34. backend/api/__pycache__/text_translator.cpython-312.pyc +0 -0
  35. backend/api/__pycache__/web_scrap.cpython-312.pyc +0 -0
  36. backend/api/cloudflare_turnstile.py +36 -0
  37. backend/api/queue.py +109 -0
  38. backend/api/stream_file.py +53 -0
  39. backend/api/test.py +17 -0
  40. backend/api/web_scrap.py +111 -0
  41. backend/apps.py +6 -0
  42. backend/invoke_worker/__init__.py +11 -0
  43. backend/invoke_worker/__pycache__/__init__.cpython-312.pyc +0 -0
  44. backend/invoke_worker/__pycache__/chapter_queue.cpython-312.pyc +0 -0
  45. backend/invoke_worker/__pycache__/session.cpython-312.pyc +0 -0
  46. backend/invoke_worker/chapter_queue.py +266 -0
  47. backend/invoke_worker/session.py +24 -0
  48. backend/management/commands/__pycache__/clear_all_sessions.cpython-312.pyc +0 -0
  49. backend/management/commands/__pycache__/custom_clearsessions.cpython-312.pyc +0 -0
  50. backend/management/commands/clear_all_sessions.py +11 -0
.dockerignore ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ fly.toml
2
+ .git/
3
+ *.sqlite3
4
+ frontend/node_modules
5
+ frontend/src
6
+ venv
7
+ credentials.txt
8
+
.expo/README.md ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ > Why do I have a folder named ".expo" in my project?
2
+
3
+ The ".expo" folder is created when an Expo project is started using "expo start" command.
4
+
5
+ > What do the files contain?
6
+
7
+ - "devices.json": contains information about devices that have recently opened this project. This is used to populate the "Development sessions" list in your development builds.
8
+ - "packager-info.json": contains port numbers and process PIDs that are used to serve the application to the mobile device/simulator.
9
+ - "settings.json": contains the server configuration that is used to serve the application manifest.
10
+
11
+ > Should I commit the ".expo" folder?
12
+
13
+ No, you should not share the ".expo" folder. It does not contain any information that is relevant for other developers working on the project, it is specific to your machine.
14
+
15
+ Upon project creation, the ".expo" folder is already added to your ".gitignore" file.
.expo/settings.json ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "hostType": "lan",
3
+ "lanType": "ip",
4
+ "dev": true,
5
+ "minify": false,
6
+ "urlRandomness": null,
7
+ "https": false
8
+ }
.gitattributes CHANGED
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ *.ttf filter=lfs diff=lfs merge=lfs -text
37
+ *.ttc filter=lfs diff=lfs merge=lfs -text
38
+ *.png filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ frontend/node_modules
2
+ frontend/static/frontend
3
+ venv/
4
+ frontend/.git
5
+ .env
6
+ .cache
7
+ .git
8
+ *.sqlite3
9
+
10
+ frontend/android/
11
+ log/
12
+ temp/
13
+ media/
14
+ storage/
15
+ backend/module/utils/image_translator/models/
16
+
17
+ backend/module/utils/image_translator/result
18
+ backend/module/utils/image_translator/*.ckpt
19
+ backend/module/utils/image_translator/*.pt
20
+ backend/module/utils/image_translator/.vscode
21
+ backend/module/utils/image_translator/*.onnx
22
+ backend/module/utils/image_translator/__pycache__
23
+ backend/module/utils/image_translator/ocrs
24
+ backend/module/utils/image_translator/Manga
25
+ backend/module/utils/image_translator/Manga-translated
26
+ backend/module/utils/image_translator//models
27
+ backend/module/utils/image_translator/.env
28
+ backend/module/utils/image_translator/*.local
29
+ backend/module/utils/image_translator/*.local.*
30
+ backend/module/utils/image_translator/test/testdata
31
+ backend/module/utils/image_translator/.idea
32
+ backend/module/utils/image_translator/pyvenv.cfg
33
+ backend/module/utils/image_translator/Scripts
34
+ backend/module/utils/image_translator/Lib
35
+ backend/module/utils/image_translator/include
36
+ backend/module/utils/image_translator/share
37
+
38
+ # Distribution / packaging
39
+ backend/module/utils/image_translator/.Python
40
+ backend/module/utils/image_translator/build/
41
+ backend/module/utils/image_translator/develop-eggs/
42
+ backend/module/utils/image_translator/dist/
43
+ backend/module/utils/image_translator/downloads/
44
+ backend/module/utils/image_translator/eggs/
45
+ backend/module/utils/image_translator/.eggs/
46
+ backend/module/utils/image_translator/lib/
47
+ backend/module/utils/image_translator/lib64/
48
+ backend/module/utils/image_translator/parts/
49
+ backend/module/utils/image_translator/sdist/
50
+ backend/module/utils/image_translator/var/
51
+ backend/module/utils/image_translator/wheels/
52
+ backend/module/utils/image_translator/share/python-wheels/
53
+ backend/module/utils/image_translator/*.egg-info/
54
+ backend/module/utils/image_translator/.installed.cfg
55
+ backend/module/utils/image_translator/*.egg
56
+ backend/module/utils/image_translator/MANIFEST
57
+ backend/module/utils/image_translator/.history
Dockerfile ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ARG PYTHON_VERSION=3.12-slim-bullseye
2
+
3
+ FROM python:${PYTHON_VERSION}
4
+
5
+ ENV PYTHONDONTWRITEBYTECODE 1
6
+ ENV PYTHONUNBUFFERED 1
7
+
8
+ # install psycopg2 dependencies.
9
+ RUN apt-get update && apt-get install -y \
10
+ libpq-dev \
11
+ gcc \
12
+ && rm -rf /var/lib/apt/lists/*
13
+
14
+ RUN mkdir -p /code
15
+
16
+ WORKDIR /code
17
+
18
+ COPY requirements.txt /tmp/requirements.txt
19
+ RUN set -ex && \
20
+ pip install --upgrade pip && \
21
+ pip install -r /tmp/requirements.txt && \
22
+ rm -rf /root/.cache/
23
+ COPY . /code
24
+
25
+
26
+ RUN python manage.py makemigrations
27
+ RUN python manage.py migrate --database=default
28
+ RUN python manage.py migrate --database=cache
29
+ RUN python manage.py migrate --database=DB1
30
+ RUN python manage.py migrate --database=DB2
31
+
32
+
33
+
34
+ EXPOSE 8000
35
+
36
+ # CMD ["gunicorn", "--bind", ":8000", "--workers", "1", "--worker-class", "gevent", "core.wsgi:application"]
37
+ # CMD ["daphne", "-u", "/tmp/daphne.sock", "core.asgi:application"]
38
+ CMD ["daphne", "-b", "0.0.0.0", "-p", "7860", "core.asgi:application"]
39
+
40
+ # CMD ["gunicorn", "--bind", ":8000", "--workers", "1", "--worker-class", "uvicorn.workers.UvicornWorker", "core.asgi:application"]
41
+
42
+
README.md CHANGED
@@ -1,10 +1,8 @@
1
- ---
2
- title: ComicMTL
3
- emoji: 🚀
4
- colorFrom: green
5
- colorTo: purple
6
- sdk: docker
7
- pinned: false
8
- ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
1
+ # ComicMTL
2
+ App that translate whole manga into prefer languages. Currently In development.
 
 
 
 
 
 
3
 
4
+
5
+ Project Status: https://github.com/GoodDay360/ComicMTL/projects?query=is%3Aopen
6
+
7
+ ### Utilities that used for this project:
8
+ - [Translator](https://github.com/zyddnys/manga-image-translator)
backend/__init__.py ADDED
File without changes
backend/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (134 Bytes). View file
 
backend/__pycache__/admin.cpython-312.pyc ADDED
Binary file (178 Bytes). View file
 
backend/__pycache__/apps.cpython-312.pyc ADDED
Binary file (442 Bytes). View file
 
backend/__pycache__/socket_routing.cpython-312.pyc ADDED
Binary file (440 Bytes). View file
 
backend/__pycache__/urls.cpython-312.pyc ADDED
Binary file (1.31 kB). View file
 
backend/admin.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ from django.contrib import admin
2
+
3
+ # Register your models here.
backend/api/__init__.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ import backend.invoke_worker
2
+
3
+
4
+
backend/api/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (163 Bytes). View file
 
backend/api/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (183 Bytes). View file
 
backend/api/__pycache__/admin.cpython-311.pyc ADDED
Binary file (2.99 kB). View file
 
backend/api/__pycache__/admin.cpython-312.pyc ADDED
Binary file (13.1 kB). View file
 
backend/api/__pycache__/borrow_book.cpython-311.pyc ADDED
Binary file (2.08 kB). View file
 
backend/api/__pycache__/borrow_book.cpython-312.pyc ADDED
Binary file (4.72 kB). View file
 
backend/api/__pycache__/cloudflare_turnstile.cpython-312.pyc ADDED
Binary file (1.91 kB). View file
 
backend/api/__pycache__/get_join_library_action.cpython-311.pyc ADDED
Binary file (1.52 kB). View file
 
backend/api/__pycache__/get_join_library_action.cpython-312.pyc ADDED
Binary file (1.37 kB). View file
 
backend/api/__pycache__/get_media_feedback.cpython-311.pyc ADDED
Binary file (1.97 kB). View file
 
backend/api/__pycache__/get_media_feedback.cpython-312.pyc ADDED
Binary file (1.79 kB). View file
 
backend/api/__pycache__/join_library.cpython-311.pyc ADDED
Binary file (1.74 kB). View file
 
backend/api/__pycache__/join_library.cpython-312.pyc ADDED
Binary file (2.47 kB). View file
 
backend/api/__pycache__/queue.cpython-312.pyc ADDED
Binary file (6.34 kB). View file
 
backend/api/__pycache__/security.cpython-311.pyc ADDED
Binary file (1.7 kB). View file
 
backend/api/__pycache__/security.cpython-312.pyc ADDED
Binary file (1.51 kB). View file
 
backend/api/__pycache__/stream_file.cpython-312.pyc ADDED
Binary file (3.72 kB). View file
 
backend/api/__pycache__/test.cpython-312.pyc ADDED
Binary file (781 Bytes). View file
 
backend/api/__pycache__/text_translator.cpython-312.pyc ADDED
Binary file (1.28 kB). View file
 
backend/api/__pycache__/web_scrap.cpython-312.pyc ADDED
Binary file (7.43 kB). View file
 
backend/api/cloudflare_turnstile.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import requests, environ, json
3
+ from django.http import JsonResponse, HttpResponseBadRequest
4
+
5
+ from backend.models.model_cache import CloudflareTurnStileCache
6
+
7
+ from django_ratelimit.decorators import ratelimit
8
+ from django.views.decorators.csrf import csrf_exempt
9
+ from ipware import get_client_ip
10
+
11
+ env = environ.Env()
12
+
13
+ @csrf_exempt
14
+ @ratelimit(key='ip', rate='60/m')
15
+ def verify(request):
16
+ if request.method != "POST": return HttpResponseBadRequest('Allowed POST request only!', status=400)
17
+ client_ip, is_routable = get_client_ip(request)
18
+ payload = json.loads(request.body)
19
+ token = payload.get("token")
20
+ form_data = {
21
+ "secret": env("CLOUDFLARE_TURNSTILE_SECRET"),
22
+ "response": token,
23
+ "remoteip": client_ip
24
+ }
25
+ req = requests.post(
26
+ url="https://challenges.cloudflare.com/turnstile/v0/siteverify",
27
+ data=form_data,
28
+ )
29
+ result = req.json()
30
+ status = result.get("success")
31
+ if (status):
32
+
33
+ queryset = CloudflareTurnStileCache.objects.create(token=token)
34
+ queryset.refresh_from_db()
35
+ return JsonResponse(result)
36
+ else: return HttpResponseBadRequest('Cloudflare turnstile token verificaion failed!', status=511)
backend/api/queue.py ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import json, environ, requests, os, subprocess
3
+ import asyncio, uuid
4
+
5
+ from django.http import HttpResponse, JsonResponse, HttpResponseBadRequest
6
+ from django_ratelimit.decorators import ratelimit
7
+ from django.views.decorators.csrf import csrf_exempt
8
+ from asgiref.sync import sync_to_async
9
+
10
+ from backend.module import web_scrap
11
+ from backend.module.utils import manage_image
12
+ from backend.models.model_cache import SocketRequestChapterQueueCache, ComicStorageCache
13
+ from core.settings import BASE_DIR
14
+ from backend.module.utils import cloudflare_turnstile
15
+
16
+
17
+ env = environ.Env()
18
+
19
+
20
+ @csrf_exempt
21
+ @ratelimit(key='ip', rate='60/m')
22
+ def request_chapter(request):
23
+ try:
24
+ if request.method != "POST": return HttpResponseBadRequest('Allowed POST request only!', status=400)
25
+ token = request.META.get('HTTP_X_CLOUDFLARE_TURNSTILE_TOKEN')
26
+ if not cloudflare_turnstile.check(token): return HttpResponseBadRequest('Cloudflare turnstile token not existed or expired!', status=511)
27
+
28
+ payload = json.loads(request.body)
29
+ source = payload.get("source")
30
+ comic_id = payload.get("comic_id")
31
+ chapter_id = payload.get("chapter_id")
32
+ chapter_idx = payload.get("chapter_idx")
33
+ socket_id = payload.get("socket_id")
34
+ channel_name = payload.get("channel_name")
35
+ options = payload.get("options") or {}
36
+
37
+ options["translate"]["target"] = options.get("translate").get("target") if options.get("translate").get("state") else ""
38
+ query_count = ComicStorageCache.objects.filter(
39
+ source=source,
40
+ comic_id=comic_id,
41
+ chapter_id=chapter_id,
42
+ chapter_idx=chapter_idx,
43
+ colorize=options.get("colorize"),
44
+ translate=options.get("translate").get("state"),
45
+ target_lang = options.get("translate").get("target")
46
+ ).count()
47
+
48
+ if query_count: return JsonResponse({"status":"ready"})
49
+ else:
50
+ SocketRequestChapterQueueCache(
51
+ socket_id=socket_id,
52
+ channel_name=channel_name,
53
+ source=source,
54
+ comic_id=comic_id,
55
+ chapter_id=chapter_id,
56
+ chapter_idx=chapter_idx,
57
+ options=options
58
+ ).save()
59
+ return JsonResponse({"status":"queue"})
60
+ except Exception as e:
61
+ print(e)
62
+ return HttpResponseBadRequest('Internal Error.', status=500)
63
+
64
+
65
+ @csrf_exempt
66
+ @ratelimit(key='ip', rate='60/m')
67
+ def request_info(request):
68
+ try:
69
+ if request.method != "POST": return HttpResponseBadRequest('Allowed POST request only!', status=400)
70
+ token = request.META.get('HTTP_X_CLOUDFLARE_TURNSTILE_TOKEN')
71
+ if not cloudflare_turnstile.check(token): return HttpResponseBadRequest('Cloudflare turnstile token not existed or expired!', status=511)
72
+
73
+ payload = json.loads(request.body)
74
+ socket_id = payload.get("socket_id")
75
+ source = payload.get("source")
76
+ comic_id = payload.get("comic_id")
77
+ chapter_requested = payload.get("chapter_requested")
78
+
79
+
80
+ result_request = {}
81
+ for chapter in chapter_requested:
82
+ options = chapter.get("options")
83
+ query_count = SocketRequestChapterQueueCache.objects.filter(socket_id=socket_id, source=source, comic_id=comic_id, chapter_id=chapter.get("chapter_id")).count()
84
+ if query_count: result_request[chapter.get("chapter_id")] = {"state":"queue","chapter_idx":chapter.get("chapter_idx"),"options":options}
85
+ else:
86
+ query_result = ComicStorageCache.objects.filter(
87
+ source=source,
88
+ comic_id=comic_id,
89
+ chapter_id=chapter.get("chapter_id"),
90
+ chapter_idx=chapter.get("chapter_idx"),
91
+ colorize=options.get("colorize"),
92
+ translate=options.get("translate").get("state"),
93
+ target_lang = options.get("translate").get("target") if options.get("translate").get("state") else ""
94
+ ).first()
95
+
96
+ if query_result:
97
+ file_path = query_result.file_path
98
+ if os.path.exists(file_path):
99
+ result_request[chapter.get("chapter_id")] = {"state":"ready","chapter_idx":chapter.get("chapter_idx"),"options":options}
100
+ else:
101
+ ComicStorageCache.objects.filter(id=query_result.id).delete()
102
+ result_request[chapter.get("chapter_id")] = {"state":"unkown","chapter_idx":chapter.get("chapter_idx"),"options":options}
103
+ else:
104
+ result_request[chapter.get("chapter_id")] = {"state":"unkown","chapter_idx":chapter.get("chapter_idx"),"options":options}
105
+ return JsonResponse(result_request)
106
+
107
+ except Exception as e:
108
+ print(e)
109
+ return HttpResponseBadRequest('Internal Error.', status=500)
backend/api/stream_file.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from django.http import StreamingHttpResponse, HttpResponseBadRequest
2
+ from core.settings import BASE_DIR
3
+ from django_ratelimit.decorators import ratelimit
4
+ from django.views.decorators.csrf import csrf_exempt
5
+ from backend.module.utils import cloudflare_turnstile
6
+ from backend.models.model_cache import SocketRequestChapterQueueCache, ComicStorageCache
7
+
8
+ import os, json, sys
9
+
10
+ @csrf_exempt
11
+ @ratelimit(key='ip', rate='60/m')
12
+ def download_chapter(request):
13
+ if request.method != "POST": return HttpResponseBadRequest('Allowed POST request only!', status=400)
14
+ token = request.META.get('HTTP_X_CLOUDFLARE_TURNSTILE_TOKEN')
15
+ if not cloudflare_turnstile.check(token): return HttpResponseBadRequest('Cloudflare turnstile token not existed or expired!', status=511)
16
+ try:
17
+ payload = json.loads(request.body)
18
+ source = payload.get("source")
19
+ comic_id = payload.get("comic_id")
20
+ chapter_id = payload.get("chapter_id")
21
+ chapter_idx = payload.get("chapter_idx")
22
+ options = payload.get("options")
23
+
24
+ query_result = ComicStorageCache.objects.filter(
25
+ source=source,
26
+ comic_id=comic_id,
27
+ chapter_id=chapter_id,
28
+ chapter_idx=chapter_idx,
29
+ colorize=options.get("colorize"),
30
+ translate=options.get("translate").get("state"),
31
+ target_lang = options.get("translate").get("target") if options.get("translate").get("state") else ""
32
+ ).first()
33
+
34
+
35
+ file_path = query_result.file_path
36
+ file_name = os.path.basename(file_path)
37
+ chunk_size = 8192
38
+
39
+ def file_iterator():
40
+ with open(file_path, 'rb') as f:
41
+ while chunk := f.read(chunk_size):
42
+ yield chunk
43
+
44
+ response = StreamingHttpResponse(file_iterator())
45
+ response['Content-Type'] = 'application/octet-stream'
46
+ response['Content-Length'] = os.path.getsize(file_path)
47
+ response['Content-Disposition'] = f'attachment; filename="{file_name}"'
48
+ return response
49
+ except Exception as e:
50
+ exc_type, exc_obj, exc_tb = sys.exc_info()
51
+ line_number = exc_tb.tb_lineno
52
+ print(f"Error on line {line_number}: {e}")
53
+ return HttpResponseBadRequest('Internal Error.', status=500)
backend/api/test.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import json, environ, requests, os
3
+
4
+ from django.core.files.uploadedfile import TemporaryUploadedFile
5
+ from django.http import HttpResponse, JsonResponse, HttpResponseBadRequest
6
+ from django_ratelimit.decorators import ratelimit
7
+
8
+ from core.settings import BASE_DIR
9
+
10
+ from backend.module import web_scrap
11
+
12
+ env = environ.Env()
13
+
14
+
15
+ @ratelimit(key='ip', rate='60/m')
16
+ def run_1(request):
17
+ pass
backend/api/web_scrap.py ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import json, environ, requests, os, subprocess
3
+ import asyncio, uuid
4
+
5
+ from django.http import HttpResponse, JsonResponse, HttpResponseBadRequest
6
+ from django_ratelimit.decorators import ratelimit
7
+ from django.views.decorators.csrf import csrf_exempt
8
+ from asgiref.sync import sync_to_async
9
+
10
+ from backend.module import web_scrap
11
+ from backend.module.utils import manage_image
12
+ from backend.models.model_cache import RequestCache
13
+ from core.settings import BASE_DIR
14
+ from backend.module.utils import cloudflare_turnstile
15
+
16
+
17
+ env = environ.Env()
18
+
19
+
20
+ @csrf_exempt
21
+ @ratelimit(key='ip', rate='60/m')
22
+ def get_list(request):
23
+ if request.method != "POST": return HttpResponseBadRequest('Allowed POST request only!', status=400)
24
+ token = request.META.get('HTTP_X_CLOUDFLARE_TURNSTILE_TOKEN')
25
+ if not cloudflare_turnstile.check(token): return HttpResponseBadRequest('Cloudflare turnstile token not existed or expired!', status=511)
26
+
27
+ payload = json.loads(request.body)
28
+ search = payload.get("search")
29
+ page = payload.get("page")
30
+ source = payload.get("source")
31
+
32
+ if search.get("text"): DATA = web_scrap.source_control[source].search.scrap(search=search,page=page)
33
+ else: DATA = web_scrap.source_control["colamanga"].get_list.scrap(page=page)
34
+
35
+ return JsonResponse({"data":DATA})
36
+
37
+
38
+ @ratelimit(key='ip', rate='60/m')
39
+ def search(request):
40
+ # if request.method != "POST": return HttpResponseBadRequest('Allowed POST request only!', status=400)
41
+ try:
42
+ DATA = web_scrap.source_control["colamanga"].search.scrap(search="妖")
43
+ return JsonResponse({"data":DATA})
44
+ except Exception as e:
45
+ return HttpResponseBadRequest(str(e), status=500)
46
+
47
+
48
+
49
+ @csrf_exempt
50
+ @ratelimit(key='ip', rate='60/m')
51
+ def get(request):
52
+ if request.method != "POST": return HttpResponseBadRequest('Allowed POST request only!', status=400)
53
+ token = request.META.get('HTTP_X_CLOUDFLARE_TURNSTILE_TOKEN')
54
+ if not cloudflare_turnstile.check(token): return HttpResponseBadRequest('Cloudflare turnstile token not existed or expired!', status=511)
55
+
56
+ payload = json.loads(request.body)
57
+ id = payload.get("id")
58
+ source = payload.get("source")
59
+
60
+ try:
61
+ DATA = web_scrap.source_control[source].get.scrap(id=id)
62
+ return JsonResponse({"data":DATA})
63
+ except Exception as e:
64
+
65
+ return HttpResponseBadRequest(str(e), status=500)
66
+
67
+
68
+ @ratelimit(key='ip', rate='60/m')
69
+ def get_cover(request,source,id,cover_id):
70
+ token = request.META.get('HTTP_X_CLOUDFLARE_TURNSTILE_TOKEN')
71
+ if not cloudflare_turnstile.check(token): return HttpResponseBadRequest('Cloudflare turnstile token not existed or expired!', status=511)
72
+
73
+ try:
74
+ DATA = web_scrap.source_control[source].get_cover.scrap(id=id,cover_id=cover_id)
75
+ if not DATA: HttpResponseBadRequest('Image Not found!', status=404)
76
+ response = HttpResponse(DATA, content_type="image/png")
77
+ response['Content-Disposition'] = f'inline; filename="{id}-{cover_id}.png"'
78
+ return response
79
+ except Exception as e:
80
+ return HttpResponseBadRequest(str(e), status=500)
81
+
82
+
83
+ def get_chapter(request):
84
+ try:
85
+ id = "manga-lo816008/1/410.html"
86
+ job = web_scrap.source_control["colamanga"].get_chapter.scrap(id=id,output_dir=os.path.join(BASE_DIR,"media"))
87
+ if job.get("status") == "success":
88
+ chapter_id = id.split("/")[-1].split(".")[0]
89
+ input_dir = os.path.join(BASE_DIR,"media",id.split("/")[0],chapter_id,"original")
90
+ merged_output_dir = os.path.join(BASE_DIR,"media",id.split("/")[0],chapter_id,"merged")
91
+ os.makedirs(merged_output_dir,exist_ok=True)
92
+
93
+ manage_image.merge_images_vertically(input_dir=input_dir,output_dir=merged_output_dir,max_size=10*1024*1024)
94
+
95
+ translated_merged_output_dir = os.path.join(BASE_DIR,"media",id.split("/")[0],chapter_id,"merged_translated")
96
+ os.makedirs(translated_merged_output_dir,exist_ok=True)
97
+
98
+
99
+ subprocess.run(
100
+ ["python", "-m", "manga_translator", "-v", "--manga2eng", "--translator=m2m100_big", "-l", "ENG", "-i", f"{merged_output_dir}", "-o", f"{translated_merged_output_dir}"],
101
+ cwd=os.path.join(BASE_DIR,"backend","module","utils","image_translator"), shell=True, check=True
102
+ )
103
+
104
+ translated_splited_output_dir = os.path.join(BASE_DIR,"media",id.split("/")[0],chapter_id,"translated")
105
+
106
+ os.makedirs(translated_splited_output_dir,exist_ok=True)
107
+ manage_image.split_image_vertically(input_dir=translated_merged_output_dir,output_dir=translated_splited_output_dir)
108
+ except Exception as e:
109
+ print(e)
110
+
111
+ return JsonResponse({})
backend/apps.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class BackendConfig(AppConfig):
5
+ default_auto_field = 'django.db.models.BigAutoField'
6
+ name = 'backend'
backend/invoke_worker/__init__.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from core.settings import DEBUG
2
+ import sys
3
+
4
+ if len(sys.argv) > 1 and not (sys.argv[1] in ['migrate', "makemigrations", "clear_all_sessions"]):
5
+ print("[Worker] Starting Thread...")
6
+ from backend.invoke_worker import (
7
+ session,
8
+ chapter_queue,
9
+ )
10
+ print("[Worker] Thread Started!")
11
+
backend/invoke_worker/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (588 Bytes). View file
 
backend/invoke_worker/__pycache__/chapter_queue.cpython-312.pyc ADDED
Binary file (14.4 kB). View file
 
backend/invoke_worker/__pycache__/session.cpython-312.pyc ADDED
Binary file (1.4 kB). View file
 
backend/invoke_worker/chapter_queue.py ADDED
@@ -0,0 +1,266 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from django_thread import Thread
2
+ from time import sleep
3
+ from backend.module.utils import date_utils
4
+ from django.db import connections
5
+ from backend.models.model_cache import SocketRequestChapterQueueCache, ComicStorageCache
6
+ from core.settings import BASE_DIR
7
+ from backend.module import web_scrap
8
+ from backend.module.utils import manage_image
9
+ from backend.module.utils.directory_info import GetDirectorySize
10
+
11
+ from channels.layers import get_channel_layer
12
+ from asgiref.sync import async_to_sync
13
+
14
+ from django.db.models import Count
15
+
16
+ import requests, environ, os, subprocess, shutil, zipfile, uuid
17
+
18
+ env = environ.Env()
19
+
20
+ # os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"
21
+
22
+ STORAGE_DIR = os.path.join(BASE_DIR,"storage")
23
+ LOG_DIR = os.path.join(BASE_DIR, "log")
24
+ if not os.path.exists(LOG_DIR): os.makedirs(LOG_DIR)
25
+
26
+ MAX_STORAGE_SIZE = 20 * 1024**3
27
+
28
+ class Job(Thread):
29
+ def run(self):
30
+ while True:
31
+ input_dir = ""
32
+ managed_output_dir = ""
33
+ try:
34
+
35
+ query_result = SocketRequestChapterQueueCache.objects.order_by("datetime").first()
36
+ while True:
37
+ if (GetDirectorySize(STORAGE_DIR) >= MAX_STORAGE_SIZE):
38
+ query_result_2 = ComicStorageCache.objects.order_by("datetime").first()
39
+ if (query_result_2):
40
+ file_path = query_result_2.file_path
41
+ if os.path.exists(file_path): os.remove(file_path)
42
+ ComicStorageCache.objects.filter(id=query_result_2.id).delete()
43
+ else: break
44
+
45
+ if (query_result):
46
+ socket_id = query_result.socket_id
47
+ channel_name = query_result.channel_name
48
+ source = query_result.source
49
+ comic_id = query_result.comic_id
50
+ chapter_id = query_result.chapter_id
51
+ chapter_idx = query_result.chapter_idx
52
+ options = query_result.options
53
+ if (options.get("translate").get("state")):
54
+ target_lang = options.get("translate").get("target")
55
+ else: target_lang = ""
56
+
57
+ stored = ComicStorageCache.objects.filter(
58
+ source=source,
59
+ comic_id=comic_id,
60
+ chapter_id=chapter_id,
61
+ chapter_idx=chapter_idx,
62
+ colorize= options.get("colorize"),
63
+ translate= options.get("translate").get("state"),
64
+ target_lang= target_lang,
65
+ ).first()
66
+ if (stored):
67
+ SocketRequestChapterQueueCache.objects.filter(id=query_result.id).delete()
68
+ connections['cache'].close()
69
+ else:
70
+ connections['cache'].close()
71
+
72
+ script = []
73
+
74
+ input_dir = os.path.join(STORAGE_DIR,source,comic_id,str(chapter_idx),"temp")
75
+
76
+ if (options.get("translate").get("state") and options.get("colorize")):
77
+
78
+ managed_output_dir = os.path.join(STORAGE_DIR,source,comic_id,str(chapter_idx),f"{options.get("translate").get("target")}_translated_colorized")
79
+ script = ["python", "-m", "manga_translator", "-v", "--overwrite", "--attempts=3", "--ocr=mocr", "--no-text-lang-skip", "--det-auto-rotate", "--det-gamma-correct", "--colorize=mc2", "--translator=m2m100_big", "-l", f"{options.get("translate").get("target")}", "-i", f"{input_dir}", "-o", f"{managed_output_dir}"]
80
+ elif (options.get("translate").get("state") and not options.get("colorize")):
81
+
82
+ managed_output_dir = os.path.join(STORAGE_DIR,source,comic_id,str(chapter_idx),f"{options.get("translate").get("target")}_translated")
83
+ script = ["python", "-m", "manga_translator", "-v", "--overwrite", "--attempts=3", "--ocr=mocr", "--no-text-lang-skip", "--det-auto-rotate", "--det-gamma-correct", "--translator=m2m100_big", "-l", f"{options.get("translate").get("target")}", "-i", f"{input_dir}", "-o", f"{managed_output_dir}"]
84
+ elif (options.get("colorize") and not options.get("translate").get("state")):
85
+
86
+ managed_output_dir = os.path.join(STORAGE_DIR,source,comic_id,str(chapter_idx),"colorized")
87
+ script = ["python", "-m", "manga_translator", "-v", "--overwrite", "--attempts=3", "--detector=none", "--translator=original", "--colorize=mc2", "--colorization-size=-1", "-i", f"{input_dir}", "-o", f"{managed_output_dir}"]
88
+
89
+ if target_lang == "ENG": script.append("--manga2eng")
90
+
91
+
92
+
93
+ if (options.get("colorize") or options.get("translate").get("state")):
94
+ if os.path.exists(input_dir): shutil.rmtree(input_dir)
95
+ if os.path.exists(managed_output_dir): shutil.rmtree(managed_output_dir)
96
+
97
+ job = web_scrap.source_control[source].get_chapter.scrap(comic_id=comic_id,chapter_id=chapter_id,output_dir=input_dir)
98
+ if job.get("status") == "success":
99
+ with open(os.path.join(LOG_DIR,"image_translator_output.log"), "w") as file:
100
+ result = subprocess.run(
101
+ script,
102
+ cwd=os.path.join(BASE_DIR, "backend", "module", "utils", "image_translator"),
103
+ shell=True,
104
+ check=True,
105
+ stdout=file,
106
+ stderr=file,
107
+ text=True,
108
+ )
109
+ if result.returncode != 0: raise Exception("Image Translator Execution error!")
110
+ os.makedirs(managed_output_dir,exist_ok=True)
111
+ shutil.rmtree(input_dir)
112
+
113
+ with zipfile.ZipFile(managed_output_dir + '.zip', 'w') as zipf:
114
+ for foldername, subfolders, filenames in os.walk(managed_output_dir):
115
+ for filename in filenames:
116
+ if filename.endswith(('.png', '.jpg', '.jpeg')):
117
+ file_path = os.path.join(foldername, filename)
118
+ zipf.write(file_path, os.path.basename(file_path))
119
+ shutil.rmtree(managed_output_dir)
120
+
121
+
122
+ ComicStorageCache(
123
+ source = source,
124
+ comic_id = comic_id,
125
+ chapter_id = chapter_id,
126
+ chapter_idx = chapter_idx,
127
+ file_path = managed_output_dir + ".zip",
128
+ colorize = options.get("colorize"),
129
+ translate = options.get("translate").get("state"),
130
+ target_lang = target_lang,
131
+
132
+ ).save()
133
+
134
+
135
+ query_result_3 = SocketRequestChapterQueueCache.objects.filter(id=query_result.id).first()
136
+ channel_name = query_result_3.channel_name if query_result_3 else ""
137
+ channel_layer = get_channel_layer()
138
+ async_to_sync(channel_layer.send)(channel_name, {
139
+ 'type': 'event_send',
140
+ 'data': {
141
+ "type": "chapter_ready_to_download",
142
+ "data": {
143
+ "source": source,
144
+ "comic_id": comic_id,
145
+ "chapter_id": chapter_id,
146
+ "chapter_idx": chapter_idx
147
+ }
148
+ }
149
+ })
150
+ SocketRequestChapterQueueCache.objects.filter(id=query_result.id).delete()
151
+ else:
152
+ input_dir = os.path.join(STORAGE_DIR,source,comic_id,str(chapter_idx),"original")
153
+ if os.path.exists(input_dir): shutil.rmtree(input_dir)
154
+
155
+ job = web_scrap.source_control["colamanga"].get_chapter.scrap(comic_id=comic_id,chapter_id=chapter_id,output_dir=input_dir)
156
+
157
+ with zipfile.ZipFile(input_dir + '.zip', 'w') as zipf:
158
+ for foldername, subfolders, filenames in os.walk(input_dir):
159
+ for filename in filenames:
160
+ if filename.endswith(('.png', '.jpg', '.jpeg')):
161
+ file_path = os.path.join(foldername, filename)
162
+ zipf.write(file_path, os.path.basename(file_path))
163
+ shutil.rmtree(input_dir)
164
+
165
+ ComicStorageCache(
166
+ source = source,
167
+ comic_id = comic_id,
168
+ chapter_id = chapter_id,
169
+ chapter_idx = chapter_idx,
170
+ file_path = input_dir + '.zip',
171
+ colorize = False,
172
+ translate = False,
173
+ target_lang = "",
174
+
175
+ ).save()
176
+ query_result_3 = SocketRequestChapterQueueCache.objects.filter(id=query_result.id).first()
177
+ channel_name = query_result_3.channel_name if query_result_3 else ""
178
+ channel_layer = get_channel_layer()
179
+ async_to_sync(channel_layer.send)(channel_name, {
180
+ 'type': 'event_send',
181
+ 'data': {
182
+ "type": "chapter_ready_to_download",
183
+ "data": {
184
+ "source": source,
185
+ "comic_id": comic_id,
186
+ "chapter_id": chapter_id,
187
+ "chapter_idx": chapter_idx
188
+ }
189
+ }
190
+ })
191
+ SocketRequestChapterQueueCache.objects.filter(id=query_result.id).delete()
192
+
193
+ connections['cache'].close()
194
+ else:
195
+ connections['cache'].close()
196
+ sleep(5)
197
+ except Exception as e:
198
+ print("[Error] Chapter Queue Socket:", e)
199
+ if (input_dir):
200
+ if os.path.exists(input_dir): shutil.rmtree(input_dir)
201
+ if (managed_output_dir):
202
+ if os.path.exists(managed_output_dir): shutil.rmtree(managed_output_dir)
203
+ query_result_3 = SocketRequestChapterQueueCache.objects.filter(id=query_result.id).first()
204
+ channel_name = query_result_3.channel_name if query_result_3 else ""
205
+ channel_layer = get_channel_layer()
206
+ async_to_sync(channel_layer.send)(channel_name, {
207
+ 'type': 'event_send',
208
+ 'data': {
209
+ "type": "chapter_ready_to_download",
210
+ "data": {"state":"error"}
211
+ }
212
+ })
213
+
214
+ SocketRequestChapterQueueCache.objects.filter(id=query_result.id).delete()
215
+ connections['cache'].close()
216
+ sleep(10)
217
+
218
+ thread = Job()
219
+ thread.daemon = True
220
+ thread.start()
221
+
222
+ class UpdateSocketQueue(Thread):
223
+ def run(self):
224
+ while True:
225
+ try:
226
+ queue = 0
227
+ MAX_QUEUE = SocketRequestChapterQueueCache.objects.count()
228
+
229
+ if (MAX_QUEUE):
230
+ query_result = list(set(SocketRequestChapterQueueCache.objects.order_by("datetime").values_list('socket_id', flat=True).distinct()))
231
+ for socket_id in query_result:
232
+ object = {}
233
+ query_result_2 = SocketRequestChapterQueueCache.objects.filter(socket_id=socket_id).order_by("datetime").values("source","comic_id","chapter_idx")
234
+
235
+ for item in query_result_2:
236
+ source = item.get("source")
237
+ comic_id = item.get("comic_id")
238
+ chapter_idx = item.get("chapter_idx")
239
+
240
+ object[f"{source}-{comic_id}-{chapter_idx}"] = queue
241
+
242
+ queue += 1
243
+
244
+ query_result_3 = SocketRequestChapterQueueCache.objects.filter(socket_id=socket_id).first()
245
+ if (query_result_3):
246
+ channel_layer = get_channel_layer()
247
+ async_to_sync(channel_layer.send)(query_result_3.channel_name, {
248
+ 'type': 'event_send',
249
+ 'data': {
250
+ "type": "chapter_queue_info",
251
+ "chapter_queue": {
252
+ "queue": object,
253
+ "max_queue": MAX_QUEUE,
254
+ }
255
+ }
256
+ })
257
+ else:
258
+ pass
259
+ except Exception as e:
260
+ print(e)
261
+
262
+ connections['cache'].close()
263
+ sleep(10)
264
+ thread = UpdateSocketQueue()
265
+ thread.daemon = True
266
+ thread.start()
backend/invoke_worker/session.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from django_thread import Thread
2
+ from time import sleep
3
+ from backend.module.utils import date_utils
4
+ import requests, environ
5
+ from core import settings
6
+
7
+ env = environ.Env()
8
+
9
+ class Delete(Thread):
10
+ def run(self):
11
+ while True:
12
+ try:
13
+ requests.post(f'{settings.WORKER_SERVED_SCOPE}/worker/session/delete_outdated/', headers={"Worker-Token": env("WORKER_TOKEN")})
14
+ print('[Worker] All expired sessions have been cleared successfully.')
15
+ sleep(1.5 * 60 * 60)
16
+ except Exception as e:
17
+ print(e)
18
+ sleep(10)
19
+
20
+ thread = Delete()
21
+ thread.daemon = True
22
+ thread.start()
23
+
24
+
backend/management/commands/__pycache__/clear_all_sessions.cpython-312.pyc ADDED
Binary file (1.07 kB). View file
 
backend/management/commands/__pycache__/custom_clearsessions.cpython-312.pyc ADDED
Binary file (893 Bytes). View file
 
backend/management/commands/clear_all_sessions.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from django.core.management.base import BaseCommand
2
+ from django.contrib.sessions.models import Session
3
+ from django.db import connections
4
+
5
+ class Command(BaseCommand):
6
+ help = 'Clear all sessions'
7
+
8
+ def handle(self, *args, **options):
9
+ for db in connections.databases:
10
+ Session.objects.using(db).all().delete()
11
+ self.stdout.write('All sessions have been cleared.')