2023-09-06 01:07:05 +00:00
// @ts-check
const assert = require ( "assert" ) . strict
const fs = require ( "fs" )
const sqlite = require ( "better-sqlite3" )
const HeatSync = require ( "heatsync" )
2024-09-05 03:36:43 +00:00
const { prompt , Prompt } = require ( "enquirer" )
const Input = require ( "enquirer/lib/prompts/input" )
const fetch = require ( "node-fetch" )
const { magenta , bold } = require ( "ansi-colors" )
2023-09-06 01:07:05 +00:00
2023-10-07 10:39:49 +00:00
const args = require ( "minimist" ) ( process . argv . slice ( 2 ) , { string : [ "emoji-guild" ] } )
2023-09-06 01:07:05 +00:00
const config = require ( "../config" )
const passthrough = require ( "../passthrough" )
const db = new sqlite ( "db/ooye.db" )
2023-10-02 01:00:14 +00:00
const migrate = require ( "../db/migrate" )
2023-09-06 01:07:05 +00:00
const sync = new HeatSync ( { watchFS : false } )
2023-10-07 08:57:09 +00:00
Object . assign ( passthrough , { sync , config , db } )
const orm = sync . require ( "../db/orm" )
passthrough . from = orm . from
passthrough . select = orm . select
const DiscordClient = require ( "../d2m/discord-client" )
const discord = new DiscordClient ( config . discordToken , "no" )
passthrough . discord = discord
2023-09-06 01:07:05 +00:00
2024-09-05 03:36:43 +00:00
let registration = require ( "../matrix/read-registration" )
let { reg , getTemplateRegistration , writeRegistration , readRegistration } = registration
2023-09-06 01:07:05 +00:00
2023-10-07 10:39:49 +00:00
function die ( message ) {
console . error ( message )
process . exit ( 1 )
}
async function uploadAutoEmoji ( guild , name , filename ) {
let emoji = guild . emojis . find ( e => e . name === name )
if ( ! emoji ) {
console . log ( ` Uploading ${ name } ... ` )
const data = fs . readFileSync ( filename , null )
emoji = await discord . snow . guildAssets . createEmoji ( guild . id , { name , image : "data:image/png;base64," + data . toString ( "base64" ) } )
} else {
console . log ( ` Reusing ${ name } ... ` )
}
db . prepare ( "REPLACE INTO auto_emoji (name, emoji_id, guild_id) VALUES (?, ?, ?)" ) . run ( emoji . name , emoji . id , guild . id )
return emoji
}
2023-09-06 01:07:05 +00:00
; ( async ( ) => {
2024-09-05 03:36:43 +00:00
// 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 : async 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
}
} )
/** @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 = Object . assign ( template , urlResponse , { ooye : { ... template . ooye , ... serverNameResponse , ... serverUrlResponse } } )
registration . reg = reg
writeRegistration ( reg )
}
// done with user prompts, reg is now guaranteed to be valid
console . log ( "Processing. This could take up to 30 seconds. Please be patient..." )
const api = require ( "../matrix/api" )
const file = require ( "../matrix/file" )
const utils = require ( "../m2d/converters/utils" )
2023-09-06 01:07:05 +00:00
const mxid = ` @ ${ reg . sender _localpart } : ${ reg . ooye . server _name } `
// ensure registration is correctly set...
2024-03-23 05:39:37 +00:00
assert ( reg . sender _localpart . startsWith ( reg . ooye . namespace _prefix ) , "appservice's localpart must be in the namespace it controls" )
assert ( utils . eventSenderIsFromDiscord ( mxid ) , "appservice's mxid 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" )
const botID = Buffer . from ( config . discordToken . split ( "." ) [ 0 ] , "base64" ) . toString ( )
assert ( botID . match ( /^[0-9]{10,}$/ ) , "discord token must follow the correct format" )
2024-08-28 00:51:28 +00:00
assert . match ( reg . url , /^https?:/ , "url must start with http:// or https://" )
2024-09-05 03:36:43 +00:00
// TODO: appservice ping until it works
2023-10-07 08:57:09 +00:00
console . log ( "✅ Configuration looks good..." )
2023-09-06 01:07:05 +00:00
2023-09-12 07:23:23 +00:00
// database ddl...
2023-10-02 01:00:14 +00:00
await migrate . migrate ( db )
2023-09-12 07:23:23 +00:00
2023-10-07 08:57:09 +00:00
// add initial rows to database, like adding the bot to sim...
2024-03-23 05:39:37 +00:00
db . prepare ( "INSERT OR IGNORE INTO sim (user_id, sim_name, localpart, mxid) VALUES (?, ?, ?, ?)" ) . run ( botID , reg . sender _localpart . slice ( reg . ooye . namespace _prefix . length ) , reg . sender _localpart , mxid )
2023-10-07 08:57:09 +00:00
console . log ( "✅ Database is ready..." )
2023-10-07 10:39:49 +00:00
2023-10-16 03:47:42 +00:00
// ensure appservice bot user is registered...
try {
await api . register ( reg . sender _localpart )
} catch ( e ) {
2024-01-20 04:03:03 +00:00
if ( e . errcode === "M_USER_IN_USE" || e . data ? . error === "Internal server error" ) {
// "Internal server error" is the only OK error because older versions of Synapse say this if you try to register the same username twice.
} else {
throw e
}
2023-10-16 03:47:42 +00:00
}
// upload initial images...
const avatarUrl = await file . uploadDiscordFileToMxc ( "https://cadence.moe/friends/out_of_your_element.png" )
console . log ( "✅ Matrix appservice login works..." )
2023-10-07 10:39:49 +00:00
// upload the L1 L2 emojis to some guild
const emojis = db . prepare ( "SELECT name FROM auto_emoji WHERE name = 'L1' OR name = 'L2'" ) . pluck ( ) . all ( )
if ( emojis . length !== 2 ) {
// If an argument was supplied, always use that one
let guild = null
if ( args [ "emoji-guild" ] ) {
if ( typeof args [ "emoji-guild" ] === "string" ) {
guild = await discord . snow . guild . getGuild ( args [ "emoji-guild" ] )
}
if ( ! guild ) return die ( ` Error: You asked emojis to be uploaded to guild ID ${ args [ "emoji-guild" ] } , but the bot isn't in that guild. ` )
}
// Otherwise, check if we have already registered an auto emoji guild
if ( ! guild ) {
const guildID = passthrough . select ( "auto_emoji" , "guild_id" , { name : "_" } ) . pluck ( ) . get ( )
if ( guildID ) {
guild = await discord . snow . guild . getGuild ( guildID , false )
}
}
// Otherwise, check if we should create a new guild
if ( ! guild ) {
const guilds = await discord . snow . user . getGuilds ( { limit : 11 , with _counts : false } )
if ( guilds . length < 10 ) {
console . log ( " Creating a guild for emojis..." )
guild = await discord . snow . guild . createGuild ( { name : "OOYE Emojis" } )
}
}
// Otherwise, it's the user's problem
if ( ! guild ) {
return die ( ` Error: The bot needs to upload some emojis. Please say where to upload them to. Run seed.js again with --emoji-guild=GUILD_ID ` )
}
// Upload those emojis to the chosen location
db . prepare ( "REPLACE INTO auto_emoji (name, emoji_id, guild_id) VALUES ('_', '_', ?)" ) . run ( guild . id )
await uploadAutoEmoji ( guild , "L1" , "docs/img/L1.png" )
await uploadAutoEmoji ( guild , "L2" , "docs/img/L2.png" )
}
console . log ( "✅ Emojis are ready..." )
2023-10-07 08:57:09 +00:00
// set profile data on discord...
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" ) } )
await discord . snow . requestHandler . request ( ` /applications/@me ` , { } , "patch" , "json" , { description : "Powered by **Out Of Your Element**\nhttps://gitdab.com/cadence/out-of-your-element" } )
console . log ( "✅ Discord profile updated..." )
2023-09-06 01:07:05 +00:00
// set profile data on homeserver...
await api . profileSetDisplayname ( mxid , "Out Of Your Element" )
await api . profileSetAvatarUrl ( mxid , avatarUrl )
2023-10-07 08:57:09 +00:00
console . log ( "✅ Matrix profile updated..." )
2023-09-06 01:07:05 +00:00
2023-10-07 08:57:09 +00:00
console . log ( "Good to go. I hope you enjoy Out Of Your Element." )
process . exit ( )
2023-09-06 01:07:05 +00:00
} ) ( )