diff --git a/MTKruto b/MTKruto
index 51f5f05..14f6ef4 160000
--- a/MTKruto
+++ b/MTKruto
@@ -1 +1 @@
-Subproject commit 51f5f0582c417ace4c5f0988d083db325581e5b4
+Subproject commit 14f6ef49659e1995082ab565c6897d43275b8737
diff --git a/client_manager.ts b/client_manager.ts
index 1edce1d..1f5ef8e 100644
--- a/client_manager.ts
+++ b/client_manager.ts
@@ -23,11 +23,11 @@ import * as path from "std/path/mod.ts";
import { existsSync } from "std/fs/exists.ts";
import { unreachable } from "std/assert/unreachable.ts";
+import { InputError } from "mtkruto/0_errors.ts";
import { Mutex, Queue } from "mtkruto/1_utilities.ts";
import {
Client,
errors,
- InputError,
InvokeErrorHandler,
NetworkStatistics,
Update,
diff --git a/disallowed_functions.ts b/disallowed_functions.ts
index bb82308..3ad9da0 100644
--- a/disallowed_functions.ts
+++ b/disallowed_functions.ts
@@ -18,7 +18,6 @@
* along with this program. If not, see .
*/
-import { functions, name } from "mtkruto/mod.ts";
import { isMtprotoFunction } from "mtkruto/client/0_utilities.ts";
const DISALLOWED_FUNCTIONS = [
@@ -66,11 +65,11 @@ const DISALLOWED_FUNCTIONS = [
];
export function isFunctionDisallowed(function_: any) {
- if (function_ instanceof functions.ping) {
+ if (function_._ == "ping") {
return false;
}
- if (DISALLOWED_FUNCTIONS.includes(function_[name])) {
+ if (DISALLOWED_FUNCTIONS.includes(function_._)) {
return true;
}
diff --git a/tests/tl_json/deserialize_test.ts b/tests/tl_json/deserialize_test.ts
deleted file mode 100644
index 788d082..0000000
--- a/tests/tl_json/deserialize_test.ts
+++ /dev/null
@@ -1,138 +0,0 @@
-/**
- * MTKruto Server
- * Copyright (C) 2024 Roj
- *
- * This file is part of MTKruto Server.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-
-import { assertEquals, assertThrows } from "std/assert/mod.ts";
-
-import { functions, types } from "mtkruto/mod.ts";
-
-import { deserialize } from "../../tl_json.ts";
-
-Deno.test("errors on null, symbol, undefined", () => {
- assertThrows(() => deserialize(null));
- assertThrows(() => deserialize(Symbol.for("err")));
- assertThrows(() => deserialize(undefined));
-});
-
-Deno.test("returns strings as-is", () => {
- for (let i = 0; i < 100; ++i) {
- const id = crypto.randomUUID();
- assertEquals(deserialize(id), id);
- }
-});
-
-Deno.test("returns true as-is", () => {
- assertEquals(deserialize(true), true);
-});
-
-Deno.test("returns false as undefined", () => {
- assertEquals(deserialize(false), undefined);
-});
-
-Deno.test("errors on objects with no _ field", () => {
- assertThrows(() => deserialize({}));
- assertThrows(() => deserialize({ a: 1 }));
-});
-
-Deno.test("errors on BigInt objects with no value field", () => {
- assertThrows(() => deserialize({ _: "bigint" }));
-});
-
-Deno.test("errors on BigInt objects with non-string value field", () => {
- assertThrows(() => deserialize({ _: "bigint", value: 1 }));
-});
-Deno.test("errors on BigInt objects with empty value field", () => {
- assertThrows(() => deserialize({ _: "bigint", value: "" }));
-});
-
-Deno.test("properly deserializes BigInt objects", () => {
- for (let i = 0; i < 1000; ++i) {
- const bi = BigInt(Math.ceil(Math.random() * 1000));
- assertEquals(deserialize({ _: "bigint", value: String(bi) }), bi);
- }
-});
-Deno.test("errors on bytes objects with no value field", () => {
- assertThrows(() => deserialize({ _: "bytes" }));
-});
-
-Deno.test("errors on bytes objects with non-string value field", () => {
- assertThrows(() => deserialize({ _: "bytes", value: 1 }));
-});
-Deno.test("errors on bytes objects with empty value field", () => {
- assertThrows(() => deserialize({ _: "bytes", value: "" }));
-});
-
-Deno.test("properly deserializes bytes objects", () => {
- const enc = new TextEncoder();
- for (let i = 0; i < 1000; ++i) {
- const bytes = Math.ceil(Math.random() * 1000) + "";
- assertEquals(
- deserialize({ _: "bytes", value: btoa(bytes) }),
- enc.encode(bytes),
- );
- }
-});
-
-Deno.test("properly deserializes functions", () => {
- for (let i = 0; i < 1000; ++i) {
- const pingId = BigInt(1 + Math.ceil(Math.random() * 1000));
- assertEquals(
- deserialize({ _: "ping", ping_id: { _: "bigint", value: pingId + "" } }),
- new functions.ping({ ping_id: pingId }),
- );
- }
-
- const actual = {
- _: "messages.sendMedia",
- random_id: {
- _: "bigint",
- value: "123",
- },
- peer: {
- _: "inputPeerSelf",
- },
- media: {
- _: "inputMediaPhoto",
- id: {
- _: "inputPhoto",
- id: { _: "bigint", value: "123" },
- access_hash: { _: "bigint", value: "123" },
- file_reference: { _: "bytes", value: btoa("R") },
- },
- spoiler: true,
- },
- message: "Test",
- silent: false,
- };
- const expected = new functions.messages.sendMedia({
- random_id: 123n,
- peer: new types.InputPeerSelf(),
- media: new types.InputMediaPhoto({
- id: new types.InputPhoto({
- id: 123n,
- access_hash: 123n,
- file_reference: new Uint8Array([0x52]),
- }),
- spoiler: true,
- }),
- message: "Test",
- silent: undefined,
- });
- assertEquals(deserialize(actual), expected);
-});
diff --git a/tests/tl_json/serialize_test.ts b/tests/tl_json/serialize_test.ts
deleted file mode 100644
index 20bc064..0000000
--- a/tests/tl_json/serialize_test.ts
+++ /dev/null
@@ -1,114 +0,0 @@
-/**
- * MTKruto Server
- * Copyright (C) 2024 Roj
- *
- * This file is part of MTKruto Server.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-
-import { assertEquals } from "std/assert/mod.ts";
-import { encodeBase64 } from "std/encoding/base64.ts";
-
-import { functions, types } from "mtkruto/mod.ts";
-
-import { serialize } from "../../tl_json.ts";
-
-Deno.test("string", () => {
- assertEquals(serialize("string"), "string");
-});
-
-Deno.test("number", () => {
- assertEquals(serialize("number"), "number");
-});
-
-Deno.test("bigint", () => {
- assertEquals(serialize(123n), { _: "bigint", value: "123" });
-});
-
-Deno.test("boolean", () => {
- assertEquals(serialize(true), true);
- assertEquals(serialize(false), false);
-});
-
-Deno.test("undefined", () => {
- assertEquals(serialize(undefined), false);
-});
-
-Deno.test("bytes", () => {
- const enc = new TextEncoder();
- const bytes = enc.encode("Hello, world!");
- assertEquals(serialize(bytes), { _: "bytes", value: encodeBase64(bytes) });
-});
-
-Deno.test("object", () => {
- const actual = new functions.messages.sendMedia({
- random_id: 123n,
- peer: new types.InputPeerSelf(),
- media: new types.InputMediaPhoto({
- id: new types.InputPhoto({
- id: 123n,
- access_hash: 123n,
- file_reference: new Uint8Array([0x52]),
- }),
- spoiler: true,
- }),
- message: "Test",
- silent: undefined,
- });
- const expected = {
- _: "messages.sendMedia",
- background: false,
- clear_draft: false,
- entities: false,
- invert_media: false,
- media: {
- _: "inputMediaPhoto",
- id: {
- _: "inputPhoto",
- access_hash: {
- _: "bigint",
- value: "123",
- },
- file_reference: {
- _: "bytes",
- value: "Ug==",
- },
- id: {
- _: "bigint",
- value: "123",
- },
- },
- spoiler: true,
- ttl_seconds: false,
- },
- message: "Test",
- noforwards: false,
- peer: {
- _: "inputPeerSelf",
- },
- quick_reply_shortcut: false,
- random_id: {
- _: "bigint",
- value: "123",
- },
- reply_markup: false,
- reply_to: false,
- schedule_date: false,
- send_as: false,
- silent: false,
- update_stickersets_order: false,
- };
- assertEquals(serialize(actual), expected);
-});
diff --git a/tests/transform_test.ts b/tests/transform_test.ts
index aa7bb66..b07812e 100644
--- a/tests/transform_test.ts
+++ b/tests/transform_test.ts
@@ -24,6 +24,12 @@ import { transform } from "../transform.ts";
Deno.test("transform", () => {
const date = new Date();
- const a = { _: { _: { _: date, a: [date, date] } } };
+ const bigint = 123123n;
+ const buffer = crypto.getRandomValues(new Uint8Array(1024));
+ const a = {
+ _: {
+ _: { _: date, a: [buffer, date, date], bigint, c: bigint, x: buffer },
+ },
+ };
assertEquals(transform(transform(a)), a);
});
diff --git a/tl_json.ts b/tl_json.ts
deleted file mode 100644
index d86a7ac..0000000
--- a/tl_json.ts
+++ /dev/null
@@ -1,143 +0,0 @@
-/**
- * MTKruto Server
- * Copyright (C) 2024 Roj
- *
- * This file is part of MTKruto Server.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-
-import { decodeBase64, encodeBase64 } from "std/encoding/base64.ts";
-
-import { functions, InputError, name, types } from "mtkruto/mod.ts";
-
-function collectObjects(types: any, map: Record) {
- for (const type of Object.values(types)) {
- const name_: string | undefined = (type as any)[name];
- if (name_) {
- map[name_] = type;
- }
- if ((type as any).constructor == Object) {
- collectObjects(type, map);
- }
- }
-}
-
-export const typeNameMap = {} as Record;
-export const functionNameMap = {} as Record;
-collectObjects(types, typeNameMap);
-collectObjects(functions, functionNameMap);
-
-export function deserialize(value: any): any {
- if (typeof value === "string") {
- return value;
- } else if (typeof value === "number") {
- return value;
- } else if (typeof value === "boolean") {
- return value ? true : undefined;
- } else if (typeof value === "object") {
- if (!("_" in value)) {
- throw new InputError("Expected object to contain the field _");
- }
- if (typeof value._ !== "string") {
- throw new InputError("Expected the _ field to be string");
- }
- if (value._ == "bigint") {
- if (!("value" in value)) {
- throw new InputError(
- "Expected BigInt object to include the field value",
- );
- }
- if (typeof value.value !== "string") {
- throw new InputError("Expected BigInt value to be string");
- }
- const v = value.value.trim();
- if (!v.trim()) {
- throw new InputError("Expected BigInt value to be non-empty");
- }
- try {
- return BigInt(v);
- } catch {
- throw new InputError(`Invalid BigInt: ${v}`);
- }
- }
- if (value._ == "bytes") {
- if (!("value" in value)) {
- throw new InputError(
- "Expected bytes object to include the field value",
- );
- }
- if (typeof value.value !== "string") {
- throw new InputError("Expected bytes value to be string");
- }
- const v = value.value.trim();
- if (!v.trim()) {
- throw new InputError("Expected bytes value to be non-empty");
- }
- try {
- return decodeBase64(v);
- } catch {
- throw new InputError("Expected bytes value to be vaild Base64");
- }
- }
-
- if (!(value._ in typeNameMap) && !(value._ in functionNameMap)) {
- throw new InputError(`Invalid type: ${value._}`);
- }
-
- // @ts-ignore: no idea why it errors, but trust me it works
- const params = Object.entries(value)
- .filter(([k]) => k != "_")
- .map(([k, v]) => [k, deserialize(v)]);
- // @ts-ignore: too complex to represent, I feel you
- const constructor = value._ in typeNameMap
- ? typeNameMap[value._]
- : functionNameMap[value._];
- return new constructor(Object.fromEntries(params));
- } else {
- throw new InputError(`Unexpected value: ${typeof value}`);
- }
-}
-
-export function serialize(value: any): any {
- if (typeof value === "string") {
- return value;
- } else if (typeof value === "number") {
- return value;
- } else if (typeof value === "boolean") {
- return value;
- } else if (value === undefined) {
- return false;
- } else if (typeof value === "bigint") {
- return {
- _: "bigint",
- value: value + "",
- };
- } else if (value instanceof Uint8Array) {
- return {
- _: "bytes",
- value: encodeBase64(value),
- };
- }
-
- // @ts-ignore: please
- const params = Object.entries(value)
- .filter(([k]) => k !== "__R")
- .map(([k, v]) => [k, serialize(v)]);
-
- return {
- _: value.constructor[name],
- ...Object.fromEntries(params),
- };
-}
diff --git a/transform.ts b/transform.ts
index 2ffd3fc..e2b95de 100644
--- a/transform.ts
+++ b/transform.ts
@@ -18,6 +18,8 @@
* along with this program. If not, see .
*/
+import { decodeBase64, encodeBase64 } from "std/encoding/base64.ts";
+
/**
* Utility function to transform dates in objects into a JSON-(de)serializable format and vice-verca.
*/
@@ -27,14 +29,28 @@ export function transform(a: any) {
if (a[key] != null && typeof a[key] === "object") {
if (a[key] instanceof Date) {
a[key] = { _: "date", value: a[key].toJSON() };
+ } else if (a[key] instanceof Uint8Array) {
+ a[key] = { _: "bytes", value: encodeBase64(a[key]) };
} else if (
- "_" in a[key] && a[key] == "date" && "value" in a[key] &&
+ "_" in a[key] && a[key]._ == "date" && "value" in a[key] &&
typeof a[key].value === "string"
) {
a[key] = new Date(a[key].value);
+ } else if (
+ "_" in a[key] && a[key]._ == "bigint" && "value" in a[key] &&
+ typeof a[key].value === "string"
+ ) {
+ a[key] = BigInt(a[key].value);
+ } else if (
+ "_" in a[key] && a[key]._ == "bytes" && "value" in a[key] &&
+ typeof a[key].value === "string"
+ ) {
+ a[key] = decodeBase64(a[key].value);
} else {
transform(a[key]);
}
+ } else if (typeof a[key] === "bigint") {
+ a[key] = { _: "bigint", value: String(a[key]) };
}
}
}
diff --git a/worker.ts b/worker.ts
index 928899b..327bbfc 100644
--- a/worker.ts
+++ b/worker.ts
@@ -26,11 +26,10 @@ import { existsSync } from "std/fs/mod.ts";
import { InputError } from "mtkruto/0_errors.ts";
import { setLogVerbosity } from "mtkruto/1_utilities.ts";
-import { functions, setLoggingProvider, types } from "mtkruto/mod.ts";
+import { errors, isValidType, setLoggingProvider } from "mtkruto/mod.ts";
import { transform } from "./transform.ts";
import { fileLogger } from "./file_logger.ts";
-import { deserialize, serialize } from "./tl_json.ts";
import { isFunctionDisallowed } from "./disallowed_functions.ts";
import { ClientManager, ClientStats } from "./client_manager.ts";
import { ALLOWED_METHODS, AllowedMethod } from "./allowed_methods.ts";
@@ -60,9 +59,9 @@ addEventListener("message", async (e) => {
status: 400,
headers: { "x-error-type": "input" },
}];
- } else if (err instanceof types.Rpc_error) {
- result = [err.error_message, {
- status: err.error_code,
+ } else if (err instanceof errors.TelegramError) {
+ result = [err.errorMessage, {
+ status: err.errorCode,
headers: { "x-error-type": "rpc" },
}];
} else {
@@ -207,15 +206,16 @@ async function invoke(
id: string,
function_: any,
): Promise> {
- const function__ = deserialize(function_);
- if (!(function__ instanceof functions.Function)) {
- throw new InputError("Expected a function");
+ function_ = transform(function_);
+ console.log(function_);
+ if (!isValidType(function_)) {
+ throw new InputError("Invalid function");
}
- if (isFunctionDisallowed(function__)) {
+ if (isFunctionDisallowed(function_)) {
throw new InputError("Unallowed function");
}
const client = await clientManager.getClient(id);
- const result = serialize(await client.invoke(function__));
+ const result = transform(await client.invoke(function_));
if (result !== undefined) {
return [result];
} else {