lining things up for UI goodness
This commit is contained in:
parent
2244bbcf7a
commit
b8429533df
6 changed files with 75 additions and 149 deletions
|
@ -99,24 +99,24 @@ func (self *MastoAdapter) DefaultSubscriptionFilter() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *MastoAdapter) mastoUpdateToMessage(status madon.Status) *Message {
|
func (self *MastoAdapter) mastoUpdateToMessage(status madon.Status) *Message {
|
||||||
var parent *madon.Status
|
|
||||||
|
|
||||||
if status.InReplyToID != nil {
|
|
||||||
parent, _ = self.masto.GetStatus(*status.InReplyToID)
|
|
||||||
}
|
|
||||||
msg := Message{
|
msg := Message{
|
||||||
|
Datagram: Datagram{
|
||||||
Protocol: "mastodon",
|
Protocol: "mastodon",
|
||||||
Content: status.Content,
|
Adapter: self.nickname,
|
||||||
|
Id: fmt.Sprintf("%d", status.ID),
|
||||||
Uri: status.URI,
|
Uri: status.URI,
|
||||||
|
Type: "message",
|
||||||
|
},
|
||||||
|
Content: status.Content,
|
||||||
Author: status.Account.Acct,
|
Author: status.Account.Acct,
|
||||||
Created: status.CreatedAt,
|
Created: status.CreatedAt,
|
||||||
Visibility: status.Visibility,
|
Visibility: status.Visibility,
|
||||||
}
|
}
|
||||||
if parent != nil {
|
if status.InReplyToID != nil {
|
||||||
msg.ReplyTo = &parent.URI
|
idStr := fmt.Sprintf("%d", *status.InReplyToID)
|
||||||
|
msg.ReplyTo = &idStr
|
||||||
}
|
}
|
||||||
// TODO: mentions and replies
|
// TODO: mentions and replies
|
||||||
msg.Aux = make(map[string]string)
|
|
||||||
msg.Aux["visibility"] = status.Visibility
|
|
||||||
return &msg
|
return &msg
|
||||||
}
|
}
|
||||||
|
|
|
@ -176,11 +176,17 @@ func (self *MisskeyAdapter) cacheAndConvert(n mkm.Note) *Message {
|
||||||
if !exists || timestamp.Before(n.CreatedAt) {
|
if !exists || timestamp.Before(n.CreatedAt) {
|
||||||
self.cache[n.ID] = n.CreatedAt
|
self.cache[n.ID] = n.CreatedAt
|
||||||
msg := Message{
|
msg := Message{
|
||||||
|
Datagram: Datagram{
|
||||||
|
Id: n.ID,
|
||||||
Uri: n.URI,
|
Uri: n.URI,
|
||||||
Author: fmt.Sprintf("%s@%s", n.User.Username, mkcore.StringValue(n.User.Host)),
|
|
||||||
Protocol: "misskey",
|
Protocol: "misskey",
|
||||||
Adapter: self.nickname,
|
Adapter: self.nickname,
|
||||||
|
Type: "message",
|
||||||
|
},
|
||||||
|
|
||||||
Created: n.CreatedAt,
|
Created: n.CreatedAt,
|
||||||
|
|
||||||
|
Author: fmt.Sprintf("%s@%s", n.User.Username, mkcore.StringValue(n.User.Host)),
|
||||||
Content: n.Text,
|
Content: n.Text,
|
||||||
Attachments: []Attachment{},
|
Attachments: []Attachment{},
|
||||||
Visibility: n.Visibility,
|
Visibility: n.Visibility,
|
||||||
|
|
|
@ -89,15 +89,18 @@ func (self *NostrAdapter) DefaultSubscriptionFilter() string {
|
||||||
|
|
||||||
func (self *NostrAdapter) nostrEventToMsg(evt *nostr.Event) (Message, error) {
|
func (self *NostrAdapter) nostrEventToMsg(evt *nostr.Event) (Message, error) {
|
||||||
m := Message{
|
m := Message{
|
||||||
|
Datagram: Datagram{
|
||||||
Protocol: "nostr",
|
Protocol: "nostr",
|
||||||
Adapter: self.nickname,
|
Adapter: self.nickname,
|
||||||
|
Type: "message",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
if evt == nil {
|
if evt == nil {
|
||||||
return m, errors.New("no event")
|
return m, errors.New("no event")
|
||||||
}
|
}
|
||||||
switch evt.Kind {
|
switch evt.Kind {
|
||||||
case nostr.KindTextNote:
|
case nostr.KindTextNote:
|
||||||
m.Uri = evt.ID
|
m.Id = evt.ID
|
||||||
m.Author = evt.PubKey
|
m.Author = evt.PubKey
|
||||||
m.Created = evt.CreatedAt.Time()
|
m.Created = evt.CreatedAt.Time()
|
||||||
m.Content = evt.Content
|
m.Content = evt.Content
|
||||||
|
|
|
@ -5,11 +5,18 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Message struct {
|
type Datagram struct {
|
||||||
|
Id string
|
||||||
Uri string
|
Uri string
|
||||||
Author string
|
|
||||||
Protocol string
|
Protocol string
|
||||||
Adapter string
|
Adapter string
|
||||||
|
Type string
|
||||||
|
Target *string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Message struct {
|
||||||
|
Datagram
|
||||||
|
Author string
|
||||||
Content string
|
Content string
|
||||||
Attachments []Attachment
|
Attachments []Attachment
|
||||||
ReplyTo *string
|
ReplyTo *string
|
||||||
|
@ -22,11 +29,11 @@ type Message struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Author struct {
|
type Author struct {
|
||||||
Id string
|
Datagram
|
||||||
Name string
|
Name string
|
||||||
ProfileData interface{}
|
ProfileData interface{}
|
||||||
ProfileUri string
|
|
||||||
ProfilePic string
|
ProfilePic string
|
||||||
|
Messages []Message
|
||||||
}
|
}
|
||||||
|
|
||||||
type Attachment struct {
|
type Attachment struct {
|
||||||
|
|
125
ts/adapter.ts
125
ts/adapter.ts
|
@ -1,118 +1,17 @@
|
||||||
import NDK, {NDKPrivateKeySigner} from "@nostr-dev-kit/ndk";
|
import {Message, Author} from "./message"
|
||||||
import * as nip19 from 'nostr-tools/nip19'
|
export class AdapterData {
|
||||||
import { createRestAPIClient, createStreamingAPIClient } from "masto";
|
public protocol: string;
|
||||||
import * as masto from "masto";
|
public directMessages Map<string, Message>();
|
||||||
|
public messages: Map<string, Message>();
|
||||||
|
public profileCache: Map<string, Author>;
|
||||||
|
|
||||||
type MastodonClient = masto.mastodon.rest.Client;
|
constructor(protocol: string) {
|
||||||
type MastodonStreamClient = masto.mastodon.streaming.Client;
|
this.protocol = protocol;
|
||||||
|
this.messages = [];
|
||||||
export class MastodonCompoundClient {
|
this.profileCache = [];
|
||||||
public rest: MastodonClient;
|
|
||||||
public stream: MastodonStreamClient;
|
|
||||||
|
|
||||||
public constructor(c: MastodonClient, s: MastodonStreamClient) {
|
|
||||||
this.rest = c;
|
|
||||||
this.stream = s;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Adapter {
|
export interface AdapterState {
|
||||||
public nickname: string = "";
|
[nickname: string]: AdapterData;
|
||||||
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 }
|
|
||||||
|
|
||||||
|
|
33
ts/index.ts
33
ts/index.ts
|
@ -11,17 +11,10 @@ function main():void {
|
||||||
|
|
||||||
if (settings != null) {
|
if (settings != null) {
|
||||||
for (let s of settings.adapters ?? []) {
|
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) {
|
if (adapters.length > 0) {
|
||||||
_("currentAdapter", 0);
|
_("currentAdapter", adapters[0].nickname);
|
||||||
// update tabbar and tabcontent with first adapter
|
// update tabbar and tabcontent with first adapter
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -197,6 +190,9 @@ async function authorizedFetch(method: string, uri: string, body: any): Promise<
|
||||||
|
|
||||||
function connect() {
|
function connect() {
|
||||||
|
|
||||||
|
var datastore: AdapterState = {}
|
||||||
|
datastore = _("datastore", datastore);
|
||||||
|
|
||||||
const wsProto = location.protocol == "https:" ? "wss" : "ws";
|
const wsProto = location.protocol == "https:" ? "wss" : "ws";
|
||||||
_conn = new WebSocket(`${wsProto}://${location.host}/subscribe`, "underbbs");
|
_conn = new WebSocket(`${wsProto}://${location.host}/subscribe`, "underbbs");
|
||||||
_conn.addEventListener("open", (e: any) => {
|
_conn.addEventListener("open", (e: any) => {
|
||||||
|
@ -214,13 +210,28 @@ function connect() {
|
||||||
_("skey", data.key)
|
_("skey", data.key)
|
||||||
authorizedFetch("POST", "/api/adapters", JSON.stringify(_("settings").adapters))
|
authorizedFetch("POST", "/api/adapters", JSON.stringify(_("settings").adapters))
|
||||||
} else {
|
} else {
|
||||||
// typeswitch on the incoming data and adapters
|
if (!datastore[data.adapter]) {
|
||||||
|
datastore[data.adapter] = new AdapterData(data.protocol);
|
||||||
|
}
|
||||||
|
|
||||||
// if it's a regular message, add it to the store
|
// 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
|
// if the adapter is active, inject the web components
|
||||||
// FOR HOTFETCHED DATA:
|
// FOR HOTFETCHED DATA:
|
||||||
// before fetching, we can set properties on the DOM,
|
// before fetching, we can set properties on the DOM,
|
||||||
// so when those data return to us we know where to inject components!
|
// 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) => {
|
_conn.addEventListener("error", (e: any) => {
|
||||||
|
|
Loading…
Reference in a new issue