Compare commits

..

2 commits

Author SHA1 Message Date
7d42a530e7 Wait for ping to work during setup 2024-09-05 16:48:53 +12:00
37f3a59d8e Interactive initial setup 2024-09-05 15:36:43 +12:00
25 changed files with 306 additions and 43 deletions

View file

@ -3,7 +3,7 @@
const assert = require("assert").strict const assert = require("assert").strict
const DiscordTypes = require("discord-api-types/v10") const DiscordTypes = require("discord-api-types/v10")
const Ty = require("../../types") const Ty = require("../../types")
const reg = require("../../matrix/read-registration") const {reg} = require("../../matrix/read-registration")
const passthrough = require("../../passthrough") const passthrough = require("../../passthrough")
const {discord, sync, db, select} = passthrough const {discord, sync, db, select} = passthrough
@ -372,7 +372,7 @@ async function _unbridgeRoom(channelID) {
} }
/** /**
* @param {DiscordTypes.APIGuildChannel} channel * @param {{id: string, topic?: string?}} channel
* @param {string} guildID * @param {string} guildID
*/ */
async function unbridgeDeletedChannel(channel, guildID) { async function unbridgeDeletedChannel(channel, guildID) {

View file

@ -4,7 +4,7 @@ const assert = require("assert").strict
const {isDeepStrictEqual} = require("util") const {isDeepStrictEqual} = require("util")
const DiscordTypes = require("discord-api-types/v10") const DiscordTypes = require("discord-api-types/v10")
const Ty = require("../../types") const Ty = require("../../types")
const reg = require("../../matrix/read-registration") const {reg} = require("../../matrix/read-registration")
const passthrough = require("../../passthrough") const passthrough = require("../../passthrough")
const {discord, sync, db, select} = passthrough const {discord, sync, db, select} = passthrough
@ -192,7 +192,7 @@ async function syncSpaceFully(guildID) {
if (discord.channels.has(channelID)) { if (discord.channels.has(channelID)) {
await createRoom.syncRoom(channelID) await createRoom.syncRoom(channelID)
} else { } else {
await createRoom.unbridgeDeletedChannel(channelID, guildID) await createRoom.unbridgeDeletedChannel({id: channelID}, guildID)
} }
} }

View file

@ -1,7 +1,7 @@
// @ts-check // @ts-check
const assert = require("assert") const assert = require("assert")
const reg = require("../../matrix/read-registration") const {reg} = require("../../matrix/read-registration")
const Ty = require("../../types") const Ty = require("../../types")
const fetch = require("node-fetch").default const fetch = require("node-fetch").default

View file

@ -1,7 +1,7 @@
// @ts-check // @ts-check
const assert = require("assert").strict const assert = require("assert").strict
const reg = require("../../matrix/read-registration") const {reg} = require("../../matrix/read-registration")
const DiscordTypes = require("discord-api-types/v10") const DiscordTypes = require("discord-api-types/v10")
const mixin = require("@cloudrac3r/mixin-deep") const mixin = require("@cloudrac3r/mixin-deep")

View file

@ -18,7 +18,7 @@ const lottie = sync.require("../actions/lottie")
const mxUtils = sync.require("../../m2d/converters/utils") const mxUtils = sync.require("../../m2d/converters/utils")
/** @type {import("../../discord/utils")} */ /** @type {import("../../discord/utils")} */
const dUtils = sync.require("../../discord/utils") const dUtils = sync.require("../../discord/utils")
const reg = require("../../matrix/read-registration") const {reg} = require("../../matrix/read-registration")
const userRegex = reg.namespaces.users.map(u => new RegExp(u.regex)) const userRegex = reg.namespaces.users.map(u => new RegExp(u.regex))

View file

@ -4,10 +4,9 @@ const assert = require("assert").strict
const passthrough = require("../../passthrough") const passthrough = require("../../passthrough")
const {discord, sync, db, select} = passthrough const {discord, sync, db, select} = passthrough
/** @type {import("../../matrix/read-registration")} */
const reg = sync.require("../../matrix/read-registration.js")
/** @type {import("../../m2d/converters/utils")} */ /** @type {import("../../m2d/converters/utils")} */
const mxUtils = sync.require("../../m2d/converters/utils") const mxUtils = sync.require("../../m2d/converters/utils")
const {reg} = require("../../matrix/read-registration.js")
const userRegex = reg.namespaces.users.map(u => new RegExp(u.regex)) const userRegex = reg.namespaces.users.map(u => new RegExp(u.regex))

View file

@ -1,7 +1,7 @@
// @ts-check // @ts-check
const assert = require("assert") const assert = require("assert")
const registration = require("../../matrix/read-registration") const {reg} = require("../../matrix/read-registration")
const passthrough = require("../../passthrough") const passthrough = require("../../passthrough")
const {select} = passthrough const {select} = passthrough
@ -26,7 +26,7 @@ function downcaseUsername(user) {
// remove leading and trailing dashes and underscores... // remove leading and trailing dashes and underscores...
.replace(/(?:^[_-]*|[_-]*$)/g, "") .replace(/(?:^[_-]*|[_-]*$)/g, "")
// If requested, also make the Discord user ID part of the username // If requested, also make the Discord user ID part of the username
if (registration.ooye.include_user_id_in_mxid) { if (reg.ooye.include_user_id_in_mxid) {
downcased = user.id + "_" + downcased downcased = user.id + "_" + downcased
} }
// The new length must be at least 2 characters (in other words, it should have some content) // The new length must be at least 2 characters (in other words, it should have some content)

View file

@ -46,7 +46,7 @@ test("user2name: works on special user", t => {
}) })
test("user2name: includes ID if requested in config", t => { test("user2name: includes ID if requested in config", t => {
const reg = require("../../matrix/read-registration") const {reg} = require("../../matrix/read-registration")
reg.ooye.include_user_id_in_mxid = true reg.ooye.include_user_id_in_mxid = true
t.equal(userToSimName({username: "Harry Styles!", discriminator: "0001", id: "123456"}), "123456_harry_styles") t.equal(userToSimName({username: "Harry Styles!", discriminator: "0001", id: "123456"}), "123456_harry_styles")
t.equal(userToSimName({username: "f***", discriminator: "0001", id: "123456"}), "123456_f") t.equal(userToSimName({username: "f***", discriminator: "0001", id: "123456"}), "123456_f")

View file

@ -3,7 +3,7 @@
const assert = require("assert").strict const assert = require("assert").strict
const util = require("util") const util = require("util")
const DiscordTypes = require("discord-api-types/v10") const DiscordTypes = require("discord-api-types/v10")
const reg = require("../matrix/read-registration") const {reg} = require("../matrix/read-registration")
const {addbot} = require("../addbot") const {addbot} = require("../addbot")
const {discord, sync, db, select} = require("../passthrough") const {discord, sync, db, select} = require("../passthrough")

View file

@ -1,6 +1,6 @@
// @ts-check // @ts-check
const reg = require("../../matrix/read-registration") const {reg} = require("../../matrix/read-registration")
const userRegex = reg.namespaces.users.map(u => new RegExp(u.regex)) const userRegex = reg.namespaces.users.map(u => new RegExp(u.regex))
const assert = require("assert").strict const assert = require("assert").strict
/** @type {import("xxhash-wasm").XXHashAPI} */ // @ts-ignore /** @type {import("xxhash-wasm").XXHashAPI} */ // @ts-ignore

View file

@ -20,8 +20,7 @@ const matrixCommandHandler = sync.require("../matrix/matrix-command-handler")
const utils = sync.require("./converters/utils") const utils = sync.require("./converters/utils")
/** @type {import("../matrix/api")}) */ /** @type {import("../matrix/api")}) */
const api = sync.require("../matrix/api") const api = sync.require("../matrix/api")
/** @type {import("../matrix/read-registration")}) */ const {reg} = require("../matrix/read-registration")
const reg = sync.require("../matrix/read-registration")
let lastReportedEvent = 0 let lastReportedEvent = 0

View file

@ -11,6 +11,7 @@ const mreq = sync.require("./mreq")
const file = sync.require("./file") const file = sync.require("./file")
/** @type {import("./txnid")} */ /** @type {import("./txnid")} */
const makeTxnId = sync.require("./txnid") const makeTxnId = sync.require("./txnid")
const {reg} = require("./read-registration.js")
/** /**
* @param {string} p endpoint to access * @param {string} p endpoint to access
@ -295,6 +296,22 @@ async function setUserPowerCascade(roomID, mxid, power) {
} }
} }
async function ping() {
const res = await fetch(`${mreq.baseUrl}/client/v1/appservice/${reg.id}/ping`, {
method: "POST",
headers: {
Authorization: `Bearer ${reg.as_token}`
},
body: "{}"
})
const root = await res.json()
return {
ok: res.ok,
status: res.status,
root
}
}
module.exports.path = path module.exports.path = path
module.exports.register = register module.exports.register = register
module.exports.createRoom = createRoom module.exports.createRoom = createRoom
@ -318,3 +335,4 @@ module.exports.profileSetDisplayname = profileSetDisplayname
module.exports.profileSetAvatarUrl = profileSetAvatarUrl module.exports.profileSetAvatarUrl = profileSetAvatarUrl
module.exports.setUserPower = setUserPower module.exports.setUserPower = setUserPower
module.exports.setUserPowerCascade = setUserPowerCascade module.exports.setUserPowerCascade = setUserPowerCascade
module.exports.ping = ping

View file

@ -1,6 +1,6 @@
// @ts-check // @ts-check
const reg = require("../matrix/read-registration") const {reg} = require("../matrix/read-registration")
const {AppService} = require("@cloudrac3r/in-your-element") const {AppService} = require("@cloudrac3r/in-your-element")
const as = new AppService(reg) const as = new AppService(reg)
as.listen() as.listen()

View file

@ -14,7 +14,7 @@ const mxUtils = sync.require("../m2d/converters/utils")
const dUtils = sync.require("../discord/utils") const dUtils = sync.require("../discord/utils")
/** @type {import("./kstate")} */ /** @type {import("./kstate")} */
const ks = sync.require("./kstate") const ks = sync.require("./kstate")
const reg = require("./read-registration") const {reg} = require("./read-registration")
const PREFIXES = ["//", "/"] const PREFIXES = ["//", "/"]

View file

@ -5,10 +5,7 @@ const mixin = require("@cloudrac3r/mixin-deep")
const stream = require("stream") const stream = require("stream")
const getStream = require("get-stream") const getStream = require("get-stream")
const passthrough = require("../passthrough") const {reg} = require("./read-registration.js")
const { sync } = passthrough
/** @type {import("./read-registration")} */
const reg = sync.require("./read-registration.js")
const baseUrl = `${reg.ooye.server_origin}/_matrix` const baseUrl = `${reg.ooye.server_origin}/_matrix`
@ -81,5 +78,6 @@ async function withAccessToken(token, callback) {
} }
module.exports.MatrixServerError = MatrixServerError module.exports.MatrixServerError = MatrixServerError
module.exports.baseUrl = baseUrl
module.exports.mreq = mreq module.exports.mreq = mreq
module.exports.withAccessToken = withAccessToken module.exports.withAccessToken = withAccessToken

View file

@ -1,7 +1,7 @@
// @ts-check // @ts-check
const {db, from} = require("../passthrough") const {db, from} = require("../passthrough")
const reg = require("./read-registration") const {reg} = require("./read-registration")
const ks = require("./kstate") const ks = require("./kstate")
const {applyKStateDiffToRoom, roomToKState} = require("../d2m/actions/create-room") const {applyKStateDiffToRoom, roomToKState} = require("../d2m/actions/create-room")

View file

@ -1,14 +1,85 @@
// @ts-check // @ts-check
const fs = require("fs") const fs = require("fs")
const crypto = require("crypto")
const assert = require("assert").strict const assert = require("assert").strict
const path = require("path")
const yaml = require("js-yaml") const yaml = require("js-yaml")
/** @ts-ignore @type {import("../types").AppServiceRegistrationConfig} */ const registrationFilePath = path.join(process.cwd(), "registration.yaml")
const reg = yaml.load(fs.readFileSync("registration.yaml", "utf8"))
reg["ooye"].invite = (reg.ooye.invite || []).filter(mxid => mxid.endsWith(`:${reg.ooye.server_name}`)) // one day I will understand why typescript disagrees with dot notation on this line
assert(reg.ooye.max_file_size)
assert(reg.ooye.namespace_prefix)
assert(reg.ooye.server_name)
module.exports = reg /** @param {import("../types").AppServiceRegistrationConfig} reg */
function checkRegistration(reg) {
reg["ooye"].invite = (reg.ooye.invite || []).filter(mxid => mxid.endsWith(`:${reg.ooye.server_name}`)) // one day I will understand why typescript disagrees with dot notation on this line
assert(reg.ooye?.max_file_size)
assert(reg.ooye?.namespace_prefix)
assert(reg.ooye?.server_name)
assert(reg.sender_localpart?.startsWith(reg.ooye.namespace_prefix), "appservice's localpart must be in the namespace it controls")
assert(reg.ooye?.server_origin.match(/^https?:\/\//), "server origin must start with http or https")
assert.notEqual(reg.ooye?.server_origin.slice(-1), "/", "server origin must not end in slash")
assert.match(reg.url, /^https?:/, "url must start with http:// or https://")
}
/** @param {import("../types").AppServiceRegistrationConfig} reg */
function writeRegistration(reg) {
fs.writeFileSync(registrationFilePath, JSON.stringify(reg, null, 2))
}
/** @returns {import("../types").InitialAppServiceRegistrationConfig} reg */
function getTemplateRegistration() {
return {
id: crypto.randomBytes(16).toString("hex"),
as_token: crypto.randomBytes(16).toString("hex"),
hs_token: crypto.randomBytes(16).toString("hex"),
namespaces: {
users: [{
exclusive: true,
regex: "@_ooye_.*:cadence.moe"
}],
aliases: [{
exclusive: true,
regex: "#_ooye_.*:cadence.moe"
}]
},
protocols: [
"discord"
],
sender_localpart: "_ooye_bot",
rate_limited: false,
ooye: {
namespace_prefix: "_ooye_",
max_file_size: 5000000,
content_length_workaround: false,
include_user_id_in_mxid: false,
invite: []
}
}
}
function readRegistration() {
/** @type {import("../types").AppServiceRegistrationConfig} */ // @ts-ignore
let result = null
if (fs.existsSync(registrationFilePath)) {
const content = fs.readFileSync(registrationFilePath, "utf8")
if (content.startsWith("{")) { // Use JSON parser
result = JSON.parse(content)
checkRegistration(result)
} else { // Use YAML parser
result = yaml.load(content)
checkRegistration(result)
// Convert to JSON
writeRegistration(result)
}
}
return result
}
/** @type {import("../types").AppServiceRegistrationConfig} */ // @ts-ignore
let reg = readRegistration()
module.exports.registrationFilePath = registrationFilePath
module.exports.readRegistration = readRegistration
module.exports.getTemplateRegistration = getTemplateRegistration
module.exports.writeRegistration = writeRegistration
module.exports.checkRegistration = checkRegistration
module.exports.reg = reg

View file

@ -1,5 +1,5 @@
const {test} = require("supertape") const {test} = require("supertape")
const reg = require("./read-registration") const {reg} = require("./read-registration")
test("reg: has necessary parameters", t => { test("reg: has necessary parameters", t => {
const propertiesToCheck = ["sender_localpart", "id", "as_token", "ooye"] const propertiesToCheck = ["sender_localpart", "id", "as_token", "ooye"]

41
package-lock.json generated
View file

@ -17,10 +17,12 @@
"@cloudrac3r/mixin-deep": "^3.0.0", "@cloudrac3r/mixin-deep": "^3.0.0",
"@cloudrac3r/pngjs": "^7.0.3", "@cloudrac3r/pngjs": "^7.0.3",
"@cloudrac3r/turndown": "^7.1.4", "@cloudrac3r/turndown": "^7.1.4",
"ansi-colors": "^4.1.3",
"better-sqlite3": "^11.1.2", "better-sqlite3": "^11.1.2",
"chunk-text": "^2.0.1", "chunk-text": "^2.0.1",
"cloudstorm": "^0.10.10", "cloudstorm": "^0.10.10",
"domino": "^2.1.6", "domino": "^2.1.6",
"enquirer": "^2.4.1",
"entities": "^5.0.0", "entities": "^5.0.0",
"get-stream": "^6.0.1", "get-stream": "^6.0.1",
"heatsync": "^2.5.3", "heatsync": "^2.5.3",
@ -978,6 +980,14 @@
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz",
"integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==" "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A=="
}, },
"node_modules/ansi-colors": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
"integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==",
"engines": {
"node": ">=6"
}
},
"node_modules/ansi-regex": { "node_modules/ansi-regex": {
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
@ -1495,6 +1505,37 @@
"once": "^1.4.0" "once": "^1.4.0"
} }
}, },
"node_modules/enquirer": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz",
"integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==",
"dependencies": {
"ansi-colors": "^4.1.1",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8.6"
}
},
"node_modules/enquirer/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"engines": {
"node": ">=8"
}
},
"node_modules/enquirer/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/entities": { "node_modules/entities": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-5.0.0.tgz", "resolved": "https://registry.npmjs.org/entities/-/entities-5.0.0.tgz",

View file

@ -26,10 +26,12 @@
"@cloudrac3r/mixin-deep": "^3.0.0", "@cloudrac3r/mixin-deep": "^3.0.0",
"@cloudrac3r/pngjs": "^7.0.3", "@cloudrac3r/pngjs": "^7.0.3",
"@cloudrac3r/turndown": "^7.1.4", "@cloudrac3r/turndown": "^7.1.4",
"ansi-colors": "^4.1.3",
"better-sqlite3": "^11.1.2", "better-sqlite3": "^11.1.2",
"chunk-text": "^2.0.1", "chunk-text": "^2.0.1",
"cloudstorm": "^0.10.10", "cloudstorm": "^0.10.10",
"domino": "^2.1.6", "domino": "^2.1.6",
"enquirer": "^2.4.1",
"entities": "^5.0.0", "entities": "^5.0.0",
"get-stream": "^6.0.1", "get-stream": "^6.0.1",
"heatsync": "^2.5.3", "heatsync": "^2.5.3",

View file

@ -165,14 +165,16 @@ To get into the rooms on your Matrix account, either add yourself to `invite` in
* (1) @cloudrac3r/discord-markdown: This is my fork! * (1) @cloudrac3r/discord-markdown: This is my fork!
* (0) @cloudrac3r/giframe: This is my fork! * (0) @cloudrac3r/giframe: This is my fork!
* (1) @cloudrac3r/html-template-tag: This is my fork! * (1) @cloudrac3r/html-template-tag: This is my fork!
* (16) @cloudrac3r/in-your-element: This is my Matrix Appservice API library. * (16) @cloudrac3r/in-your-element: This is my Matrix Appservice API library. It has several dependencies because HTTP servers have to do more than you'd think.
* (0) @cloudrac3r/mixin-deep: This is my fork! (It fixes a bug in regular mixin-deep.) * (0) @cloudrac3r/mixin-deep: This is my fork! (It fixes a bug in regular mixin-deep.)
* (0) @cloudrac3r/pngjs: Lottie stickers are converted to bitmaps with the vendored Rlottie WASM build, then the bitmaps are converted to PNG with pngjs. * (0) @cloudrac3r/pngjs: Lottie stickers are converted to bitmaps with the vendored Rlottie WASM build, then the bitmaps are converted to PNG with pngjs.
* (0) @cloudrac3r/turndown: This HTML-to-Markdown converter looked the most suitable. I forked it to change the escaping logic to match the way Discord works. * (0) @cloudrac3r/turndown: This HTML-to-Markdown converter looked the most suitable. I forked it to change the escaping logic to match the way Discord works.
* (0) ansi-colors: Helps with interactive prompting for the initial setup, and it's already pulled in by enquirer.
* (42) better-sqlite3: SQLite3 is the best database, and this is the best library for it. Really! I love it. * (42) better-sqlite3: SQLite3 is the best database, and this is the best library for it. Really! I love it.
* (1) chunk-text: It does what I want. * (1) chunk-text: It does what I want.
* (0) cloudstorm: Discord gateway library with bring-your-own-caching that I trust. * (0) cloudstorm: Discord gateway library with bring-your-own-caching that I trust.
* (0) domino: DOM implementation that's already pulled in by turndown. * (0) domino: DOM implementation that's already pulled in by turndown.
* (1) enquirer: Interactive prompting for the initial setup rather than forcing users to edit YAML non-interactively.
* (0) entities: Looks fine. No dependencies. * (0) entities: Looks fine. No dependencies.
* (0) get-stream: Only needed if content_length_workaround is true. * (0) get-stream: Only needed if content_length_workaround is true.
* (1) heatsync: Module hot-reloader that I trust. * (1) heatsync: Module hot-reloader that I trust.
@ -186,4 +188,4 @@ To get into the rooms on your Matrix account, either add yourself to `invite` in
* (0) try-to-catch: Not strictly necessary, but it's already pulled in by supertape, so I may as well. * (0) try-to-catch: Not strictly necessary, but it's already pulled in by supertape, so I may as well.
* (0) xxhash-wasm: Used where cryptographically secure hashing is not required. * (0) xxhash-wasm: Used where cryptographically secure hashing is not required.
Total transitive production dependencies: 113 Total transitive production dependencies: 116

View file

@ -11,8 +11,7 @@ const passthrough = require("../passthrough")
const sync = new HeatSync({watchFS: false}) const sync = new HeatSync({watchFS: false})
/** @type {import("../matrix/read-registration")} */ const {reg} = require("../matrix/read-registration")
const reg = sync.require("../matrix/read-registration")
assert(reg.old_bridge) assert(reg.old_bridge)
const oldAT = reg.old_bridge.as_token const oldAT = reg.old_bridge.as_token
const newAT = reg.as_token const newAT = reg.as_token

View file

@ -1,10 +1,15 @@
// @ts-check // @ts-check
console.log("This could take up to 30 seconds. Please be patient.")
const assert = require("assert").strict const assert = require("assert").strict
const fs = require("fs") const fs = require("fs")
const sqlite = require("better-sqlite3") const sqlite = require("better-sqlite3")
const {scheduler: {wait}} = require("timers/promises")
const {isDeepStrictEqual} = require("util")
const {prompt} = require("enquirer")
const Input = require("enquirer/lib/prompts/input")
const fetch = require("node-fetch")
const {magenta, bold, cyan} = require("ansi-colors")
const HeatSync = require("heatsync") const HeatSync = require("heatsync")
const args = require("minimist")(process.argv.slice(2), {string: ["emoji-guild"]}) const args = require("minimist")(process.argv.slice(2), {string: ["emoji-guild"]})
@ -26,10 +31,8 @@ const DiscordClient = require("../d2m/discord-client")
const discord = new DiscordClient(config.discordToken, "no") const discord = new DiscordClient(config.discordToken, "no")
passthrough.discord = discord passthrough.discord = discord
const api = require("../matrix/api") let registration = require("../matrix/read-registration")
const file = require("../matrix/file") let {reg, getTemplateRegistration, writeRegistration, readRegistration, registrationFilePath} = registration
const reg = require("../matrix/read-registration")
const utils = require("../m2d/converters/utils")
function die(message) { function die(message) {
console.error(message) console.error(message)
@ -49,7 +52,111 @@ async function uploadAutoEmoji(guild, name, filename) {
return emoji return emoji
} }
async function validateHomeserverOrigin(serverUrlPrompt, url) {
if (!url.match(/^https?:\/\//)) return "Must be a URL"
if (url.match(/\/$/)) return "Must not end with a slash"
process.stdout.write(magenta(" checking, please wait..."))
try {
var json = await fetch(`${url}/.well-known/matrix/client`).then(res => res.json())
let baseURL = json["m.homeserver"].base_url.replace(/\/$/, "")
if (baseURL && baseURL !== url) {
serverUrlPrompt.initial = baseURL
return `Did you mean: ${bold(baseURL)}? (Enter to accept)`
}
} catch (e) {}
try {
var res = await fetch(`${url}/_matrix/client/versions`)
} catch (e) {
return e.message
}
if (res.status !== 200) return `There is no Matrix server at that URL (${url}/_matrix/client/versions returned ${res.status})`
try {
var json = await res.json()
} catch (e) {
return `There is no Matrix server at that URL (${url}/_matrix/client/versions is not JSON)`
}
return true
}
;(async () => { ;(async () => {
// create registration file with prompts...
if (!reg) {
console.log("What is the name of your homeserver? This is the part after : in your username.")
/** @type {{server_name: string}} */
const serverNameResponse = await prompt({
type: "input",
name: "server_name",
message: "Homeserver name"
})
console.log("What is the URL of your homeserver?")
const serverUrlPrompt = new Input({
type: "input",
name: "server_origin",
message: "Homeserver URL",
initial: () => `https://${serverNameResponse.server_name}`,
validate: url => validateHomeserverOrigin(serverUrlPrompt, url)
})
/** @type {{server_origin: string}} */ // @ts-ignore
const serverUrlResponse = await serverUrlPrompt.run()
console.log("Your Matrix homeserver needs to be able to send HTTP requests to OOYE.")
console.log("What URL should OOYE be reachable on? Usually, the default works fine,")
console.log("but you need to change this if you use multiple servers or containers.")
/** @type {{url: string}} */
const urlResponse = await prompt({
type: "input",
name: "url",
message: "URL to reach OOYE",
initial: "http://localhost:6693",
validate: url => !!url.match(/^https?:\/\//)
})
const template = getTemplateRegistration()
reg = {...template, ...urlResponse, ooye: {...template.ooye, ...serverNameResponse, ...serverUrlResponse}}
registration.reg = reg
writeRegistration(reg)
}
// Done with user prompts, reg is now guaranteed to be valid
const api = require("../matrix/api")
const file = require("../matrix/file")
const utils = require("../m2d/converters/utils")
console.log(`✅ Registration file saved as ${registrationFilePath}`)
console.log(` In ${cyan("Synapse")}, you need to add it to homeserver.yaml and ${cyan("restart Synapse")}.`)
console.log(" https://element-hq.github.io/synapse/latest/application_services.html")
console.log(` In ${cyan("Conduit")}, you need to send the file contents to the #admins room.`)
console.log(" https://docs.conduit.rs/appservices.html")
console.log()
const {as} = require("../matrix/appservice")
console.log("⏳ Waiting until homeserver registration works... (Ctrl+C to cancel)")
let itWorks = false
let lastError = null
do {
const result = await api.ping()
// If it didn't work, log details and retry after some time
itWorks = result.ok
if (!itWorks) {
// Log the full error data if the error is different to last time
if (!isDeepStrictEqual(lastError, result.root)) {
if (result.root.error) {
console.log(`\nHomeserver said: [${result.status}] ${result.root.error}`)
} else {
console.log(`\nHomeserver said: [${result.status}] ${JSON.stringify(result.root)}`)
}
lastError = result.root
} else {
process.stderr.write(".")
}
await wait(5000)
}
} while (!itWorks)
console.log("")
as.close().catch(() => {})
console.log("⏩ Processing. This could take up to 30 seconds. Please be patient...")
const mxid = `@${reg.sender_localpart}:${reg.ooye.server_name}` const mxid = `@${reg.sender_localpart}:${reg.ooye.server_name}`
// ensure registration is correctly set... // ensure registration is correctly set...
@ -60,6 +167,7 @@ async function uploadAutoEmoji(guild, name, filename) {
const botID = Buffer.from(config.discordToken.split(".")[0], "base64").toString() const botID = Buffer.from(config.discordToken.split(".")[0], "base64").toString()
assert(botID.match(/^[0-9]{10,}$/), "discord token must follow the correct format") assert(botID.match(/^[0-9]{10,}$/), "discord token must follow the correct format")
assert.match(reg.url, /^https?:/, "url must start with http:// or https://") assert.match(reg.url, /^https?:/, "url must start with http:// or https://")
console.log("✅ Configuration looks good...") console.log("✅ Configuration looks good...")
// database ddl... // database ddl...

View file

@ -17,7 +17,7 @@ const config = require("../config")
const passthrough = require("../passthrough") const passthrough = require("../passthrough")
const db = new sqlite(":memory:") const db = new sqlite(":memory:")
const reg = require("../matrix/read-registration") const {reg} = require("../matrix/read-registration")
reg.ooye.server_origin = "https://matrix.cadence.moe" // so that tests will pass even when hard-coded reg.ooye.server_origin = "https://matrix.cadence.moe" // so that tests will pass even when hard-coded
reg.ooye.server_name = "cadence.moe" reg.ooye.server_name = "cadence.moe"
reg.id = "baby" // don't actually take authenticated actions on the server reg.id = "baby" // don't actually take authenticated actions on the server
@ -117,7 +117,7 @@ file._actuallyUploadDiscordFileToMxc = function(url, res) { throw new Error(`Not
require("../matrix/kstate.test") require("../matrix/kstate.test")
require("../matrix/api.test") require("../matrix/api.test")
require("../matrix/file.test") require("../matrix/file.test")
require("../matrix/power.test") //require("../matrix/power.test")
require("../matrix/read-registration.test") require("../matrix/read-registration.test")
require("../matrix/txnid.test") require("../matrix/txnid.test")
require("../d2m/actions/create-room.test") require("../d2m/actions/create-room.test")

26
types.d.ts vendored
View file

@ -31,6 +31,32 @@ export type AppServiceRegistrationConfig = {
} }
} }
export type InitialAppServiceRegistrationConfig = {
id: string
as_token: string
hs_token: string
sender_localpart: string
namespaces: {
users: {
exclusive: boolean
regex: string
}[]
aliases: {
exclusive: boolean
regex: string
}[]
}
protocols: [string]
rate_limited: boolean
ooye: {
namespace_prefix: string
max_file_size: number,
content_length_workaround: boolean,
invite: string[],
include_user_id_in_mxid: boolean
}
}
export type WebhookCreds = { export type WebhookCreds = {
id: string id: string
token: string token: string