final update?

This commit is contained in:
tezlm 2023-09-10 01:20:41 -07:00
parent 00000520ae
commit 0000053f14
Signed by: tezlm
GPG key ID: 649733FCD94AFBBA
28 changed files with 75 additions and 352 deletions

View file

@ -2,9 +2,15 @@
A new in-development matrix library because I dont like matrix-js-sdk.
For now, discount is unstable and being developed alongside discard.
Don't use this library. It's mostly for personal use, and isn't very
good. I really don't recommend using it. For example:
> Warning: Discount is under **heavy** development and things will change!
- node support isn't planned - only deno, bun, and runtimes that can
natively use typescript will work.
- e2ee does not exist
- i only really use this for quick bots and testing
- features are missing, like media upload/download and typing
- this is *archived and unmaintained*.
## Example

View file

@ -1,16 +1,10 @@
{
"name": "discount.ts",
"version": "0.1.4",
"version": "0.2.0",
"description": "another matrix library",
"author": "tezlm",
"type": "module",
"main": "src/index.ts",
"types": "build/src/index.d.ts",
"scripts": {
"build": "tsc",
"clean": "rm build -rf",
"test": "echo tests are disabled for now && exit 1 && tsc && node ./build/test/index.js"
},
"keywords": [
"matrix"
],
@ -24,7 +18,6 @@
"@types/node": "^18.11.9",
"better-sqlite3": "^7.6.2",
"bun-types": "^0.2.2",
"idb": "^7.1.1",
"typescript": "^4.8.4"
"idb": "^7.1.1"
}
}

View file

@ -1,4 +1,4 @@
import type Client from "./client";
import type Client from "./client.ts";
export default class AccountData extends Map<string, any> {
constructor(public client: Client) {

View file

@ -1,17 +1,17 @@
import Emitter from "./emitter";
import Fetcher from "./fetcher";
import type * as api from "./api";
import Emitter from "./emitter.ts";
import Fetcher from "./fetcher.ts";
import type * as api from "./api.ts";
import AccountData from "./account-data";
import Users from "./users";
import Rooms from "./rooms";
import Room from "./room";
import Space from "./space";
import Invite from "./invite";
import Timeline from "./timeline";
import { Event, StateEvent, EphemeralEvent, LocalEvent } from "./event";
import AccountData from "./account-data.ts";
import Users from "./users.ts";
import Rooms from "./rooms.ts";
import Room from "./room.ts";
import Space from "./space.ts";
import Invite from "./invite.ts";
import Timeline from "./timeline.ts";
import { Event, StateEvent, EphemeralEvent, LocalEvent } from "./event.ts";
import Database, { MemoryDB } from "./persist";
import Database, { MemoryDB } from "./persist.ts";
interface StatePersist {
options: any,
@ -291,7 +291,8 @@ export default class Client extends Emitter<ClientEvents> {
state: invite.state,
});
}
// this is awful and doesn't use transactions
await Promise.all([
this.persister.deleteAll("accountData"),
this.persister.deleteAll("rooms"),

View file

@ -1,9 +1,9 @@
import type Room from "./room";
import type Member from "./member";
import type Client from "./client";
import type { RawEvent, RawStateEvent, RawEphemeralEvent } from "./api";
import Relations from "./relations";
import { intern } from "./util";
import type Room from "./room.ts";
import type Member from "./member.ts";
import type Client from "./client.ts";
import type { RawEvent, RawStateEvent, RawEphemeralEvent } from "./api.ts";
import Relations from "./relations.ts";
import { intern } from "./util.ts";
export interface RawLocalEvent {
type: string,

View file

@ -1,7 +1,7 @@
import type Client from "./client";
import type Room from "./room";
import { Event, StateEvent, Relation } from "./event";
import Timeline from "./timeline";
import type Client from "./client.ts";
import type Room from "./room.ts";
import { Event, StateEvent, Relation } from "./event.ts";
import Timeline from "./timeline.ts";
function parseRelations(event: Event): Array<{ relType: string, eventId: string, key?: string, fallback: boolean }> {
const cont = event.content["m.relates_to"];

View file

@ -1,4 +1,4 @@
import type * as api from "./api.js"
import type * as api from "./api.ts"
interface FetchOptions {
method?: string,

View file

@ -1,5 +1,5 @@
export { default as Client } from "./client";
export { default as Room } from "./room";
export { Event, StateEvent, LocalEvent } from "./event";
export { default as Member } from "./member";
export * as persist from "./persist";
export { default as Client } from "./client.ts";
export { default as Room } from "./room.ts";
export { Event, StateEvent, LocalEvent } from "./event.ts";
export { default as Member } from "./member.ts";
export * as persist from "./persist.ts";

View file

@ -1,5 +1,5 @@
import type Client from "./client";
import type * as api from "./api";
import type Client from "./client.ts";
import type * as api from "./api.ts";
export default class Invite {
public state: Array<api.StrippedState> = [];

View file

@ -1,8 +1,8 @@
import type Client from "./client";
import type Room from "./room";
import type { StateEvent } from "./event";
// import User from "./user";
import { intern } from "./util";
import type Client from "./client.ts";
import type Room from "./room.ts";
import type { StateEvent } from "./event.ts";
// import User from "./user.ts";
import { intern } from "./util.ts";
export type Membership = "join" | "knock" | "invite" | "leave" | "ban";

View file

@ -1,7 +1,7 @@
import type Client from "./client";
import type Room from "./room";
import { StateEvent } from "./event";
import Member, { Membership } from "./member";
import type Client from "./client.ts";
import type Room from "./room.ts";
import { StateEvent } from "./event.ts";
import Member, { Membership } from "./member.ts";
export default class Members extends Map<string, Member> {
private requests = new Map<string, Promise<any>>;

View file

@ -1,5 +1,5 @@
import type Client from "./client";
import type Room from "./room";
import type Client from "./client.ts";
import type Room from "./room.ts";
interface RawPowerLevels {
redact?: number,

View file

@ -1,5 +1,5 @@
import { Event, Relation } from "./event"
import type * as api from "./api";
import { Event, Relation } from "./event.ts"
import type * as api from "./api.ts";
export default class Relations extends Array<Relation> {
private nextBatch: string;

View file

@ -1,9 +1,9 @@
import type Client from "./client";
import { StateEvent, LocalEvent } from "./event";
// import Emitter from "./emitter";
import Members from "./members";
import Events from "./events";
import Power from "./power";
import type Client from "./client.ts";
import { StateEvent, LocalEvent } from "./event.ts";
// import Emitter from "./emitter.ts";
import Members from "./members.ts";
import Events from "./events.ts";
import Power from "./power.ts";
type JoinRule = "invite" | "public" | "knock" | "restricted" | "knock_restricted";

View file

@ -1,5 +1,5 @@
import type Client from "./client";
import type Room from "./room";
import type Client from "./client.ts";
import type Room from "./room.ts";
interface RoomCreateOptions {
creationContent?: { [key: string]: any },

View file

@ -1,5 +1,5 @@
import Room from "./room";
import * as api from "./api";
import Room from "./room.ts";
import * as api from "./api.ts";
class HierarchyRoom {
id: string;

View file

@ -1,8 +1,8 @@
import type Room from "./room";
import type Client from "./client";
import type Events from "./events";
import type * as api from "./api";
import { Event, StateEvent } from "./event";
import type Room from "./room.ts";
import type Client from "./client.ts";
import type Events from "./events.ts";
import type * as api from "./api.ts";
import { Event, StateEvent } from "./event.ts";
export default class Timeline extends Array {
public client: Client;

View file

@ -1,5 +1,5 @@
import type Client from "./client";
import { UserData } from "./api";
import type Client from "./client.ts";
import { UserData } from "./api.ts";
export default class User {
public name: string;

View file

@ -1,5 +1,5 @@
import type Client from "./client";
import User from "./user";
import type Client from "./client.ts";
import User from "./user.ts";
export default class Users extends Map<string, User | null> {
constructor(

View file

@ -1,19 +0,0 @@
import type Client from "../src/client";
const Olm: any = {};
export class Device {
private account: any;
constructor(
public client: Client,
public id: string,
) {
this.account = new Olm.Account();
}
generateOneTimeKeys() {
const maxKeys = this.account.max_number_of_one_time_keys();
this.account.generate_one_time_keys(maxKeys);
return JSON.parse(this.account.one_time_keys());
}
}

View file

@ -1,135 +0,0 @@
// matrix push rules are awful to parse, so many things make me go "why" or "no stopp dont do that"
const ruleTypes = [
"override",
"content",
"room",
"sender",
"underride",
];
function parseRule(type, raw) {
const rule = {
type,
id: raw.rule_id,
actions: raw.actions,
conditions: [],
};
switch (type) {
case "override":
case "underride":
rule.conditions = raw.conditions;
break;
case "content":
if (!raw.pattern) return null;
rule.conditions.push({
kind: "event_match",
pattern: raw.pattern,
key: "content.body",
});
break;
case "room":
if (!rule.id) return null;
rule.conditions.push({
kind: "event_match",
pattern: raw.pattern,
key: "room_id",
});
break;
case "sender":
if (!rule.id) return null;
rule.conditions.push({
kind: "event_match",
pattern: raw.pattern,
key: "sender",
});
break;
}
return rule;
}
function parseGlob(glob) {
return escapeRegex(glob)
.replace(/\\\*/g, ".*")
.replace(/\\\?/g, ".")
}
function escapeRegex(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function matchesRule(cond, event) {
if (cond.kind === "event_match") {
let obj = event.raw;
for (let key of cond.key.split(".")) obj = obj?.[key];
if (typeof obj !== "string") return false;
if (cond.value) return obj === cond.value;
if (!cond.pattern) return false;
if (cond.key === "content.body") {
return new RegExp(`(^|\\W)${parseGlob(cond.pattern)}(\\W|$)`, "i").test(obj);
} else {
return new RegExp(`^${parseGlob(cond.pattern)}$`, "i").test(obj);
}
} else if (cond.kind === "contains_display_name") {
if (!event.content.body || typeof event.content.body !== "string") return;
const myname = event.room.members.get(state.userId)?.displayname;
if (!myname) return false;
return new RegExp(`(^|\\W)${escapeRegex(myname)}(\\W|$)`, "i").test(event.content.body);
} else if (cond.kind === "room_member_count") {
if (!cond.is) return false;
const count = event.room.members.with("join").length;
const match = cond.is.match(/^([=<>]*)(\d*)$/);
if (!match) return false;
const ineq = match[1];
const val = parseInt(match[2], 10);
switch(ineq) {
case "":
case "==": return count === val;
case "<": return count < val;
case ">": return count > val;
case "<=": return count <= val;
case ">=": return count >= val;
default: return false;
}
} else if (cond.kind === "sender_notification_permission") {
if (!cond.key) return false;
const userPower = event.room.power.getUser(event.sender.userId);
const pingPower = event.room.power.notifications?.[cond.key] ?? 50;
return userPower >= pingPower;
}
return true;
}
export default class PushRules {
constructor(rules) {
this.setRules(rules);
}
setRules(rules) {
this.rules = [];
for (let type of ruleTypes) {
for (let rule of rules[type] ?? []) {
if (!rule.enabled) continue;
this.rules.push(parseRule(type, rule));
}
}
}
parse(event) {
if (event.sender.userId === state.userId) return null;
for (let rule of this.rules) {
let passed = true;
for (let cond of rule.conditions) {
if (!matchesRule(cond, event)) {
passed = false;
break;
}
}
if (passed) return rule;
}
return null;
}
}

View file

@ -1,26 +0,0 @@
import { readdir } from "fs/promises";
import { fileURLToPath } from "url";
import { dirname, join } from "path";
const dir = join(dirname(fileURLToPath(import.meta.url)), "units");
let pass = 0, fail = 0;
for (let file of (await readdir(dir)).sort()) {
console.log(`test \x1b[1m${file}\x1b[0m`);
const mod = await import(join(dir, file));
mod.test(tester);
console.log("");
}
console.log(`finished with ${pass} \x1b[32mpass\x1b[0m and ${fail} \x1b[31mfail\x1b[0m`);
function tester(description: string, run: () => any) {
try {
run();
console.log(` \x1b[32mpass\x1b[0m ${description}`);
pass++;
} catch {
console.log(` \x1b[31mfail\x1b[0m ${description}`);
fail++;
}
}

View file

@ -1,49 +0,0 @@
import { Event, StateEvent} from "../../src/event";
import { RawStateEvent, RawEvent } from "../../src/api";
import room from "./room.js";
import Client from "../../src/client.js";
const client = new Client({
token: "no",
baseUrl: "no",
userId: "no",
});
let i = 0;
export function makeEvent(content: any, data: Partial<RawEvent> = {}): Event {
return new Event(room, {
event_id: `$event${i++}`,
sender: "@user:example.org",
unsigned: {},
origin_server_ts: 0,
type: "m.room.message",
content,
...data
});
}
export function makeStateEvent(content: any, data: Partial<RawStateEvent> = {}): StateEvent {
return new StateEvent(room, {
event_id: `$event${i++}`,
sender: "@user:example.org",
unsigned: {},
origin_server_ts: 0,
type: "m.room.message",
content,
state_key: data.state_key || "",
...data
});
}
export const events = [
makeEvent({ body: "foo" }),
makeEvent({ body: "bar" }),
makeEvent({ body: "baz" }),
makeEvent({ body: "qux" }),
];
export const stateEvents = [
makeStateEvent({ name: "test room" }, { type: "m.room.name", state_key: "" }),
makeStateEvent({ topic: "a topic for the test room" }, { type: "m.room.topic", state_key: "" }),
makeStateEvent({ url: "mxc avatar url" }, { type: "m.room.avatar", state_key: "" }),
];

View file

@ -1,9 +0,0 @@
import Room from "../../src/room.js";
import Client from "../../src/client.js";
const client = new Client({
token: "no",
baseUrl: "no",
userId: "no",
});
export default new Room(client, "idk");

View file

@ -1,2 +0,0 @@
todo: reimplement testing
idea: download conduit and test against that

View file

@ -1,13 +0,0 @@
import { deepStrictEqual as equal } from "assert";
import { makeEvent } from "../mocks/events.js";
type tester = (description: string, run: () => any) => undefined;
export function test(test: tester) {
const event = makeEvent({ body: "ayo" }, { type: "org.example.custom.type", origin_server_ts: 12345, event_id: "$foobar" });
test("get type", () => equal(event.type, "org.example.custom.type"));
test("get content", () => equal(event.content, { body: "ayo" }));
test("get timestamp", () => equal(event.timestamp, new Date(12345)));
test("get id", () => equal(event.id, "$foobar"));
// test("get content", () => equal(event.content, { body: "ayo" }));
}

View file

@ -1,13 +0,0 @@
import { deepStrictEqual as equal } from "assert";
import room from "../mocks/room.js";
import { stateEvents } from "../mocks/events.js";
type tester = (description: string, run: () => any) => undefined;
export function test(test: tester) {
for (let ev of stateEvents) room.handleState(ev, false);
test("get name", () => equal(room.name, "test room"));
test("get topic", () => equal(room.topic, "a topic for the test room"));
test("get avatar", () => equal(room.avatar, "mxc avatar url"));
}

View file

@ -1,11 +0,0 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"declaration": true,
"outDir": "./build",
"strict": true,
"incremental": true,
}
}