1- import { MeteorError , Team , api , Calendar } from '@rocket.chat/core-services' ;
2- import type { IExportOperation , ILoginToken , IPersonalAccessToken , IUser , UserStatus } from '@rocket.chat/core-typings' ;
1+ import { MeteorError , Presence , Team } from '@rocket.chat/core-services' ;
2+ import type { IExportOperation , ILoginToken , IPersonalAccessToken , IUser } from '@rocket.chat/core-typings' ;
3+ import { UserStatus } from '@rocket.chat/core-typings' ;
34import { Users , Subscriptions , Sessions } from '@rocket.chat/models' ;
45import {
56 isUserCreateParamsPOST ,
@@ -29,7 +30,7 @@ import {
2930 validateForbiddenErrorResponse ,
3031} from '@rocket.chat/rest-typings' ;
3132import { escapeRegExp } from '@rocket.chat/string-helpers' ;
32- import { getLoginExpirationInMs , wrapExceptions } from '@rocket.chat/tools' ;
33+ import { getLoginExpirationInMs } from '@rocket.chat/tools' ;
3334import { Accounts } from 'meteor/accounts-base' ;
3435import { Match , check } from 'meteor/check' ;
3536import { Meteor } from 'meteor/meteor' ;
@@ -66,7 +67,6 @@ import { saveCustomFieldsWithoutValidation } from '../../../lib/server/functions
6667import { saveUser } from '../../../lib/server/functions/saveUser' ;
6768import { sendWelcomeEmail } from '../../../lib/server/functions/saveUser/sendUserEmail' ;
6869import { canEditExtension } from '../../../lib/server/functions/saveUser/validateUserEditing' ;
69- import { setStatusText } from '../../../lib/server/functions/setStatusText' ;
7070import { setUserAvatar } from '../../../lib/server/functions/setUserAvatar' ;
7171import { setUsernameWithValidation } from '../../../lib/server/functions/setUsername' ;
7272import { validateCustomFields } from '../../../lib/server/functions/validateCustomFields' ;
@@ -649,7 +649,7 @@ API.v1.addRoute(
649649 if ( ! canViewFullOtherUserInfo ) {
650650 return API . v1 . forbidden ( ) ;
651651 }
652- const escapedEmail = escapeRegExp ( this . queryParams . email as string ) ;
652+ const escapedEmail = escapeRegExp ( this . queryParams . email ) ;
653653 nonEmptyQuery [ 'emails.address' ] = {
654654 $regex : `^${ escapedEmail } $` ,
655655 $options : 'i' ,
@@ -1870,6 +1870,28 @@ API.v1
18701870
18711871const statusType = { type : 'string' , enum : [ 'online' , 'offline' , 'away' , 'busy' ] } as const ;
18721872
1873+ const getStatusResponseSchema = ajv . compile < {
1874+ _id : string ;
1875+ status : string ;
1876+ connectionStatus ?: string ;
1877+ statusSource ?: string ;
1878+ statusEmoji ?: string ;
1879+ statusExpiresAt ?: string ;
1880+ } > ( {
1881+ type : 'object' ,
1882+ properties : {
1883+ _id : { type : 'string' } ,
1884+ status : statusType ,
1885+ connectionStatus : { type : 'string' , nullable : true } ,
1886+ statusSource : { type : 'string' , nullable : true } ,
1887+ statusEmoji : { type : 'string' , nullable : true } ,
1888+ statusExpiresAt : { type : 'string' , nullable : true } ,
1889+ success : { type : 'boolean' , enum : [ true ] } ,
1890+ } ,
1891+ required : [ '_id' , 'status' , 'success' ] ,
1892+ additionalProperties : false ,
1893+ } ) ;
1894+
18731895API . v1
18741896 . get (
18751897 'users.getPresence' ,
@@ -1920,6 +1942,7 @@ API.v1
19201942 body : ajv . compile < {
19211943 status ?: UserStatus ;
19221944 message ?: string ;
1945+ expiresAt ?: string ;
19231946 userId ?: string ;
19241947 username ?: string ;
19251948 user ?: string ;
@@ -1928,6 +1951,7 @@ API.v1
19281951 properties : {
19291952 status : { type : 'string' , enum : [ 'online' , 'away' , 'offline' , 'busy' ] } ,
19301953 message : { type : 'string' , nullable : true } ,
1954+ expiresAt : { type : 'string' , format : 'date-time' , nullable : true } ,
19311955 userId : { type : 'string' } ,
19321956 username : { type : 'string' } ,
19331957 user : { type : 'string' } ,
@@ -1975,48 +1999,29 @@ API.v1
19751999 return API . v1 . forbidden ( ) ;
19762000 }
19772001
1978- const { _id, username, roles, name } = user ;
1979- let { statusText, status } = user ;
1980-
1981- if ( this . bodyParams . message || this . bodyParams . message === '' ) {
1982- await setStatusText ( user , this . bodyParams . message , { emit : false } ) ;
1983- statusText = this . bodyParams . message ;
2002+ if ( this . bodyParams . status === UserStatus . OFFLINE && ! settings . get ( 'Accounts_AllowInvisibleStatusOption' ) ) {
2003+ throw new Meteor . Error ( 'error-status-not-allowed' , 'Invisible status is disabled' , {
2004+ method : 'users.setStatus' ,
2005+ } ) ;
19842006 }
19852007
1986- if ( this . bodyParams . status ) {
1987- const validStatus = [ 'online' , 'away' , 'offline' , 'busy' ] ;
1988- if ( validStatus . includes ( this . bodyParams . status ) ) {
1989- status = this . bodyParams . status ;
1990-
1991- if ( status === 'offline' && ! settings . get ( 'Accounts_AllowInvisibleStatusOption' ) ) {
1992- throw new Meteor . Error ( 'error-status-not-allowed' , 'Invisible status is disabled' , {
1993- method : 'users.setStatus' ,
1994- } ) ;
1995- }
1996-
1997- await Users . updateOne (
1998- { _id : user . _id } ,
1999- {
2000- $set : {
2001- status,
2002- statusDefault : status ,
2003- } ,
2004- } ,
2005- ) ;
2006-
2007- void wrapExceptions ( ( ) => Calendar . cancelUpcomingStatusChanges ( user . _id ) ) . suppress ( ) ;
2008- } else {
2009- throw new Meteor . Error ( 'error-invalid-status' , 'Valid status types include online, away, offline, and busy.' , {
2010- method : 'users.setStatus' ,
2011- } ) ;
2012- }
2008+ const finalStatus = this . bodyParams . status ?? user . status ?? UserStatus . ONLINE ;
2009+ const finalText = this . bodyParams . message ?? user . statusText ?? '' ;
2010+ const expiresAt = this . bodyParams . expiresAt ? new Date ( this . bodyParams . expiresAt ) : undefined ;
2011+
2012+ // "Online" with no text is a status reset — removes the active claim
2013+ // and lets the system manage presence automatically (auto-away, connection-based)
2014+ if ( finalStatus === UserStatus . ONLINE && ! finalText ) {
2015+ await Presence . clearActiveState ( user . _id ) ;
2016+ } else {
2017+ await Presence . setActiveState ( user . _id , {
2018+ statusDefault : finalStatus ,
2019+ statusText : finalText ,
2020+ statusSource : 'manual' ,
2021+ ...( expiresAt && { statusExpiresAt : expiresAt } ) ,
2022+ } ) ;
20132023 }
20142024
2015- void api . broadcast ( 'presence.status' , {
2016- user : { status, _id, username, statusText, roles, name } ,
2017- previousStatus : user . status ,
2018- } ) ;
2019-
20202025 return API . v1 . success ( ) ;
20212026 } ,
20222027 )
@@ -2026,17 +2031,7 @@ API.v1
20262031 authRequired : true ,
20272032 query : isUsersGetStatusParamsGET ,
20282033 response : {
2029- 200 : ajv . compile < { _id : string ; status : string ; connectionStatus ?: string } > ( {
2030- type : 'object' ,
2031- properties : {
2032- _id : { type : 'string' } ,
2033- status : statusType ,
2034- connectionStatus : { type : 'string' , nullable : true } ,
2035- success : { type : 'boolean' , enum : [ true ] } ,
2036- } ,
2037- required : [ '_id' , 'status' , 'success' ] ,
2038- additionalProperties : false ,
2039- } ) ,
2034+ 200 : getStatusResponseSchema ,
20402035 400 : validateBadRequestErrorResponse ,
20412036 401 : validateUnauthorizedErrorResponse ,
20422037 } ,
@@ -2045,18 +2040,22 @@ API.v1
20452040 if ( isUserFromParams ( this . queryParams , this . userId , this . user ) ) {
20462041 return API . v1 . success ( {
20472042 _id : this . userId ,
2048- // message: user.statusText,
2049- connectionStatus : ( this . user . statusConnection || 'offline' ) as 'online' | 'offline' | 'away' | 'busy' ,
2050- status : ( this . user . status || 'offline' ) as 'online' | 'offline' | 'away' | 'busy' ,
2043+ connectionStatus : this . user . statusConnection || 'offline' ,
2044+ status : this . user . status || 'offline' ,
2045+ statusSource : this . user . statusSource ,
2046+ statusEmoji : this . user . statusEmoji ,
2047+ statusExpiresAt : this . user . statusExpiresAt ?. toISOString ( ) ,
20512048 } ) ;
20522049 }
20532050
20542051 const user = await getUserFromParams ( this . queryParams ) ;
20552052
20562053 return API . v1 . success ( {
20572054 _id : user . _id ,
2058- // message: user.statusText,
2059- status : ( user . status || 'offline' ) as 'online' | 'offline' | 'away' | 'busy' ,
2055+ status : user . status || 'offline' ,
2056+ statusSource : user . statusSource ,
2057+ statusEmoji : user . statusEmoji ,
2058+ statusExpiresAt : user . statusExpiresAt ?. toISOString ( ) ,
20602059 } ) ;
20612060 } ,
20622061 ) ;
0 commit comments