Compare commits

...

1 Commits

Author SHA1 Message Date
katboi01 f2909d2992 removed katworks api files
added postgresql database
2025-07-17 02:37:27 +02:00
32 changed files with 99 additions and 1900 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
__pycache__/ __pycache__/
data/ data/
test.py test.py
database_config.txt

14
app.py
View File

@ -1,10 +1,9 @@
from flask import Flask from flask import Flask
from waitress import serve
from flask_restful import Api from flask_restful import Api
from modules.KF3.database import Database as KF3DB
from modules.Kingdom.database import Database as KFKDB
from modules.Archive.database import Database as KFADB from modules.Archive.database import Database as KFADB
from modules.proxy import AgreementProxy, LaunchProxy, UpdateProxy import logging
app = Flask(__name__) app = Flask(__name__)
app.config['JSON_AS_ASCII'] = False app.config['JSON_AS_ASCII'] = False
@ -13,11 +12,8 @@ app.databases = {}
api = Api(app) api = Api(app)
KFADB(api) KFADB(api)
api.add_resource(LaunchProxy, "/DMM/launch")
api.add_resource(UpdateProxy, "/DMM/filelist", "/DMM/update")
api.add_resource(AgreementProxy, "/DMM/agreement")
KF3DB(api)
KFKDB(api)
if __name__ == '__main__': if __name__ == '__main__':
app.run(host='127.0.0.1', port=8080, debug=True) #app.run(host='127.0.0.1', port=8081, debug=True)
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(message)s')
serve(app, listen='*:8081', threads=1)

View File

@ -1,6 +1,7 @@
import json import json
from flask import Flask from flask import Flask
from modules.Archive.endpoints.tag_stats import TagStats
from .databaseController import DatabaseController from .databaseController import DatabaseController
from .endpoints.query import Query from .endpoints.query import Query
@ -23,108 +24,93 @@ class Database:
if "Archive" in self.app.databases: if "Archive" in self.app.databases:
del self.app.databases["Archive"] del self.app.databases["Archive"]
self.reload_data("/home/pi/python/Katbots/JapariArchive/database.db") self.reload_data()
self.app.databases["Archive"] = self self.app.databases["Archive"] = self
api.add_resource(Query, "/Archive/Query") api.add_resource(Query, "/Query")
api.add_resource(NewQuery, "/Archive/NewQuery") api.add_resource(NewQuery, "/NewQuery")
api.add_resource(Command, "/Archive/Command") api.add_resource(Command, "/Command")
api.add_resource(Commands, "/Archive/Commands") api.add_resource(Commands, "/Commands")
api.add_resource(GetPost, "/Archive/GetPost/<id>") api.add_resource(GetPost, "/GetPost/<id>")
api.add_resource(GetPosts, "/Archive/GetPosts") api.add_resource(GetPosts, "/GetPosts")
api.add_resource(GetPostsCount, "/Archive/GetPosts/Count") api.add_resource(GetPostsCount, "/GetPosts/Count")
api.add_resource(AccountStats, "/Archive/AccountStats") api.add_resource(AccountStats, "/AccountStats")
api.add_resource(SetAction, "/Archive/SetAction") api.add_resource(TagStats, "/TagStats")
api.add_resource(SetAction, "/SetAction")
def get_accounts(self): def get_accounts(self):
query = f''' query = f'''
SELECT x_handle, id FROM accounts SELECT x_handle, id FROM x_accounts
ORDER BY x_handle ASC''' ORDER BY x_handle ASC'''
return self.db.run_query(query) return self.db.run_query(query)
def get_account_stats(self): def get_account_stats(self):
query = '''SELECT x_handle, query = 'SELECT * FROM "Artist Stats"'
SUM(IIF(x_posts.action_taken = 0, 1, 0)) as undecided,
SUM(IIF(x_posts.action_taken = 1, 1, 0)) as approved,
SUM(IIF(x_posts.action_taken = 2, 1, 0)) as deleted,
SUM(IIF(x_posts.action_taken = 3, 1, 0)) as hidden,
SUM(IIF(x_post_details.id is NULL, 1, 0)) as invalid
FROM accounts
LEFT JOIN x_posts on accounts.id = x_posts.account_id
LEFT JOIN x_post_details on x_post_details.id = x_posts.id
WHERE accounts.download_mode != 4
GROUP BY account_id
ORDER BY x_handle'''
return self.db.run_query(query) return self.db.run_query(query)
def get_tag_stats(self):
query = 'SELECT * FROM "Tag Stats"'
return self.db.run_query(query)
def get_post(self, id): def get_post(self, id):
query = f'''SELECT x_posts.id, cast(x_posts.id as TEXT) as id_str, x_posts.error_id, x_posts.action_taken, x_posts.is_saved, x_post_details.text, x_post_details.files, x_post_details.date, accounts.x_handle, accounts.rating from x_posts query = f'''SELECT cast(x_posts.id as TEXT), x_posts.*, x_accounts.x_handle, x_accounts.rating from x_posts
LEFT JOIN x_post_details LEFT JOIN x_accounts
ON x_posts.id = x_post_details.id ON x_posts.account_id = x_accounts.id WHERE x_posts.id = {id}
LEFT JOIN accounts
ON x_posts.account_id = accounts.id WHERE x_posts.id = {id}
LIMIT 1''' LIMIT 1'''
result = self.db.run_query(query) return self.db.run_query(query)
if len(result) == 0:
return None
else:
#return most recent post
return result[-1]
def build_where_query(self, artist, actions_taken = [0, 1, 2, 3], last_id = -1, include_ratings = ["SFW", "NSFW"]): def build_where_query(self, artist, actions_taken = [0, 1, 2, 3], last_id = -1, include_ratings = ["SFW", "NSFW"], tags = []):
where_query = "WHERE x_post_details.id NOT NULL AND x_posts.error_id = 0" where_query = "WHERE x_posts.error_id = 0"
if last_id != -1: if last_id != -1:
where_query += f" AND x_posts.id < {last_id}" where_query += f" AND x_posts.id < {last_id}"
if actions_taken != [0, 1, 2, 3]: if actions_taken != [0, 1, 2, 3]:
where_query += " AND (" + " OR ".join([f"x_posts.action_taken = {action}" for action in actions_taken]) + ")" where_query += " AND (" + " OR ".join([f"x_posts.action_taken = {action}" for action in actions_taken]) + ")"
if include_ratings != ["SFW", "NSFW", "NSFL"]: if include_ratings != ["SFW", "NSFW", "NSFL"]:
where_query += " AND (" + " OR ".join([f'accounts.rating = "{rating}"' for rating in include_ratings]) + ")" where_query += " AND (" + " OR ".join([f'x_accounts.rating = \'{rating}\'' for rating in include_ratings]) + ")"
if artist is not None and artist != "": if artist is not None and artist != "":
where_query += f' AND accounts.x_handle = "{artist}"' where_query += f' AND x_accounts.x_handle = \'{artist}\''
if len(tags) > 0:
tags = ", ".join(["'" + tag + "'" for tag in tags])
where_query += f' AND tags @> ARRAY[{tags}]'
return where_query return where_query
def get_posts_count(self, artist, actions_taken = [0, 1, 2, 3], last_id = -1, include_ratings = ["SFW", "NSFW"]): def get_posts_count(self, artist, actions_taken = [0, 1, 2, 3], last_id = -1, include_ratings = ["SFW", "NSFW"], tags = []):
where_query = self.build_where_query(artist, actions_taken, last_id, include_ratings) where_query = self.build_where_query(artist, actions_taken, last_id, include_ratings, tags)
query = f''' query = f'''
SELECT count(*) as count FROM x_posts SELECT count(*) as count FROM x_posts
LEFT JOIN x_post_details LEFT JOIN x_accounts
ON x_posts.id = x_post_details.id ON x_posts.account_id = x_accounts.id
LEFT JOIN accounts
ON x_posts.account_id = accounts.id
{where_query}''' {where_query}'''
result = self.db.run_query(query) _, result = self.db.run_query(query)
return result[0]["count"] return result[0]["count"]
def get_posts(self, num_posts, artist, actions_taken = [0, 1, 2, 3], last_id = -1, offset = 0, include_ratings = ["SFW", "NSFW"], order = "DESC"): def get_posts(self, num_posts, artist, actions_taken = [0, 1, 2, 3], last_id = -1, offset = 0, include_ratings = ["SFW", "NSFW"], tags = [], order = "DESC"):
num_posts = max(1, min(num_posts, 100)) num_posts = max(1, min(num_posts, 100))
order_by = "RANDOM()" if order == "RAND" else "x_posts.id ASC" if order == "ASC" else "x_posts.id DESC" order_by = "RANDOM()" if order == "RAND" else "x_posts.id ASC" if order == "ASC" else "x_posts.id DESC"
where_query = self.build_where_query(artist, actions_taken, last_id, include_ratings) where_query = self.build_where_query(artist, actions_taken, last_id, include_ratings, tags)
query = f''' query = f'''
SELECT x_posts.id, cast(x_posts.id as TEXT) as id_str, x_posts.action_taken, x_posts.is_saved, x_post_details.text, x_post_details.files, x_post_details.date, accounts.x_handle, accounts.rating FROM x_posts SELECT x_posts.id, cast(x_posts.id as TEXT) as id_str, action_taken, saved_files != \'{{}}\' as is_saved, text, files, date, x_handle, x_accounts.rating FROM x_posts
LEFT JOIN x_post_details LEFT JOIN x_accounts
ON x_posts.id = x_post_details.id ON x_posts.account_id = x_accounts.id
LEFT JOIN accounts
ON x_posts.account_id = accounts.id
{where_query} {where_query}
ORDER BY {order_by} ORDER BY {order_by}
LIMIT {num_posts} OFFSET {offset}''' LIMIT {num_posts} OFFSET {offset}'''
return self.db.run_query(query) return self.db.run_query(query)
def wrap_query_response(self, result, mode = "json"): def wrap_query_response(self, result, mode = "json", error : str = ""):
if result is None: if result is None:
response = self.app.response_class(status=400) response = self.app.response_class(response=error, status=400)
else: else:
if mode == "json": if mode == "json":
response = self.app.response_class( response = self.app.response_class(
response=json.dumps(result, ensure_ascii=False, indent=1), response=json.dumps(result, ensure_ascii=False, indent=1, default=str),
status=200, status=200,
mimetype='application/json' mimetype='application/json'
) )
@ -138,5 +124,5 @@ ORDER BY x_handle'''
response.headers.add("Access-Control-Allow-Origin", "*") response.headers.add("Access-Control-Allow-Origin", "*")
return response return response
def reload_data(self, database_path): def reload_data(self):
self.db = DatabaseController(database_path) self.db = DatabaseController()

View File

@ -1,23 +1,29 @@
import os import os
import sqlite3 import psycopg
from psycopg.rows import dict_row
TABLE_ACCOUNTS = "accounts" TABLE_ACCOUNTS = "x_accounts"
TABLE_X = "x_posts" TABLE_X = "x_posts"
class DatabaseController: class DatabaseController:
def __init__(self, db_name): def __init__(self):
self.conn = sqlite3.connect(db_name) if not os.path.exists("database_config.txt"):
self.conn.row_factory = sqlite3.Row raise Exception(f"Create 'database_config.txt' in {os.path.abspath(os.curdir)} with database connection string, example: 'dbname=japariarchive user=postgres password=password123'")
with open("database_config.txt", "rt") as file:
connection_string = file.read()
self.conn = psycopg.connect(connection_string, row_factory=dict_row)
self.cursor = self.conn.cursor() self.cursor = self.conn.cursor()
def run_query(self, query): def run_query(self, query):
try: try:
self.cursor.execute(query) self.cursor.execute(query)
results = [dict(result) for result in self.cursor.fetchall()] results = self.cursor.fetchall()
return results self.conn.rollback()
return True, results
except Exception as e: except Exception as e:
print(e) print(query, e)
return [] self.conn.rollback()
return False, str(e)
def run_command(self, command): def run_command(self, command):
try: try:

View File

@ -9,5 +9,5 @@ if TYPE_CHECKING:
class AccountStats(Resource): class AccountStats(Resource):
def get(self): def get(self):
db : Database = app.databases["Archive"] db : Database = app.databases["Archive"]
result = db.get_account_stats() _, result = db.get_account_stats()
return db.wrap_query_response(result) return db.wrap_query_response(result)

View File

@ -12,6 +12,8 @@ class NewQuery(Resource):
db : Database = app.databases["Archive"] db : Database = app.databases["Archive"]
result = db.db.run_query(query) status, result = db.db.run_query(query)
if status:
return db.wrap_query_response(result) return db.wrap_query_response(result)
else:
return db.wrap_query_response(None, error = result)

View File

@ -11,5 +11,5 @@ class GetPost(Resource):
id = int(id) id = int(id)
db : Database = app.databases["Archive"] db : Database = app.databases["Archive"]
result = db.get_post(id) _, result = db.get_post(id)
return db.wrap_query_response(result) return db.wrap_query_response(result)

View File

@ -14,6 +14,7 @@ class GetPosts(Resource):
page = int(request.args["page"]) if "page" in request.args else 1 page = int(request.args["page"]) if "page" in request.args else 1
artist = request.args["artist"] if "artist" in request.args else None artist = request.args["artist"] if "artist" in request.args else None
last_id = int(request.args["last_id"]) if "last_id" in request.args else -1 last_id = int(request.args["last_id"]) if "last_id" in request.args else -1
tags = request.args["tags"].split() if "tags" in request.args else []
except: except:
response = app.response_class(status=400) response = app.response_class(status=400)
response.headers.add("Access-Control-Allow-Origin", "*") response.headers.add("Access-Control-Allow-Origin", "*")
@ -51,5 +52,8 @@ class GetPosts(Resource):
real_page = page-1 real_page = page-1
offset = real_page * count offset = real_page * count
result = db.get_posts(count, artist, actions_taken=actions, last_id= -1, offset= offset, include_ratings= ratings, order=order) status, result = db.get_posts(count, artist, actions_taken=actions, last_id= -1, offset= offset, include_ratings= ratings, order=order, tags=tags)
return db.wrap_query_response(result) if status:
return db.wrap_query_response(result)
else:
return db.wrap_query_response(None, error=result)

View File

@ -11,6 +11,7 @@ class GetPostsCount(Resource):
db : Database = app.databases["Archive"] db : Database = app.databases["Archive"]
try: try:
artist = request.args["artist"] if "artist" in request.args else None artist = request.args["artist"] if "artist" in request.args else None
tags = request.args["tags"].split() if "tags" in request.args else []
except: except:
response = app.response_class(status=400) response = app.response_class(status=400)
response.headers.add("Access-Control-Allow-Origin", "*") response.headers.add("Access-Control-Allow-Origin", "*")
@ -38,5 +39,5 @@ class GetPostsCount(Resource):
if ratings == []: if ratings == []:
ratings = ["SFW", "NSFW"] ratings = ["SFW", "NSFW"]
result = db.get_posts_count(artist, actions_taken=actions, last_id= -1, include_ratings= ratings) result = db.get_posts_count(artist, actions_taken=actions, last_id= -1, include_ratings= ratings, tags=tags)
return db.wrap_query_response(result, mode="text") return db.wrap_query_response(result, mode="text")

View File

@ -12,7 +12,9 @@ class Query(Resource):
db : Database = app.databases["Archive"] db : Database = app.databases["Archive"]
result = db.db.run_query(query) status, result = db.db.run_query(query)
result = [list(d.values()) for d in result] if status:
result = [list(d.values()) for d in result]
return db.wrap_query_response(result) return db.wrap_query_response(result)
else:
return db.wrap_query_response(None, error = result)

View File

@ -0,0 +1,13 @@
from __future__ import annotations
from flask_restful import Resource
from flask import current_app as app
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from modules.Archive.database import Database
class TagStats(Resource):
def get(self):
db : Database = app.databases["Archive"]
_, result = db.get_tag_stats()
return db.wrap_query_response(result)

View File

@ -1,562 +0,0 @@
import gzip
import json
import platform
import UnityPy
from datetime import datetime
from .statCalculation import get_all_stats, fill_miracle_numbers
from .endpoints.friend import KF3_Friend
from .endpoints.friends import KF3_Friends
from .endpoints.item import KF3_Item
from .endpoints.items import KF3_Items
from .endpoints.update import KF3_Update
class Database:
def __init__(self, api) -> None:
app = api.app
if "KF3" in app.databases:
del app.databases["KF3"]
self.reload_data()
app.databases["KF3"] = self
api.add_resource(KF3_Friend, "/KF3/Friend/<int:id>")
api.add_resource(KF3_Friends, "/KF3/Friends")
api.add_resource(KF3_Item, "/KF3/Item/<int:id>")
api.add_resource(KF3_Items, "/KF3/Items")
api.add_resource(KF3_Update, "/KF3/Update")
def unload_data(self):
self.charaData = {}
self.itemCommon = {}
self.promoteData = {}
self.charaClothesData = {}
self.promotePresetData = {}
self.limitlevel_rising_status = {}
self.paramArts = {}
self.paramAbilities = {}
self.paramAbilities1 = {}
self.paramAbilities2 = {}
self.paramAlphaBases = {}
self.paramWaitActions = {}
self.paramSpecialAttacks = {}
self.processed_friends = {}
def reload_data(self):
self.unload_data()
if platform.system() == "Windows":
path = "H:\\Apache\\Katworks\\KF\\assets\\KF3\\cache\\"
else:
path = "/var/www/html/Katworks/KF/assets/KF3/develop01/cache/"
with gzip.open(path + "CHARA_DATA.d", mode="rt", encoding="utf-8") as file:
for entry in json.loads(file.read()):
self.charaData[entry["id"]] = entry
with gzip.open(path + "ITEM_COMMON.d", mode="rt", encoding="utf-8") as file:
for entry in json.loads(file.read()):
self.itemCommon[entry["id"]] = entry
with gzip.open(path + "CHARA_PROMOTE_DATA.d", mode="rt", encoding="utf-8") as file:
for entry in json.loads(file.read()):
self.promoteData[entry["promoteId"]] = entry
with gzip.open(path + "CHARA_PROMOTE_PRESET_DATA.d", mode="rt", encoding="utf-8") as file:
for entry in json.loads(file.read()):
if entry["promotePresetId"] not in self.promotePresetData:
self.promotePresetData[entry["promotePresetId"]] = []
self.promotePresetData[entry["promotePresetId"]].append(entry)
with gzip.open(path + "CHARA_CLOTHES_DATA.d", mode="rt", encoding="utf-8") as file:
for entry in json.loads(file.read()):
if entry["clothesPresetId"] not in self.charaClothesData:
self.charaClothesData[entry["clothesPresetId"]] = []
self.charaClothesData[entry["clothesPresetId"]].append(entry)
with gzip.open(path + "LIMITLEVEL_RISING_STATUS.d", mode="rt", encoding="utf-8") as file:
for entry in json.loads(file.read()):
self.limitlevel_rising_status[entry["patternId"]] = entry
self.parse_parameter()
self.process_friends()
print("Data reload finished")
self.update_in_progress = False
def parse_parameter(self):
if platform.system() == "Windows":
path = "H:\\Apache\\Katworks\\KF\\assets\\KF3\\assets\\"
else:
path = "/var/www/html/Katworks/KF/assets/KF3/develop01/assets/Windows/"
paramAsset = UnityPy.load(path + "parameter.asset")
for obj in paramAsset.objects:
data = obj.read()
if data.name.split('_')[0] == "ParamAbility":
id = int(data.name.split('_')[1])
if data.name.endswith("_1"):
self.paramAbilities1[id] = obj.read_typetree()
elif data.name.endswith("_2"):
self.paramAbilities2[id] = obj.read_typetree()
else:
self.paramAbilities[id] = obj.read_typetree()
elif data.name.split('_')[0] == "ParamAlphaBase":
id = int(data.name.split('_')[1])
self.paramAlphaBases[id] = obj.read_typetree()
elif data.name.split('_')[0] == "ParamArts":
id = int(data.name.split('_')[1])
self.paramArts[id] = obj.read_typetree()
elif data.name.split('_')[0] == "ParamSpecialAttack":
id = int(data.name.split('_')[1])
self.paramSpecialAttacks[id] = obj.read_typetree()
elif data.name.split('_')[0] == "ParamWaitAction":
id = int(data.name.split('_')[1])
self.paramWaitActions[id] = obj.read_typetree()
def process_friends(self):
for charaID in self.charaData:
self.processed_friends[charaID] = self.process_chara(charaID)
def process_chara(self, id : int):
chara = {}
charaData = self.charaData[id]
alphaBase = self.paramAlphaBases[id] if id in self.paramAlphaBases else None
wrLocked = False
promoIds = []
promotePresetId = charaData["promotePresetId"]
promos = self.promotePresetData[promotePresetId] if promotePresetId in self.promotePresetData else []
for promo in promos:
if promo["promoteStepDatetime"] == 1917860400000:
wrLocked = True
continue
else:
promoIds.append([promo["promoteId00"], promo["promoteId01"], promo["promoteId02"], promo["promoteId03"], promo["promoteId04"], promo["promoteId05"]])
emptyPromote = {"promoteAtk": 0, "promoteDef": 0, "promoteHp": 0, "promoteAvoid": 0, "promoteActionDamageRatio": 0, "promoteBeatDamageRatio": 0, "promoteTryDamageRatio": 0}
promoteDatas = [[(self.promoteData[id] if id in self.promoteData else emptyPromote) for id in promo] for promo in promoIds]
promote_bonus = {"atk" : 0, "def" : 0, "hp" : 0, "evd" : 0, "beat" : 0, "act" : 0, "try" : 0}
for promoTier in promoteDatas:
for promoteStep in promoTier:
promote_bonus["atk"] += promoteStep["promoteAtk"]
promote_bonus["def"] += promoteStep["promoteDef"]
promote_bonus["hp"] += promoteStep["promoteHp"]
promote_bonus["evd"] += promoteStep["promoteAvoid"]
promote_bonus["act"] += promoteStep["promoteActionDamageRatio"]
promote_bonus["beat"] += promoteStep["promoteBeatDamageRatio"]
promote_bonus["try"] += promoteStep["promoteTryDamageRatio"]
clothesPresetId = charaData["clothesPresetId"]
clothesDatas = self.charaClothesData[clothesPresetId] if clothesPresetId in self.charaClothesData else []
costume_bonus = {"atk" : 0, "def" : 0, "hp" : 0, "evd" : 0, "beat" : 0, "act" : 0, "try" : 0}
for clothesData in clothesDatas:
costume_bonus["atk"] += clothesData["atkBonus"]
costume_bonus["def"] += clothesData["defBonus"]
costume_bonus["hp"] += clothesData["hpBonus"]
chara["id"] = id
chara["promoteBonus"] = promote_bonus
chara["costumeBonus"] = costume_bonus
chara["attribute"] = charaData["attribute"]
chara["name"] = charaData["name"]
chara["nameEn"] = charaData["nameEn"]
chara["nickname"] = charaData["nickname"]
chara["rankLow"] = charaData["rankLow"]
chara["rankHigh"] = charaData["rankHigh"]
chara["castName"] = charaData["castName"]
chara["loginText"] = charaData["loginText"]
chara["flavorText"] = charaData["flavorText"]
chara["kizunaupText"] = charaData["kizunaupText"]
chara["greetingText"] = charaData["greetingText"]
chara["startTimeRaw"] = charaData["startTime"]
chara["startTime"] = datetime.fromtimestamp(charaData["startTime"]/1000.0).strftime('%A, %B %d, %Y')
chara["wr"] = 4 if wrLocked else 5
chara["is_wr5"] = chara["wr"] == 5
chara["has_party_dress"] = False
for clothesData in clothesDatas:
if clothesData["clothesId"] == 8:
chara["has_party_dress"] = True
break
chara["has_rainbow_trait"] = id in self.paramAbilities2
chara["risingStatusPatternId"] = patternId = charaData["risingStatusPatternId"]
if patternId not in self.limitlevel_rising_status:
level_curve = None
else:
level_curve = self.limitlevel_rising_status[patternId] if patternId != 0 else None
if alphaBase is None:
chara["stats_min"] = {"level" : 0,"status" : 0,"wr" : 0,"hp" : 0,"atk" : 0,"def" : 0,"evd" : 0,"beat" : 0,"act" : 0,"try" : 0}
chara["stats_max"] = {"level" : 0,"status" : 0,"wr" : 0,"hp" : 0,"atk" : 0,"def" : 0,"evd" : 0,"beat" : 0,"act" : 0,"try" : 0}
chara["plasmPoint"] = 0
chara["cards"] = {0,0,0,0,0}
else:
chara["stats_min"] = get_all_stats(chara, alphaBase, max_level = False, rising_status_pattern=level_curve)
chara["stats_max"] = get_all_stats(chara, alphaBase, max_level = True, rising_status_pattern=level_curve)
chara["plasmPoint"] = alphaBase["plasmPoint"]
chara["cards"] = [
{"type":alphaBase["orderCardType00"], "value":alphaBase["orderCardValue00"]},
{"type":alphaBase["orderCardType01"], "value":alphaBase["orderCardValue01"]},
{"type":alphaBase["orderCardType02"], "value":alphaBase["orderCardValue02"]},
{"type":alphaBase["orderCardType03"], "value":alphaBase["orderCardValue03"]},
{"type":alphaBase["orderCardType04"], "value":alphaBase["orderCardValue04"]}
]
chara["synergy_flag"] = self.paramArts[id]["authParam"]["SynergyFlag"] if id in self.paramArts else 0
chara["arts"] = self.paramArts[id] if id in self.paramArts else None
chara["ability"] = self.paramAbilities[id] if id in self.paramAbilities else None
chara["ability1"] = self.paramAbilities1[id] if id in self.paramAbilities1 else None
chara["ability2"] = self.paramAbilities2[id] if id in self.paramAbilities2 else None
chara["wait_action"] = self.paramWaitActions[id] if id in self.paramWaitActions else None
chara["special_attack"] = self.paramSpecialAttacks[id] if id in self.paramSpecialAttacks else None
chara["costumes"] = self.get_costumes(id, chara["rankLow"])
return chara
def get_item(self, id : int):
if id in self.itemCommon:
return self.itemCommon[id]
else:
return None
def get_chara(self, id : int):
if id in self.processed_friends:
return self.processed_friends[id]
else:
return None
def get_chara_wiki(self, id : int):
chara = self.get_chara(id)
if chara is None:
return None
lines = []
stars_word = chara["rankLow"]
stars_word = "Two" if stars_word == 2 else "Three" if stars_word == 3 else "Four" if stars_word == 4 else "Five" if stars_word == 5 else "Unknown"
attribute_word = chara["attribute"]
attribute_word = "Funny" if attribute_word == 1 else "Friendly" if attribute_word == 2 else "Relaxed" if attribute_word == 3 else "Lovely" if attribute_word == 4 else "Active" if attribute_word == 5 else "Carefree" if attribute_word == 6 else "Unknown"
categories = [
f"[[Category:{attribute_word} KF3 Friends]]",
f"[[Category:{stars_word} Star KF3 Friends]]",
f"[[Category:Missing Content]]",
f"[[Category:Needs Audio]]",
f"{{{{#vardefine:id|{str(chara['id']).zfill(4)}}}}}"
]
if chara['has_party_dress']:
categories.append("[[Category:Party Dress KF3 Friends]]")
if chara['is_wr5']:
categories.append("[[Category:Wild Release 5 KF3 Friends]]")
if chara['has_rainbow_trait']:
categories.append("[[Category:Rainbow Trait KF3 Friends]]")
lines.append(" ".join(categories))
lines.append("")
lines.append("{{FriendBox/KF3")
lines.append(f"|name={chara['nameEn'].replace('_', ' ')}")
lines.append(f"|apppic={chara['nameEn'].replace('_', ' ')}KF3.png")
lines.append(f"|apprarity={{{{KF3{chara['rankLow']}Star}}}}")
lines.append(f"|seiyuu={chara['castName']}")
lines.append(f"|attribute={attribute_word} {{{{KF3{attribute_word}}}}}")
lines.append(f"|implemented={chara['startTime']} (App)")
lines.append(f"|id={chara['id']}")
lines.append(f"|wr5={'Yes' if chara['is_wr5'] else 'No'}")
lines.append(f"|rainbowtrait={'Yes' if chara['has_rainbow_trait'] else 'No'}")
lines.append(f"|partydress={'Yes' if chara['has_party_dress'] else 'No'}")
lines.append("}}")
lines.append("")
lines.append("{{FriendBuilder/KF3")
lines.append(f"|introduction = '''{chara['nameEn'].replace('_', ' ')}''' is a Friend that appears in the app version of [[Kemono Friends 3]].")
lines.append(f"|status={chara['stats_min']['status']}")
lines.append(f"|hp={chara['stats_min']['hp']}")
lines.append(f"|atk={chara['stats_min']['atk']}")
lines.append(f"|def={chara['stats_min']['def']}")
lines.append(f"|evd={round(chara['stats_min']['evd']/10,1)}%")
lines.append(f"|beat={chara['stats_min']['beat']}%")
lines.append(f"|action={chara['stats_min']['act']}%")
lines.append(f"|try={chara['stats_min']['try']}%")
lines.append(f"|plasm={chara['plasmPoint']}")
lines.append(f"|maxstatus={chara['stats_max']['status']}")
lines.append(f"|maxhp={chara['stats_max']['hp']}")
lines.append(f"|maxatk={chara['stats_max']['atk']}")
lines.append(f"|maxdef={chara['stats_max']['def']}")
lines.append(f"|maxevd={round(chara['stats_max']['evd']/10,1)}%")
lines.append(f"|maxbeat={chara['stats_max']['beat']}%")
lines.append(f"|maxaction={chara['stats_max']['act']}%")
lines.append(f"|maxtry={chara['stats_max']['try']}%")
lines.append("")
cards_str = "|flags="
for card in chara["cards"]:
cards_str += " {{"
if card["type"] == 1:
cards_str += "Beat}}"
elif card["type"] == 2:
cards_str += f"Action{card['value']}}}}}"
elif card["type"] == 3:
if card["value"] == 20:
cards_str += "TryLow}}"
if card["value"] == 30:
cards_str += "TryMiddle}}"
if card["value"] == 40:
cards_str += "TryHigh}}"
lines.append(cards_str)
cardType = "Beat" if chara["synergy_flag"] == 1 else "Action20" if chara["synergy_flag"] == 2 else "TryHigh" if chara["synergy_flag"] == 3 else ""
lines.append(f"|miracleplus={{{{{cardType}}}}}")
if chara["arts"] is not None:
miracles = fill_miracle_numbers(chara)
lines.append(f"|miracle={chara['arts']['actionName']}")
for idx,miracle in enumerate(miracles):
lines.append("|miracle" + str(idx + 1) + "=" + miracle.replace("\r\n", "\n").replace("\\n", "\n").replace("\\", " "))
if chara["special_attack"] is not None:
lines.append(f"|beatname={chara['special_attack']['actionName']}")
lines.append(f"|beatskill={chara['special_attack']['actionEffect']}")
if chara["wait_action"] is not None:
lines.append(f"|standby={chara['wait_action']['skillName']}")
lines.append(f"|standbyskill={chara['wait_action']['skillEffect']}")
if chara["ability"] is not None:
lines.append(f"|unique={chara['ability']['abilityName']}")
lines.append(f"|uniqueskill={chara['ability']['abilityEffect']}")
if chara["ability1"] is not None:
lines.append(f"|miracletrait={chara['ability1']['abilityName']}")
lines.append(f"|miracletraitskill={chara['ability1']['abilityEffect']}")
else:
lines.append(f"|miracletrait=N/A")
lines.append(f"|miracletraitskill=Not Implemented.")
if chara["ability2"] is not None:
lines.append(f"|rainbowtrait={chara['ability2']['abilityName']}")
lines.append(f"|rainbowtraitskill={chara['ability2']['abilityEffect']}")
else:
lines.append(f"|rainbowtrait=N/A")
lines.append(f"|rainbowtraitskill=Not Implemented.")
lines.append("")
lines.append(f"|bondup={chara['kizunaupText']}")
lines.append(f"|profile={chara['flavorText']}")
lines.append(f"|greeting={chara['loginText']}")
lines.append(f"|selfintro={chara['greetingText']}")
lines.append(f"|enselfintro=")
cos_images = "|cos = " + ",".join([costume["image_name"] for costume in chara["costumes"]]) + ",end"
cos_obtain = "|cosobt = " + ",".join([costume["obtain"] for costume in chara["costumes"]])
cos_names = "|cosname = " + ",".join([costume["name"] for costume in chara["costumes"]])
lines.append("")
lines.append(cos_images)
lines.append(cos_names)
lines.append(cos_obtain)
lines.append("}}")
return "\n".join(lines)
def get_costumes(self, id : int, defaultRarity : int):
if id not in self.charaClothesData:
return []
output = []
data = self.charaClothesData[id]
for costume in data:
cos = {
"image_name" : f"icon dressup {costume['id']}.png",
"obtain" : "Limited"
}
cosId = costume['clothesId']
if cosId == 1:
cos["name"] = "Default"
cos["obtain"] = "Default"
elif cosId == 2:
cos["name"] = "Personal Fashion"
cos["obtain"] = f"Upgrade to {costume['getRank']} Stars" if defaultRarity < costume['getRank'] else "Default"
elif cosId == 3:
cos["name"] = "Tracksuit"
cos["obtain"] = f"Upgrade to {costume['getRank']} Stars" if defaultRarity < costume['getRank'] else "Default"
elif cosId == 4:
cos["name"] = "Park Staff"
cos["obtain"] = f"Upgrade to {costume['getRank']} Stars" if defaultRarity < costume['getRank'] else "Default"
elif cosId == 5:
cos["name"] = "Café Uniform"
cos["obtain"] = f"Upgrade to {costume['getRank']} Stars" if defaultRarity < costume['getRank'] else "Default"
elif cosId == 6:
cos["name"] = "Special Outfit 1"
elif cosId == 8:
cos["name"] = "Party Dress"
cos["obtain"] = "Increase Photo Pocket level to 12"
elif cosId == 9:
cos["name"] = "School Uniform"
elif cosId == 10:
cos["name"] = "Gym Clothes"
elif cosId == 11:
cos["name"] = "Raincoat"
elif cosId == 12:
cos["name"] = "Hanamaru Animal Swimsuit"
elif cosId == 13:
cos["name"] = "Cafe Uniform (Green)"
cos["obtain"] = "Dojo reward"
elif cosId == 14:
cos["name"] = "Cafe Uniform (Blue)"
cos["obtain"] = "Dojo reward"
elif cosId == 15:
cos["name"] = "Cafe Uniform (Pink)"
cos["obtain"] = "Dojo reward"
elif cosId == 16:
cos["name"] = "Orihime Costume"
elif cosId == 17:
cos["name"] = "Arle's Outfit"
elif cosId == 18:
cos["name"] = "Marine Nanoda! Swimsuit"
elif cosId == 19:
cos["name"] = "Batten Japari Dan Swimsuit"
elif cosId == 20:
cos["name"] = "Lifeguard Swimsuit"
elif cosId == 21:
cos["name"] = "Harvest Festival Outfit"
elif cosId == 22:
cos["name"] = "Halloween Costume"
elif cosId == 23:
cos["name"] = "Classic Maid Uniform"
elif cosId == 24:
cos["name"] = "Arle's Outfit"
elif cosId == 25:
cos["name"] = "Swimsuit"
elif cosId == 26:
cos["name"] = "Swimsuit"
elif cosId == 27:
cos["name"] = "Swimsuit"
elif cosId == 28:
cos["name"] = "Matching Clothes"
elif cosId == 29:
cos["name"] = "Frilly Frilly PPP Bikini"
elif cosId == 30:
cos["name"] = "Venus Flower Bikini"
elif cosId == 31:
cos["name"] = "Christmas Dress"
elif cosId == 32:
cos["name"] = "Chinese Shrine Maiden"
elif cosId == 33:
cos["name"] = "Chinese Dress"
elif cosId == 34:
cos["name"] = "Hanamaru Stage Costume"
elif cosId == 35:
cos["name"] = "Camo Outfit"
elif cosId == 36:
cos["name"] = "でびでび・ふりる"
elif cosId == 37:
cos["name"] = "夜に紛れし闇の衣"
elif cosId == 38:
cos["name"] = "Santa"
elif cosId == 39:
cos["name"] = "Fluffy Pajamas"
elif cosId == 40:
cos["name"] = "Japanese Armor"
elif cosId == 41:
cos["name"] = "Miko Outfit"
elif cosId == 42:
cos["name"] = "PPP Hoodie"
elif cosId == 43:
cos["name"] = "Blue Tracksuit"
elif cosId == 44:
cos["name"] = "Park Staff (beige)"
elif cosId == 45:
cos["name"] = "Sailor School Uniform"
elif cosId == 46:
cos["name"] = "Snow Knight Dress"
elif cosId == 47:
cos["name"] = "Sparkling Tree Dress"
elif cosId == 48:
cos["name"] = "Retro Detective"
elif cosId == 49:
cos["name"] = "Steampunk Detective"
elif cosId == 50:
cos["name"] = "鞠と花びらのはんなり和風服"
elif cosId == 51:
cos["name"] = "Numazu Deep Sea Aquarium Clothes"
elif cosId == 52:
cos["name"] = "たのしいことをお届け!"
elif cosId == 53:
cos["name"] = "かわいい勝負ですよ!ワンピース水着"
elif cosId == 54:
cos["name"] = "どきどき♡ずっきゅんビキニ"
elif cosId == 55:
cos["name"]= "のんほいパークの服"
elif cosId == 56:
cos["name"]= "なないろのはごろも"
elif cosId == 57:
cos["name"]= "4周年ハッピーラッキーTシャツ"
elif cosId == 58:
cos["name"]= "純情色のワンピース"
else:
print(f"{costume['clothesId']}\t{costume['id']}\t{costume['name']}")
continue
output.append(cos)
return output

View File

@ -1,276 +0,0 @@
import base64
import os
import gzip
import json
import asyncio
import hashlib
import platform
import aiohttp
from UnityPy import enums
from ..Shared.utility import divide_chunks
from ..Shared.downloading import download_bytes, download_text
from ..Shared.convert import convert, extract
servers = [
"https://parade-mobile-stg-app.kemono-friends-3.jp/",
"https://parade-mobile-develop01-app.kemono-friends-3.jp/",
#"https://parade-mobile-develop02-app.kemono-friends-3.jp/paradesv/",
#"https://parade-mobile-develop03-app.kemono-friends-3.jp/paradesv/",
#"https://parade-mobile-develop04-app.kemono-friends-3.jp/paradesv/",
]
def decode(data):
# cut off the md5 checksum at the end and the four bytes at the start
hash = bytearray.fromhex(data[:-(2*16)][2*4:])
key = hashlib.md5(bytearray.fromhex(data[:2*4])).digest() # md5 the key
# xor with the key
for i in range(0, len(hash)):
hash[i] = hash[i] ^ key[i % (len(key))]
return hash.decode("utf-8")
def encode(text):
hashed = hashlib.md5(text.encode()).digest()
checksum = hashlib.md5((text + "DARAPAB ").encode()).digest()
key = hashlib.md5(hashed[:4]).digest() # md5 the key
# xor the data with the key
text = bytearray(text.encode())
for i in range(0, len(text)):
text[i] = text[i] ^ key[i % (len(key))]
return hashed[:4].hex() + text.hex() + checksum.hex()
async def download_cache(server_name, server : str):
session = aiohttp.ClientSession()
downloaded_files = []
if platform.system() == "Windows":
path = f"D:\\Codebase\\KFKDecrypt\\assets\\KF3\\{server_name}\\cache\\"
else:
path = f"/var/www/html/Katworks/KF/assets/KF3/{server_name}/cache/"
os.makedirs(path, exist_ok=True)
new_mst_ver = {}
old_mst_ver = {}
file_path_mst = path + "mstVersion.txt"
if os.path.exists(file_path_mst):
with open(file_path_mst, "rt", encoding="utf-8") as file:
old_mst_ver = dict([(entry["type"], entry["version"]) for entry in json.load(file)])
param = encode(json.dumps({'dmm_viewer_id':0}))
request = await download_bytes(server + "paradesv/common/MstVersion.do?param=" + param, session)
result = gzip.decompress(request)
new_mst = json.loads(result)
new_mst_ver = dict([(entry["type"], entry["version"]) for entry in new_mst["mst_ver"]])
for key in new_mst_ver:
file_path_gzip = path + key + ".d"
file_path_json = path + key + ".json"
if os.path.exists(file_path_gzip) and os.path.exists(file_path_json) and key in old_mst_ver:
if new_mst_ver[key] == old_mst_ver[key]:
continue
downloaded_files.append(key)
param = encode(json.dumps({'type':key, 'dmm_viewer_id':0}))
request = await download_bytes(server + "paradesv/common/MstData.do?param=" + param, session)
result = gzip.decompress(request)
response = json.loads(result)
data = base64.b64decode(response["data"])
with open(file_path_gzip, "wb") as file:
file.write(data)
with open(file_path_json, "wt", encoding="utf-8") as out_file:
data = gzip.decompress(data)
data = json.loads(data)
# if key == "GACHA_DATA":
# download_banners(data, server_name)
json.dump(data, out_file, ensure_ascii=False, indent=1)
old_mst_ver[key] = new_mst_ver[key]
with open(file_path_mst, "wt", encoding="utf-8") as file:
new_json = [{"type":type, "version":version} for type, version in zip(old_mst_ver.keys(), old_mst_ver.values())]
json.dump(new_json, file, ensure_ascii=False)
await session.close()
return downloaded_files
async def download_banners(gacha_data, server_name, session):
path = f"/var/www/html/Katworks/KF/assets/KF3/{server_name}/banners/"
os.makedirs(path, exist_ok=True)
for entry in gacha_data:
banner_name = entry["banner"]
if banner_name == "" or banner_name == None:
continue
banner_name += ".png"
file_path = path + banner_name
if os.path.exists(file_path):
continue
file_url = "https://parade-mobile-prod-cdn.kemono-friends-3.jp/Texture2D/GachaTop/" + banner_name
file_url_alt = "https://parade-mobile-develop01-app.kemono-friends-3.jp/Texture2D/GachaTop/" + banner_name
status = 0
async with session.get(file_url) as resp:
response = await resp.read()
status = resp.status
if status != 200:
async with session.get(file_url_alt) as resp:
response = await resp.read()
status = resp.status
if status != 200: continue
with open(file_path, "wb") as file:
file.write(response)
async def download_files(server_name, asset_bundle_url, srv_platform : str):
def parse_ab_list(filecontent : str):
out = {}
lines = filecontent.replace("\r\n", "\n").split('\n')
for line in lines:
split = line.split('\t')
if len(split) > 1:
out[split[0]] = split[-2]
return out
async def download_file(url_assets : str, file_name : str, download_path : str, session : aiohttp.ClientSession):
data = await download_bytes(url_assets + file_name, session)
if data != None:
with open(download_path + file_name, "wb") as file:
file.write(data)
if server_name == "develop01":
convert_path = f"/var/www/html/Katworks/KF/assets/KF3/WebGL/assets/" + file_name
extract_path = f"/var/www/html/Katworks/KF/assets/KF3/extracted/"
try:
convert(data, convert_path, enums.BuildTarget.WebGL)
except:
with open(convert_path, "wb") as file:
file.write(data)
if file_name.endswith(".png"):
extract(data, extract_path)
session = aiohttp.ClientSession()
path = f"/var/www/html/Katworks/KF/assets/KF3/{server_name}/assets/{srv_platform}/"
os.makedirs(path, exist_ok=True)
files_to_download = []
url_base = asset_bundle_url + "/" + srv_platform + "/1.0.0/ja/"
url_list = url_base + "ab_list.txt"
url_env = url_base + "ab_env.txt"
url_assets = url_base + "assets/"
file_path_env = path + "ab_env.txt"
file_path_list = path + "ab_list.txt"
old_ab_env = ""
if os.path.exists(file_path_env):
with open(file_path_env, "rt") as file:
old_ab_env = file.read()
new_ab_env = await download_text(url_env, session)
if new_ab_env != old_ab_env:
old_ab_list = {}
if os.path.exists(file_path_list):
with open(file_path_list, "rt") as file:
old_ab_list = parse_ab_list(file.read())
new_ab_list_file = await download_text(url_list, session)
new_ab_list = parse_ab_list(new_ab_list_file)
for key in new_ab_list:
file_path = path + key
if os.path.exists(file_path) and key in old_ab_list:
if new_ab_list[key] == old_ab_list[key]:
continue
files_to_download.append(key)
chunked_files_to_download = list(divide_chunks(files_to_download, 5))
for chunk in chunked_files_to_download:
tasks = [asyncio.create_task(download_file(url_assets, file, path, session)) for file in chunk]
await asyncio.wait(tasks)
print(chunk)
print()
with open(file_path_env, "wt", encoding="utf-8") as file:
file.write(new_ab_env)
with open(file_path_list, "wt", encoding="utf-8") as file:
file.write(new_ab_list_file)
await session.close()
return files_to_download
async def convert_files():
directory = f"/var/www/html/Katworks/KF/assets/KF3/develop01/assets/Windows/"
with open("/var/www/html/Katworks/KF/assets/KF3/lastUpdate_dev_files.json", "rt", encoding="utf-8") as file:
files_to_convert = json.load(file)
for file_name in os.listdir(directory):
if file_name not in files_to_convert:
continue
f = os.path.join(directory, file_name)
if not os.path.isfile(f):
return
convert_path = f"/var/www/html/Katworks/KF/assets/KF3/WebGL/assets/" + file_name
try:
print(f)
convert(f, convert_path, enums.BuildTarget.WebGL)
except:
print("Conversion failed", f)
async def manual():
# session = aiohttp.ClientSession()
# path = f"/var/www/html/Katworks/KF/assets/KF3/{server_name}/cache/"
# await session.close()
# return
downloaded_cache = {}
downloaded_files = {}
async with aiohttp.ClientSession() as session:
param = encode(json.dumps({"version":"1.0.0","dmm_viewer_id":0,"platform":1}))
request = await download_bytes(servers[0] + "paradesv/common/GetUrl.do?param=" + param, session)
result = gzip.decompress(request)
response = json.loads(result)
asset_bundle_url = response["asset_bundle_url"]
urlName = asset_bundle_url.split("-")[2]
print("downloading from", servers[0])
downloaded_cache = await download_cache(urlName, servers[0])
downloaded_files = await download_files(urlName, asset_bundle_url, "Windows")
if downloaded_cache != [] and downloaded_cache != None:
with open("/var/www/html/Katworks/KF/assets/KF3/lastUpdate_prod_cache.json", "wt", encoding="utf-8") as file:
json.dump(downloaded_cache, file, ensure_ascii=False, indent=1)
if downloaded_files != [] and downloaded_files != None:
with open("/var/www/html/Katworks/KF/assets/KF3/lastUpdate_prod_files.json", "wt", encoding="utf-8") as file:
json.dump(downloaded_files, file, ensure_ascii=False, indent=1)
print("downloading from", servers[1])
asset_bundle_url = "https://parade-mobile-develop01-app.kemono-friends-3.jp/AssetBundles/0.0.0/latest"
urlName = asset_bundle_url.split("-")[2]
downloaded_cache = await download_cache(urlName, servers[1])
downloaded_files = await download_files(urlName, asset_bundle_url, "Windows")
if downloaded_cache != [] and downloaded_cache != None:
with open("/var/www/html/Katworks/KF/assets/KF3/lastUpdate_dev_cache.json", "wt", encoding="utf-8") as file:
json.dump(downloaded_cache, file, ensure_ascii=False, indent=1)
if downloaded_files != [] and downloaded_files != None:
with open("/var/www/html/Katworks/KF/assets/KF3/lastUpdate_dev_files.json", "wt", encoding="utf-8") as file:
json.dump(downloaded_files, file, ensure_ascii=False, indent=1)
if __name__ == "__main__":
asyncio.run(manual())
#asyncio.run(convert_files())

View File

@ -1,30 +0,0 @@
from flask_restful import Resource
from flask import current_app as app
from flask import request
import json
class KF3_Friend(Resource):
def get(self, id:int):
from ..database import Database
db : Database = app.databases["KF3"]
if "wiki" in request.args:
result = db.get_chara_wiki(id)
response = app.response_class(
response=result,
status=200,
mimetype='text/plain'
)
else:
result = db.get_chara(id)
result = json.dumps(result, ensure_ascii=False, indent=1)
response = app.response_class(
response=result,
status=200,
mimetype='application/json'
)
response.headers.add("Access-Control-Allow-Origin", "*")
return response

View File

@ -1,28 +0,0 @@
import json
from flask_restful import Resource
from flask import current_app as app
from flask import request
class KF3_Friends(Resource):
def get(self):
from ..database import Database
db : Database = app.databases["KF3"]
result = []
for value in db.processed_friends.values():
result.append({"id": value["id"], "name": value["nameEn"], "startTime" : value["startTime"], "startTimeRaw" : value["startTimeRaw"]})
sort_arg = request.args["sort"] if "sort" in request.args and request.args["sort"] in result[0] else "id"
if sort_arg == "startTime":
sort_arg = "startTimeRaw"
result = sorted(result, key=lambda f: f[sort_arg])
response = app.response_class(
response=json.dumps(result, ensure_ascii=False, indent=1),
status=200,
mimetype='application/json'
)
response.headers.add("Access-Control-Allow-Origin", "*")
return response

View File

@ -1,21 +0,0 @@
from flask_restful import Resource
from flask import current_app as app
from flask import request_tearing_down
import json
class KF3_Item(Resource):
def get(self, id:int):
from ..database import Database
db : Database = app.databases["KF3"]
result = db.get_item(id)
result = json.dumps(result, ensure_ascii=False, indent=1)
response = app.response_class(
response=result,
status=200,
mimetype='application/json'
)
response.headers.add("Access-Control-Allow-Origin", "*")
return response

View File

@ -1,21 +0,0 @@
import json
from flask_restful import Resource
from flask import current_app as app
class KF3_Items(Resource):
def get(self):
from ..database import Database
db : Database = app.databases["KF3"]
result = []
for value in db.itemCommon.values():
result.append({"id": value["id"], "name": value["name"], "iconName" : value["iconName"], "flavorText" : value["flavorText"]})
response = app.response_class(
response=json.dumps(result, ensure_ascii=False, indent=1),
status=200,
mimetype='application/json'
)
response.headers.add("Access-Control-Allow-Origin", "*")
return response

View File

@ -1,36 +0,0 @@
import asyncio
from flask_restful import Resource
from flask import current_app as app
from ..downloader import manual
class KF3_Update(Resource):
def post(self):
from ..database import Database
db : Database = app.databases["KF3"]
if db.update_in_progress:
response = app.response_class(
response="update in progress",
status=200,
mimetype='text/plain'
)
else:
self.perform_update()
response = app.response_class(
response="update started, reload the site in a while",
status=200,
mimetype='text/plain'
)
response.headers.add("Access-Control-Allow-Origin", "*")
return response
def get(self):
return self.post()
def perform_update(self):
from ..database import Database
db : Database = app.databases["KF3"]
print("Update")
#await manual()
db.reload_data()

View File

@ -1,133 +0,0 @@
import json
import math
from collections import defaultdict
def GetName(charaData):
name = "no name"
if charaData is not None:
if charaData["nameEn"] != "":
name = charaData["nameEn"]
elif charaData["name"] != "":
name = charaData["name"]
if charaData["nickname"] != "":
name += " " + charaData["nickname"]
return name
def GetAttackBase(alphaBase, level):
if level <= alphaBase["atkLvMiddleNum"]:
t = (level - 1) / (alphaBase["atkLvMiddleNum"]-1)
return lerp(alphaBase["atkParamLv1"], alphaBase["atkParamLvMiddle"], t)
else:
t = (level - alphaBase["atkLvMiddleNum"]) / (99 - alphaBase["atkLvMiddleNum"])
return lerp(alphaBase["atkParamLvMiddle"], alphaBase["atkParamLv99"], t)
def GetDefenseBase(alphaBase, level):
if level <= alphaBase["defLvMiddleNum"]:
t = (level - 1) / (alphaBase["defLvMiddleNum"]-1)
return lerp(alphaBase["defParamLv1"], alphaBase["defParamLvMiddle"], t)
else:
t = (level - alphaBase["defLvMiddleNum"]) / (99 - alphaBase["defLvMiddleNum"])
return lerp(alphaBase["defParamLvMiddle"], alphaBase["defParamLv99"], t)
def GetHealthBase(alphaBase, level):
if level <= alphaBase["hpLvMiddleNum"]:
t = (level - 1) / (alphaBase["hpLvMiddleNum"]-1)
return lerp(alphaBase["hpParamLv1"], alphaBase["hpParamLvMiddle"], t)
else:
t = (level - alphaBase["hpLvMiddleNum"]) / (99 - alphaBase["hpLvMiddleNum"])
return lerp(alphaBase["hpParamLvMiddle"], alphaBase["hpParamLv99"], t)
def get_all_stats(chara, alphaBase, max_level: bool, rising_status_pattern):
level = 99 if max_level else 1
hp = GetHealthBase(alphaBase, level)
atk = GetAttackBase(alphaBase, level)
defe = GetDefenseBase(alphaBase, level)
if max_level:
starBoost = 1 + (chara["rankHigh"] - 1) * 0.02
hp = int(math.ceil((hp + chara["promoteBonus"]["hp"]) * starBoost) + chara["costumeBonus"]["hp"])
atk = int(math.ceil((atk + chara["promoteBonus"]["atk"]) * starBoost) + chara["costumeBonus"]["atk"])
defe = int(math.ceil((defe + chara["promoteBonus"]["def"]) * starBoost) + chara["costumeBonus"]["def"])
evd = 10 * alphaBase["avoidRatio"] + chara["promoteBonus"]["evd"]
beatBonus = chara["promoteBonus"]["beat"] / 10
actBonus = chara["promoteBonus"]["act"] / 10
tryBonus = chara["promoteBonus"]["try"] / 10
if rising_status_pattern is not None:
for i in range(51):
hp += rising_status_pattern["hp"]
atk += rising_status_pattern["atk"]
defe += rising_status_pattern["def"]
else:
starBoost = 1 + (chara["rankLow"] - 1) * 0.02
hp = int(math.ceil(hp * starBoost))
atk = int(math.ceil(atk * starBoost))
defe = int(math.ceil(defe * starBoost))
evd = 10 * alphaBase["avoidRatio"]
beatBonus = 0
actBonus = 0
tryBonus = 0
status = hp * 8 / 10
if hp * 8 % 10 > 0:
status += 1
status += atk * 3 + defe * 2
result = {
"level" : level,
"status" : int(status),
"wr" : chara["rankHigh"] if max_level else chara["rankLow"],
"hp" : hp,
"atk" : atk,
"def" : defe,
"evd" : evd,
"beat" : beatBonus,
"act" : actBonus,
"try" : tryBonus,
}
return result
def fill_miracle_numbers(chara):
output = []
damages = {}
buffs = defaultdict(list)
for i, damage_param in enumerate(chara["arts"]["damageList"]):
damages[f"[DAMAGE{i}]"] = damage_param
for i, buff_param in enumerate(chara["arts"]["buffList"]):
buffs[f"[BUFF{i}]"].append(buff_param)
buffs[f"[HEAL{i}]"].append(buff_param)
buffs[f"[INCREMENT{i}]"].append(buff_param)
for i in range(1, 7):
base_text = chara["arts"]["actionEffect"]
for damage_key, damage_value in damages.items():
new_value = str(int(damage_value["damageRate"] * (1 + damage_value["growthRate"] * (i - 1)) * 100 + 0.01))
base_text = base_text.replace(damage_key, new_value)
for buff_key, buff_value_list in buffs.items():
buff_value = buff_value_list[0]
if buff_key[1] == 'B':
new_value = str(int(abs(buff_value["coefficient"] * (1 + buff_value["growthRate"] * (i - 1)) - 1) * 100 + 0.01))
base_text = base_text.replace(buff_key, new_value)
elif buff_key[1] == 'H':
new_value = str(int(buff_value["coefficient"] * (1 + buff_value["growthRate"] * (i - 1)) * 100 + 0.01))
base_text = base_text.replace(buff_key, new_value)
elif buff_key[1] == 'I':
new_value = str(int(abs(buff_value["increment"]) * (1 + buff_value["growthRate"] * (i - 1)) + 0.01))
base_text = base_text.replace(buff_key, new_value)
output.append(base_text)
return output
def toJSON(chara):
return json.dumps(chara, default=lambda o: o.__dict__,
sort_keys=True, indent=1, ensure_ascii=False)
def lerp(a, b, t):
return (1 - t) * a + t * b

View File

@ -1,298 +0,0 @@
import json
import platform
from .numeric import numeric
from .kemono import clean_skill_string, calculate_stats, fill_miracle, get_awaken_materials, get_skill_materials
from .endpoints.item import Kingdom_Item
from .endpoints.items import Kingdom_Items
from .endpoints.update import Kingdom_Update
from .endpoints.friend import Kingdom_Friend
from .endpoints.friends import Kingdom_Friends
#element 1-orange 2-blue 3-green
#showSkillType 1-control 2-guard 3-heal 4-support 5-assault 6-aoe
class Database:
processed_friends = {}
item_stages = {}
def __init__(self, api) -> None:
app = api.app
if "Kingdom" in app.databases:
del app.databases["Kingdom"]
self.reload_data()
app.databases["Kingdom"] = self
api.add_resource(Kingdom_Friend, "/Kingdom/Friend/<int:id>")
api.add_resource(Kingdom_Friends, "/Kingdom/Friends")
api.add_resource(Kingdom_Item, "/Kingdom/Item/<int:id>")
api.add_resource(Kingdom_Items, "/Kingdom/Items")
api.add_resource(Kingdom_Update, "/Kingdom/Update")
def reload_data(self):
if platform.system() == "Windows":
NUMERICPATH = "H:\\Apache\\Katworks\\KF\\assets\\Kingdom1\\NumericData"
else:
NUMERICPATH = "/var/www/html/Katworks/KF/assets/Kingdom1/NumericData"
self.kfk_drop = numeric(NUMERICPATH +"/Drop.num", 0)
self.kfk_combo = numeric(NUMERICPATH +"/Combo.num", 0)
self.kfk_skill = numeric(NUMERICPATH +"/Skill.num", 0)
self.kfk_equip = numeric(NUMERICPATH +"/Equip.num", 0)
self.kfk_kemono = numeric(NUMERICPATH +"/Kemono.num", 0)
self.kfk_stages = numeric(NUMERICPATH +"/Stage.num", 0)
self.kfk_drop_group = numeric(NUMERICPATH +"/DropGroup.num", 0)
self.kfk_trust_attri = numeric(NUMERICPATH +"/TrustAttri.num", 0)
self.kfk_kemono_skill = numeric(NUMERICPATH +"/KemonoSkill.num", 0)
self.kfk_kemono_power = numeric(NUMERICPATH +"/KemonoPower.num", 0)
self.kfk_kemono_waken = numeric(NUMERICPATH +"/KemonoWaken.num", 0)
self.kfk_en_item = numeric(NUMERICPATH + "/en/Item.ntxt", 1)
self.kfk_en_equip = numeric(NUMERICPATH + "/en/Equip.ntxt", 1)
self.kfk_en_skill = numeric(NUMERICPATH + "/en/Skill.ntxt", 1)
self.kfk_en_combo = numeric(NUMERICPATH + "/en/Combo.ntxt", 1)
self.kfk_en_kemono = numeric(NUMERICPATH + "/en/Kemono.ntxt", 1)
self.kfk_en_kemono_WkPaDc = numeric(NUMERICPATH + "/en/KemonoWkPaDc.ntxt", 1)
self.kfk_en_kemono_power = numeric(NUMERICPATH + "/en/KemonoPower.ntxt", 1)
self.kfk_en_kemono_waken = numeric(NUMERICPATH + "/en/KemonoWaken.ntxt", 1)
self.kfk_en_kemono_garden = numeric(NUMERICPATH + "/en/KemonoGarden.ntxt", 1)
self.kfk_en_str = numeric(NUMERICPATH + "/en/Str.ntxt", 1)
self.process_friends()
self.process_stages()
def process_friends(self):
self.processed_friends = {}
for friend_id in self.kfk_kemono.indexed_data:
if friend_id > 19000:
continue
friend = self.kfk_kemono.get(friend_id)
friend["comboSkill"] = ""
combo_friends = []
for entry in friend["combo"]:
combo = self.kfk_combo.get(entry)
if combo is not None:
combo_en = self.kfk_en_combo.get(combo["comboSkill"])
friend["comboSkill"] = {"name":combo_en["comboName"], "desc":combo_en["desc"]}
for combo_friend_id in combo["comboKemono"]:
combo_friend = self.kfk_en_kemono.get(combo_friend_id)
if combo_friend is not None and "name" in combo_friend:
combo_friends.append(combo_friend["name"])
else:
combo_friends.append(combo_friend_id)
friend["combo"] = combo_friends
limit_breaks = []
for bk_value in self.kfk_kemono_power.indexed_data.values():
if len(limit_breaks) == 5:
break
if(bk_value["kemonosn"] == friend_id):
if bk_value["effectType1"] == 0:
pass
if bk_value["effectType1"] == 1 or bk_value["effectType1"] == 3:
lb = self.kfk_en_kemono_power.get(friend_id * 100 + bk_value["powerlevel"])
if "name" in lb:
limit_breaks.append({"name": lb["name"], "desc": lb["describe"]})
else:
limit_breaks.append({"name": None, "desc": lb["describe"]})
if bk_value["effectType1"] == 2:
raise Exception()
if bk_value["effectType1"] == 4:
lb = self.kfk_en_kemono_WkPaDc.get(bk_value['effectParam1'][0] * 100)
if lb is None:
lb = {"name": None, "describe": None}
limit_breaks.append({"name": lb["name"], "desc": clean_skill_string(lb["describe"])})
habits = []
for habit in friend["habitSn"]:
hbt = self.kfk_en_kemono_WkPaDc.get(habit*100)
if hbt is None:
hbt = {"name": None, "describe": None}
habits.append({"name":hbt["name"], "desc":hbt["describe"]})
for i in range (3):
if len(habits) < i+1:
habits.append({"name":None, "desc":None})
awakens = []
max_levels = []
for i in range(3):
awaken_id = friend_id * 100 + i
awaken = self.kfk_kemono_waken.get(awaken_id)
awaken1 = self.kfk_en_kemono_waken.get(awaken_id)
if awaken1 == None:
awaken1 = {"describe":"missing", "brilliance":"missing"}
if awaken != None and awaken1 != None:
awaken["en"] = awaken1
awakens.append(awaken)
max_levels.append(awaken["levelmax"])
def validateValue(obj, valName):
if obj[valName] is None:
obj[valName] = {"name": "", "desc": ""}
if "desc" not in obj[valName]:
obj[valName]["desc"] = None
friend["name"] = self.kfk_en_kemono.get(friend_id)["name"]
ele = friend["element"]
friend["color"] = "Orange" if ele == 1 else "Green" if ele == 2 else "Blue" if ele == 3 else "Error"
ele = friend["showSkillType"]
friend["role"] = "Control" if ele == 1 else "Guard" if ele == 2 else "Healer" if ele == 3 else "Support" if ele == 4 else "Assault" if ele == 5 else "Assault (AOE)" if ele == 6 else "Error"
friend["maxLvl"] = max_levels
friend["awakenings"] = awakens
friend["maxAwaken"] = max_awaken = len(awakens)-1
friend["base_stats"] = calculate_stats(friend, 0, 1)
friend["max_stats"] = calculate_stats(friend, max_awaken, friend["maxLvl"][max_awaken], trust_percent=100, trustAttri= next((e for e in self.kfk_trust_attri.indexed_data.values() if e["kemonoSn"] == friend_id), None))
friend["max_miracle"] = 13 if friend["star"] == 6 or friend["star"] == 5 else 12 if friend["star"] == 4 else 11
friend["collisionSkill"] = self.kfk_en_skill.get(friend["collisionSkill"])
friend["gardenSpec1"] = self.kfk_en_kemono_garden.get(friend["gardenSpec1"])
friend["gardenSpec2"] = self.kfk_en_kemono_garden.get(friend["gardenSpec2"])
friend["gardenSpec3"] = self.kfk_en_kemono_garden.get(friend["gardenSpec3"])
friend["describe"] = self.kfk_en_str.get(friend["describe"])
friend["attack"] = self.kfk_en_skill.get(friend["attack"])
skill_id = friend["skill"]
friend["skill"] = self.kfk_en_skill.get(skill_id)
friend["skill1"] = self.kfk_en_skill.get(skill_id+1) if max_awaken >= 1 else None
friend["skill2"] = self.kfk_en_skill.get(skill_id+2) if max_awaken >= 2 else None
friend["spAttack"] = self.kfk_en_skill.get(friend["spAttack"])
friend["habitSn"] = habits
friend["limitBreaks"] = limit_breaks
validateValue(friend, "collisionSkill")
validateValue(friend, "attack")
validateValue(friend, "skill")
validateValue(friend, "skill1")
validateValue(friend, "skill2")
validateValue(friend, "spAttack")
self.processed_friends[friend_id] = friend
def process_stages(self):
self.item_stages = {}
for stage in self.kfk_stages.indexed_data.values():
merged_array = stage["fastReward2_star3"] + stage["fastReward3_star3"]
if len(merged_array) > 0:
for drop_sn in merged_array:
drop = self.kfk_drop.get(drop_sn)
for drop_group_sn in drop["groupSn"]:
drop_group = self.kfk_drop_group.get(drop_group_sn)
if drop_group["type"] == 2 or drop_group["type"] == 1:
#materials
item_id = drop_group["content"]
item = self.kfk_en_item.get(item_id)
if item_id not in self.item_stages:
self.item_stages[item_id] = []
if stage not in self.item_stages[item_id]:
self.item_stages[item_id].append(stage)
def get_chara(self, id : int):
#return next(lambda f: f["sn"] == id, None)
if id not in self.processed_friends:
return None
else:
friend = self.processed_friends[id]
data = {
"id": friend["sn"],
"name": friend["name"],
"element": friend["color"],
"role": friend["role"],
"star": friend["star"],
"combo": friend["combo"],
"comboSkill": friend["comboSkill"],
"describe": friend["describe"],
"awakenings": friend["awakenings"],
"max_miracle": friend["max_miracle"],
"stats": {
"base":friend["base_stats"],
"max":friend["max_stats"]
},
"collision": {"name": friend["collisionSkill"]["name"], "desc": clean_skill_string(friend["collisionSkill"]["desc"])},
"attack": {"name": friend["attack"]["name"], "desc": clean_skill_string(friend["attack"]["desc"])},
"skills":[
{"name": friend["skill"]["name"], "desc": fill_miracle(friend["skill"]["desc"], miracle_level=friend["max_miracle"], kemono_level=friend["maxLvl"][-1])},
{"name": friend["skill1"]["name"], "desc": fill_miracle(friend["skill1"]["desc"], miracle_level=friend["max_miracle"], kemono_level=friend["maxLvl"][-1])},
{"name": friend["skill2"]["name"], "desc": fill_miracle(friend["skill2"]["desc"], miracle_level=friend["max_miracle"], kemono_level=friend["maxLvl"][-1])},
],
"garden":[
friend["gardenSpec1"],
friend["gardenSpec2"],
friend["gardenSpec3"],
],
"habits":[
{"name": friend["habitSn"][0]["name"], "desc": clean_skill_string(friend["habitSn"][0]["desc"])},
{"name": friend["habitSn"][1]["name"], "desc": clean_skill_string(friend["habitSn"][1]["desc"])},
{"name": friend["habitSn"][2]["name"], "desc": clean_skill_string(friend["habitSn"][2]["desc"])}
],
"limitBreaks":[
friend["limitBreaks"][0],
friend["limitBreaks"][1],
friend["limitBreaks"][2],
friend["limitBreaks"][3],
friend["limitBreaks"][4]
],
"awakenMaterials": get_awaken_materials(friend, self.kfk_kemono_waken, self.kfk_en_item),
"skillMaterials": get_skill_materials(friend, self.kfk_kemono_skill, self.kfk_en_item)
}
return json.dumps(data, sort_keys=False, indent=1, ensure_ascii=False)
def get_chara_wiki(self, id : int):
friend = self.processed_friends[id]
lines = []
lines.append("|name=" + friend["name"])
#stats
lines.append("|maxLvls=" + ",".join([str(lvl) for lvl in friend["maxLvl"]]))
lines.append("|hp=" + str(friend["base_stats"]["hp"]))
lines.append("|maxhp=" + str(friend["max_stats"]["hp"]))
lines.append("|atk=" + str(friend["base_stats"]["patk"]))
lines.append("|maxatk=" + str(friend["max_stats"]["patk"]))
lines.append("|satk=" + str(friend["base_stats"]["satk"]))
lines.append("|maxsatk=" + str(friend["max_stats"]["satk"]))
lines.append("|pdef=" + str(friend["base_stats"]["pdef"]))
lines.append("|maxpdef=" + str(friend["max_stats"]["pdef"]))
lines.append("|sdef=" + str(friend["base_stats"]["sdef"]))
lines.append("|maxsdef=" + str(friend["max_stats"]["sdef"]))
lines.append("|speed=" + str(friend["base_stats"]["speed"]))
lines.append("|maxspeed=" + str(friend["max_stats"]["speed"]))
#skills
lines.append("|collisionSkill=" + friend["collisionSkill"]["name"])
lines.append("|collisionSkillEffect=" + clean_skill_string(friend["collisionSkill"]["desc"]))
lines.append("|attack=" + friend["attack"]["name"])
lines.append("|attackEffect=" + clean_skill_string(friend["attack"]["desc"]))
lines.append("|skill1=" + friend["skill"]["name"])
lines.append("|skill1Effect=" + clean_skill_string(friend["skill"]["desc"]))
lines.append("|skill2=" + friend["skill1"]["name"])
lines.append("|skill2Effect=" + clean_skill_string(friend["skill1"]["desc"]))
lines.append("|skill3=" + friend["skill2"]["name"])
lines.append("|skill3Effect=" + clean_skill_string(friend["skill2"]["desc"]))
lines.append("|spAttack=" + friend["spAttack"]["name"])
lines.append("|spAttackEffect=" + clean_skill_string(friend["spAttack"]["desc"]))
for i in range(0,5):
lb = friend['limitBreaks'][i]
if lb["name"] is None:
lines.append(f"|b{i+1}={friend['limitBreaks'][i]['desc']}")
else:
lines.append(f"|b{i+1}='''{friend['limitBreaks'][i]['name']}'''\n{friend['limitBreaks'][i]['desc']}")
return "\n".join(lines)
def get_item(self, id):
result = {"item": None, "stages": []}
item = self.kfk_en_item.get(id)
if item is None:
return result
result["item"] = item
if id in self.item_stages:
result["stages"] = self.item_stages[id]
return json.dumps(result, sort_keys=False, indent=1, ensure_ascii=False)

View File

@ -1,25 +0,0 @@
from flask_restful import Resource
from flask import current_app as app
from flask import request
class Kingdom_Friend(Resource):
def get(self, id:int):
if "wiki" in request.args:
result = app.databases["Kingdom"].get_chara_wiki(id)
response = app.response_class(
response=result,
status=200,
mimetype='text/plain'
)
else:
result = app.databases["Kingdom"].get_chara(id)
response = app.response_class(
response=result,
status=200,
mimetype='application/json'
)
response.headers.add("Access-Control-Allow-Origin", "*")
return response

View File

@ -1,21 +0,0 @@
import json
from flask_restful import Resource
from flask import current_app as app, jsonify
class Kingdom_Friends(Resource):
def get(self):
db = app.databases["Kingdom"]
result = []
for value in db.processed_friends.values():
result.append({"id": value["sn"], "name": value["name"], "describe": value["describe"]["content"] if "content" in value["describe"] else ""})
result = sorted(result, key=lambda f: f["id"])
response = app.response_class(
response=json.dumps(result, ensure_ascii=False, indent=1),
status=200,
mimetype='application/json'
)
response.headers.add("Access-Control-Allow-Origin", "*")
return response

View File

@ -1,16 +0,0 @@
from flask_restful import Resource
from flask import current_app as app
from flask import request
class Kingdom_Item(Resource):
def get(self, id:int):
result = app.databases["Kingdom"].get_item(id)
response = app.response_class(
response=result,
status=200,
mimetype='application/json'
)
response.headers.add("Access-Control-Allow-Origin", "*")
return response

View File

@ -1,23 +0,0 @@
import json
from flask_restful import Resource
from flask import current_app as app, jsonify
class Kingdom_Items(Resource):
def get(self):
db = app.databases["Kingdom"]
result = []
for key in db.kfk_en_item.indexed_data.keys():
new_value = db.kfk_en_item.indexed_data[key]
new_value["id"] = key
result.append(new_value)
result = sorted(result, key=lambda f: f["id"])
response = app.response_class(
response=json.dumps(result, ensure_ascii=False, indent=1),
status=200,
mimetype='application/json'
)
response.headers.add("Access-Control-Allow-Origin", "*")
return response

View File

@ -1,28 +0,0 @@
from flask_restful import Resource
from flask import current_app as app
from flask import request
import json
class Kingdom_Update(Resource):
def post(self):
from ..database import Database
db : Database = app.databases["Kingdom"]
try:
db.reload_data()
response = app.response_class(
response="update successful",
status=200,
mimetype='text/plain'
)
except:
response = app.response_class(
response="update failed",
status=500,
mimetype='text/plain'
)
response.headers.add("Access-Control-Allow-Origin", "*")
return response
def get(self):
return self.post()

View File

@ -1,161 +0,0 @@
import re
import math
def get_skill_materials(friend, kfk_kemono_skill, kfk_en_item):
items_total = []
kfk_skill = sorted(filter(lambda s: s["kemonosn"] == friend["sn"] and s["type"] == 2, kfk_kemono_skill.indexed_data.values()), key = lambda s: s["sn"])
for idx, s in enumerate(kfk_skill):
if len(s["item"]) == 0:
continue
items_total.append(
{
"lvl": s["skilllevel"],
"awakenReq": s["wakenlevel"],
"lvlReq": s["kemonolevel"],
"items": list([{"count": num, "name": kfk_en_item.get(item)["name"]} for item, num in zip(s["item"], s["itemnum"])])
}
)
return items_total
def get_awaken_materials(friend, kfk_kemono_waken, kfk_en_item):
items_total = []
kfk_waken = sorted(filter(lambda s: s["kemonosn"] == friend["sn"], kfk_kemono_waken.indexed_data.values()), key = lambda s: s["sn"])
for idx, s in enumerate(kfk_waken):
if len(s["item"]) == 0:
continue
items_total.append(
{
"lvl": idx+1,
"items": list([{"count": num, "name": kfk_en_item.get(item)["name"]} for item, num in zip(s["item"], s["itemnum"])])
}
)
return items_total
def calculate_stats(friend, awaken : int, level : int, trust_percent = 0, trustAttri = None):
stats_array = friend["attrScriptParam"]
max_levels = friend["maxLvl"]
max_awaken = friend["maxAwaken"]
awaken_step = max_awaken + 2
atk_ptr = 1 * awaken_step
satk_ptr = 2 * awaken_step
pdef_ptr = 3 * awaken_step
sdef_ptr = 4 * awaken_step
hp_ptr = 5 * awaken_step
speed_ptr = 6 * awaken_step
stats = {"patk":stats_array[atk_ptr], "satk":stats_array[satk_ptr], "pdef":stats_array[pdef_ptr], "sdef":stats_array[sdef_ptr], "hp":stats_array[hp_ptr], "speed":stats_array[speed_ptr]}
for i in range(awaken + 1):
if i == awaken:
max_level = level
else:
max_level = max_levels[i]
stats["patk"] += stats_array[atk_ptr + 1 + i] * (max_level-1)
stats["satk"] += stats_array[satk_ptr + 1 + i] * (max_level-1)
stats["pdef"] += stats_array[pdef_ptr + 1 + i] * (max_level-1)
stats["sdef"] += stats_array[sdef_ptr + 1 + i] * (max_level-1)
stats["hp"] += stats_array[hp_ptr + 1 + i] * (max_level-1)
stats["speed"] += stats_array[speed_ptr + 1 + i] * (max_level-1)
if trust_percent > 0 and trustAttri != None:
if trustAttri["attribute1"] == 1001:
stats["hp"] += trustAttri["attributeValue1"][trust_percent]
elif trustAttri["attribute1"] == 1021:
stats["patk"] += trustAttri["attributeValue1"][trust_percent]
elif trustAttri["attribute1"] == 1031:
stats["satk"] += trustAttri["attributeValue1"][trust_percent]
else:
print(trustAttri["attribute1"])
if trustAttri["attribute2"] == 1001:
stats["hp"] += trustAttri["attributeValue2"][trust_percent]
elif trustAttri["attribute2"] == 1021:
stats["patk"] += trustAttri["attributeValue2"][trust_percent]
elif trustAttri["attribute2"] == 1031:
stats["satk"] += trustAttri["attributeValue2"][trust_percent]
else:
print(trustAttri["attribute2"])
stats["patk"] = int(round(stats["patk"]))
stats["satk"] = int(round(stats["satk"]))
stats["pdef"] = int(round(stats["pdef"]))
stats["sdef"] = int(round(stats["sdef"]))
stats["hp"] = int(round(stats["hp"]))
stats["speed"] = int(round(stats["speed"]))
return stats
def get_stat_growth(friend, awaken : int):
stats_array = friend["attrScriptParam"]
max_awaken = friend["maxAwaken"]
awaken_step = max_awaken + 2
atk_ptr = 1 * awaken_step
satk_ptr = 2 * awaken_step
pdef_ptr = 3 * awaken_step
sdef_ptr = 4 * awaken_step
hp_ptr = 5 * awaken_step
speed_ptr = 6 * awaken_step
stats = {}
i = awaken
stats["patk"] += stats_array[atk_ptr + 1 + i]
stats["satk"] += stats_array[satk_ptr + 1 + i]
stats["pdef"] += stats_array[pdef_ptr + 1 + i]
stats["sdef"] += stats_array[sdef_ptr + 1 + i]
stats["hp"] += stats_array[hp_ptr + 1 + i]
stats["speed"] += stats_array[speed_ptr + 1 + i]
return stats
def clean_skill_string(input_string):
if input_string is None:
return None
color_tags_pattern = r"<color=#[A-Fa-f0-9]+>(.*?)<\/color>"
output_string = re.sub(color_tags_pattern, r"\1", input_string)
link_tags_pattern = r"<link=\".*?\">(.*?)<\/link>"
output_string = re.sub(link_tags_pattern, r"\1", output_string)
return output_string
def fill_miracle(input_string, miracle_level = 1, kemono_level = 0):
if input_string is None:
return ""
pattern = r"\{\{(.*?)\}\}"
def replace_match(match):
key : str = match.group(1).strip()
if key.startswith("skill | SkillDesc:"):
values = key.replace("skill | SkillDesc:", "").split(',')
values = [float(value) for value in values]
level_up_values = [
values[1],
values[1],
values[1],
values[2],
values[2],
values[2],
values[3],
values[3],
values[3],
values[4],
values[4],
values[4],
]
base_value = values[0]
level_up_value = sum(level_up_values[0:miracle_level-1]) + values[5]
level_bonus = (kemono_level - values[7]) * values[6]
return str(math.floor(base_value + level_up_value + level_bonus))
return "{{" + key + "}}"
output_string = re.sub(pattern, replace_match, input_string)
return clean_skill_string(output_string)

View File

@ -1,21 +0,0 @@
import json
class numeric:
def __init__(self, file_path:str, type:int):
self.indexed_data = {}
with open(file_path, "rt", encoding="utf-8") as f:
if type == 0:
values = json.load(f)["Data"]
for value in values:
self.indexed_data[value["sn"]] = value
if type == 1:
values = json.load(f)
for i in range(0, len(values["IDs"])):
id = values["IDs"][i]
self.indexed_data[id] = values["Data"][i]
def get(self, id):
if id not in self.indexed_data:
return None
return self.indexed_data[id]

View File

@ -1,7 +0,0 @@
class KingdomDB:
def __init__(self, app) -> None:
if "Nexon" in app.databases:
del app.databases["Nexon"]
app.databases["Nexon"] = self

View File

@ -1,45 +0,0 @@
import io
import os
import UnityPy
from UnityPy import enums
from UnityPy.files import ObjectReader
from PIL import Image
def convert(input_path : str, output_path : str, platform = enums.BuildTarget.WebGL, enable_read = False):
env = UnityPy.load(input_path)
for item in env.assets:
item._m_target_platform = int(platform)
for obj in env.objects:
if obj.type == enums.ClassIDType.Texture2D:
convert_texture(obj, platform)
if enable_read and obj.type == enums.ClassIDType.Mesh:
data = obj.read()
data.m_IsReadable = True
data.save()
os.makedirs(os.path.dirname(output_path), exist_ok=True)
with open(output_path, "wb") as f:
f.write(env.file.save(packer="lz4"))
def extract(input_path : str, output_path : str):
env = UnityPy.load(input_path)
for obj in env.objects:
if obj.type == enums.ClassIDType.Texture2D:
os.makedirs(output_path, exist_ok=True)
data = obj.read()
data.image.save(output_path + data.name + ".png", format="PNG")
def convert_texture(obj : ObjectReader, target_platform : enums.BuildTarget):
if target_platform == enums.BuildTarget.WebGL:
data = obj.read()
with io.BytesIO() as output:
data.image.save(output, format="PNG")
if data.image.width % 4 == 0 and data.image.height % 4 == 0:
data.m_TextureFormat = enums.TextureFormat.DXT5
else:
data.m_TextureFormat = enums.TextureFormat.ARGB32
data.image = Image.open(output)
data.save()

View File

@ -1,9 +0,0 @@
import aiohttp
async def download_bytes(url, session : aiohttp.ClientSession):
async with session.get(url) as resp:
return await resp.read()
async def download_text(url, session : aiohttp.ClientSession):
async with session.get(url) as resp:
return await resp.text()

View File

@ -1,3 +0,0 @@
def divide_chunks(l, n):
for i in range(0, len(l), n):
yield l[i:i + n]

View File

@ -1,48 +0,0 @@
from flask_restful import Resource
from flask import current_app as app, request, Request
import requests
def relayRequest(user_request : Request, url, headers):
with requests.Session() as session:
requests.utils.add_dict_to_cookiejar(session.cookies, user_request.cookies)
response = session.post(url, headers=headers, data=request.data)
result = app.response_class(
response=response.text,
status=200,
mimetype='application/json'
)
result.headers.add("Access-Control-Allow-Origin", "*")
return result
class LaunchProxy(Resource):
def post(self):
url = "https://apidgp-gameplayer.games.dmm.com/v5/launch/cl"
headers = {"User-Agent": "DMMGamePlayer5-Win/5.3.12 Electron/32.1.0",
"Client-App": "DMMGamePlayer5",
"Client-version": "5.3.12",
"Content-Type": "application/json"}
return relayRequest(request, url, headers)
class UpdateProxy(Resource):
def post(self):
url = "https://apidgp-gameplayer.games.dmm.com/v5/r2/launch/cl"
headers = {"User-Agent": "DMMGamePlayer5-Win/5.3.12 Electron/32.1.0",
"Client-App": "DMMGamePlayer5",
"Client-version": "5.3.12",
"Content-Type": "application/json"}
return relayRequest(request, url, headers)
class AgreementProxy(Resource):
def post(self):
url = "https://apidgp-gameplayer.games.dmm.com/v5/agreement/confirm/client"
headers = {"User-Agent": "DMMGamePlayer5-Win/5.3.12 Electron/32.1.0",
"Client-App": "DMMGamePlayer5",
"Client-version": "5.3.12",
"Content-Type": "application/json"}
return relayRequest(request, url, headers)