Compare commits
No commits in common. "main" and "mergable" have entirely different histories.
12 changed files with 29 additions and 161 deletions
|
|
@ -38,7 +38,6 @@ For more information about features, [see the user guide.](https://gitdab.com/ca
|
|||
|
||||
* This bridge is not designed for puppetting.
|
||||
* Direct Messaging is not supported until I figure out a good way of doing it.
|
||||
* Encrypted messages are not supported. Decryption is often unreliable on Matrix, and your messages end up in plaintext on Discord anyway, so there's not much advantage.
|
||||
|
||||
## Get started!
|
||||
|
||||
|
|
|
|||
|
|
@ -1,65 +0,0 @@
|
|||
// @ts-check
|
||||
|
||||
const pb = require("prettier-bytes")
|
||||
const sqlite = require("better-sqlite3")
|
||||
const HeatSync = require("heatsync")
|
||||
|
||||
const {reg} = require("../src/matrix/read-registration")
|
||||
const passthrough = require("../src/passthrough")
|
||||
|
||||
const sync = new HeatSync({watchFS: false})
|
||||
Object.assign(passthrough, {reg, sync})
|
||||
|
||||
const DiscordClient = require("../src/d2m/discord-client")
|
||||
|
||||
const discord = new DiscordClient(reg.ooye.discord_token, "no")
|
||||
passthrough.discord = discord
|
||||
|
||||
const db = new sqlite("ooye.db")
|
||||
passthrough.db = db
|
||||
|
||||
const api = require("../src/matrix/api")
|
||||
|
||||
const {room: roomID} = require("minimist")(process.argv.slice(2), {string: ["room"]})
|
||||
if (!roomID) {
|
||||
console.error("Usage: ./scripts/estimate-size.js --room=<!room id here>")
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const {channel_id, guild_id} = db.prepare("SELECT channel_id, guild_id FROM channel_room WHERE room_id = ?").get(roomID)
|
||||
|
||||
const max = 1000
|
||||
|
||||
;(async () => {
|
||||
let total = 0
|
||||
let size = 0
|
||||
let from
|
||||
|
||||
while (total < max) {
|
||||
const events = await api.getEvents(roomID, "b", {limit: 1000, from})
|
||||
total += events.chunk.length
|
||||
from = events.end
|
||||
console.log(`Fetched ${total} events so far`)
|
||||
|
||||
for (const e of events.chunk) {
|
||||
if (e.content?.info?.size) {
|
||||
size += e.content.info.size
|
||||
}
|
||||
}
|
||||
|
||||
if (events.chunk.length === 0 || !events.end) break
|
||||
}
|
||||
|
||||
console.log(`Total size of uploads: ${pb(size)}`)
|
||||
|
||||
const searchResults = await discord.snow.requestHandler.request(`/guilds/${guild_id}/messages/search`, {
|
||||
channel_id,
|
||||
offset: "0",
|
||||
limit: "1"
|
||||
}, "get", "json")
|
||||
|
||||
const totalAllTime = searchResults.total_results
|
||||
const fractionCounted = total / totalAllTime
|
||||
console.log(`That counts for ${(fractionCounted*100).toFixed(2)}% of the history on Discord (${totalAllTime.toLocaleString()} messages)`)
|
||||
console.log(`The size of uploads for the whole history would be approx: ${pb(Math.floor(size/total*totalAllTime))}`)
|
||||
})()
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
#!/usr/bin/env node
|
||||
// @ts-check
|
||||
|
||||
const Ty = require("../src/types")
|
||||
const assert = require("assert").strict
|
||||
const fs = require("fs")
|
||||
const sqlite = require("better-sqlite3")
|
||||
|
|
@ -286,8 +285,8 @@ function defineEchoHandler() {
|
|||
console.log()
|
||||
|
||||
// Done with user prompts, reg is now guaranteed to be valid
|
||||
const mreq = require("../src/matrix/mreq")
|
||||
const api = require("../src/matrix/api")
|
||||
const file = require("../src/matrix/file")
|
||||
const DiscordClient = require("../src/d2m/discord-client")
|
||||
const discord = new DiscordClient(reg.ooye.discord_token, "no")
|
||||
passthrough.discord = discord
|
||||
|
|
@ -344,13 +343,7 @@ function defineEchoHandler() {
|
|||
await api.register(reg.sender_localpart)
|
||||
|
||||
// upload initial images...
|
||||
const avatarBuffer = await fs.promises.readFile(join(__dirname, "..", "docs", "img", "icon.png"), null)
|
||||
/** @type {Ty.R.FileUploaded} */
|
||||
const root = await mreq.mreq("POST", "/media/v3/upload", avatarBuffer, {
|
||||
headers: {"Content-Type": "image/png"}
|
||||
})
|
||||
const avatarUrl = root.content_uri
|
||||
assert(avatarUrl)
|
||||
const avatarUrl = await file.uploadDiscordFileToMxc("https://cadence.moe/friends/out_of_your_element.png")
|
||||
|
||||
console.log("✅ Matrix appservice login works...")
|
||||
|
||||
|
|
@ -359,7 +352,8 @@ function defineEchoHandler() {
|
|||
console.log("✅ Emojis are ready...")
|
||||
|
||||
// set profile data on discord...
|
||||
await discord.snow.user.updateSelf({avatar: "data:image/png;base64," + avatarBuffer.toString("base64")})
|
||||
const avatarImageBuffer = await fetch("https://cadence.moe/friends/out_of_your_element.png").then(res => res.arrayBuffer())
|
||||
await discord.snow.user.updateSelf({avatar: "data:image/png;base64," + Buffer.from(avatarImageBuffer).toString("base64")})
|
||||
console.log("✅ Discord profile updated...")
|
||||
|
||||
// set profile data on homeserver...
|
||||
|
|
|
|||
|
|
@ -20,10 +20,10 @@ const SPECIAL_USER_MAPPINGS = new Map([
|
|||
function downcaseUsername(user) {
|
||||
// First, try to convert the username to the set of allowed characters
|
||||
let downcased = user.username.toLowerCase()
|
||||
// spaces and slashes to underscores...
|
||||
.replace(/[ /]/g, "_")
|
||||
// spaces to underscores...
|
||||
.replace(/ /g, "_")
|
||||
// remove disallowed characters...
|
||||
.replace(/[^a-z0-9._=-]*/g, "")
|
||||
.replace(/[^a-z0-9._=/-]*/g, "")
|
||||
// remove leading and trailing dashes and underscores...
|
||||
.replace(/(?:^[_-]*|[_-]*$)/g, "")
|
||||
// If requested, also make the Discord user ID part of the username
|
||||
|
|
|
|||
|
|
@ -21,12 +21,8 @@ test("user2name: works on single emoji at the end", t => {
|
|||
t.equal(userToSimName({username: "Melody 🎵", discriminator: "2192"}), "melody")
|
||||
})
|
||||
|
||||
test("user2name: works on really weird name", t => {
|
||||
t.equal(userToSimName({username: "*** D3 &W (89) _7//-", discriminator: "0001"}), "d3_w_89__7")
|
||||
})
|
||||
|
||||
test("user2name: treats slashes", t => {
|
||||
t.equal(userToSimName({username: "Evil Lillith (she/her)", discriminator: "5892"}), "evil_lillith_she_her")
|
||||
test("user2name: works on crazy name", t => {
|
||||
t.equal(userToSimName({username: "*** D3 &W (89) _7//-", discriminator: "0001"}), "d3_w_89__7//")
|
||||
})
|
||||
|
||||
test("user2name: adds discriminator if name is unavailable (old tag format)", t => {
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
BEGIN TRANSACTION;
|
||||
|
||||
DELETE FROM sim WHERE sim_name like '%/%';
|
||||
|
||||
COMMIT;
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
// @ts-check
|
||||
|
||||
const {Readable} = require("stream")
|
||||
const {ReadableStream} = require("stream/web")
|
||||
|
||||
const {sync} = require("../../passthrough")
|
||||
const sharp = require("sharp")
|
||||
/** @type {import("../../matrix/api")} */
|
||||
const api = sync.require("../../matrix/api")
|
||||
/** @type {import("../../matrix/mreq")} */
|
||||
const mreq = sync.require("../../matrix/mreq")
|
||||
const streamMimeType = require("stream-mime-type")
|
||||
|
||||
const WIDTH = 160
|
||||
const HEIGHT = 160
|
||||
/**
|
||||
* Downloads the sticker from the web and converts to webp data.
|
||||
* @param {string} mxc a single mxc:// URL
|
||||
* @returns {Promise<ReadableStream>} sticker webp data, or undefined if the downloaded sticker is not valid
|
||||
*/
|
||||
async function getAndResizeSticker(mxc) {
|
||||
const res = await api.getMedia(mxc)
|
||||
if (res.status !== 200) {
|
||||
const root = await res.json()
|
||||
throw new mreq.MatrixServerError(root, {mxc})
|
||||
}
|
||||
|
||||
const streamIn = Readable.fromWeb(res.body)
|
||||
const { stream, mime } = await streamMimeType.getMimeType(streamIn)
|
||||
const animated = ["image/gif", "image/webp"].includes(mime)
|
||||
|
||||
const transformer = sharp({animated: animated})
|
||||
.resize(WIDTH, HEIGHT, {fit: "inside", background: {r: 0, g: 0, b: 0, alpha: 0}})
|
||||
.webp()
|
||||
stream.pipe(transformer)
|
||||
return Readable.toWeb(transformer)
|
||||
}
|
||||
|
||||
|
||||
module.exports.getAndResizeSticker = getAndResizeSticker
|
||||
|
|
@ -631,10 +631,23 @@ async function eventToMessage(event, guild, channel, di) {
|
|||
}
|
||||
|
||||
if (event.type === "m.sticker") {
|
||||
const withoutMxc = mxUtils.makeMxcPublic(event.content.url)
|
||||
assert(withoutMxc)
|
||||
const url = `${reg.ooye.bridge_origin}/download/sticker/${withoutMxc}/_.webp`
|
||||
content = `[${event.content.body || "\u2800"}](${url})`
|
||||
content = ""
|
||||
let filename = event.content.body
|
||||
if (event.type === "m.sticker") {
|
||||
let mimetype
|
||||
if (event.content.info?.mimetype?.includes("/")) {
|
||||
mimetype = event.content.info.mimetype
|
||||
} else {
|
||||
const res = await di.api.getMedia(event.content.url, {method: "HEAD"})
|
||||
if (res.status === 200) {
|
||||
mimetype = res.headers.get("content-type")
|
||||
}
|
||||
if (!mimetype) throw new Error(`Server error ${res.status} or missing content-type while detecting sticker mimetype`)
|
||||
}
|
||||
filename += "." + mimetype.split("/")[1]
|
||||
}
|
||||
attachments.push({id: "0", filename})
|
||||
pendingFiles.push({name: filename, mxc: event.content.url})
|
||||
|
||||
} else if (event.type === "org.matrix.msc3381.poll.start") {
|
||||
const pollContent = event.content["org.matrix.msc3381.poll.start"] // just for convenience
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ async function getEventForTimestamp(roomID, ts) {
|
|||
*/
|
||||
async function getEvents(roomID, dir, pagination = {}, filter) {
|
||||
filter = filter && JSON.stringify(filter)
|
||||
/** @type {Ty.MessagesPagination<Ty.Event.Outer<any>>} */
|
||||
/** @type {Ty.Pagination<Ty.Event.Outer<any>>} */
|
||||
const root = await mreq.mreq("GET", path(`/client/v3/rooms/${roomID}/messages`, null, {...pagination, dir, filter}))
|
||||
return root
|
||||
}
|
||||
|
|
|
|||
8
src/types.d.ts
vendored
8
src/types.d.ts
vendored
|
|
@ -498,13 +498,7 @@ export type Membership = "invite" | "knock" | "join" | "leave" | "ban"
|
|||
export type Pagination<T> = {
|
||||
chunk: T[]
|
||||
next_batch?: string
|
||||
prev_batch?: string
|
||||
}
|
||||
|
||||
export type MessagesPagination<T> = {
|
||||
chunk: T[]
|
||||
start: string
|
||||
end?: string
|
||||
prev_match?: string
|
||||
}
|
||||
|
||||
export type HierarchyPagination<T> = {
|
||||
|
|
|
|||
|
|
@ -16,9 +16,6 @@ const emojiSheet = sync.require("../../m2d/actions/emoji-sheet")
|
|||
/** @type {import("../../m2d/converters/emoji-sheet")} */
|
||||
const emojiSheetConverter = sync.require("../../m2d/converters/emoji-sheet")
|
||||
|
||||
/** @type {import("../../m2d/actions/sticker")} */
|
||||
const sticker = sync.require("../../m2d/actions/sticker")
|
||||
|
||||
const schema = {
|
||||
params: z.object({
|
||||
server_name: z.string(),
|
||||
|
|
@ -26,10 +23,6 @@ const schema = {
|
|||
}),
|
||||
sheet: z.object({
|
||||
e: z.array(z.string()).or(z.string())
|
||||
}),
|
||||
sticker: z.object({
|
||||
server_name: z.string().regex(/^[^/]+$/),
|
||||
media_id: z.string().regex(/^[A-Za-z0-9_-]+$/)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -97,14 +90,3 @@ as.router.get(`/download/sheet`, defineEventHandler(async event => {
|
|||
setResponseHeader(event, "Content-Type", "image/png")
|
||||
return buffer
|
||||
}))
|
||||
|
||||
as.router.get(`/download/sticker/:server_name/:media_id/_.webp`, defineEventHandler(async event => {
|
||||
const {server_name, media_id} = await getValidatedRouterParams(event, schema.sticker.parse)
|
||||
/** remember that this has no mxc:// protocol in the string */
|
||||
const mxc = server_name + "/" + media_id
|
||||
verifyMediaHash(mxc)
|
||||
|
||||
const stream = await sticker.getAndResizeSticker(`mxc://${mxc}`)
|
||||
setResponseHeader(event, "Content-Type", "image/webp")
|
||||
return stream
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ function getChannelRoomsLinks(guild, rooms, roles) {
|
|||
let removedWrongTypeChannels = dUtils.filterTo(unlinkedChannels, c => c && [0, 5].includes(c.type))
|
||||
let removedPrivateChannels = dUtils.filterTo(unlinkedChannels, c => {
|
||||
const permissions = dUtils.getPermissions(guild.id, roles, guild.roles, botID, c["permission_overwrites"])
|
||||
return dUtils.hasSomePermissions(permissions, ["Administrator", "ViewChannel"])
|
||||
return dUtils.hasPermission(permissions, DiscordTypes.PermissionFlagsBits.ViewChannel)
|
||||
})
|
||||
unlinkedChannels.sort((a, b) => getPosition(a, discord.channels) - getPosition(b, discord.channels))
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue