Stoat (#32): voice calls in the window, Stoat invite links join the server; nobilis pointer
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
1 parent
1d674edfe6
commit
df0edf1cf8
4 files changed
+60
-4
No files matched your search
+1
-1
Submodule nobilis updated: 9648ae56c8...39a65bd0df.
@@ -2281,6 +2281,18 @@ export class ChatStore {
|
||||
return
|
||||
}
|
||||
|
||||
// A Stoat invite joins the server, in the first Stoat account there is; with none, it is a web page for the browser.
|
||||
if (link.service === 'stoat') {
|
||||
const account = this.state.accounts.find((a) => a.service === 'stoat' && a.state === 'connected') ?? this.state.accounts.find((a) => a.service === 'stoat')
|
||||
if (!account) {
|
||||
void window.moho.openExternal(link.url)
|
||||
return
|
||||
}
|
||||
this.set({ activePanel: '' })
|
||||
void this.joinAndOpen(account.id, link.url)
|
||||
return
|
||||
}
|
||||
|
||||
// A Telegram link goes to wherever it points: the chat, joined if the account is not in it, and the post.
|
||||
if (link.service === 'telegram') {
|
||||
void this.openTelegramLink(link.url)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { isDeepLink, isTelegramLink, parseDeepLink } from './deeplink'
|
||||
import { isDeepLink, isTelegramLink, parseDeepLink, stoatInviteCode } from './deeplink'
|
||||
|
||||
describe('a Telegram link', () => {
|
||||
it('is what the View in Telegram button of a post opens, and what a message says', () => {
|
||||
@@ -44,3 +44,17 @@ describe('a Telegram link', () => {
|
||||
expect(parseDeepLink('https://kick.com/xqc')?.service).toBe('kick')
|
||||
})
|
||||
})
|
||||
|
||||
describe('a Stoat invite', () => {
|
||||
it('is read from the official addresses, and only those', () => {
|
||||
expect(stoatInviteCode('https://stoat.chat/invite/gJhyqXTt')).toBe('gJhyqXTt')
|
||||
expect(stoatInviteCode('https://app.stoat.chat/invite/gJhyqXTt/')).toBe('gJhyqXTt')
|
||||
expect(stoatInviteCode('https://rvlt.gg/Abc123')).toBe('Abc123')
|
||||
expect(stoatInviteCode('https://stoat.chat/')).toBeNull()
|
||||
expect(stoatInviteCode('https://example.com/invite/abc')).toBeNull()
|
||||
expect(stoatInviteCode('https://stoat.chat.example.com/invite/abc')).toBeNull()
|
||||
expect(stoatInviteCode('https://stoat.chat/invite/a b')).toBeNull()
|
||||
expect(parseDeepLink('https://stoat.chat/invite/gJhyqXTt')).toEqual({ service: 'stoat', code: 'gJhyqXTt', url: 'https://stoat.chat/invite/gJhyqXTt' })
|
||||
expect(isDeepLink('https://stoat.chat/invite/gJhyqXTt')).toBe(true)
|
||||
})
|
||||
})
|
||||
+32
-2
@@ -36,7 +36,14 @@ export interface TelegramLink {
|
||||
url: string
|
||||
}
|
||||
|
||||
export type DeepLink = IrcLink | KickLink | TelegramLink
|
||||
/** An invitation to a Stoat server: its link in a message, which joins it where the account has one. */
|
||||
export interface StoatInvite {
|
||||
service: 'stoat'
|
||||
code: string
|
||||
url: string
|
||||
}
|
||||
|
||||
export type DeepLink = IrcLink | KickLink | TelegramLink | StoatInvite
|
||||
|
||||
/**
|
||||
* Schemes moho asks the desktop to send it.
|
||||
@@ -50,7 +57,28 @@ export const DEEP_LINK_SCHEMES = ['irc', 'ircs', 'tg']
|
||||
/** Whether a string is a link this client would act on. */
|
||||
export function isDeepLink(value: string): boolean {
|
||||
const lower = value.toLowerCase()
|
||||
return DEEP_LINK_SCHEMES.some((s) => lower.startsWith(`${s}:`)) ? isTelegramOrIrc(value) : !!kickHandle(value) || isTelegramLink(value)
|
||||
return DEEP_LINK_SCHEMES.some((s) => lower.startsWith(`${s}:`)) ? isTelegramOrIrc(value) : !!kickHandle(value) || isTelegramLink(value) || !!stoatInviteCode(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* The code in a link to a Stoat invite (`stoat.chat/invite/Code`, the same on the web app or Revolt's old names, or the
|
||||
* short `rvlt.gg/Code`), or null. Only the official addresses: a self-hosted instance's own are joined by pasting them
|
||||
* into the join panel, since nothing here can tell its address from any other website's.
|
||||
*/
|
||||
export function stoatInviteCode(value: string): string | null {
|
||||
let url: URL
|
||||
try {
|
||||
url = new URL(value.trim())
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
if (!/^https?:$/.test(url.protocol)) return null
|
||||
const host = url.hostname.replace(/^www\./, '').toLowerCase()
|
||||
const parts = url.pathname.split('/').filter(Boolean)
|
||||
let code: string | undefined
|
||||
if (['stoat.chat', 'app.stoat.chat', 'revolt.chat', 'app.revolt.chat'].includes(host) && parts.length === 2 && parts[0] === 'invite') code = parts[1]
|
||||
else if (['rvlt.gg', 'stt.gg'].includes(host) && parts.length === 1) code = parts[0]
|
||||
return code && /^[A-Za-z0-9_-]{1,32}$/.test(code) ? code : null
|
||||
}
|
||||
|
||||
/** A `tg:` link is only one of ours if it is a kind that opens a chat; the others (login, proxy, themes) are not. */
|
||||
@@ -143,6 +171,8 @@ export function parseDeepLink(value: string): DeepLink | null {
|
||||
const kick = kickHandle(value)
|
||||
if (kick) return { service: 'kick', handle: kick }
|
||||
if (isTelegramLink(value)) return { service: 'telegram', url: value.trim() }
|
||||
const invite = stoatInviteCode(value)
|
||||
if (invite) return { service: 'stoat', code: invite, url: value.trim() }
|
||||
|
||||
let url: URL
|
||||
try {
|
||||
|
||||
Reference in new issue
Block a user