all repos — underbbs @ b8429533df17831ce4f23398d735f1cd97475fd5

decentralized social media client

lining things up for UI goodness
Iris Lightshard nilix@nilfm.cc
PGP Signature
-----BEGIN PGP SIGNATURE-----

iHUEABYKAB0WIQT/foVVmI9pK13hPWFohAcXSWbK8wUCZoCkVwAKCRBohAcXSWbK
804mAQChuMD4bjSk2ZMCjX3KSP8I/Ps3dJVdY1XTwXUuXXe+tQEAlzxqeCv4DERV
On3HO0g5JmeBQfXlr7bPSqmgEN9kwQs=
=gQ7t
-----END PGP SIGNATURE-----
commit

b8429533df17831ce4f23398d735f1cd97475fd5

parent

2244bbcf7af3ee1b3489243a3fc74e3b6eb603ba

6 files changed, 75 insertions(+), 149 deletions(-)

jump to
M adapter/mastodon.goadapter/mastodon.go

@@ -99,24 +99,24 @@ return "user"

} func (self *MastoAdapter) mastoUpdateToMessage(status madon.Status) *Message { - var parent *madon.Status - - if status.InReplyToID != nil { - parent, _ = self.masto.GetStatus(*status.InReplyToID) - } msg := Message{ - Protocol: "mastodon", + Datagram: Datagram{ + Protocol: "mastodon", + Adapter: self.nickname, + Id: fmt.Sprintf("%d", status.ID), + Uri: status.URI, + Type: "message", + }, Content: status.Content, - Uri: status.URI, Author: status.Account.Acct, Created: status.CreatedAt, Visibility: status.Visibility, } - if parent != nil { - msg.ReplyTo = &parent.URI + if status.InReplyToID != nil { + idStr := fmt.Sprintf("%d", *status.InReplyToID) + msg.ReplyTo = &idStr } // TODO: mentions and replies - msg.Aux = make(map[string]string) - msg.Aux["visibility"] = status.Visibility + return &msg }
M adapter/misskey.goadapter/misskey.go

@@ -176,11 +176,17 @@ timestamp, exists := self.cache[n.ID]

if !exists || timestamp.Before(n.CreatedAt) { self.cache[n.ID] = n.CreatedAt msg := Message{ - Uri: n.URI, + Datagram: Datagram{ + Id: n.ID, + Uri: n.URI, + Protocol: "misskey", + Adapter: self.nickname, + Type: "message", + }, + + Created: n.CreatedAt, + Author: fmt.Sprintf("%s@%s", n.User.Username, mkcore.StringValue(n.User.Host)), - Protocol: "misskey", - Adapter: self.nickname, - Created: n.CreatedAt, Content: n.Text, Attachments: []Attachment{}, Visibility: n.Visibility,
M adapter/nostr.goadapter/nostr.go

@@ -89,15 +89,18 @@ }

func (self *NostrAdapter) nostrEventToMsg(evt *nostr.Event) (Message, error) { m := Message{ - Protocol: "nostr", - Adapter: self.nickname, + Datagram: Datagram{ + Protocol: "nostr", + Adapter: self.nickname, + Type: "message", + }, } if evt == nil { return m, errors.New("no event") } switch evt.Kind { case nostr.KindTextNote: - m.Uri = evt.ID + m.Id = evt.ID m.Author = evt.PubKey m.Created = evt.CreatedAt.Time() m.Content = evt.Content
M models/msg.gomodels/msg.go

@@ -5,11 +5,18 @@ "encoding/json"

"time" ) +type Datagram struct { + Id string + Uri string + Protocol string + Adapter string + Type string + Target *string +} + type Message struct { - Uri string + Datagram Author string - Protocol string - Adapter string Content string Attachments []Attachment ReplyTo *string

@@ -22,11 +29,11 @@ Aux map[string]string

} type Author struct { - Id string + Datagram Name string ProfileData interface{} - ProfileUri string ProfilePic string + Messages []Message } type Attachment struct {
M ts/adapter.tsts/adapter.ts

@@ -1,118 +1,17 @@

-import NDK, {NDKPrivateKeySigner} from "@nostr-dev-kit/ndk"; -import * as nip19 from 'nostr-tools/nip19' -import { createRestAPIClient, createStreamingAPIClient } from "masto"; -import * as masto from "masto"; - -type MastodonClient = masto.mastodon.rest.Client; -type MastodonStreamClient = masto.mastodon.streaming.Client; - -export class MastodonCompoundClient { - public rest: MastodonClient; - public stream: MastodonStreamClient; +import {Message, Author} from "./message" +export class AdapterData { + public protocol: string; + public directMessages Map<string, Message>(); + public messages: Map<string, Message>(); + public profileCache: Map<string, Author>; - public constructor(c: MastodonClient, s: MastodonStreamClient) { - this.rest = c; - this.stream = s; + constructor(protocol: string) { + this.protocol = protocol; + this.messages = []; + this.profileCache = []; } } -export class Adapter { - public nickname: string = ""; - public protocol: string = ""; - public identity: any | null; - - private _self: NDK | MastodonCompoundClient | null = null ; - - public init(): void {}; - public getInbox(): void {}; - public publish(): void {}; - public getFollowers(): any[] { return [] }; - public getFollowing(): any[] { return [] }; - public updateMetadata(): void {}; - public getMetadata(): any { return {} }; - - // according to the docs NDK must be a singleton... - // this probalby will make having more than one nostr adapter at once problematic - private static ndk: NDK | null = null; - - public static create(): Adapter { - return new Adapter(); - } - - public static toNostr(adapter: Adapter, settings: any): Adapter { - adapter.identity = { privkey: settings.privkey }; - adapter.nickname = settings.nickname; - - adapter.init = ()=> { - if (!Adapter.ndk) { - let privkey_raw = nip19.decode(settings.privkey); - Adapter.ndk = new NDK({ - signer: new NDKPrivateKeySigner(<string>privkey_raw.data), - explicitRelayUrls: [ settings.relays ] - }); - adapter._self = Adapter.ndk; - Adapter.ndk.connect(); - } else { - Adapter.ndk.signer = new NDKPrivateKeySigner(settings.privatekey); - for (let i of settings.relays) { - Adapter.ndk.addExplicitRelay(i); - } - } - }; - - adapter.getInbox = async () => { - if (Adapter.ndk) { - const sub = Adapter.ndk.subscribe({ kinds: [1] }); // Get all kind:1s - sub.on("event", (event) => console.log(event.content)); // Show the content - sub.on("eose", () => console.log("All relays have reached the end of the event stream")); - sub.on("close", () => console.log("Subscription closed")); - setTimeout(() => sub.stop(), 10000); // Stop the subscription after 10 seconds - } - }; - - return adapter; - } - - public static toMasto(adapter: Adapter, settings: any): Adapter { - adapter.identity = { server: settings.server, apiKey: settings.apiKey }; - adapter.nickname = settings.nickname; - - adapter.init = () => { - const rawServer: string = adapter.identity.server.split("://")[1]; - - adapter._self = new MastodonCompoundClient(createRestAPIClient({ - url: adapter.identity.server, - accessToken: adapter.identity.apiKey - }), - createStreamingAPIClient({ - streamingApiUrl: `https://${rawServer}/v1/api/streaming`, - accessToken: adapter.identity.apiKey - })); - - } - - adapter.getInbox = async () => { - const rawServer: string = adapter.identity.server.split("://")[1]; - let conn = new WebSocket(`wss://${rawServer}/streaming/?i=${adapter.identity.apiKey}`) - conn.addEventListener("open", async (e:any)=> { - console.log(e); - let filter = { type: "connect", body: { channel: "localTimeline", id: crypto.randomUUID() }}; - let data = await JSON.stringify(filter); - console.log(data); - conn.send(data); - conn.addEventListener("message", (e:any)=>{console.log(e)}); - }); - - - - - } - - return adapter; - } -} - - - -export default { Adapter } - +export interface AdapterState { + [nickname: string]: AdapterData; +}
M ts/index.tsts/index.ts

@@ -11,17 +11,10 @@ const adapters = _("adapters", []);

if (settings != null) { for (let s of settings.adapters ?? []) { - let a: Adapter = Adapter.create() - switch (s.protocol) { - case "nostr": - adapters.push(Adapter.toNostr(a, s)); - break; - case "mastodon": - adapters.push(Adapter.toMasto(a, s)); - } + } if (adapters.length > 0) { - _("currentAdapter", 0); + _("currentAdapter", adapters[0].nickname); // update tabbar and tabcontent with first adapter } } else {

@@ -197,6 +190,9 @@ }

function connect() { + var datastore: AdapterState = {} + datastore = _("datastore", datastore); + const wsProto = location.protocol == "https:" ? "wss" : "ws"; _conn = new WebSocket(`${wsProto}://${location.host}/subscribe`, "underbbs"); _conn.addEventListener("open", (e: any) => {

@@ -214,13 +210,28 @@ if (data.key) {

_("skey", data.key) authorizedFetch("POST", "/api/adapters", JSON.stringify(_("settings").adapters)) } else { - // typeswitch on the incoming data and adapters - - // if it's a regular message, add it to the store + if (!datastore[data.adapter]) { + datastore[data.adapter] = new AdapterData(data.protocol); + } + + // typeswitch on the incoming data type and fill the memory + switch (data.type) { + case "message": + datastore[data.adapter].messages[data.id] = <Message>data; + break; + case "author": + datastore[data.adapter].profileCache[data.id] = <Author>data; + break; + default: + break; + } // if the adapter is active, inject the web components // FOR HOTFETCHED DATA: // before fetching, we can set properties on the DOM, // so when those data return to us we know where to inject components! + if (_("currentAdapter") == data.adapter) { + // dive in and insert that shit in the dom + } } }); _conn.addEventListener("error", (e: any) => {