[WIP] threads. #74
14 changed files with 160 additions and 76 deletions
Fix conflicts; rename a var to something more sensible; less string concat because Cadence said she doesn't like it.
commit
ea43d3b285
|
|
@ -14,8 +14,6 @@ const channelWebhook = sync.require("./channel-webhook")
|
|||
const eventToMessage = sync.require("../converters/event-to-message")
|
||||
/** @type {import("../../matrix/api")}) */
|
||||
const api = sync.require("../../matrix/api")
|
||||
/** @type {import("../../matrix/utils")}) */
|
||||
const utils = sync.require("../../matrix/utils")
|
||||
/** @type {import("../../d2m/actions/register-user")} */
|
||||
const registerUser = sync.require("../../d2m/actions/register-user")
|
||||
/** @type {import("../../d2m/actions/edit-message")} */
|
||||
|
|
@ -23,6 +21,12 @@ const editMessage = sync.require("../../d2m/actions/edit-message")
|
|||
/** @type {import("../actions/emoji-sheet")} */
|
||||
const emojiSheet = sync.require("../actions/emoji-sheet")
|
||||
|
||||
/**
|
||||
* @type {Map<string, string>} room id -> mxid
|
||||
* Keep track of the sender of the latest event per room, this is needed to trim display name runoff for subsequent messages
|
||||
*/
|
||||
const latestSenderPerRoom = sync.remember(() => new Map())
|
||||
|
||||
/**
|
||||
* @param {DiscordTypes.RESTPostAPIWebhookWithTokenJSONBody & {files?: {name: string, file: Buffer | stream.Readable}[], pendingFiles?: ({name: string, mxc: string} | {name: string, mxc: string, key: string, iv: string} | {name: string, buffer: Buffer | stream.Readable})[]}} message
|
||||
* @returns {Promise<DiscordTypes.RESTPostAPIWebhookWithTokenJSONBody & {files?: {name: string, file: Buffer | stream.Readable}[]}>}
|
||||
|
|
@ -87,7 +91,12 @@ async function sendEvent(event) {
|
|||
|
||||
// no need to sync the matrix member to the other side. but if I did need to, this is where I'd do it
|
||||
|
||||
const di = {api, snow: discord.snow, mxcDownloader: emojiSheet.getAndConvertEmoji}
|
||||
const di = {
|
||||
api,
|
||||
snow: discord.snow,
|
||||
mxcDownloader: emojiSheet.getAndConvertEmoji,
|
||||
noRunoff: latestSenderPerRoom.get(event.room_id) === event.sender
|
||||
}
|
||||
|
||||
if (event.type === "org.matrix.msc3381.poll.end") {
|
||||
// Validity already checked by dispatcher. Poll is definitely closed. Update it and DI necessary data.
|
||||
|
|
@ -193,3 +202,4 @@ async function sendEvent(event) {
|
|||
}
|
||||
|
||||
module.exports.sendEvent = sendEvent
|
||||
module.exports.latestSenderPerRoom = latestSenderPerRoom
|
||||
|
|
|
|||
|
|
@ -538,7 +538,7 @@ async function getL1L2ReplyLine(called = false) {
|
|||
* @param {Ty.Event.Outer_M_Room_Message | Ty.Event.Outer_M_Room_Message_File | Ty.Event.Outer_M_Sticker | Ty.Event.Outer_M_Room_Message_Encrypted_File | Ty.Event.Outer_Org_Matrix_Msc3381_Poll_Start | Ty.Event.Outer_Org_Matrix_Msc3381_Poll_End} event
|
||||
* @param {DiscordTypes.APIGuild} guild
|
||||
* @param {DiscordTypes.APIGuildTextChannel} channel
|
||||
* @param {{api: import("../../matrix/api"), snow: import("snowtransfer").SnowTransfer, pollEnd?: {messageID: string}}} di simple-as-nails dependency injection for the matrix API
|
||||
* @param {{api: import("../../matrix/api"), snow: import("snowtransfer").SnowTransfer, pollEnd?: {messageID: string}, noRunoff: boolean}} di simple-as-nails dependency injection for the matrix API
|
||||
*/
|
||||
async function eventToMessage(event, guild, channel, di) {
|
||||
let displayName = event.sender
|
||||
|
|
@ -561,7 +561,16 @@ async function eventToMessage(event, guild, channel, di) {
|
|||
|
||||
// MSC4144: Override display name and avatar from per-message profile if present
|
||||
const perMessageProfile = event.content["com.beeper.per_message_profile"]
|
||||
if (perMessageProfile?.displayname) displayName = perMessageProfile.displayname
|
||||
if (perMessageProfile?.displayname) {
|
||||
// TODO: Ideally this should filter on the "language" key, rather than just picking the first, once we're aware of the guild's preferred locale.
|
||||
// Regardless, these are intended to be in preference order, and the PMP user could always choose to reorder them if the wrong choice is made here.
|
||||
const pronouns = perMessageProfile["io.fsky.nyx.pronouns"]?.[0]?.summary
|
||||
let pronounsSuffix = ""
|
||||
if (pronouns) {
|
||||
pronounsSuffix = ` (${pronouns})`
|
||||
}
|
||||
displayName = `${perMessageProfile.displayname}${pronounsSuffix} | ${displayName}`
|
||||
}
|
||||
if (perMessageProfile && "avatar_url" in perMessageProfile) {
|
||||
if (perMessageProfile.avatar_url) {
|
||||
// use provided avatar_url
|
||||
|
|
@ -577,7 +586,7 @@ async function eventToMessage(event, guild, channel, di) {
|
|||
// put the excess characters into displayNameRunoff, later to be put at the top of the message
|
||||
let [displayNameShortened, displayNameRunoff] = splitDisplayName(displayName)
|
||||
// If the message type is m.emote, the full name is already included at the start of the message, so remove any runoff
|
||||
if (event.type === "m.room.message" && event.content.msgtype === "m.emote") {
|
||||
if (di?.noRunoff || (event.type === "m.room.message" && event.content.msgtype === "m.emote")) {
|
||||
displayNameRunoff = ""
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5985,7 +5985,7 @@ test("event2message: com.beeper.per_message_profile overrides displayname and av
|
|||
messagesToDelete: [],
|
||||
messagesToEdit: [],
|
||||
messagesToSend: [{
|
||||
username: "Unstable Name",
|
||||
username: "Unstable Name | cadence [they]",
|
||||
content: "hello from unstable profile",
|
||||
avatar_url: "https://bridge.example.org/download/matrix/maunium.net/hgXsKqlmRfpKvCZdUoWDkFQo?preset=avatar",
|
||||
allowed_mentions: {
|
||||
|
|
@ -6018,7 +6018,7 @@ test("event2message: com.beeper.per_message_profile empty avatar_url clears avat
|
|||
messagesToDelete: [],
|
||||
messagesToEdit: [],
|
||||
messagesToSend: [{
|
||||
username: "No Avatar User",
|
||||
username: "No Avatar User | cadence [they]",
|
||||
content: "hello with cleared avatar",
|
||||
avatar_url: "https://bridge.example.org/download/letter-avatar?letter=N&hue=90",
|
||||
allowed_mentions: {
|
||||
|
|
@ -6054,7 +6054,7 @@ test("event2message: data-mx-profile-fallback element is stripped from formatted
|
|||
messagesToDelete: [],
|
||||
messagesToEdit: [],
|
||||
messagesToSend: [{
|
||||
username: "Tidus Herboren",
|
||||
username: "Tidus Herboren | cadence [they]",
|
||||
content: "one more test",
|
||||
avatar_url: "https://bridge.example.org/download/matrix/maunium.net/hgXsKqlmRfpKvCZdUoWDkFQo?preset=avatar",
|
||||
allowed_mentions: {
|
||||
|
|
@ -6087,7 +6087,7 @@ test("event2message: displayname prefix is stripped from plain body when per-mes
|
|||
messagesToDelete: [],
|
||||
messagesToEdit: [],
|
||||
messagesToSend: [{
|
||||
username: "Tidus Herboren",
|
||||
username: "Tidus Herboren | cadence [they]",
|
||||
content: "one more test",
|
||||
avatar_url: "https://bridge.example.org/download/letter-avatar?letter=T&hue=90",
|
||||
allowed_mentions: {
|
||||
|
|
@ -6098,6 +6098,57 @@ test("event2message: displayname prefix is stripped from plain body when per-mes
|
|||
)
|
||||
})
|
||||
|
||||
test("event2message: pronouns can be displayed with per-message profile", async t => {
|
||||
t.deepEqual(
|
||||
await eventToMessage({
|
||||
type: "m.room.message",
|
||||
sender: "@exa:sleeping.town",
|
||||
content: {
|
||||
msgtype: "m.text",
|
||||
body: "Well, I can't really do it now...",
|
||||
"com.beeper.per_message_profile": {
|
||||
id: "Kryn",
|
||||
displayname: "Kryn",
|
||||
"eu.she-a.color": {
|
||||
on_dark: "#FFCC00",
|
||||
on_light: "#976800"
|
||||
},
|
||||
"io.fsky.nyx.pronouns": [{
|
||||
language: "en",
|
||||
summary: "fae/she"
|
||||
}]
|
||||
}
|
||||
},
|
||||
event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU",
|
||||
room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe"
|
||||
}, {}, {}, {
|
||||
api: {
|
||||
async getStateEvent(roomID, type, key) {
|
||||
t.equal(type, "m.room.member")
|
||||
t.equal(key, "@exa:sleeping.town")
|
||||
return {
|
||||
displayname: "Exa",
|
||||
membership: "join"
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
{
|
||||
ensureJoined: [],
|
||||
messagesToDelete: [],
|
||||
messagesToEdit: [],
|
||||
messagesToSend: [{
|
||||
username: "Kryn (fae/she) | Exa",
|
||||
content: "Well, I can't really do it now...",
|
||||
avatar_url: "https://bridge.example.org/download/letter-avatar?letter=K&hue=150",
|
||||
allowed_mentions: {
|
||||
parse: ["users", "roles"]
|
||||
}
|
||||
}]
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test("event2message: all unknown chess emojis are used for sprite sheet", async t => {
|
||||
t.deepEqual(
|
||||
await eventToMessage({
|
||||
|
|
|
|||
|
|
@ -192,23 +192,23 @@ sync.addTemporaryListener(as, "type:m.room.message", guard("m.room.message",
|
|||
* @param {Ty.Event.Outer_M_Room_Message | Ty.Event.Outer_M_Room_Message_File} event it is a m.room.message because that's what this listener is filtering for
|
||||
*/
|
||||
async event => {
|
||||
if (utils.eventSenderIsFromDiscord(event.sender)) return
|
||||
if (!utils.eventSenderIsFromDiscord(event.sender)){
|
||||
if (await handleForums(event)) return
|
||||
|
||||
let processCommands = true
|
||||
if (event.content["m.relates_to"]?.rel_type === "m.thread") {
|
||||
/**@type {string|null} */
|
||||
let toRedact = event.room_id
|
||||
let redactAndMoveShouldOccurIn = event.room_id
|
||||
const bridgedTo = utils.getThreadRoomFromThreadEvent(event.content["m.relates_to"].event_id)
|
||||
processCommands = false
|
||||
|
||||
if (bridgedTo) event.room_id = bridgedTo;
|
||||
else if (!await bridgeThread(event)) toRedact = null; //Don't remove anything, if there is nowhere to relocate it to.
|
||||
else if (!await bridgeThread(event)) redactAndMoveShouldOccurIn = null; //Don't remove anything, if there is nowhere to relocate it to.
|
||||
|
||||
if (toRedact) {
|
||||
api.redactEvent(toRedact, event.event_id)
|
||||
if (redactAndMoveShouldOccurIn) {
|
||||
api.redactEvent(redactAndMoveShouldOccurIn, event.event_id)
|
||||
event.content["m.relates_to"] = undefined
|
||||
api.sendEvent(event.room_id, event.type, {...event.content, body: event.content.body+"\n ~ "+event.sender, formatted_body: event.content.formatted_body ? event.content.formatted_body+"<br> ~ "+event.sender :undefined })
|
||||
api.sendEvent(event.room_id, event.type, {...event.content, body: `${event.content.body}\n ~ ${event.sender}`, formatted_body: event.content.formatted_body ? `${event.content.formatted_body}<br> ~ ${event.sender}` : undefined })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -221,8 +221,10 @@ async event => {
|
|||
event
|
||||
)
|
||||
}
|
||||
retrigger.finishedBridging(event.event_id)
|
||||
await api.ackEvent(event)
|
||||
}
|
||||
if (!event.content["m.relates_to"]) { // Ignore edits since they don't update the timeline on Discord
|
||||
sendEvent.latestSenderPerRoom.set(event.room_id, event.sender)
|
||||
}
|
||||
}))
|
||||
|
||||
sync.addTemporaryListener(as, "type:m.sticker", guard("m.sticker",
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ const walkAST = require("pug-walk")
|
|||
const pretty = process.argv.join(" ").includes("test")
|
||||
|
||||
const {sync} = require("../passthrough")
|
||||
/** @type {import("./auth")} */
|
||||
const auth = sync.require("./auth")
|
||||
/** @type {import("./web-utils")} */
|
||||
const webUtils = sync.require("./web-utils")
|
||||
/** @type {import("../js/errors")} */
|
||||
const errors = sync.require("../js/errors")
|
||||
|
||||
|
|
@ -88,8 +88,8 @@ function renderPath(event, path, fragments, locals) {
|
|||
const requiredVars = []
|
||||
const template = compileFile(path, {pretty, plugins: fragmentSelectorPlugins(fragments, requiredVars)})
|
||||
pugCache.set(cacheKey, async (event, locals) => {
|
||||
const session = await auth.useSession(event)
|
||||
const managed = await auth.getManagedGuilds(event)
|
||||
const session = await webUtils.useSession(event)
|
||||
const managed = await webUtils.getManagedGuilds(event)
|
||||
const rel = (to, paramsObject) => {
|
||||
let result = getRelativePath(event.path, to)
|
||||
if (paramsObject) {
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ const {defineEventHandler, createError, readValidatedBody, getRequestHeader, set
|
|||
|
||||
const {as, db, sync, select, discord} = require("../../passthrough")
|
||||
|
||||
/** @type {import("../auth")} */
|
||||
const auth = sync.require("../auth")
|
||||
/** @type {import("../web-utils")} */
|
||||
const webUtils = sync.require("../web-utils")
|
||||
/** @type {import("../pug-sync")} */
|
||||
const pugSync = sync.require("../pug-sync")
|
||||
/** @type {import("../../d2m/actions/set-presence")} */
|
||||
|
|
@ -56,7 +56,7 @@ function defineToggle(key, inputOptions) {
|
|||
})
|
||||
/** @type {Record<T, string?> & Record<"guild_id", string> & Record<string, unknown>} */ // @ts-ignore
|
||||
const parsedBody = await readValidatedBody(event, bodySchema.parse)
|
||||
const managed = await auth.getManagedGuilds(event)
|
||||
const managed = await webUtils.getManagedGuilds(event)
|
||||
if (!managed.has(parsedBody.guild_id)) throw createError({status: 403, message: "Forbidden", data: "Can't change settings for a guild you don't have Manage Server permissions in"})
|
||||
|
||||
const value = options.transform(parsedBody[key])
|
||||
|
|
@ -108,7 +108,7 @@ as.router.post("/api/privacy-level", defineToggle("privacy_level", {
|
|||
as.router.post("/api/default-roles", defineEventHandler(async event => {
|
||||
const parsedBody = await readValidatedBody(event, schema.defaultRoles.parse)
|
||||
|
||||
const managed = await auth.getManagedGuilds(event)
|
||||
const managed = await webUtils.getManagedGuilds(event)
|
||||
const guildID = parsedBody.guild_id
|
||||
if (!managed.has(guildID)) throw createError({status: 403, message: "Forbidden", data: "Can't change settings for a guild you don't have Manage Server permissions in"})
|
||||
|
||||
|
|
@ -148,7 +148,7 @@ as.router.post("/api/room-mention-role", defineEventHandler(async event => {
|
|||
channel_id: z.string(),
|
||||
room_mention_role: z.string().regex(/^(?:[0-9]+)?$/)
|
||||
}).parse)
|
||||
const managed = await auth.getManagedGuilds(event)
|
||||
const managed = await webUtils.getManagedGuilds(event)
|
||||
if (!managed.has(body.guild_id)) throw createError({status: 403, message: "Forbidden", data: "Can't change settings for a guild you don't have Manage Server permissions in"})
|
||||
const room = select("channel_room", "room_id", {channel_id: body.channel_id, guild_id: body.guild_id}).get()
|
||||
if (!room) throw createError({status: 404, message: "Linked channel not found"})
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ const {discord, as, sync, select, from, db} = require("../../passthrough")
|
|||
const pugSync = sync.require("../pug-sync")
|
||||
/** @type {import("../../d2m/actions/create-space")} */
|
||||
const createSpace = sync.require("../../d2m/actions/create-space")
|
||||
/** @type {import("../auth")} */
|
||||
const auth = require("../auth")
|
||||
/** @type {import("../web-utils")} */
|
||||
const webUtils = require("../web-utils")
|
||||
/** @type {import("../../discord/utils")} */
|
||||
const dUtils = sync.require("../../discord/utils")
|
||||
/** @type {import("../../matrix/utils")} */
|
||||
|
|
@ -178,8 +178,8 @@ function getInviteTargetSpaces(mxid) {
|
|||
|
||||
as.router.get("/guild", defineEventHandler(async event => {
|
||||
const {guild_id} = await getValidatedQuery(event, schema.guild.parse)
|
||||
const session = await auth.useSession(event)
|
||||
const managed = await auth.getManagedGuilds(event)
|
||||
const session = await webUtils.useSession(event)
|
||||
const managed = await webUtils.getManagedGuilds(event)
|
||||
const row = from("guild_active").join("guild_space", "guild_id", "left").select("space_id", "privacy_level", "autocreate").where({guild_id}).get()
|
||||
// @ts-ignore
|
||||
const guild = discord.guilds.get(guild_id)
|
||||
|
|
@ -220,7 +220,7 @@ as.router.get("/explain", defineEventHandler(async event => {
|
|||
|
||||
as.router.get("/qr", defineEventHandler(async event => {
|
||||
const {guild_id} = await getValidatedQuery(event, schema.qr.parse)
|
||||
const managed = await auth.getManagedGuilds(event)
|
||||
const managed = await webUtils.getManagedGuilds(event)
|
||||
const row = from("guild_active").join("guild_space", "guild_id", "left").select("space_id", "privacy_level", "autocreate").where({guild_id}).get()
|
||||
// @ts-ignore
|
||||
const guild = discord.guilds.get(guild_id)
|
||||
|
|
@ -253,7 +253,7 @@ as.router.get("/invite", defineEventHandler(async event => {
|
|||
|
||||
as.router.post("/api/invite", defineEventHandler(async event => {
|
||||
const parsedBody = await readValidatedBody(event, schema.invite.parse)
|
||||
const managed = await auth.getManagedGuilds(event)
|
||||
const managed = await webUtils.getManagedGuilds(event)
|
||||
const api = getAPI(event)
|
||||
|
||||
// Check guild ID or nonce
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ const Ty = require("../../types")
|
|||
const DiscordTypes = require("discord-api-types/v10")
|
||||
|
||||
const {discord, db, as, sync, select, from} = require("../../passthrough")
|
||||
/** @type {import("../auth")} */
|
||||
const auth = sync.require("../auth")
|
||||
/** @type {import("../web-utils")} */
|
||||
const webUtils = sync.require("../web-utils")
|
||||
/** @type {import("../../matrix/utils")}*/
|
||||
const utils = sync.require("../../matrix/utils")
|
||||
/** @type {import("./guild")}*/
|
||||
|
|
@ -98,8 +98,8 @@ async function validateAndUnbridgeChannel(event, channel_id, guild_id) {
|
|||
|
||||
as.router.post("/api/link-space", defineEventHandler(async event => {
|
||||
const parsedBody = await readValidatedBody(event, schema.linkSpace.parse)
|
||||
const session = await auth.useSession(event)
|
||||
const managed = await auth.getManagedGuilds(event)
|
||||
const session = await webUtils.useSession(event)
|
||||
const managed = await webUtils.getManagedGuilds(event)
|
||||
const api = getAPI(event)
|
||||
|
||||
// Check guild ID
|
||||
|
|
@ -148,7 +148,7 @@ as.router.post("/api/link-space", defineEventHandler(async event => {
|
|||
|
||||
as.router.post("/api/link", defineEventHandler(async event => {
|
||||
const parsedBody = await readValidatedBody(event, schema.link.parse)
|
||||
const managed = await auth.getManagedGuilds(event)
|
||||
const managed = await webUtils.getManagedGuilds(event)
|
||||
const api = getAPI(event)
|
||||
const createRoom = getCreateRoom(event)
|
||||
const createSpace = getCreateSpace(event)
|
||||
|
|
@ -249,7 +249,7 @@ as.router.post("/api/link", defineEventHandler(async event => {
|
|||
|
||||
as.router.post("/api/unlink", defineEventHandler(async event => {
|
||||
const {channel_id, guild_id} = await readValidatedBody(event, schema.unlink.parse)
|
||||
const managed = await auth.getManagedGuilds(event)
|
||||
const managed = await webUtils.getManagedGuilds(event)
|
||||
|
||||
// Check guild ID or nonce
|
||||
if (!managed.has(guild_id)) throw createError({status: 403, message: "Forbidden", data: "Can't edit a guild you don't have Manage Server permissions in"})
|
||||
|
|
@ -266,7 +266,7 @@ as.router.post("/api/unlink", defineEventHandler(async event => {
|
|||
|
||||
as.router.post("/api/unlink-space", defineEventHandler(async event => {
|
||||
const {guild_id} = await readValidatedBody(event, schema.unlinkSpace.parse)
|
||||
const managed = await auth.getManagedGuilds(event)
|
||||
const managed = await webUtils.getManagedGuilds(event)
|
||||
const api = getAPI(event)
|
||||
const snow = getSnow(event)
|
||||
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ const {sync} = require("../../passthrough")
|
|||
const assert = require("assert").strict
|
||||
/** @type {import("../pug-sync")} */
|
||||
const pugSync = sync.require("../pug-sync")
|
||||
/** @type {import("../auth")} */
|
||||
const auth = sync.require("../auth")
|
||||
/** @type {import("../web-utils")} */
|
||||
const webUtils = sync.require("../web-utils")
|
||||
|
||||
const schema = {
|
||||
form: z.object({
|
||||
|
|
@ -52,12 +52,11 @@ as.router.get("/log-in-with-matrix", defineEventHandler(async event => {
|
|||
return pugSync.render(event, "log-in-with-matrix.pug", {next})
|
||||
}
|
||||
|
||||
const userAgent = getRequestHeader(event, "User-Agent")
|
||||
if (userAgent?.match(/bot|matrix/)) throw createError({status: 400, data: "Sorry URL previewer, you can't have this URL."})
|
||||
webUtils.rejectUrlPreviewer(event)
|
||||
|
||||
if (!validToken.has(token)) return sendRedirect(event, `${reg.ooye.bridge_origin}/log-in-with-matrix`, 302)
|
||||
|
||||
const session = await auth.useSession(event)
|
||||
const session = await webUtils.useSession(event)
|
||||
const mxid = validToken.get(token)
|
||||
assert(mxid)
|
||||
validToken.delete(token)
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ const getRelativePath = require("get-relative-path")
|
|||
|
||||
const {as, db, sync} = require("../../passthrough")
|
||||
const {id, permissions} = require("../../../addbot")
|
||||
/** @type {import("../auth")} */
|
||||
const auth = sync.require("../auth")
|
||||
/** @type {import("../web-utils")} */
|
||||
const webUtils = sync.require("../web-utils")
|
||||
const {reg} = require("../../matrix/read-registration")
|
||||
|
||||
const redirect_uri = `${reg.ooye.bridge_origin}/oauth`
|
||||
|
|
@ -53,7 +53,7 @@ function getOauth2Token(event) {
|
|||
}
|
||||
|
||||
as.router.get("/oauth", defineEventHandler(async event => {
|
||||
const session = await auth.useSession(event)
|
||||
const session = await webUtils.useSession(event)
|
||||
const parsedFirstQuery = await getValidatedQuery(event, schema.first.safeParse)
|
||||
let scope = "guilds"
|
||||
|
||||
|
|
|
|||
|
|
@ -6,10 +6,8 @@ const {defineEventHandler, createError, readValidatedBody, getRequestHeader, set
|
|||
|
||||
const {as, db, sync, select, discord} = require("../../passthrough")
|
||||
|
||||
/** @type {import("../auth")} */
|
||||
const auth = sync.require("../auth")
|
||||
/** @type {import("../pug-sync")} */
|
||||
const pugSync = sync.require("../pug-sync")
|
||||
/** @type {import("../web-utils")} */
|
||||
const webUtils = sync.require("../web-utils")
|
||||
|
||||
const schema = {
|
||||
optOut: z.object({
|
||||
|
|
@ -23,7 +21,7 @@ as.router.post("/api/opt-out", defineEventHandler(async event => {
|
|||
throw createError({status: 403, message: "Forbidden", data: "JavaScript is required for the opt-out form."})
|
||||
}
|
||||
|
||||
const session = await auth.useSession(event)
|
||||
const session = await webUtils.useSession(event)
|
||||
if (!session.data.userID) {
|
||||
throw createError({status: 401, message: "Unauthorised", data: "Log in first."})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ const {z} = require("zod")
|
|||
const {defineEventHandler, readValidatedBody, sendRedirect} = require("h3")
|
||||
const {as, sync} = require("../../passthrough")
|
||||
|
||||
/** @type {import("../auth")} */
|
||||
const auth = sync.require("../auth")
|
||||
/** @type {import("../web-utils")} */
|
||||
const webUtils = sync.require("../web-utils")
|
||||
|
||||
const schema = {
|
||||
password: z.object({
|
||||
|
|
@ -15,7 +15,7 @@ const schema = {
|
|||
|
||||
as.router.post("/api/password", defineEventHandler(async event => {
|
||||
const {password} = await readValidatedBody(event, schema.password.parse)
|
||||
const session = await auth.useSession(event)
|
||||
const session = await webUtils.useSession(event)
|
||||
await session.update({password})
|
||||
return sendRedirect(event, "../")
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ const {z} = require("zod")
|
|||
const pugSync = sync.require("../pug-sync")
|
||||
/** @type {import("../../discord/time")} */
|
||||
const discordTime = sync.require("../../discord/time")
|
||||
/** @type {import("../web-utils")} */
|
||||
const webUtils = sync.require("../web-utils")
|
||||
|
||||
const schema = {
|
||||
time: z.object({
|
||||
|
|
@ -17,6 +19,7 @@ const schema = {
|
|||
}
|
||||
|
||||
as.router.get("/time/:timestamp", defineEventHandler(async event => {
|
||||
webUtils.rejectUrlPreviewer(event) // url previewers can't show the local time, making it useless. hiding for everyone's sake
|
||||
const {timestamp} = await getValidatedRouterParams(event, schema.time.parse)
|
||||
const formatOptions = discordTime.getFormatOptions()
|
||||
const text = discordTime.convertDiscordTime(timestamp)
|
||||
|
|
@ -24,6 +27,7 @@ as.router.get("/time/:timestamp", defineEventHandler(async event => {
|
|||
}))
|
||||
|
||||
as.router.get("/time/:timestamp/:style", defineEventHandler(async event => {
|
||||
webUtils.rejectUrlPreviewer(event)
|
||||
const {timestamp, style} = await getValidatedRouterParams(event, schema.time.parse)
|
||||
const formatOptions = discordTime.getFormatOptions(style)
|
||||
const text = discordTime.convertDiscordTime(timestamp, style)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// @ts-check
|
||||
|
||||
const h3 = require("h3")
|
||||
const {getRequestHeader, createError} = require("h3")
|
||||
const {db} = require("../passthrough")
|
||||
const {reg} = require("../matrix/read-registration")
|
||||
|
||||
|
|
@ -29,5 +30,15 @@ function useSession(event) {
|
|||
return h3.useSession(event, {password: reg.as_token, maxAge: 365 * 24 * 60 * 60})
|
||||
}
|
||||
|
||||
/**
|
||||
* Rejects by throwing createError.
|
||||
* @param {h3.H3Event} event
|
||||
*/
|
||||
function rejectUrlPreviewer(event) {
|
||||
const userAgent = getRequestHeader(event, "User-Agent")
|
||||
if (userAgent?.match(/bot|matrix|tuwunel/i)) throw createError({status: 400, data: "Sorry URL previewer, you can't have this URL."})
|
||||
}
|
||||
|
||||
module.exports.getManagedGuilds = getManagedGuilds
|
||||
module.exports.useSession = useSession
|
||||
module.exports.rejectUrlPreviewer = rejectUrlPreviewer
|
||||
Loading…
Add table
Add a link
Reference in a new issue