2022-03-29 16:10:59 -04:00
import { Axios } from 'axios' ;
2022-01-24 16:26:37 -05:00
import { randomUUID } from 'crypto' ;
2021-12-05 18:33:49 -05:00
import { Socket } from 'net' ;
2022-04-08 12:35:38 -04:00
import { Batch } from './Batch.js' ;
2021-12-06 10:23:29 -05:00
import { Channel } from './Channel.js' ;
2022-03-29 16:10:59 -04:00
import { MatrixUser } from './MatrixUser.js' ;
2021-12-05 18:33:49 -05:00
import { IRCMessage , parseIRCMessage } from './Message.js' ;
import { Server } from './Server.js' ;
2022-05-18 12:09:35 -04:00
export abstract class Client {
2021-12-05 18:33:49 -05:00
capVersion : string
enabledCaps : Map < string , string >
allCaps : Map < string , string >
2022-03-29 16:10:59 -04:00
user : MatrixUser
isRegistered : boolean
2022-04-08 12:35:38 -04:00
apiCall : Axios
batchesInProgress : Map < string , Batch >
2022-05-18 12:09:35 -04:00
constructor ( public server : Server ) {
2021-12-05 18:33:49 -05:00
this . capVersion = '301' ;
this . enabledCaps = new Map ( ) ;
this . allCaps = new Map ( [
[ "account-tag" , "" ] ,
[ "batch" , "" ] ,
2022-01-21 12:14:53 -05:00
[ "draft/channel-rename" , "" ] ,
2021-12-05 18:33:49 -05:00
[ "echo-message" , "" ] ,
2022-03-30 07:15:10 -04:00
[ "reflectionircd.chat/edit-message" , "" ] ,
[ "reflectionircd.chat/extended-invite" , "" ] ,
2021-12-06 10:23:29 -05:00
[ "extended-join" , "" ] ,
2021-12-05 18:33:49 -05:00
[ "invite-notify" , "" ] ,
2022-03-29 18:23:20 -04:00
[ "labeled-response" , "" ] ,
2021-12-05 18:33:49 -05:00
[ "message-tags" , "" ] ,
[ "sasl" , "PLAIN" ] ,
[ "server-time" , "" ] ,
] ) ;
2022-03-29 16:10:59 -04:00
this . user = this . server . ourMatrixUser ;
this . isRegistered = false ;
this . apiCall = this . server . apiCall ;
2022-04-08 12:35:38 -04:00
this . batchesInProgress = new Map ( ) ;
2021-12-05 18:33:49 -05:00
}
2022-03-29 16:10:59 -04:00
checkIfRegistered() {
return this . isRegistered ;
}
2021-12-05 18:33:49 -05:00
receiveData ( data : Buffer | String ) {
const dataArray = data . toString ( ) . split ( '\r\n' ) ;
dataArray . forEach ( m = > {
const trimmedMsg = m . replace ( '\r' , '' ) . replace ( '\n' , '' ) ;
if ( trimmedMsg !== '' )
this . routeMessage ( trimmedMsg ) ;
} ) ;
}
2022-06-02 07:21:11 -04:00
getMatrixUserFromNick ( targetNick : string , message : IRCMessage ) {
2022-03-29 16:10:59 -04:00
const target = this . server . nickToMatrixUser . get ( targetNick ) ;
2022-01-24 12:25:30 -05:00
if ( ! target ) {
2022-06-02 07:21:11 -04:00
this . sendMessage ( this . server . name , "401" , [ this . user . nick , targetNick , "No such nick" ] , message . tags ) ;
2022-01-24 12:25:30 -05:00
return false ;
}
return target ;
}
2022-06-02 07:21:11 -04:00
getChannel ( channel : string , message : IRCMessage ) {
2022-03-29 16:10:59 -04:00
const targetChannel = this . server . channels . get ( channel ) ;
2022-01-24 12:25:30 -05:00
if ( ! targetChannel ) {
2022-06-02 07:21:11 -04:00
this . sendMessage ( this . server . name , "403" , [ this . user . nick , channel , "No such channel" ] , message . tags ) ;
2022-01-24 12:25:30 -05:00
return false ;
}
return targetChannel ;
}
2022-06-02 07:21:11 -04:00
checkIfInChannel ( channel : Channel , message : IRCMessage ) {
2022-03-29 16:10:59 -04:00
if ( ! this . server . channels . get ( channel . name ) ) {
2022-06-02 07:21:11 -04:00
this . sendMessage ( this . server . name , "442" , [ this . user . nick , "You're not on that channel" ] , message . tags ) ;
2022-01-24 12:25:30 -05:00
return false ;
}
return true ;
}
checkMinParams ( message : IRCMessage , neededNumber : number ) {
if ( message . params . length < neededNumber ) {
2022-06-02 07:21:11 -04:00
this . sendMessage ( this . server . name , "461" , [ this . user . nick , message . command , "Not enough parameters" ] , message . tags ) ;
2022-01-24 12:25:30 -05:00
return false ;
}
return true ;
}
2022-01-24 16:26:37 -05:00
getCapString ( capVersion : string ) {
2022-10-14 07:55:53 -04:00
let capArray : string [ ] = [ ] ;
2022-01-24 16:26:37 -05:00
for ( const [ key , value ] of this . allCaps . entries ( ) ) {
if ( capVersion === '301' || value . length === 0 ) {
capArray . push ( key ) ;
}
else {
capArray . push ( ` ${ key } = ${ value } ` ) ;
}
}
return capArray . join ( ' ' ) ;
}
2021-12-05 18:33:49 -05:00
routeMessage ( data : string ) {
const message = parseIRCMessage ( data ) ;
2022-04-08 12:35:38 -04:00
const maybeBatchRef = message . tags . get ( 'batch' ) ;
if ( maybeBatchRef ) {
const maybeBatch = this . batchesInProgress . get ( maybeBatchRef ) ;
if ( maybeBatch ) {
maybeBatch . messages . add ( message ) ;
}
return ;
}
2021-12-05 18:33:49 -05:00
switch ( message . command . toUpperCase ( ) ) {
2022-01-24 16:26:37 -05:00
case 'AUTHENTICATE' :
2021-12-05 18:33:49 -05:00
this . doAUTHENTICATE ( message ) ;
break ;
2022-10-14 08:47:26 -04:00
case 'AWAY' :
this . doAWAY ( message ) ;
break ;
2022-06-02 07:21:11 -04:00
case 'BATCH' :
this . doBATCH ( message ) ;
break ;
2022-01-24 16:26:37 -05:00
case 'CAP' :
2021-12-05 18:33:49 -05:00
this . doCAP ( message ) ;
break ;
2022-02-02 18:55:09 -05:00
case 'DELETEMSG' :
this . doDELETEMSG ( message ) ;
break ;
2022-01-24 16:26:37 -05:00
case 'INVITE' :
this . doINVITE ( message ) ;
2022-01-24 10:23:06 -05:00
break ;
2022-01-24 16:26:37 -05:00
case 'KICK' :
this . doKICK ( message ) ;
2022-01-24 10:23:06 -05:00
break ;
2022-01-24 16:26:37 -05:00
case 'MODE' :
this . doMODE ( message ) ;
2021-12-06 10:23:29 -05:00
break ;
2022-01-24 16:26:37 -05:00
case 'NAMES' :
this . doNAMES ( message ) ;
2021-12-06 10:23:29 -05:00
break ;
2022-01-24 16:26:37 -05:00
case 'NOTICE' :
this . doMSG ( message ) ;
2021-12-07 10:36:09 -05:00
break ;
2022-01-24 16:26:37 -05:00
case 'PART' :
this . doPART ( message ) ;
2022-01-24 10:23:06 -05:00
break ;
2022-01-24 16:26:37 -05:00
case 'PING' :
2021-12-05 18:47:52 -05:00
this . sendMessage ( this . server . name , "PONG" , message . params , message . tags ) ;
break ;
2022-01-24 16:26:37 -05:00
case 'PRIVMSG' :
this . doMSG ( message ) ;
2021-12-06 10:23:29 -05:00
break ;
2022-01-24 16:26:37 -05:00
case 'TAGMSG' :
this . doTAGMSG ( message ) ;
2021-12-05 18:33:49 -05:00
break ;
2022-01-24 16:26:37 -05:00
case 'TOPIC' :
this . doTOPIC ( message ) ;
2021-12-05 18:33:49 -05:00
break ;
2022-01-24 16:26:37 -05:00
case 'WHO' :
this . doWHO ( message ) ;
2021-12-05 18:33:49 -05:00
break ;
2022-06-02 07:21:11 -04:00
case 'WHOIS' :
this . doWHOIS ( message ) ;
break ;
case 'JOIN' :
case 'NICK' :
case 'USER' :
// Exempting these from sending a 421, otherwise it will get annoying
break ;
default :
this . sendMessage ( this . server . name , "421" , [ message . command , 'Unknown command' ] , message . tags ) ;
console . log ( ` unknown command ${ message . command } ` ) ;
break ;
2021-12-05 18:33:49 -05:00
}
}
doAUTHENTICATE ( message : IRCMessage ) {
if ( message . params [ 0 ] === "PLAIN" ) {
2022-01-24 16:36:25 -05:00
this . sendMessage ( "" , "AUTHENTICATE" , [ "+" ] ) ;
2021-12-05 18:33:49 -05:00
}
else {
const authArray = Buffer . from ( message . params [ 0 ] , 'base64' ) . toString ( 'utf-8' ) . split ( '\0' ) ;
if ( ! authArray || authArray . length !== 3 ) {
2022-06-02 07:21:11 -04:00
this . sendMessage ( this . server . name , '904' , [ '*' , "SASL Authentication failed" ] , message . tags )
2021-12-05 18:33:49 -05:00
this . closeConnectionWithError ( 'Invalid authentication' )
}
2022-03-29 16:10:59 -04:00
if ( authArray [ 2 ] === this . server . config . SASLPassword ) {
2022-06-02 07:21:11 -04:00
this . sendMessage ( this . server . name , '900' , [ this . user . nick , this . server . getMask ( ) , this . user . accountName , ` You are now logged in as ${ this . user . nick } ` ] , message . tags ) ;
this . sendMessage ( this . server . name , '903' , [ this . user . nick , "SASL authentication successful" ] , message . tags ) ;
2022-03-29 16:10:59 -04:00
this . isRegistered = true ;
}
2021-12-05 18:33:49 -05:00
}
}
2022-10-14 08:47:26 -04:00
doAWAY ( message : IRCMessage ) {
const data = {
presence : 'online' ,
status_msg : ''
}
if ( message . params . length === 1 ) {
data . presence = 'unavailable' ;
data . status_msg = message . params [ 0 ] ;
}
this . apiCall . put ( ` /presence/ ${ this . user . mxid } /status ` , data ) . then ( r = > {
// Returning the IRC numerics here because most servers have presence disabled anyways
if ( data . presence === 'online' ) {
this . sendMessage ( this . server . name , "305" , [ this . user . nick , "You are no longer marked as being away" ] ) ;
} else {
this . sendMessage ( this . server . name , "306" , [ this . user . nick , "You have been marked as being away" ] ) ;
}
} ) . catch ( function ( error ) {
if ( error . response ) {
console . log ( error . response . data ) ;
} else if ( error . request ) {
console . log ( error . request ) ;
} else {
console . log ( 'Error' , error . message ) ;
console . log ( error . config ) ;
}
} )
}
2022-04-08 12:35:38 -04:00
doBATCH ( message : IRCMessage ) {
const referenceTag = message . params [ 0 ] . substring ( 1 ) ;
if ( message . params [ 0 ] . startsWith ( '+' ) ) {
this . batchesInProgress . set ( referenceTag , new Batch ( referenceTag , message ) )
} else if ( message . params [ 0 ] . startsWith ( '-' ) ) {
const readyBatch = this . batchesInProgress . get ( referenceTag ) ;
if ( readyBatch ) {
switch ( readyBatch . batchType ) {
case 'draft/multiline' :
case 'multiline' :
this . doMultiline ( readyBatch ) ;
break ;
default :
break ;
}
}
this . batchesInProgress . delete ( referenceTag ) ;
}
}
2022-01-24 16:26:37 -05:00
doCAP ( message : IRCMessage ) {
switch ( message . params [ 0 ] ) {
case 'LS' : {
if ( message . params . length === 2 ) {
this . capVersion = message . params [ 1 ] ;
}
2022-06-02 07:21:11 -04:00
this . sendMessage ( this . server . name , "CAP" , [ "*" , "LS" , this . getCapString ( this . capVersion ) ] , message . tags ) ;
2022-01-24 16:26:37 -05:00
break ;
}
case 'LIST' : {
2022-06-02 07:21:11 -04:00
this . sendMessage ( this . server . name , "CAP" , [ "*" , "LIST" , this . getCapString ( this . capVersion ) ] , message . tags ) ;
2022-01-24 16:26:37 -05:00
break ;
}
case 'REQ' : {
const capsToChange = ( message . params [ 1 ] . indexOf ( ' ' ) === - 1 ) ? [ message . params [ 1 ] ] : message . params [ 1 ] . split ( ' ' ) ;
const capsEnabled : string [ ] = [ ] ;
capsToChange . forEach ( cap = > {
if ( this . allCaps . has ( cap ) ) {
this . enabledCaps . set ( cap , '' ) ;
capsEnabled . push ( cap ) ;
}
} ) ;
2022-06-02 07:21:11 -04:00
this . sendMessage ( this . server . name , "CAP" , [ "*" , "ACK" , capsEnabled . join ( ' ' ) ] , message . tags ) ;
2022-01-24 16:26:37 -05:00
break ;
}
case 'END' : {
2022-03-29 16:10:59 -04:00
if ( this . isRegistered ) {
2022-01-24 16:26:37 -05:00
this . doRegistration ( message ) ;
}
else {
this . closeConnectionWithError ( "You must use SASL to connect to this server" ) ;
}
}
}
}
2022-02-02 18:55:09 -05:00
doDELETEMSG ( message : IRCMessage ) {
if ( ! this . checkIfRegistered ( ) || ! this . checkMinParams ( message , 1 ) )
return ;
2022-06-02 07:21:11 -04:00
const targetChannel = this . getChannel ( message . params [ 0 ] , message ) ;
2022-03-30 07:15:10 -04:00
const eventId = message . tags . get ( "reflectionircd.chat/delete-message" ) ;
2022-02-02 18:55:09 -05:00
if ( ! this . user || ! targetChannel || ! eventId ) return ;
2022-06-02 07:21:11 -04:00
if ( ! this . checkIfInChannel ( targetChannel , message ) ) return ;
2022-02-02 18:55:09 -05:00
const data = {
"reason" : ( message . params . length === 2 ) ? message . params [ 1 ] : ""
}
const newTxnid = randomUUID ( ) ;
2022-03-29 18:23:20 -04:00
this . apiCall . put ( ` /rooms/ ${ targetChannel . roomId } /redact/ ${ eventId } / ${ newTxnid } ` , data ) . then ( r = > {
const maybeEventID = r . data [ "event_id" ] ;
if ( maybeEventID ) {
this . server . eventIdStore . set ( maybeEventID , this ) ;
const maybeLabel = message . tags . get ( "label" ) || "" ;
if ( maybeLabel !== "" ) {
this . server . eventIDToLabel . set ( maybeEventID , maybeLabel )
}
}
} ) . catch ( function ( error ) {
2022-02-02 18:55:09 -05:00
if ( error . response ) {
console . log ( error . response . data ) ;
} else if ( error . request ) {
console . log ( error . request ) ;
} else {
console . log ( 'Error' , error . message ) ;
console . log ( error . config ) ;
}
} )
}
2022-01-24 16:26:37 -05:00
doINVITE ( message : IRCMessage ) {
2022-01-24 16:36:25 -05:00
if ( ! this . checkIfRegistered ( ) || ! this . checkMinParams ( message , 2 ) )
2022-01-24 16:26:37 -05:00
return ;
2022-06-02 07:21:11 -04:00
const targetUser = this . getMatrixUserFromNick ( message . params [ 0 ] , message ) ;
const targetChannel = this . getChannel ( message . params [ 1 ] , message ) ;
2022-01-24 16:26:37 -05:00
if ( ! this . user || ! targetUser || ! targetChannel ) return ;
2022-06-02 07:21:11 -04:00
if ( ! this . checkIfInChannel ( targetChannel , message ) ) return ;
2022-01-24 16:26:37 -05:00
if ( targetChannel . matrixUsers . has ( targetUser . nick ) ) {
2022-01-24 16:36:25 -05:00
this . sendMessage ( this . server . name , "443" , [ this . user . nick , targetUser . nick , "is already on channel" ] ) ;
2022-01-24 16:26:37 -05:00
return ;
}
const reason = ( message . params . length === 3 ) ? message . params [ 2 ] : "" ;
const data = {
"reason" : reason ,
"user_id" : targetUser . mxid
}
2022-03-29 18:23:20 -04:00
this . apiCall . post ( ` /rooms/ ${ targetChannel . roomId } /invite ` , data ) . then ( r = > {
const maybeEventID = r . data [ "event_id" ] ;
if ( maybeEventID ) {
this . server . eventIdStore . set ( maybeEventID , this ) ;
const maybeLabel = message . tags . get ( "label" ) || "" ;
if ( maybeLabel !== "" ) {
this . server . eventIDToLabel . set ( maybeEventID , maybeLabel )
}
}
} ) . catch ( function ( error ) {
2022-01-24 16:26:37 -05:00
if ( error . response ) {
console . log ( error . response . data ) ;
} else if ( error . request ) {
console . log ( error . request ) ;
} else {
console . log ( 'Error' , error . message ) ;
console . log ( error . config ) ;
}
} )
}
doKICK ( message : IRCMessage ) {
2022-01-24 16:36:25 -05:00
if ( ! this . checkIfRegistered ( ) || ! this . checkMinParams ( message , 2 ) )
2022-01-24 16:26:37 -05:00
return ;
2022-06-02 07:21:11 -04:00
const targetChannel = this . getChannel ( message . params [ 0 ] , message ) ;
const targetUser = this . getMatrixUserFromNick ( message . params [ 1 ] , message ) ;
2022-01-24 16:26:37 -05:00
if ( ! this . user || ! targetUser || ! targetChannel ) return ;
2022-06-02 07:21:11 -04:00
if ( ! this . checkIfInChannel ( targetChannel , message ) ) return ;
2022-01-24 16:26:37 -05:00
if ( ! targetChannel . matrixUsers . has ( targetUser . nick ) ) {
2022-01-24 16:36:25 -05:00
this . sendMessage ( this . server . name , "441" , [ this . user . nick , targetUser . nick , targetChannel . name , "They aren't on that channel" ] ) ;
2022-01-24 16:26:37 -05:00
return ;
}
const reason = ( message . params . length === 3 ) ? message . params [ 2 ] : "" ;
const data = {
"reason" : reason ,
"user_id" : targetUser . mxid
}
2022-03-29 18:23:20 -04:00
this . apiCall . post ( ` /rooms/ ${ targetChannel . roomId } /kick ` , data ) . then ( r = > {
const maybeEventID = r . data [ "event_id" ] ;
if ( maybeEventID ) {
this . server . eventIdStore . set ( maybeEventID , this ) ;
const maybeLabel = message . tags . get ( "label" ) || "" ;
if ( maybeLabel !== "" ) {
this . server . eventIDToLabel . set ( maybeEventID , maybeLabel )
}
}
} ) . catch ( function ( error ) {
2022-01-24 16:26:37 -05:00
if ( error . response ) {
console . log ( error . response . data ) ;
} else if ( error . request ) {
console . log ( error . request ) ;
} else {
console . log ( 'Error' , error . message ) ;
console . log ( error . config ) ;
}
} )
}
doMODE ( message : IRCMessage ) {
2022-01-24 16:36:25 -05:00
if ( ! this . checkIfRegistered ( ) || ! this . checkMinParams ( message , 1 ) || ! this . user )
2022-01-24 16:26:37 -05:00
return ;
2022-03-29 16:10:59 -04:00
const targetChannel = this . server . channels . get ( message . params [ 0 ] ) ;
2022-01-24 16:26:37 -05:00
if ( ! targetChannel ) {
if ( message . params [ 0 ] !== this . user . nick ) {
2022-06-02 07:21:11 -04:00
this . sendMessage ( this . server . name , "502" , [ this . user . nick , "Can't view mode for other users" ] , message . tags ) ;
2022-01-24 16:26:37 -05:00
return ;
}
2022-01-24 16:36:25 -05:00
this . sendMessage ( this . server . name , "221" , [ this . user . nick , "+i" ] ) ;
2022-01-24 16:26:37 -05:00
return ;
}
2022-06-02 07:21:11 -04:00
if ( ! this . checkIfInChannel ( targetChannel , message ) ) return ;
2022-01-24 16:26:37 -05:00
if ( message . params . length === 1 ) {
2022-02-18 17:07:51 -05:00
const chanModes = [ . . . targetChannel . channelModes . keys ( ) ] . sort ( ) . join ( '' ) ;
2022-06-02 07:21:11 -04:00
this . sendMessage ( this . server . name , "324" , [ this . user . nick , targetChannel . name , ` + ${ chanModes } ` ] , message . tags ) ;
2022-01-24 16:26:37 -05:00
return ;
}
}
2022-04-08 12:35:38 -04:00
doMultiline ( batch : Batch ) {
}
2022-01-24 16:26:37 -05:00
doMSG ( message : IRCMessage ) {
2022-01-24 16:36:25 -05:00
if ( ! this . checkIfRegistered ( ) || ! this . checkMinParams ( message , 2 ) )
2022-01-24 16:26:37 -05:00
return ;
2022-06-02 07:21:11 -04:00
const targetChannel = this . getChannel ( message . params [ 0 ] , message ) ;
2022-01-24 16:26:37 -05:00
if ( ! this . user || ! targetChannel ) return ;
2022-06-02 07:21:11 -04:00
if ( ! this . checkIfInChannel ( targetChannel , message ) ) return ;
2022-05-02 19:08:22 -04:00
if ( targetChannel . roomType === "m.space" ) {
this . sendMessage ( this . server . name , "NOTICE" , [ targetChannel . name , "Sending messages to spaces is not allowed" ] , new Map ( ) ) ;
return ;
}
2022-01-24 16:26:37 -05:00
let msgtype = 'm.text' ;
let msgbody = message . params [ 1 ] ;
if ( message . command === 'NOTICE' )
msgtype = 'm.notice' ;
else if ( message . params [ 1 ] . startsWith ( '\x01' ) ) {
msgtype = 'm.emote' ;
msgbody = msgbody . replace ( /\x01(ACTION\s)?/gi , '' ) ;
}
2022-02-02 14:01:02 -05:00
const highlightFilteredMsg = msgbody . split ( " " ) . map ( w = > {
if ( ! w . startsWith ( '@' ) ) return w ;
const endingCharMatch = w . match ( /[,:]$/ ) ;
const endingChar = ( endingCharMatch ) ? endingCharMatch [ 0 ] : "" ;
const endingCharIndex = ( endingCharMatch ) ? endingCharMatch.index : 0 ;
const nickToSearch = ( endingCharIndex === 0 ) ? w . substring ( 1 ) : w . substring ( 1 , endingCharIndex ) ;
const maybeHighlight = targetChannel . matrixUsers . get ( nickToSearch ) ;
return ( maybeHighlight ) ? ` ${ maybeHighlight . mxid } ${ endingChar } ` : w ;
} )
2022-01-24 16:26:37 -05:00
const content = {
2022-02-02 14:01:02 -05:00
"body" : highlightFilteredMsg . join ( " " ) ,
2022-01-24 16:26:37 -05:00
"msgtype" : msgtype ,
"m.relates_to" : { }
}
if ( message . tags . has ( "+draft/reply" ) ) {
content [ "m.relates_to" ] = {
"m.in_reply_to" : {
"event_id" : message . tags . get ( "+draft/reply" )
}
}
}
const newTxnid = randomUUID ( ) ;
2022-03-29 18:23:20 -04:00
this . apiCall . put ( ` /rooms/ ${ targetChannel . roomId } /send/m.room.message/ ${ newTxnid } ` , content ) . then ( r = > {
const maybeEventID = r . data [ "event_id" ] ;
if ( maybeEventID ) {
this . server . eventIdStore . set ( maybeEventID , this ) ;
const maybeLabel = message . tags . get ( "label" ) || "" ;
if ( maybeLabel !== "" ) {
this . server . eventIDToLabel . set ( maybeEventID , maybeLabel )
}
}
} ) . catch ( function ( error ) {
2022-01-24 16:26:37 -05:00
if ( error . response ) {
console . log ( error . response . data ) ;
} else if ( error . request ) {
console . log ( error . request ) ;
} else {
console . log ( 'Error' , error . message ) ;
console . log ( error . config ) ;
}
} ) ;
}
2022-06-02 07:21:11 -04:00
sendNAMES ( targetChannel : Channel , batchLabel : string = "" ) {
2022-01-24 16:26:37 -05:00
if ( ! this . user ) return ;
2022-06-02 07:21:11 -04:00
const newTag = new Map ( ) ;
if ( batchLabel ) {
newTag . set ( 'batch' , batchLabel ) ;
}
2022-01-24 16:26:37 -05:00
let namesList : string [ ] = [ ] ;
for ( const matrixUser of targetChannel . matrixUsers . values ( ) ) {
const opStatus = targetChannel . getNickPowerLevelMapping ( matrixUser . nick ) ;
namesList . push ( ` ${ opStatus } ${ matrixUser . nick } ` ) ;
}
let singleNamesList : string [ ] = [ ]
namesList . forEach ( ( singleName , index ) = > {
if ( index === 0 || index % 20 !== 0 ) {
singleNamesList . push ( singleName ) ;
}
else {
2022-06-02 07:21:11 -04:00
this . sendMessage ( this . server . name , "353" , [ this . user . nick , "=" , targetChannel . name , ` ${ singleNamesList . join ( ' ' ) } ` ] , newTag ) ;
2022-01-24 16:26:37 -05:00
singleNamesList = [ ] ;
}
} )
if ( singleNamesList . length !== 0 ) {
2022-06-02 07:21:11 -04:00
this . sendMessage ( this . server . name , "353" , [ this . user . nick , "=" , targetChannel . name , ` ${ singleNamesList . join ( ' ' ) } ` ] , newTag ) ;
2022-01-24 16:26:37 -05:00
}
2022-06-02 07:21:11 -04:00
this . sendMessage ( this . server . name , "366" , [ this . user . nick , targetChannel . name , "End of /NAMES list" ] , newTag ) ;
2022-01-24 16:26:37 -05:00
}
doNAMES ( message : IRCMessage ) {
2022-01-24 16:36:25 -05:00
if ( ! this . checkIfRegistered ( ) || ! this . checkMinParams ( message , 1 ) )
2022-01-24 16:26:37 -05:00
return ;
2022-06-02 07:21:11 -04:00
const targetChannel = this . getChannel ( message . params [ 0 ] , message ) ;
2022-01-24 16:26:37 -05:00
if ( ! this . user || ! targetChannel ) return ;
2022-06-02 07:21:11 -04:00
if ( ! this . checkIfInChannel ( targetChannel , message ) ) return ;
const messageLabel = message . tags . get ( 'label' ) || "" ;
let batchLabel = "" ;
if ( messageLabel ) {
batchLabel = Math . random ( ) . toString ( 36 ) . substring ( 2 , 7 ) ;
this . sendMessage ( this . server . name , 'BATCH' , [ ` + ${ batchLabel } ` , 'labeled-response' ] , message . tags ) ;
}
this . sendNAMES ( targetChannel , batchLabel ) ;
if ( messageLabel ) {
this . sendMessage ( this . server . name , 'BATCH' , [ ` - ${ batchLabel } ` ] ) ;
}
2022-01-24 16:26:37 -05:00
}
doPART ( message : IRCMessage ) {
2022-01-24 16:36:25 -05:00
if ( ! this . checkIfRegistered ( ) || ! this . checkMinParams ( message , 1 ) )
2022-01-24 16:26:37 -05:00
return ;
2022-06-02 07:21:11 -04:00
const targetChannel = this . getChannel ( message . params [ 0 ] , message ) ;
2022-01-24 16:26:37 -05:00
if ( ! this . user || ! targetChannel ) return ;
2022-06-02 07:21:11 -04:00
if ( ! this . checkIfInChannel ( targetChannel , message ) ) return ;
2022-01-24 16:26:37 -05:00
const reason = ( message . params . length === 2 ) ? message . params [ 1 ] : "" ;
2022-03-29 16:10:59 -04:00
this . apiCall . post ( ` /rooms/ ${ targetChannel . roomId } /leave ` , { "reason" : reason } ) . then ( response = > {
2022-01-24 16:26:37 -05:00
if ( response . status === 200 ) {
//@ts-ignore
this . user . getClients ( ) . forEach ( c = > {
//@ts-ignore
2022-01-24 16:36:25 -05:00
c . sendMessage ( this . user . getMask ( ) , "PART" , [ targetChannel . name , reason ] ) ;
2022-01-24 16:26:37 -05:00
} )
//@ts-ignore
this . user . channels . delete ( targetChannel . name ) ;
//@ts-ignore
this . user . roomIdToChannel . delete ( targetChannel . roomId ) ;
}
else {
//@ts-ignore
2022-01-24 16:36:25 -05:00
this . sendMessage ( this . server . name , "NOTICE" , [ this . user . nick , JSON . stringify ( response . data ) ] ) ;
2022-01-24 16:26:37 -05:00
}
} ) . catch ( function ( error ) {
if ( error . response ) {
console . log ( error . response . data ) ;
} else if ( error . request ) {
console . log ( error . request ) ;
} else {
console . log ( 'Error' , error . message ) ;
console . log ( error . config ) ;
}
} )
}
doTAGMSG ( message : IRCMessage ) {
2022-01-24 16:36:25 -05:00
if ( ! this . checkIfRegistered ( ) || ! this . checkMinParams ( message , 1 ) )
2022-01-24 16:26:37 -05:00
return ;
2022-06-02 07:21:11 -04:00
const targetChannel = this . getChannel ( message . params [ 0 ] , message ) ;
2022-01-24 16:26:37 -05:00
if ( ! this . user || ! targetChannel ) return ;
2022-06-02 07:21:11 -04:00
if ( ! this . checkIfInChannel ( targetChannel , message ) ) return ;
2022-01-24 16:26:37 -05:00
if ( message . tags . has ( "+draft/react" ) && message . tags . has ( "+draft/reply" ) ) {
const content = {
"m.relates_to" : {
"event_id" : message . tags . get ( "+draft/reply" ) ,
"key" : message . tags . get ( "+draft/react" ) ,
"rel_type" : "m.annotation"
}
}
const newTxnid = randomUUID ( ) ;
2022-03-29 18:23:20 -04:00
this . apiCall . put ( ` /rooms/ ${ targetChannel . roomId } /send/m.reaction/ ${ newTxnid } ` , content ) . then ( r = > {
const maybeEventID = r . data [ "event_id" ] ;
if ( maybeEventID ) {
this . server . eventIdStore . set ( maybeEventID , this ) ;
const maybeLabel = message . tags . get ( "label" ) || "" ;
if ( maybeLabel !== "" ) {
this . server . eventIDToLabel . set ( maybeEventID , maybeLabel )
}
}
} ) . catch ( function ( error ) {
2022-01-24 16:26:37 -05:00
if ( error . response ) {
console . log ( error . response . data ) ;
} else if ( error . request ) {
console . log ( error . request ) ;
} else {
console . log ( 'Error' , error . message ) ;
console . log ( error . config ) ;
}
} ) ;
}
}
2022-01-24 16:36:25 -05:00
sendTOPIC ( targetChannel : Channel ) {
2022-01-24 16:26:37 -05:00
if ( ! this . user ) return ;
const topicText = targetChannel . topic . get ( 'text' ) || '' ;
const topicSetter = targetChannel . topic . get ( 'setter' ) || 'matrix' ;
const topicTimestamp = targetChannel . topic . get ( 'timestamp' ) || '0' ;
if ( topicText === '' ) {
2022-01-24 16:36:25 -05:00
this . sendMessage ( this . server . name , '331' , [ this . user . nick , targetChannel . name , 'No topic is set' ] ) ;
2022-01-24 16:26:37 -05:00
return ;
}
2022-01-24 16:36:25 -05:00
this . sendMessage ( this . server . name , '332' , [ this . user . nick , targetChannel . name , topicText ] ) ;
this . sendMessage ( this . server . name , '333' , [ this . user . nick , targetChannel . name , topicSetter , topicTimestamp ] ) ;
2022-01-24 16:26:37 -05:00
}
doTOPIC ( message : IRCMessage ) {
2022-01-24 16:36:25 -05:00
if ( ! this . checkIfRegistered ( ) || ! this . checkMinParams ( message , 1 ) )
2022-01-24 16:26:37 -05:00
return ;
2022-06-02 07:21:11 -04:00
const targetChannel = this . getChannel ( message . params [ 0 ] , message ) ;
2022-01-24 16:26:37 -05:00
if ( ! this . user || ! targetChannel ) return ;
2022-06-02 07:21:11 -04:00
if ( ! this . checkIfInChannel ( targetChannel , message ) ) return ;
2022-01-24 16:26:37 -05:00
if ( message . params . length === 1 ) {
2022-01-24 16:36:25 -05:00
this . sendTOPIC ( targetChannel ) ;
2022-01-24 16:26:37 -05:00
return ;
}
const topic = message . params [ 1 ] ;
2022-03-29 18:23:20 -04:00
this . apiCall . put ( ` /rooms/ ${ targetChannel . roomId } /state/m.room.topic ` , { "topic" : topic } ) . then ( r = > {
const maybeEventID = r . data [ "event_id" ] ;
if ( maybeEventID ) {
this . server . eventIdStore . set ( maybeEventID , this ) ;
const maybeLabel = message . tags . get ( "label" ) || "" ;
if ( maybeLabel !== "" ) {
this . server . eventIDToLabel . set ( maybeEventID , maybeLabel )
}
}
} ) . catch ( function ( error ) {
2022-01-24 16:26:37 -05:00
if ( error . response ) {
console . log ( error . response . data ) ;
} else if ( error . request ) {
console . log ( error . request ) ;
} else {
console . log ( 'Error' , error . message ) ;
console . log ( error . config ) ;
}
} )
}
doWHO ( message : IRCMessage ) {
2022-01-24 16:36:25 -05:00
if ( ! this . checkIfRegistered ( ) || ! this . checkMinParams ( message , 1 ) )
2022-01-24 16:26:37 -05:00
return ;
2022-06-02 07:21:11 -04:00
const targetChannel = this . getChannel ( message . params [ 0 ] , message ) ;
2022-01-24 16:26:37 -05:00
if ( ! this . user || ! targetChannel ) return ;
2022-06-02 07:21:11 -04:00
if ( ! this . checkIfInChannel ( targetChannel , message ) ) return ;
const newTags = new Map ( ) ;
if ( message . tags . get ( 'label' ) ) {
const batchLabel = Math . random ( ) . toString ( 36 ) . substring ( 2 , 7 ) ;
this . sendMessage ( this . server . name , 'BATCH' , [ ` + ${ batchLabel } ` , 'labeled-response' ] , message . tags ) ;
newTags . set ( 'batch' , batchLabel ) ;
}
2022-01-24 16:26:37 -05:00
for ( const matrixUser of targetChannel . matrixUsers . values ( ) ) {
const opStatus = targetChannel . getNickPowerLevelMapping ( matrixUser . nick ) ;
const userParams = [
this . user . nick ,
targetChannel . name ,
matrixUser . ident ,
matrixUser . hostname ,
this . server . name ,
matrixUser . nick ,
` H ${ opStatus } ` ,
` 0 ${ matrixUser . realname } `
]
2022-06-02 07:21:11 -04:00
this . sendMessage ( this . server . name , '352' , userParams , newTags ) ;
}
this . sendMessage ( this . server . name , '315' , [ this . user . nick , targetChannel . name , "End of /WHO" ] , newTags ) ;
if ( message . tags . get ( 'label' ) ) {
this . sendMessage ( this . server . name , 'BATCH' , [ ` - ${ newTags . get ( 'batch' ) } ` ] ) ;
}
}
doWHOIS ( message : IRCMessage ) {
if ( ! this . checkIfRegistered ( ) || ! this . checkMinParams ( message , 1 ) )
return ;
const targetUser = this . getMatrixUserFromNick ( message . params [ 0 ] , message ) ;
if ( ! targetUser ) return ;
const tNick = targetUser . nick ;
const newTags = new Map ( ) ;
if ( message . tags . get ( 'label' ) ) {
const batchLabel = Math . random ( ) . toString ( 36 ) . substring ( 2 , 7 ) ;
this . sendMessage ( this . server . name , 'BATCH' , [ ` + ${ batchLabel } ` , 'labeled-response' ] , message . tags ) ;
newTags . set ( 'batch' , batchLabel ) ;
}
this . sendMessage ( this . server . name , '311' , [ this . user . nick , tNick , targetUser . ident , targetUser . hostname , '*' , targetUser . realname ] , newTags ) ;
this . sendMessage ( this . server . name , '330' , [ this . user . nick , tNick , targetUser . accountName , 'is logged in as' ] , newTags ) ;
this . sendMessage ( this . server . name , '318' , [ this . user . nick , tNick , "End of /WHOIS list" ] , newTags ) ;
if ( message . tags . get ( 'label' ) ) {
this . sendMessage ( this . server . name , 'BATCH' , [ ` - ${ newTags . get ( 'batch' ) } ` ] ) ;
2022-01-24 16:26:37 -05:00
}
}
2021-12-05 18:33:49 -05:00
doRegistration ( message : IRCMessage ) {
if ( this . user === null ) {
2021-12-05 18:44:21 -05:00
this . closeConnectionWithError ( "You must use SASL to connect to this server" ) ;
2021-12-05 18:33:49 -05:00
return ;
}
2022-03-29 16:22:05 -04:00
this . sendMessage ( this . server . name , '001' , [ this . user . nick , ` Welcome to the ${ this . server . name } network, ${ this . user . nick } ` ] )
this . sendMessage ( this . server . name , '002' , [ this . user . nick , ` Your host is ${ this . server . name } , running version 0.1.0 ` ] ) ;
this . sendMessage ( this . server . name , '003' , [ this . user . nick , ` This server was created yesterday ` ] ) ;
2022-05-02 18:59:49 -04:00
this . sendMessage ( this . server . name , '004' , [ this . user . nick , this . server . name , '0.1.0' , 'i' , 'Shnouv' ] ) ;
2021-12-05 18:33:49 -05:00
const iSupportArray = [
'CASEMAPPING=ascii' ,
2022-05-02 18:59:49 -04:00
'CHANMODES=,,,Snu' ,
2021-12-05 18:33:49 -05:00
'CHANTYPES=#&!' ,
'MAXTARGETS=1' ,
2022-05-02 17:43:14 -04:00
'MODES=1' ,
2021-12-05 18:33:49 -05:00
'PREFIX=(ohv)@%+' ,
]
if ( this . enabledCaps . has ( 'draft/chathistory' ) ) {
iSupportArray . push ( 'CHATHISTORY=50' ) ;
}
2022-03-29 16:22:05 -04:00
this . sendMessage ( this . server . name , '005' , [ this . user . nick , . . . iSupportArray , 'are supported by this server' ] ) ;
2021-12-05 18:33:49 -05:00
2022-03-29 16:22:05 -04:00
this . sendMessage ( this . server . name , '375' , [ this . user . nick , "- Start of MOTD" ] ) ;
this . sendMessage ( this . server . name , '372' , [ this . user . nick , "It's an MOTD" ] ) ;
this . sendMessage ( this . server . name , '376' , [ this . user . nick , "- End of MOTD" ] ) ;
2021-12-05 18:33:49 -05:00
2022-01-24 16:36:25 -05:00
this . sendMessage ( this . user . nick , 'MODE' , [ this . user . nick , '+i' ] ) ;
2022-03-29 16:10:59 -04:00
this . server . addClient ( this ) ;
2021-12-05 18:33:49 -05:00
}
2022-01-24 16:36:25 -05:00
sendMessage ( prefix : string , command : string , params : string [ ] , tags : Map < string , string > = new Map ( ) ) {
2021-12-05 18:33:49 -05:00
const capTagMapping = new Map ( [
[ 'account' , 'account-tag' ] ,
2022-06-02 07:21:11 -04:00
[ 'batch' , 'batch' ] ,
2021-12-05 18:33:49 -05:00
[ 'label' , 'labeled-response' ] ,
[ 'msgid' , 'message-tags' ] ,
2022-03-30 07:15:10 -04:00
[ 'reflectionircd.chat/delete-message' , 'reflectionircd.chat/edit-message' ] ,
[ 'reflectionircd.chat/edit-message' , 'reflectionircd.chat/edit-message' ] ,
2021-12-05 18:33:49 -05:00
[ 'time' , 'server-time' ] ,
] )
const ourTags : Map < string , string > = new Map ( ) ;
if ( this . enabledCaps . has ( 'server-time' ) && ! tags . has ( 'time' ) )
ourTags . set ( 'time' , new Date ( ) . toISOString ( ) ) ;
tags . forEach ( ( v , k ) = > {
if ( k . startsWith ( '+' ) ) {
if ( this . enabledCaps . has ( 'message-tags' ) ) {
ourTags . set ( k , v ) ;
}
}
else {
const capToCheck = capTagMapping . get ( k ) || '' ;
if ( this . enabledCaps . has ( capToCheck ) ) {
ourTags . set ( k , v ) ;
}
}
} )
const newMsg = new IRCMessage ( ourTags , prefix , command , params ) ;
const msgToSend = newMsg . toString ( ) ;
2022-05-18 12:09:35 -04:00
this . writeMessage ( ` ${ msgToSend } \ r \ n ` ) ;
2021-12-05 18:33:49 -05:00
}
2022-05-18 12:09:35 -04:00
abstract writeMessage ( message : string ) : void ;
2021-12-05 18:33:49 -05:00
closeConnectionWithError ( message : string ) {
this . sendMessage ( this . server . name , 'ERROR' , [ message ] , new Map ( ) ) ;
2022-05-18 12:09:35 -04:00
this . closeConnection ( ) ;
2021-12-05 18:33:49 -05:00
}
2022-05-18 12:09:35 -04:00
abstract closeConnection ( ) : void ;
2021-12-05 18:33:49 -05:00
}