Compare commits
3 commits
a41e3c45e7
...
6a452bd935
Author | SHA1 | Date | |
---|---|---|---|
6a452bd935 | |||
75414678c8 | |||
749f721aac |
10 changed files with 295 additions and 112 deletions
|
@ -1,28 +1,54 @@
|
|||
async function editMessage() {
|
||||
// Action time!
|
||||
// @ts-check
|
||||
|
||||
const passthrough = require("../../passthrough")
|
||||
const { sync, db } = passthrough
|
||||
/** @type {import("../converters/edit-to-changes")} */
|
||||
const editToChanges = sync.require("../converters/edit-to-changes")
|
||||
/** @type {import("../../matrix/api")} */
|
||||
const api = sync.require("../../matrix/api")
|
||||
|
||||
/**
|
||||
* @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message
|
||||
* @param {import("discord-api-types/v10").APIGuild} guild
|
||||
*/
|
||||
async function editMessage(message, guild) {
|
||||
console.log(`*** applying edit for message ${message.id} in channel ${message.channel_id}`)
|
||||
const {roomID, eventsToRedact, eventsToReplace, eventsToSend, senderMxid} = await editToChanges.editToChanges(message, guild, api)
|
||||
console.log("making these changes:", {eventsToRedact, eventsToReplace, eventsToSend})
|
||||
|
||||
// 1. Replace all the things.
|
||||
for (const {oldID, newContent} of eventsToReplace) {
|
||||
const eventType = newContent.$type
|
||||
/** @type {Pick<typeof newContent, Exclude<keyof newContent, "$type">> & { $type?: string }} */
|
||||
const newContentWithoutType = {...newContent}
|
||||
delete newContentWithoutType.$type
|
||||
|
||||
|
||||
// 2. Redact all the things.
|
||||
|
||||
// 3. Send all the things.
|
||||
|
||||
// old code lies here
|
||||
let eventPart = 0 // TODO: what to do about eventPart when editing? probably just need to make sure that exactly 1 value of '1' remains in the database?
|
||||
for (const event of events) {
|
||||
const eventType = event.$type
|
||||
/** @type {Pick<typeof event, Exclude<keyof event, "$type">> & { $type?: string }} */
|
||||
const eventWithoutType = {...event}
|
||||
delete eventWithoutType.$type
|
||||
|
||||
const eventID = await api.sendEvent(roomID, eventType, event, senderMxid)
|
||||
db.prepare("INSERT INTO event_message (event_id, message_id, channel_id, part, source) VALUES (?, ?, ?, ?, 1)").run(eventID, message.id, message.channel_id, eventPart) // source 1 = discord
|
||||
|
||||
eventPart = 1 // TODO: use more intelligent algorithm to determine whether primary or supporting
|
||||
eventIDs.push(eventID)
|
||||
await api.sendEvent(roomID, eventType, newContentWithoutType, senderMxid)
|
||||
// Ensure the database is up to date.
|
||||
// The columns are event_id, event_type, event_subtype, message_id, channel_id, part, source. Only event_subtype could potentially be changed by a replacement event.
|
||||
const subtype = newContentWithoutType.msgtype ?? null
|
||||
db.prepare("UPDATE event_message SET event_subtype = ? WHERE event_id = ?").run(subtype, oldID)
|
||||
}
|
||||
|
||||
return eventIDs
|
||||
// 2. Redact all the things.
|
||||
// Not redacting as the last action because the last action is likely to be shown in the room preview in clients, and we don't want it to look like somebody actually deleted a message.
|
||||
for (const eventID of eventsToRedact) {
|
||||
await api.redactEvent(roomID, eventID, senderMxid)
|
||||
// TODO: I should almost certainly remove the redacted event from our database now, shouldn't I? I mean, it's literally not there any more... you can't do anything else with it...
|
||||
// TODO: If I just redacted part = 0, I should update one of the other events to make it the new part = 0, right?
|
||||
// TODO: Consider whether this code could be reused between edited messages and deleted messages.
|
||||
}
|
||||
|
||||
{eventsToReplace, eventsToRedact, eventsToSend}
|
||||
// 3. Send all the things.
|
||||
for (const content of eventsToSend) {
|
||||
const eventType = content.$type
|
||||
/** @type {Pick<typeof content, Exclude<keyof content, "$type">> & { $type?: string }} */
|
||||
const contentWithoutType = {...content}
|
||||
delete contentWithoutType.$type
|
||||
|
||||
const eventID = await api.sendEvent(roomID, eventType, contentWithoutType, senderMxid)
|
||||
db.prepare("INSERT INTO event_message (event_id, event_type, event_subtype, message_id, channel_id, part, source) VALUES (?, ?, ?, ?, ?, 1, 1)").run(eventID, eventType, content.msgtype || null, message.id, message.channel_id) // part 1 = supporting; source 1 = discord
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.editMessage = editMessage
|
||||
|
|
|
@ -43,7 +43,7 @@ async function createSim(user) {
|
|||
* Ensure a sim is registered for the user.
|
||||
* If there is already a sim, use that one. If there isn't one yet, register a new sim.
|
||||
* @param {import("discord-api-types/v10").APIUser} user
|
||||
* @returns mxid
|
||||
* @returns {Promise<string>} mxid
|
||||
*/
|
||||
async function ensureSim(user) {
|
||||
let mxid = null
|
||||
|
@ -60,7 +60,7 @@ async function ensureSim(user) {
|
|||
* Ensure a sim is registered for the user and is joined to the room.
|
||||
* @param {import("discord-api-types/v10").APIUser} user
|
||||
* @param {string} roomID
|
||||
* @returns mxid
|
||||
* @returns {Promise<string>} mxid
|
||||
*/
|
||||
async function ensureSimJoined(user, roomID) {
|
||||
// Ensure room ID is really an ID, not an alias
|
||||
|
|
|
@ -6,8 +6,6 @@ const passthrough = require("../../passthrough")
|
|||
const { discord, sync, db } = passthrough
|
||||
/** @type {import("./message-to-event")} */
|
||||
const messageToEvent = sync.require("../converters/message-to-event")
|
||||
/** @type {import("../../matrix/api")} */
|
||||
const api = sync.require("../../matrix/api")
|
||||
/** @type {import("../actions/register-user")} */
|
||||
const registerUser = sync.require("../actions/register-user")
|
||||
/** @type {import("../actions/create-room")} */
|
||||
|
@ -18,8 +16,9 @@ const createRoom = sync.require("../actions/create-room")
|
|||
* IMPORTANT: This may not have all the normal fields! The API documentation doesn't provide possible types, just says it's all optional!
|
||||
* Since I don't have a spec, I will have to capture some real traffic and add it as test cases... I hope they don't change anything later...
|
||||
* @param {import("discord-api-types/v10").APIGuild} guild
|
||||
* @param {import("../../matrix/api")} api simple-as-nails dependency injection for the matrix API
|
||||
*/
|
||||
async function editToChanges(message, guild) {
|
||||
async function editToChanges(message, guild, api) {
|
||||
// Figure out what events we will be replacing
|
||||
|
||||
const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(message.channel_id)
|
||||
|
@ -76,14 +75,14 @@ async function editToChanges(message, guild) {
|
|||
}
|
||||
}
|
||||
// If we got this far, we could not pair it to an existing event, so it'll have to be a new one
|
||||
eventsToSend.push(newe)
|
||||
eventsToSend.push(newInnerContent[0])
|
||||
shift()
|
||||
}
|
||||
// Anything remaining in oldEventRows is present in the old version only and should be redacted.
|
||||
eventsToRedact = oldEventRows
|
||||
|
||||
// Now, everything in eventsToSend and eventsToRedact is a real change, but everything in eventsToReplace might not have actually changed!
|
||||
// (Consider a MESSAGE_UPDATE for a text+image message - Discord does not allow the image to be changed, but the text might have been.)
|
||||
// (Example: a MESSAGE_UPDATE for a text+image message - Discord does not allow the image to be changed, but the text might have been.)
|
||||
// So we'll remove entries from eventsToReplace that *definitely* cannot have changed. (This is category 4 mentioned above.) Everything remaining *may* have changed.
|
||||
eventsToReplace = eventsToReplace.filter(ev => {
|
||||
// Discord does not allow files, images, attachments, or videos to be edited.
|
||||
|
@ -100,9 +99,9 @@ async function editToChanges(message, guild) {
|
|||
|
||||
// Removing unnecessary properties before returning
|
||||
eventsToRedact = eventsToRedact.map(e => e.event_id)
|
||||
eventsToReplace = eventsToReplace.map(e => ({oldID: e.old.event_id, new: eventToReplacementEvent(e.old.event_id, e.newFallbackContent, e.newInnerContent)}))
|
||||
eventsToReplace = eventsToReplace.map(e => ({oldID: e.old.event_id, newContent: makeReplacementEventContent(e.old.event_id, e.newFallbackContent, e.newInnerContent)}))
|
||||
|
||||
return {eventsToReplace, eventsToRedact, eventsToSend}
|
||||
return {roomID, eventsToReplace, eventsToRedact, eventsToSend, senderMxid}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -112,7 +111,7 @@ async function editToChanges(message, guild) {
|
|||
* @param {T} newInnerContent
|
||||
* @returns {import("../../types").Event.ReplacementContent<T>} content
|
||||
*/
|
||||
function eventToReplacementEvent(oldID, newFallbackContent, newInnerContent) {
|
||||
function makeReplacementEventContent(oldID, newFallbackContent, newInnerContent) {
|
||||
const content = {
|
||||
...newFallbackContent,
|
||||
"m.mentions": {},
|
||||
|
@ -131,4 +130,4 @@ function eventToReplacementEvent(oldID, newFallbackContent, newInnerContent) {
|
|||
}
|
||||
|
||||
module.exports.editToChanges = editToChanges
|
||||
module.exports.eventToReplacementEvent = eventToReplacementEvent
|
||||
module.exports.makeReplacementEventContent = makeReplacementEventContent
|
||||
|
|
|
@ -1,17 +1,35 @@
|
|||
// @ts-check
|
||||
|
||||
const {test} = require("supertape")
|
||||
const {editToChanges} = require("./edit-to-changes")
|
||||
const data = require("../../test/data")
|
||||
const Ty = require("../../types")
|
||||
|
||||
test("edit2changes: bot response", async t => {
|
||||
const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.bot_response, data.guild.general)
|
||||
const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.bot_response, data.guild.general, {
|
||||
async getJoinedMembers(roomID) {
|
||||
t.equal(roomID, "!uCtjHhfGlYbVnPVlkG:cadence.moe")
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
joined: {
|
||||
"@cadence:cadence.moe": {
|
||||
display_name: "cadence [they]",
|
||||
avatar_url: "whatever"
|
||||
},
|
||||
"@_ooye_botrac4r:cadence.moe": {
|
||||
display_name: "botrac4r",
|
||||
avatar_url: "whatever"
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
t.deepEqual(eventsToRedact, [])
|
||||
t.deepEqual(eventsToSend, [])
|
||||
t.deepEqual(eventsToReplace, [{
|
||||
oldID: "$fdD9OZ55xg3EAsfvLZza5tMhtjUO91Wg3Otuo96TplY",
|
||||
new: {
|
||||
newContent: {
|
||||
$type: "m.room.message",
|
||||
msgtype: "m.text",
|
||||
body: "* :ae_botrac4r: @cadence asked ````, I respond: Stop drinking paint. (No)\n\nHit :bn_re: to reroll.",
|
||||
|
@ -39,13 +57,32 @@ test("edit2changes: bot response", async t => {
|
|||
}])
|
||||
})
|
||||
|
||||
test("edit2changes: remove caption from image", async t => {
|
||||
const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.removed_caption_from_image, data.guild.general, {})
|
||||
t.deepEqual(eventsToRedact, ["$mtR8cJqM4fKno1bVsm8F4wUVqSntt2sq6jav1lyavuA"])
|
||||
t.deepEqual(eventsToSend, [])
|
||||
t.deepEqual(eventsToReplace, [])
|
||||
})
|
||||
|
||||
test("edit2changes: add caption back to that image", async t => {
|
||||
const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.added_caption_to_image, data.guild.general, {})
|
||||
t.deepEqual(eventsToRedact, [])
|
||||
t.deepEqual(eventsToSend, [{
|
||||
$type: "m.room.message",
|
||||
msgtype: "m.text",
|
||||
body: "some text",
|
||||
"m.mentions": {}
|
||||
}])
|
||||
t.deepEqual(eventsToReplace, [])
|
||||
})
|
||||
|
||||
test("edit2changes: edit of reply to skull webp attachment with content", async t => {
|
||||
const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.edit_of_reply_to_skull_webp_attachment_with_content, data.guild.general)
|
||||
const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.edit_of_reply_to_skull_webp_attachment_with_content, data.guild.general, {})
|
||||
t.deepEqual(eventsToRedact, [])
|
||||
t.deepEqual(eventsToSend, [])
|
||||
t.deepEqual(eventsToReplace, [{
|
||||
oldID: "$vgTKOR5ZTYNMKaS7XvgEIDaOWZtVCEyzLLi5Pc5Gz4M",
|
||||
new: {
|
||||
newContent: {
|
||||
$type: "m.room.message",
|
||||
msgtype: "m.text",
|
||||
body: "> Extremity: Image\n\n* Edit",
|
||||
|
|
|
@ -3,6 +3,9 @@ const {sync, db} = require("../passthrough")
|
|||
|
||||
/** @type {import("./actions/send-message")}) */
|
||||
const sendMessage = sync.require("./actions/send-message")
|
||||
/** @type {import("./actions/edit-message")}) */
|
||||
const editMessage = sync.require("./actions/edit-message")
|
||||
|
||||
/** @type {import("./actions/add-reaction")}) */
|
||||
const addReaction = sync.require("./actions/add-reaction")
|
||||
|
||||
|
@ -29,6 +32,25 @@ module.exports = {
|
|||
sendMessage.sendMessage(message, guild)
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {import("./discord-client")} client
|
||||
* @param {import("discord-api-types/v10").GatewayMessageUpdateDispatchData} message
|
||||
*/
|
||||
onMessageUpdate(client, data) {
|
||||
// Based on looking at data they've sent me over the gateway, this is the best way to check for meaningful changes.
|
||||
// If the message content is a string then it includes all interesting fields and is meaningful.
|
||||
if (typeof data.content === "string") {
|
||||
/** @type {import("discord-api-types/v10").GatewayMessageCreateDispatchData} */
|
||||
const message = data
|
||||
/** @type {import("discord-api-types/v10").APIGuildChannel} */
|
||||
const channel = client.channels.get(message.channel_id)
|
||||
if (!channel.guild_id) return // Nothing we can do in direct messages.
|
||||
const guild = client.guilds.get(channel.guild_id)
|
||||
if (message.guild_id !== "112760669178241024" && message.guild_id !== "497159726455455754") return // TODO: activate on other servers (requires the space creation flow to be done first)
|
||||
editMessage.editMessage(message, guild)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {import("./discord-client")} client
|
||||
* @param {import("discord-api-types/v10").GatewayMessageReactionAddDispatchData} data
|
||||
|
|
BIN
db/ooye.db
BIN
db/ooye.db
Binary file not shown.
71
gorge.js
71
gorge.js
|
@ -1,71 +0,0 @@
|
|||
// @ts-check
|
||||
|
||||
const assert = require("assert").strict
|
||||
const {test} = require("supertape")
|
||||
|
||||
|
||||
/**
|
||||
* @param {{id: string, name: string, description?: string?}} route
|
||||
* @param {{id: string}} vehicle
|
||||
* @param {string?} customName
|
||||
*
|
||||
function convertNameAndTopic(route, vehicle, customName) {
|
||||
const convertedName = customName || route.name
|
||||
const convertedTopic = (() => {
|
||||
// custom name + description
|
||||
if (customName && route.description)
|
||||
return `${route.name} | ${route.description}\n\nRoute ID: ${route.id}\nVehicle ID: ${vehicle.id}`
|
||||
// custom name + no description
|
||||
if (customName)
|
||||
return `${route.name}\n\nRoute ID: ${route.id}\nVehicle ID: ${vehicle.id}`
|
||||
// no custom name + description
|
||||
if (route.description)
|
||||
return `${route.description}\n\nRoute ID: ${route.id}\nVehicle ID: ${vehicle.id}`
|
||||
// neither
|
||||
return `Route ID: ${route.id}\nVehicle ID: ${vehicle.id}`
|
||||
})()
|
||||
|
||||
return [convertedName, convertedTopic]
|
||||
}*/
|
||||
|
||||
function convertNameAndTopic(route, vehicle, customName) {
|
||||
const convertedName = customName || route.name;
|
||||
const maybeDescriptionWithPipe = route.description ? ` | ${route.description}` : '';
|
||||
const maybeDescriptionWithNewlines = route.description ? `${route.description}\n\n` : '';
|
||||
const routeIdPart = `Route ID: ${route.id}`;
|
||||
const vehicleIdPart = `Vehicle ID: ${vehicle.id}`;
|
||||
|
||||
const convertedTopic = customName
|
||||
? `${route.name}${maybeDescriptionWithPipe}\n\n${routeIdPart}\n${vehicleIdPart}`
|
||||
: `${maybeDescriptionWithNewlines}${routeIdPart}\n${vehicleIdPart}`;
|
||||
|
||||
return [convertedName, convertedTopic];
|
||||
}
|
||||
|
||||
test("convertNameAndTopic: description and custom name", t => {
|
||||
t.deepEqual(
|
||||
convertNameAndTopic({id: "111", name: "I-386", description: "Through the gorge southeast of Lonely Peak"}, {id: "111"}, "gorge"),
|
||||
["gorge", "I-386 | Through the gorge southeast of Lonely Peak\n\nRoute ID: 111\nVehicle ID: 111"]
|
||||
)
|
||||
})
|
||||
|
||||
test("convertNameAndTopic: custom name only", t => {
|
||||
t.deepEqual(
|
||||
convertNameAndTopic({id: "222", name: "I-386"}, {id: "222"}, "gorge"),
|
||||
["gorge", "I-386\n\nRoute ID: 222\nVehicle ID: 222"]
|
||||
)
|
||||
})
|
||||
|
||||
test("convertNameAndTopic: description only", t => {
|
||||
t.deepEqual(
|
||||
convertNameAndTopic({id: "333", name: "I-386", description: "Through the gorge southeast of Lonely Peak"}, {id: "333"}, null),
|
||||
["I-386", "Through the gorge southeast of Lonely Peak\n\nRoute ID: 333\nVehicle ID: 333"]
|
||||
)
|
||||
})
|
||||
|
||||
test("convertNameAndTopic: neither", t => {
|
||||
t.deepEqual(
|
||||
convertNameAndTopic({id: "444", name: "I-386"}, {id: "444"}, null),
|
||||
["I-386", "Route ID: 444\nVehicle ID: 444"]
|
||||
)
|
||||
})
|
|
@ -14,7 +14,7 @@ const makeTxnId = sync.require("./txnid")
|
|||
|
||||
/**
|
||||
* @param {string} p endpoint to access
|
||||
* @param {string} [mxid] optional: user to act as, for the ?user_id parameter
|
||||
* @param {string?} [mxid] optional: user to act as, for the ?user_id parameter
|
||||
* @param {{[x: string]: any}} [otherParams] optional: any other query parameters to add
|
||||
* @returns {string} the new endpoint
|
||||
*/
|
||||
|
@ -119,7 +119,7 @@ async function sendState(roomID, type, stateKey, content, mxid) {
|
|||
* @param {string} roomID
|
||||
* @param {string} type
|
||||
* @param {any} content
|
||||
* @param {string} [mxid]
|
||||
* @param {string?} [mxid]
|
||||
* @param {number} [timestamp] timestamp of the newly created event, in unix milliseconds
|
||||
*/
|
||||
async function sendEvent(roomID, type, content, mxid, timestamp) {
|
||||
|
@ -129,6 +129,15 @@ async function sendEvent(roomID, type, content, mxid, timestamp) {
|
|||
return root.event_id
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<string>} room ID
|
||||
*/
|
||||
async function redactEvent(roomID, eventID, mxid) {
|
||||
/** @type {Ty.R.EventRedacted} */
|
||||
const root = await mreq.mreq("PUT", path(`/client/v3/rooms/${roomID}/redact/${eventID}/${makeTxnId.makeTxnId()}`, mxid))
|
||||
return root.event_id
|
||||
}
|
||||
|
||||
async function profileSetDisplayname(mxid, displayname) {
|
||||
await mreq.mreq("PUT", path(`/client/v3/profile/${mxid}/displayname`, mxid), {
|
||||
displayname
|
||||
|
@ -152,5 +161,6 @@ module.exports.getAllState = getAllState
|
|||
module.exports.getJoinedMembers = getJoinedMembers
|
||||
module.exports.sendState = sendState
|
||||
module.exports.sendEvent = sendEvent
|
||||
module.exports.redactEvent = redactEvent
|
||||
module.exports.profileSetDisplayname = profileSetDisplayname
|
||||
module.exports.profileSetAvatarUrl = profileSetAvatarUrl
|
||||
|
|
156
test/data.js
156
test/data.js
|
@ -867,6 +867,162 @@ module.exports = {
|
|||
tts: false,
|
||||
type: 0
|
||||
},
|
||||
removed_caption_from_image: {
|
||||
attachments: [
|
||||
{
|
||||
content_type: "image/png",
|
||||
filename: "piper_2.png",
|
||||
height: 163,
|
||||
id: "1141501302497615912",
|
||||
proxy_url: "https://media.discordapp.net/attachments/112760669178241024/1141501302497615912/piper_2.png",
|
||||
size: 43231,
|
||||
url: "https://cdn.discordapp.com/attachments/112760669178241024/1141501302497615912/piper_2.png",
|
||||
width: 188
|
||||
}
|
||||
],
|
||||
author: {
|
||||
avatar: "47db1be7ab77e1d812a4573177af0692",
|
||||
avatar_decoration: null,
|
||||
discriminator: "0",
|
||||
global_name: "wing",
|
||||
id: "112890272819507200",
|
||||
public_flags: 0,
|
||||
username: ".wing."
|
||||
},
|
||||
channel_id: "112760669178241024",
|
||||
components: [],
|
||||
content: "",
|
||||
edited_timestamp: "2023-08-16T22:38:43.075298+00:00",
|
||||
embeds: [],
|
||||
flags: 0,
|
||||
guild_id: "112760669178241024",
|
||||
id: "1141501302736695316",
|
||||
member: {
|
||||
avatar: null,
|
||||
communication_disabled_until: null,
|
||||
deaf: false,
|
||||
flags: 0,
|
||||
joined_at: "2015-11-08T12:25:38.461000+00:00",
|
||||
mute: false,
|
||||
nick: "windfucker",
|
||||
pending: false,
|
||||
premium_since: null,
|
||||
roles: [
|
||||
"204427286542417920",
|
||||
"118924814567211009",
|
||||
"222168467627835392",
|
||||
"265239342648131584",
|
||||
"303273332248412160",
|
||||
"303319030163439616",
|
||||
"305775031223320577",
|
||||
"318243902521868288",
|
||||
"349185088157777920",
|
||||
"378402925128712193",
|
||||
"391076926573510656",
|
||||
"230462991751970827",
|
||||
"392141548932038658",
|
||||
"397533096012152832",
|
||||
"454567553738473472",
|
||||
"482658335536185357",
|
||||
"482860581670486028",
|
||||
"495384759074160642",
|
||||
"638988388740890635",
|
||||
"764071315388629012",
|
||||
"373336013109461013",
|
||||
"872274377150980116",
|
||||
"1034022405275910164",
|
||||
"790724320824655873",
|
||||
"1040735082610167858",
|
||||
"1123730787653660742",
|
||||
"1070177137367208036"
|
||||
]
|
||||
},
|
||||
mention_everyone: false,
|
||||
mention_roles: [],
|
||||
mentions: [],
|
||||
pinned: false,
|
||||
timestamp: "2023-08-16T22:38:38.641000+00:00",
|
||||
tts: false,
|
||||
type: 0
|
||||
},
|
||||
added_caption_to_image: {
|
||||
attachments: [
|
||||
{
|
||||
content_type: "image/png",
|
||||
filename: "piper_2.png",
|
||||
height: 163,
|
||||
id: "1141501302497615912",
|
||||
proxy_url: "https://media.discordapp.net/attachments/112760669178241024/1141501302497615912/piper_2.png",
|
||||
size: 43231,
|
||||
url: "https://cdn.discordapp.com/attachments/112760669178241024/1141501302497615912/piper_2.png",
|
||||
width: 188
|
||||
}
|
||||
],
|
||||
author: {
|
||||
avatar: "47db1be7ab77e1d812a4573177af0692",
|
||||
avatar_decoration: null,
|
||||
discriminator: "0",
|
||||
global_name: "wing",
|
||||
id: "112890272819507200",
|
||||
public_flags: 0,
|
||||
username: ".wing."
|
||||
},
|
||||
channel_id: "112760669178241024",
|
||||
components: [],
|
||||
content: "some text",
|
||||
edited_timestamp: "2023-08-17T00:13:18.620975+00:00",
|
||||
embeds: [],
|
||||
flags: 0,
|
||||
guild_id: "112760669178241024",
|
||||
id: "1141501302736695317",
|
||||
member: {
|
||||
avatar: null,
|
||||
communication_disabled_until: null,
|
||||
deaf: false,
|
||||
flags: 0,
|
||||
joined_at: "2015-11-08T12:25:38.461000+00:00",
|
||||
mute: false,
|
||||
nick: "windfucker",
|
||||
pending: false,
|
||||
premium_since: null,
|
||||
roles: [
|
||||
"204427286542417920",
|
||||
"118924814567211009",
|
||||
"222168467627835392",
|
||||
"265239342648131584",
|
||||
"303273332248412160",
|
||||
"303319030163439616",
|
||||
"305775031223320577",
|
||||
"318243902521868288",
|
||||
"349185088157777920",
|
||||
"378402925128712193",
|
||||
"391076926573510656",
|
||||
"230462991751970827",
|
||||
"392141548932038658",
|
||||
"397533096012152832",
|
||||
"454567553738473472",
|
||||
"482658335536185357",
|
||||
"482860581670486028",
|
||||
"495384759074160642",
|
||||
"638988388740890635",
|
||||
"764071315388629012",
|
||||
"373336013109461013",
|
||||
"872274377150980116",
|
||||
"1034022405275910164",
|
||||
"790724320824655873",
|
||||
"1040735082610167858",
|
||||
"1123730787653660742",
|
||||
"1070177137367208036"
|
||||
]
|
||||
},
|
||||
mention_everyone: false,
|
||||
mention_roles: [],
|
||||
mentions: [],
|
||||
pinned: false,
|
||||
timestamp: "2023-08-16T22:38:38.641000+00:00",
|
||||
tts: false,
|
||||
type: 0
|
||||
},
|
||||
edit_of_reply_to_skull_webp_attachment_with_content: {
|
||||
type: 19,
|
||||
tts: false,
|
||||
|
|
4
types.d.ts
vendored
4
types.d.ts
vendored
|
@ -112,4 +112,8 @@ namespace R {
|
|||
export type EventSent = {
|
||||
event_id: string
|
||||
}
|
||||
|
||||
export type EventRedacted = {
|
||||
event_id: string
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue