Compare commits

..

5 Commits

Author SHA1 Message Date
Hell13Cat 612332e4ee 0.0.2s 2026-05-26 23:06:02 +03:00
Hell13Cat 270be38d1e 0.0.1s 2026-05-26 22:53:39 +03:00
Hell13Cat 6cdf1cc5eb 7s 2026-05-26 22:18:47 +03:00
Hell13Cat 535ab65fa4 6s 2026-05-26 22:07:03 +03:00
Hell13Cat bdb9d599bd 5s 2026-05-26 21:28:43 +03:00
10 changed files with 265 additions and 18 deletions
+5
View File
@@ -1,4 +1,9 @@
# ---> Python # ---> Python
# Secure data
old/
config_gb.json
migration.py
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files
__pycache__/ __pycache__/
*.py[cod] *.py[cod]
+1 -1
View File
@@ -6,7 +6,7 @@ Version Naming Rules:
All technical versions are designated as snapshots (s) All technical versions are designated as snapshots (s)
All test versions are designated as alpha and beta (a, b) All test versions are designated as beta (b)
All pre-release and release versions are designated as release candidate and release (rc, r) All pre-release and release versions are designated as release candidate and release (rc, r)
+1 -1
View File
@@ -6,7 +6,7 @@
Все технические версии обозначаються как снапшоты (s) Все технические версии обозначаються как снапшоты (s)
Все тестовые версии обозначаються как альфа и бета (a, b) Все тестовые версии обозначаються как бета (b)
Все предрелизны и релизные версии обозначаються как кандидат в релиз и релиз (rc, r) Все предрелизны и релизные версии обозначаються как кандидат в релиз и релиз (rc, r)
+15 -1
View File
@@ -1,3 +1,17 @@
{ {
"port":8084 "server":{
"port":8084,
"count_page":25
},
"gb":{
"id":"",
"hash":"",
"headers":{
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Referer": "https://gelbooru.com/"
}
},
"file":{
"repos":["data/files"]
}
} }
+64
View File
@@ -0,0 +1,64 @@
{
"total_images_analyzed": 14003,
"average_weight": {
"bytes": 2980950.54,
"formatted": "2.84 MB"
},
"storage_stats": {
"folders": {
"img": "38.88 GB",
"json_db": "24.39 MB",
"thumb": "331.45 MB",
"logs": "14.44 MB"
},
"types_in_img": {
"images": "38.56 GB",
"videos": "140.80 MB",
"gifs": "181.30 MB",
"logs": "14.44 MB"
},
"grand_total": "39.24 GB"
},
"ratings_stats": {
"explicit": {
"count": 1605,
"size_formatted": "4.52 GB"
},
"sensitive": {
"count": 6623,
"size_formatted": "19.35 GB"
},
"questionable": {
"count": 1591,
"size_formatted": "4.72 GB"
},
"general": {
"count": 4184,
"size_formatted": "10.29 GB"
}
},
"resolution_stats": {
"largest": {
"filename": "5adc7e0e773417f9c379accf.png",
"md5": "5a63c28906dc7e0e7734c379accf",
"resolution": "9921x14031"
},
"smallest": {
"filename": "8fc614e712ad21d2151ccde8a3c87.gif",
"md5": "8fc614e712ae22151ccde8a3c87",
"resolution": "240x280"
}
},
"weight_stats": {
"largest": {
"filename": "4cf0a32c61c7e7914095d1fe.png",
"md5": "4ab6acf0a32c61c7e79fe",
"size_formatted": "65.21 MB"
},
"smallest": {
"filename": "2e1c8a5870b90c3f538d7.png",
"md5": "28c679ea3470b90c3f538d7",
"size_formatted": "6.49 KB"
}
}
}
-1
View File
@@ -1 +0,0 @@
{"_default": {"1": {"name": "Alex", "age": 25}}}
+82 -4
View File
@@ -1,29 +1,107 @@
from system_module import db, gelbooru, logger from system_module import db, gelbooru, logger
from flask import Flask, render_template from flask import Flask, render_template, jsonify, send_from_directory, render_template_string
import json import json
import requests
from pathlib import Path
import os
CONFIG_ROOT = json.load(open("config.json", "r", encoding="utf-8")) CONFIG_ROOT = json.load(open("config.json", "r", encoding="utf-8"))
CONFIG_PORT = CONFIG_ROOT["port"] CONFIG_ROOT_S = json.load(open("config_gb.json", "r", encoding="utf-8"))
CONFIG_PORT = CONFIG_ROOT["server"]["port"]
PAGINATION_COUNT = CONFIG_ROOT["server"]["count_page"]
GB_ID = CONFIG_ROOT_S["gb"]["id"]
GB_HASH = CONFIG_ROOT_S["gb"]["hash"]
GB_HEADERS = CONFIG_ROOT["gb"]["headers"]
FILE_FOLDERS = CONFIG_ROOT["file"]["repos"]
THUMB_FOLDER = "data/thumb"
gb = gelbooru.GB(GB_ID, GB_HASH, GB_HEADERS)
log = logger.Logger()
database = db.DB()
app = Flask(__name__) app = Flask(__name__)
def find_file(filename, file_folders):
for folder in file_folders:
folder_path = Path(folder)
if not folder_path.exists():
continue
for file_path in folder_path.rglob(filename):
return folder_path
return None
@app.route("/") @app.route("/")
def index(): def index():
return render_template("index.html") return render_template("index.html")
@app.route("/analytics") @app.route("/analytics")
def analytics(): def analytics():
return render_template("analytics.html") return render_template("analytics.html")
@app.route("/post/<path:post_id>") @app.route("/post/<path:post_id>")
def post(post_id): def post(post_id):
return render_template("post.html") return render_template("post.html")
@app.route('/file/<path:filename>')
def serve_image(filename):
folder_path = find_file(filename, FILE_FOLDERS)
return send_from_directory(folder_path, filename)
@app.route('/thumb/<path:filename>')
def serve_thumb(filename):
return send_from_directory(THUMB_FOLDER, filename)
@app.route("/dw_api", methods=['POST'])
def dw_api():
data = request.get_json()
if not data or 'id' not in data:
return jsonify({'status': 'error', 'message': 'URL not get'}), 400
image_id = data["id"]
try:
data_get, url_file, file_name = gb.get_from_id(image_id)
if os.path.exists("dw/"+file_name):
message = f"File '{file_name}' already exists. Skipping."
log.send("warning", "all", message)
return jsonify({'status': 'skipped', 'message': message})
with requests.get(url_file, stream=True, headers=GB_HEADERS, timeout=30) as r:
r.raise_for_status()
content_type = r.headers.get('Content-Type', '')
if 'text/html' in content_type:
log.send("error", "all", "The server returned HTML instead of an image. Bot protection.")
return False
with open("dw/"+file_name, 'wb') as f:
for chunk in r.iter_content(chunk_size=8192):
f.write(chunk)
database.add_raw(data_get)
return jsonify({'status': 'success'})
except requests.exceptions.RequestException as e:
error_message = f"Network error {image_id}: {e}"
log.send("error", "all", error_message)
return jsonify({'status': 'error', 'message': error_message}), 500
except Exception as e:
error_message = f"Unknown error: {e}"
log.send("error", "all", error_message)
return jsonify({'status': 'error', 'message': error_message}), 500
@app.route("/api", methods=["GET", "POST"]) @app.route("/api", methods=["GET", "POST"])
def api(): def api():
data = request.get_json()
if data["type"] == "post":
search_query = data["query"].split(" ")
search_rating = data["rating"].split("+")
results = database.get_search(search_query, search_rating)
if len(results) <= PAGINATION_COUNT:
results_send = {"results_info":{"total":len(results), "page":int(data["page"]), "count_page":PAGINATION_COUNT}, "results":results}
return jsonify(results_send)
else:
results_slile = results[int(data["page"])*PAGINATION_COUNT:(int(data["page"])+1)*PAGINATION_COUNT]
results_send = {"results_info":{"total":len(results), "page":int(data["page"]), "count_page":PAGINATION_COUNT}, "results":results_slile}
return jsonify(results_send)
elif data["type"] == "autocomplete":
tag_slise = data["q"]
result_autocomplete = database.get_autocomplete(tag_slise)
return jsonify(result_autocomplete)
else:
pass
return "", 204 return "", 204
+72 -4
View File
@@ -1,7 +1,75 @@
# Образец для расширения! from tinydb import TinyDB, Query
import os
import json
from collections import Counter
import gelbooru
from tinydb import TinyDB class DB:
def __init__(self):
db_path = "datas"
if not os.path.exists(db_path):
os.makedirs(os.path.dirname(db_path), exist_ok=True)
json.dump({}, open(db_path+"/db.json", "w", encoding="utf-8"))
self.db = TinyDB(db_path+"/db.json")
self.File = Query()
db = TinyDB("db.json") def add(self, data_add):
self.db.insert(data_add)
db.insert({"name": "Alex", "age": 25}) def add_raw(self, data_add):
is_video = False
is_gif = False
if data_add["image"].split(".")[-1] in ["webm", "mp4", "avi"]:
is_video = True
if data_add["image"].split(".")[-1] == "gif":
is_gif = True
add_data = {
"md5": data_add["md5"],
"local_filename": data_add["image"],
"is_video": is_video,
"is_gif": is_gif,
"tags": data_add["tags"].split(" "),
"rating": data_add["rating"],
"score": data_add["score"],
"timestamp": data_add["created_at"],
"display_date": gelbooru.gelbooru_date_parse(data_add["created_at"]),
"source": data_add["source"],
"width": data_add["width"],
"height": data_add["height"]
}
self.db.insert(add_data)
pass
def get_all(self):
return self.db.all()
def get_search(self, ratings, tags):
ratings
results = self.db.search(
self.File.rating.one_of(self.ratings) &
self.File.tags.any(self.tags)
)
return results
def get_unknow(self):
results = self.db.search(self.File.tags == [])
return results
def get_autocomplete(self, tag_slise):
counter = Counter()
for record in self.db.all():
counter.update(record.get("tags", []))
results = [
{
"tag": tag,
"count": count
}
for tag, count in counter.items()
if tag.lower().startswith(prefix)
]
results.sort(key=lambda x: x["count"], reverse=True)
top_10 = results[:10]
return top_10
def edit_data(self, id_file, edit_data):
self.db.update(self.edit_data, self.File.md5 == self.id_file)
+21 -1
View File
@@ -1,12 +1,32 @@
import requests import requests
from datetime import datetime
def gelbooru_date_parse(date_str):
if not self.date_str:
return 0, "Unknown"
try:
dt = datetime.strptime(date_str, "%a %b %d %H:%M:%S %z %Y")
return dt.timestamp(), dt.strftime("%Y-%m-%d")
except Exception as e:
print(f"Ошибка парсинга даты '{date_str}': {e}")
return 0, "Unknown"
class GB: class GB:
def __init__(self, api_id, api_hash): def __init__(self, api_id, api_hash, headers):
self.api_id = api_id self.api_id = api_id
self.api_hash = api_hash self.api_hash = api_hash
self.root_url = "https://gelbooru.com/index.php" self.root_url = "https://gelbooru.com/index.php"
self.HEADERS = headers
def get_from_id(self, id_post): def get_from_id(self, id_post):
params = {"page": "dapi", "s": "post", "q": "index", "json": 1, "id": id_post, "api_key":self.api_id, "user_id":self.api_hash}
response = requests.get(self.root_url, params=params, headers=self.HEADERS, timeout=15)
url_file = response.json()["post"][0]["file_url"]
file_name = response.json()["post"][0]["image"]
if os.path.exists("dw/"+file_name):
message = f"Файл '{file_name}' уже существует. Пропускаем."
print(message)
return response.json()["post"][0], url_file, file_name
pass pass
def get_from_md5(self, md5): def get_from_md5(self, md5):
+2 -3
View File
@@ -1,13 +1,12 @@
import logging import logging
class Logger: class Logger:
def __init__(self, name): def __init__(self):
self.name = name
self.logger_file = logging.getLogger(__name__) self.logger_file = logging.getLogger(__name__)
self.logger_cli = logging.getLogger(__name__) self.logger_cli = logging.getLogger(__name__)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
file_handler = logging.FileHandler("logs/"+self.name+".log", encoding="utf-8") file_handler = logging.FileHandler("logs.log", encoding="utf-8")
file_handler.setFormatter(formatter) file_handler.setFormatter(formatter)
console_handler = logging.StreamHandler() console_handler = logging.StreamHandler()