final update?
This commit is contained in:
parent
00000520ae
commit
0000053f14
28 changed files with 75 additions and 352 deletions
10
README.md
10
README.md
|
@ -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
|
||||
|
||||
|
|
11
package.json
11
package.json
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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"),
|
||||
|
|
12
src/event.ts
12
src/event.ts
|
@ -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,
|
||||
|
|
|
@ -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"];
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type * as api from "./api.js"
|
||||
import type * as api from "./api.ts"
|
||||
|
||||
interface FetchOptions {
|
||||
method?: string,
|
||||
|
|
10
src/index.ts
10
src/index.ts
|
@ -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";
|
||||
|
|
|
@ -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> = [];
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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>>;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
12
src/room.ts
12
src/room.ts
|
@ -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";
|
||||
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(
|
||||
|
|
19
temp/e2ee.ts
19
temp/e2ee.ts
|
@ -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());
|
||||
}
|
||||
}
|
135
temp/push.js
135
temp/push.js
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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++;
|
||||
}
|
||||
}
|
|
@ -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: "" }),
|
||||
];
|
|
@ -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");
|
|
@ -1,2 +0,0 @@
|
|||
todo: reimplement testing
|
||||
idea: download conduit and test against that
|
|
@ -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" }));
|
||||
}
|
|
@ -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"));
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"declaration": true,
|
||||
"outDir": "./build",
|
||||
"strict": true,
|
||||
"incremental": true,
|
||||
}
|
||||
}
|
Reference in a new issue