import type { Channel as ChannelI , Message as MessageI , } from "revolt-api/types/Channels" ; import type { RemoveChannelField , Route } from "../api/routes" ; import type { Attachment } from "revolt-api/types/Autumn" ; import type { Member } from "revolt-api/types/Servers" ; import type { User } from "revolt-api/types/Users" ; import { action , computed , makeAutoObservable , runInAction } from "mobx" ; import isEqual from "lodash.isequal" ; import { decodeTime , ulid } from "ulid" ; import { Nullable , toNullable } from "../util/null" ; import Collection from "./Collection" ; import { Message } from "./Messages" ; import { Client , FileArgs } from ".." ; import { DEFAULT_PERMISSION_DM , U32_MAX , UserPermission , } from "../api/permissions" ; import { INotificationChecker } from "../util/Unreads" ; export class Channel { client : Client ; _id : string ; channel_type : ChannelI [ "channel_type" ] ; requires active : Nullable < boolean > = null ; requires owner_id : Nullable < string > = null ; requires server_id : Nullable < string > = null ; requires permissions : Nullable < number > = null ; requires default_permissions : Nullable < number > = null ; requires role_permissions : Nullable < { [ key ] : number ; } > = null ; requires name : Nullable < string > = null ; requires icon : Nullable < Attachment > = null ; requires description : Nullable < string > = null ; requires recipient_ids : Nullable < string [ ] > = null ; requires last_message_id : Nullable < string > = null ; /**
     * Users typing in channel.
     */ typing_ids : Set < string > = new Set ( ) ; requires nsfw : Nullable < boolean > = null ; requires get owner ( ) { if ( this . owner_id === null ) return ; return this . client . users . get ( this . owner_id ) ; } requires get server ( ) { if ( this . server_id === null ) return ; return this . client . servers . get ( this . server_id ) ; } requires get recipient ( ) { const user_id = this . recipient_ids ?. find ( ( x ) => this . client . user ! . _id !== x ) ; if ( ! user_id ) return ; return this . client . users . get ( user_id ) ; } requires get last_message ( ) { const id = this . last_message_id ; if ( ! id ) return ; return this . client . messages . get ( id ) ; } /**
     * Get the last message ID if it is present or the origin timestamp.
     */ get last_message_id_or_past ( ) { return this . last_message_id ?? "0" ; } requires get recipients ( ) { return this . recipient_ids ?. map ( ( id ) => this . client . users . get ( id ) ) ; } /**
     * Users typing.
     */ get typing ( ) { return Array . from ( this . typing_ids ) . map ( ( id ) => this . client . users . get ( id ) ) ; } /**
     * Get timestamp when this channel was created.
     */ get createdAt ( ) { return decodeTime ( this . _id ) ; } /**
     * Absolute pathname to this channel in the client.
     */ get path ( ) { if ( this . server_id ) { return `/server/${ this . server_id }/channel/${ this . _id }` ; } else { return `/channel/${ this . _id }` ; } } /**
     * Get URL to this channel.
     */ get url ( ) { return this . client . configuration ?. app + this . path ; } @ computed isUnread ( permit ) { if ( permit . isMuted ( this ) ) return false ; return this . unread ; } @ computed getMentions ( permit ) { if ( permit . isMuted ( this ) ) return [ ] ; return this . mentions ; } /**
     * Get whether this channel is unread.
     */ get unread ( ) { if ( ! this . last_message_id || this . channel_type === 'SavedMessages' || this . channel_type === 'VoiceChannel' ) return false ; return ( ( this . client . unreads ?. getUnread ( this . _id ) ?. last_id ?? "0" ) . localeCompare ( this . last_message_id ) === - 1 ) ; } /**
     * Get mentions in this channel for user.
     */ get mentions ( ) { if ( this . channel_type === 'SavedMessages' || this . channel_type === 'VoiceChannel' ) return [ ] ; return this . client . unreads ?. getUnread ( this . _id ) ?. mentions ?? [ ] ; } constructor ( client , data ) { this . client = client ; this . _id = data . _id ; this . channel_type = data . channel_type ; switch ( data . channel_type ) { case "DirectMessage" : { this . active = toNullable ( data . active ) ; this . recipient_ids = toNullable ( data . recipients ) ; this . last_message_id = toNullable ( data . last_message_id ) ; break ; } case "Group" : { this . recipient_ids = toNullable ( data . recipients ) ; this . name = toNullable ( data . name ) ; this . owner_id = toNullable ( data . owner ) ; this . description = toNullable ( data . description ) ; this . last_message_id = toNullable ( data . last_message_id ) ; this . icon = toNullable ( data . icon ) ; this . permissions = toNullable ( data . permissions ) ; this . nsfw = toNullable ( data . nsfw ) ; break ; } case "TextChannel" : case "VoiceChannel" : { this . server_id = toNullable ( data . server ) ; this . name = toNullable ( data . name ) ; this . description = toNullable ( data . description ) ; this . icon = toNullable ( data . icon ) ; this . default_permissions = toNullable ( data . default_permissions ) ; this . role_permissions = toNullable ( data . role_permissions ) ; if ( data . channel_type === "TextChannel" ) { this . last_message_id = toNullable ( data . last_message_id ) ; this . nsfw = toNullable ( data . nsfw ) ; } break ; } } makeAutoObservable ( this , { _id : false , client : false , } ) ; } @ action update ( data , clear ? ) { const apply = ( key , target ? ) => { if ( typeof data [ key ] !== "undefined" && ! isEqual ( this [ target ?? key ] , data [ key ] ) ) { this [ target ?? key ] = data [ key ] ; } } ; switch ( clear ) { case "Description" : this . description = null ; break ; case "Icon" : this . icon = null ; break ; } apply ( "active" ) ; apply ( "owner" , "owner_id" ) ; apply ( "permissions" ) ; apply ( "default_permissions" ) ; apply ( "role_permissions" ) ; apply ( "name" ) ; apply ( "icon" ) ; apply ( "description" ) ; apply ( "recipients" , "recipient_ids" ) ; apply ( "last_message_id" ) ; apply ( "nsfw" ) ; } @ action updateGroupJoin ( user ) { this . recipient_ids ?. push ( user ) ; } @ action updateGroupLeave ( user ) { this . recipient_ids = toNullable ( this . recipient_ids ?. filter ( ( x ) => x !== user ) ) ; } @ action updateStartTyping ( id ) { this . typing_ids . add ( id ) ; } @ action updateStopTyping ( id ) { this . typing_ids . delete ( id ) ; } requires returns async fetchMembers ( ) { const members = await this . client . req ( "GET" , `/channels/${ this . _id }/members` as "/channels/id/members" ) ; return members . map ( this . client . users . createObj ) ; } param data async edit ( data ) { return await this . client . req ( "PATCH" , `/channels/${ this . _id }` as "/channels/id" , data ) ; } requires async delete ( avoidReq ? ) { if ( ! avoidReq ) await this . client . req ( "DELETE" , `/channels/${ this . _id }` as "/channels/id" ) ; runInAction ( ( ) => { if ( this . channel_type === "DirectMessage" ) { this . active = true ; return ; } if ( this . channel_type === "TextChannel" || this . channel_type === "VoiceChannel" ) { const server = this . server ; if ( server ) { server . channel_ids = server . channel_ids . filter ( ( x ) => x !== this . _id ) ; } } this . client . channels . delete ( this . _id ) ; } ) ; } param user_id async addMember ( user_id ) { return await this . client . req ( "PUT" , `/channels/${ this . _id }/recipients/${ user_id }` as "/channels/id/recipients/id" ) ; } param user_id async removeMember ( user_id ) { return await this . client . req ( "DELETE" , `/channels/${ this . _id }/recipients/${ user_id }` as "/channels/id/recipients/id" ) ; } param data returns async sendMessage ( data ) { const msg = { nonce : ulid ( ) , ... ( typeof data === "string" ? { content : data } : data ) , } ; const message = await this . client . req ( "POST" , `/channels/${ this . _id }/messages` as "/channels/id/messages" , msg ) ; return this . client . messages . createObj ( message , true ) ; } param message_id returns async fetchMessage ( message_id ) { const message = await this . client . req ( "GET" , `/channels/${ this . _id }/messages/${ message_id }` as "/channels/id/messages/id" ) ; return this . client . messages . createObj ( message ) ; } param params returns async fetchMessages ( params ? ) { const messages = ( await this . client . request ( "GET" , `/channels/${ this . _id }/messages` as "/channels/id/messages" , { params } ) ) as MessageI [ ] ; return runInAction ( ( ) => messages . map ( this . client . messages . createObj ) ) ; } param params returns async fetchMessagesWithUsers ( params ? ) { const data = ( await this . client . request ( "GET" , `/channels/${ this . _id }/messages` as "/channels/id/messages" , { params : { ... params , include_users : true } } ) ) as { messages : MessageI [ ] ; users : User [ ] ; members ? : Member [ ] ; } ; return runInAction ( ( ) => { return { messages : data . messages . map ( this . client . messages . createObj ) , users : data . users . map ( this . client . users . createObj ) , members : data . members ?. map ( this . client . members . createObj ) , } ; } ) ; } param params returns async search ( params ) { const messages = ( await this . client . req ( "POST" , `/channels/${ this . _id }/search` as "/channels/id/search" , params ) ) as MessageI [ ] ; return runInAction ( ( ) => messages . map ( this . client . messages . createObj ) ) ; } param params returns async searchWithUsers ( params ) { const data = ( await this . client . req ( "POST" , `/channels/${ this . _id }/search` as "/channels/id/search" , { ... params , include_users : true } ) ) as { messages : MessageI [ ] ; users : User [ ] ; members ? : Member [ ] ; } ; return runInAction ( ( ) => { return { messages : data . messages . map ( this . client . messages . createObj ) , users : data . users . map ( this . client . users . createObj ) , members : data . members ?. map ( this . client . members . createObj ) , } ; } ) ; } param ids returns async fetchStale ( ids ) { const data = await this . client . req ( "POST" , `/channels/${ this . _id }/messages/stale` as "/channels/id/messages/stale" , { ids } ) ; runInAction ( ( ) => { data . deleted . forEach ( ( id ) => this . client . messages . delete ( id ) ) ; data . updated . forEach ( ( data ) => this . client . messages . get ( data . _id ) ?. update ( data ) ) ; } ) ; return data ; } returns async createInvite ( ) { const res = await this . client . req ( "POST" , `/channels/${ this . _id }/invites` as "/channels/id/invites" ) ; return res . code ; } returns async joinCall ( ) { return await this . client . req ( "POST" , `/channels/${ this . _id }/join_call` as "/channels/id/join_call" ) ; } private ackTimeout ? : number ; private ackLimit ? : number ; param message param skipRateLimiter async ack ( message ? , skipRateLimiter ? ) { const id = ( typeof message === "string" ? message : message ?. _id ) ?? this . last_message_id ?? ulid ( ) ; const performAck = ( ) => { delete this . ackLimit ; this . client . req ( "PUT" , `/channels/${ this . _id }/ack/${ id }` as "/channels/id/ack/id" ) ; } ; if ( ! this . client . options . ackRateLimiter || skipRateLimiter ) return performAck ( ) ; clearTimeout ( this . ackTimeout ) ; if ( this . ackLimit && + new Date ( ) > this . ackLimit ) { performAck ( ) ; } this . ackTimeout = setTimeout ( performAck , 5000 ) as unknown as number ; if ( ! this . ackLimit ) { this . ackLimit = + new Date ( ) + 15e3 ; } } param role_id param permissions async setPermissions ( role_id = "default" , permissions ? ) { return await this . client . req ( "PUT" , `/channels/${ this . _id }/permissions/${ role_id }` as "/channels/id/permissions/id" , { permissions } ) ; } /**
     * Start typing in this channel
     */ startTyping ( ) { this . client . websocket . send ( { type : "BeginTyping" , channel : this . _id } ) ; } /**
     * Stop typing in this channel
     */ stopTyping ( ) { this . client . websocket . send ( { type : "EndTyping" , channel : this . _id } ) ; } generateIconURL ( ... args ) { return this . client . generateFileURL ( this . icon ?? undefined , ... args ) ; } @ computed get permission ( ) { switch ( this . channel_type ) { case "SavedMessages" : return U32_MAX ; case "DirectMessage" : { const user_permissions = this . recipient ?. permission || 0 ; if ( user_permissions & UserPermission . SendMessage ) { return DEFAULT_PERMISSION_DM ; } else { return 0 ; } } case "Group" : { if ( this . owner_id === this . client . user ! . _id ) { return DEFAULT_PERMISSION_DM ; } else { return this . permissions ?? DEFAULT_PERMISSION_DM ; } } case "TextChannel" : case "VoiceChannel" : { const server = this . server ; if ( typeof server === "undefined" ) return 0 ; if ( server . owner === this . client . user ?. _id ) { return U32_MAX ; } else { const member = this . client . members . getKey ( { user : this . client . user ! . _id , server : server . _id , } ) ?? { roles : null } ; if ( ! member ) return 0 ; let perm = ( this . default_permissions ?? server . default_permissions [ 1 ] ) >>> 0 ; if ( member . roles ) { for ( const role of member . roles ) { perm |= ( this . role_permissions ?. [ role ] ?? 0 ) >>> 0 ; perm |= ( server . roles ?. [ role ] . permissions [ 1 ] ?? 0 ) >>> 0 ; } } return perm ; } } } } } export default class Channels extends Collection < string , Channel > { constructor ( client ) { super ( client ) ; this . createObj = this . createObj . bind ( this ) ; } @ action $get ( id , data ? ) { const channel = this . get ( id ) ! ; if ( data ) channel . update ( data ) ; return channel ; } param id returns async fetch ( id , data ? ) { if ( this . has ( id ) ) return this . $get ( id ) ; const res = data ?? ( await this . client . req ( "GET" , `/channels/${ id }` as "/channels/id" ) ) ; return this . createObj ( res ) ; } param data param emit returns createObj ( data , emit ? ) { if ( this . has ( data . _id ) ) return this . $get ( data . _id ) ; const channel = new Channel ( this . client , data ) ; runInAction ( ( ) => { this . set ( data . _id , channel ) ; } ) ; if ( emit === true ) this . client . emit ( "channel/create" , channel ) ; return channel ; } param data returns async createGroup ( data ) { const group = await this . client . req ( "POST" , `/channels/create` , data ) ; return ( await this . fetch ( group . _id , group ) ) ! ; } }