Converter: cover real Ren'Py games better, add folder import and dry run
Checked against six real games (Ren'Py 7.5 and 8.x): - nested labels and named menus become jump anchors inside label bodies - call with arguments and labels with parameters (dynamically scoped) - frame-by-frame ATL images, Movie frames, layeredimage, camera, Shake, custom audio channels via register_channel, show text with expressions - generator expressions in Python, no SyntaxWarning noise from decompiled code Also: - app.import_dir converts an unpacked game folder without changing it - app.converter.analyze dry-runs the script part of the converter - imports use their own "importing" status so a worker restart can't hijack them Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
35033fb86c
commit
a97b46079e
18 files changed
+3456
-2695
No files matched your search
+118
-118
@@ -1,118 +1,118 @@
|
||||
export interface Role {
|
||||
id: number;
|
||||
name: string;
|
||||
permissions: string[];
|
||||
is_system: boolean;
|
||||
}
|
||||
|
||||
export interface User {
|
||||
id: number;
|
||||
username: string;
|
||||
role: Role;
|
||||
is_active: boolean;
|
||||
must_change_password: boolean;
|
||||
must_change_username: boolean;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface Me extends User {
|
||||
permissions: string[];
|
||||
}
|
||||
|
||||
export interface Version {
|
||||
id: number;
|
||||
game_id: number;
|
||||
parent_id: number | null;
|
||||
owner: { id: number; username: string } | null;
|
||||
name: string;
|
||||
description: string;
|
||||
status: "uploading" | "queued" | "converting" | "ready" | "failed";
|
||||
progress: string;
|
||||
size_bytes: number;
|
||||
created_at: string;
|
||||
report?: Record<string, any> | null;
|
||||
}
|
||||
|
||||
export interface Game {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
tags: string[];
|
||||
engine: string;
|
||||
cover_hash: string | null;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface GameDetail extends Game {
|
||||
versions: Version[];
|
||||
}
|
||||
|
||||
export class ApiError extends Error {
|
||||
constructor(public status: number, message: string) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
async function request<T>(method: string, url: string, body?: unknown): Promise<T> {
|
||||
const r = await fetch(url, {
|
||||
method,
|
||||
credentials: "same-origin",
|
||||
headers: body !== undefined ? { "Content-Type": "application/json" } : undefined,
|
||||
body: body !== undefined ? JSON.stringify(body) : undefined,
|
||||
});
|
||||
if (!r.ok) {
|
||||
let msg = r.statusText;
|
||||
try {
|
||||
const data = await r.json();
|
||||
msg = typeof data.detail === "string" ? data.detail : JSON.stringify(data.detail);
|
||||
} catch {
|
||||
/* not json */
|
||||
}
|
||||
if (r.status === 401) window.dispatchEvent(new Event("pg-unauthorized"));
|
||||
throw new ApiError(r.status, msg);
|
||||
}
|
||||
return r.status === 204 ? (undefined as T) : r.json();
|
||||
}
|
||||
|
||||
export const api = {
|
||||
get: <T>(url: string) => request<T>("GET", url),
|
||||
post: <T>(url: string, body?: unknown) => request<T>("POST", url, body ?? {}),
|
||||
put: <T>(url: string, body?: unknown) => request<T>("PUT", url, body ?? {}),
|
||||
patch: <T>(url: string, body?: unknown) => request<T>("PATCH", url, body ?? {}),
|
||||
del: (url: string) => request<void>("DELETE", url),
|
||||
};
|
||||
|
||||
/** Upload a raw body with progress (fetch has no upload progress). */
|
||||
export function uploadRaw<T>(method: string, url: string, file: Blob,
|
||||
onProgress: (fraction: number) => void): Promise<T> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open(method, url);
|
||||
xhr.setRequestHeader("Content-Type", "application/octet-stream");
|
||||
xhr.upload.onprogress = (e) => e.lengthComputable && onProgress(e.loaded / e.total);
|
||||
xhr.onload = () => {
|
||||
let data: any = null;
|
||||
try {
|
||||
data = JSON.parse(xhr.responseText);
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
if (xhr.status >= 200 && xhr.status < 300) resolve(data);
|
||||
else reject(new ApiError(xhr.status, data?.detail ?? xhr.statusText));
|
||||
};
|
||||
xhr.onerror = () => reject(new ApiError(0, "Сетевая ошибка"));
|
||||
xhr.send(file);
|
||||
});
|
||||
}
|
||||
|
||||
export function formatSize(bytes: number): string {
|
||||
if (bytes < 1024) return `${bytes} Б`;
|
||||
const units = ["КБ", "МБ", "ГБ", "ТБ"];
|
||||
let v = bytes / 1024;
|
||||
let i = 0;
|
||||
while (v >= 1024 && i < units.length - 1) {
|
||||
v /= 1024;
|
||||
i++;
|
||||
}
|
||||
return `${v.toFixed(v < 10 ? 1 : 0)} ${units[i]}`;
|
||||
}
|
||||
export interface Role {
|
||||
id: number;
|
||||
name: string;
|
||||
permissions: string[];
|
||||
is_system: boolean;
|
||||
}
|
||||
|
||||
export interface User {
|
||||
id: number;
|
||||
username: string;
|
||||
role: Role;
|
||||
is_active: boolean;
|
||||
must_change_password: boolean;
|
||||
must_change_username: boolean;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface Me extends User {
|
||||
permissions: string[];
|
||||
}
|
||||
|
||||
export interface Version {
|
||||
id: number;
|
||||
game_id: number;
|
||||
parent_id: number | null;
|
||||
owner: { id: number; username: string } | null;
|
||||
name: string;
|
||||
description: string;
|
||||
status: "uploading" | "queued" | "converting" | "importing" | "ready" | "failed";
|
||||
progress: string;
|
||||
size_bytes: number;
|
||||
created_at: string;
|
||||
report?: Record<string, any> | null;
|
||||
}
|
||||
|
||||
export interface Game {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
tags: string[];
|
||||
engine: string;
|
||||
cover_hash: string | null;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface GameDetail extends Game {
|
||||
versions: Version[];
|
||||
}
|
||||
|
||||
export class ApiError extends Error {
|
||||
constructor(public status: number, message: string) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
async function request<T>(method: string, url: string, body?: unknown): Promise<T> {
|
||||
const r = await fetch(url, {
|
||||
method,
|
||||
credentials: "same-origin",
|
||||
headers: body !== undefined ? { "Content-Type": "application/json" } : undefined,
|
||||
body: body !== undefined ? JSON.stringify(body) : undefined,
|
||||
});
|
||||
if (!r.ok) {
|
||||
let msg = r.statusText;
|
||||
try {
|
||||
const data = await r.json();
|
||||
msg = typeof data.detail === "string" ? data.detail : JSON.stringify(data.detail);
|
||||
} catch {
|
||||
/* not json */
|
||||
}
|
||||
if (r.status === 401) window.dispatchEvent(new Event("pg-unauthorized"));
|
||||
throw new ApiError(r.status, msg);
|
||||
}
|
||||
return r.status === 204 ? (undefined as T) : r.json();
|
||||
}
|
||||
|
||||
export const api = {
|
||||
get: <T>(url: string) => request<T>("GET", url),
|
||||
post: <T>(url: string, body?: unknown) => request<T>("POST", url, body ?? {}),
|
||||
put: <T>(url: string, body?: unknown) => request<T>("PUT", url, body ?? {}),
|
||||
patch: <T>(url: string, body?: unknown) => request<T>("PATCH", url, body ?? {}),
|
||||
del: (url: string) => request<void>("DELETE", url),
|
||||
};
|
||||
|
||||
/** Upload a raw body with progress (fetch has no upload progress). */
|
||||
export function uploadRaw<T>(method: string, url: string, file: Blob,
|
||||
onProgress: (fraction: number) => void): Promise<T> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open(method, url);
|
||||
xhr.setRequestHeader("Content-Type", "application/octet-stream");
|
||||
xhr.upload.onprogress = (e) => e.lengthComputable && onProgress(e.loaded / e.total);
|
||||
xhr.onload = () => {
|
||||
let data: any = null;
|
||||
try {
|
||||
data = JSON.parse(xhr.responseText);
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
if (xhr.status >= 200 && xhr.status < 300) resolve(data);
|
||||
else reject(new ApiError(xhr.status, data?.detail ?? xhr.statusText));
|
||||
};
|
||||
xhr.onerror = () => reject(new ApiError(0, "Сетевая ошибка"));
|
||||
xhr.send(file);
|
||||
});
|
||||
}
|
||||
|
||||
export function formatSize(bytes: number): string {
|
||||
if (bytes < 1024) return `${bytes} Б`;
|
||||
const units = ["КБ", "МБ", "ГБ", "ТБ"];
|
||||
let v = bytes / 1024;
|
||||
let i = 0;
|
||||
while (v >= 1024 && i < units.length - 1) {
|
||||
v /= 1024;
|
||||
i++;
|
||||
}
|
||||
return `${v.toFixed(v < 10 ? 1 : 0)} ${units[i]}`;
|
||||
}
|
||||
Reference in new issue
Block a user