feat: GET /api/sessions/{name}/timelock - fix #2219
This commit is contained in:
1 parent
8d4eb02202
commit
841063739e
9 files changed
+153
-36
No files matched your search
@@ -35,6 +35,7 @@ import {
|
||||
ListSessionsQuery,
|
||||
MeInfo,
|
||||
MessageCappingData,
|
||||
ReachoutTimelockData,
|
||||
SessionCreateRequest,
|
||||
SessionDTO,
|
||||
SessionExpand,
|
||||
@@ -115,6 +116,23 @@ class SessionsController {
|
||||
return session.fetchMessageCapping();
|
||||
}
|
||||
|
||||
@Get(':session/timelock')
|
||||
@SessionApiParam
|
||||
@ApiOperation({
|
||||
summary: 'Fetch the account reachout timelock state',
|
||||
description:
|
||||
'Fetch a fresh reachout timelock state from WhatsApp - the restriction ' +
|
||||
'behind "server returned error 463" when messaging new contacts. ' +
|
||||
'The same value is also available under me.reachoutTimelock in the session ' +
|
||||
'info, and changes are pushed through the session.status event.',
|
||||
})
|
||||
@CheckPolicies(CanSession(Action.Read, FromParam('session')))
|
||||
fetchReachoutTimelock(
|
||||
@SessionParam session: WhatsappSession,
|
||||
): Promise<ReachoutTimelockData> {
|
||||
return session.fetchReachoutTimelock();
|
||||
}
|
||||
|
||||
@Post('')
|
||||
@ApiOperation({
|
||||
summary: 'Create a session',
|
||||
|
||||
@@ -189,4 +189,23 @@ export class SessionTools extends McpController {
|
||||
url: `/api/sessions/${session}/capping`,
|
||||
});
|
||||
}
|
||||
|
||||
@Tool('sessions-timelock', {
|
||||
title: 'Get reachout timelock',
|
||||
description:
|
||||
'Fetch the account reachout timelock state ' +
|
||||
'(the restriction behind error 463 when messaging new contacts).',
|
||||
inputSchema: SessionNameInput,
|
||||
annotations: {
|
||||
readOnlyHint: true,
|
||||
destructiveHint: false,
|
||||
idempotentHint: true,
|
||||
},
|
||||
})
|
||||
async timelock({ session }: z.infer<typeof SessionNameInput>) {
|
||||
return this.textRequest({
|
||||
method: 'GET',
|
||||
url: `/api/sessions/${session}/timelock`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -118,6 +118,7 @@ import {
|
||||
MessageCappingData,
|
||||
MessageCappingStatus,
|
||||
ProxyConfig,
|
||||
ReachoutTimelockData,
|
||||
SessionConfig,
|
||||
} from '../../structures/sessions.dto';
|
||||
import {
|
||||
@@ -530,6 +531,10 @@ export abstract class WhatsappSession {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public fetchReachoutTimelock(): Promise<ReachoutTimelockData> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Auth methods
|
||||
*/
|
||||
|
||||
@@ -11529,6 +11529,15 @@ export namespace messages {
|
||||
responseSerialize: (message: Empty) => Buffer.from(message.serialize()),
|
||||
responseDeserialize: (bytes: Buffer) => Empty.deserialize(new Uint8Array(bytes))
|
||||
},
|
||||
FetchReachoutTimelock: {
|
||||
path: "/messages.MessageService/FetchReachoutTimelock",
|
||||
requestStream: false,
|
||||
responseStream: false,
|
||||
requestSerialize: (message: Session) => Buffer.from(message.serialize()),
|
||||
requestDeserialize: (bytes: Buffer) => Session.deserialize(new Uint8Array(bytes)),
|
||||
responseSerialize: (message: Json) => Buffer.from(message.serialize()),
|
||||
responseDeserialize: (bytes: Buffer) => Json.deserialize(new Uint8Array(bytes))
|
||||
},
|
||||
FetchMessageCapping: {
|
||||
path: "/messages.MessageService/FetchMessageCapping",
|
||||
requestStream: false,
|
||||
@@ -11855,6 +11864,7 @@ export namespace messages {
|
||||
abstract SubscribePresence(call: grpc_1.ServerUnaryCall<SubscribePresenceRequest, Empty>, callback: grpc_1.sendUnaryData<Empty>): void;
|
||||
abstract CheckPhones(call: grpc_1.ServerUnaryCall<CheckPhonesRequest, CheckPhonesResponse>, callback: grpc_1.sendUnaryData<CheckPhonesResponse>): void;
|
||||
abstract MarkChatUnread(call: grpc_1.ServerUnaryCall<ChatUnreadRequest, Empty>, callback: grpc_1.sendUnaryData<Empty>): void;
|
||||
abstract FetchReachoutTimelock(call: grpc_1.ServerUnaryCall<Session, Json>, callback: grpc_1.sendUnaryData<Json>): void;
|
||||
abstract FetchMessageCapping(call: grpc_1.ServerUnaryCall<Session, Json>, callback: grpc_1.sendUnaryData<Json>): void;
|
||||
abstract GenerateNewMessageID(call: grpc_1.ServerUnaryCall<Session, NewMessageIDResponse>, callback: grpc_1.sendUnaryData<NewMessageIDResponse>): void;
|
||||
abstract SendMessage(call: grpc_1.ServerUnaryCall<MessageRequest, MessageResponse>, callback: grpc_1.sendUnaryData<MessageResponse>): void;
|
||||
@@ -12000,6 +12010,9 @@ export namespace messages {
|
||||
MarkChatUnread: GrpcUnaryServiceInterface<ChatUnreadRequest, Empty> = (message: ChatUnreadRequest, metadata: grpc_1.Metadata | grpc_1.CallOptions | grpc_1.requestCallback<Empty>, options?: grpc_1.CallOptions | grpc_1.requestCallback<Empty>, callback?: grpc_1.requestCallback<Empty>): grpc_1.ClientUnaryCall => {
|
||||
return super.MarkChatUnread(message, metadata, options, callback);
|
||||
};
|
||||
FetchReachoutTimelock: GrpcUnaryServiceInterface<Session, Json> = (message: Session, metadata: grpc_1.Metadata | grpc_1.CallOptions | grpc_1.requestCallback<Json>, options?: grpc_1.CallOptions | grpc_1.requestCallback<Json>, callback?: grpc_1.requestCallback<Json>): grpc_1.ClientUnaryCall => {
|
||||
return super.FetchReachoutTimelock(message, metadata, options, callback);
|
||||
};
|
||||
FetchMessageCapping: GrpcUnaryServiceInterface<Session, Json> = (message: Session, metadata: grpc_1.Metadata | grpc_1.CallOptions | grpc_1.requestCallback<Json>, options?: grpc_1.CallOptions | grpc_1.requestCallback<Json>, callback?: grpc_1.requestCallback<Json>): grpc_1.ClientUnaryCall => {
|
||||
return super.FetchMessageCapping(message, metadata, options, callback);
|
||||
};
|
||||
|
||||
@@ -1145,7 +1145,19 @@ getProfilePicture: {
|
||||
//
|
||||
// Account
|
||||
//
|
||||
// Fetch the account's current new-chat message capping (per-cycle quota).
|
||||
// Fetch the account's current reachout timelock state.
|
||||
fetchReachoutTimelock: {
|
||||
path: '/messages.MessageService/FetchReachoutTimelock',
|
||||
requestStream: false,
|
||||
responseStream: false,
|
||||
requestType: gows_pb.Session,
|
||||
responseType: gows_pb.Json,
|
||||
requestSerialize: serialize_messages_Session,
|
||||
requestDeserialize: deserialize_messages_Session,
|
||||
responseSerialize: serialize_messages_Json,
|
||||
responseDeserialize: deserialize_messages_Json,
|
||||
},
|
||||
// Fetch the account's current new-chat message capping (per-cycle quota).
|
||||
fetchMessageCapping: {
|
||||
path: '/messages.MessageService/FetchMessageCapping',
|
||||
requestStream: false,
|
||||
|
||||
@@ -139,6 +139,7 @@ import {
|
||||
MeInfo,
|
||||
MessageCappingData,
|
||||
ProxyConfig,
|
||||
ReachoutTimelockData,
|
||||
SessionConfig,
|
||||
} from '@waha/structures/sessions.dto';
|
||||
import {
|
||||
@@ -2299,6 +2300,17 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
|
||||
return capping;
|
||||
}
|
||||
|
||||
@Activity()
|
||||
public async fetchReachoutTimelock(): Promise<ReachoutTimelockData> {
|
||||
const response = await promisify(this.client.FetchReachoutTimelock)(
|
||||
this.session,
|
||||
);
|
||||
const timelock = parseGowsReachoutTimelock(parseJson(response));
|
||||
// Keep the tracker in sync so MeInfo and 'session.status' reflect the fetch
|
||||
this.reachoutTimelock.update(timelock);
|
||||
return timelock;
|
||||
}
|
||||
|
||||
public async getContacts(pagination: PaginationParams) {
|
||||
const request = new messages.GetContactsRequest({
|
||||
session: this.session,
|
||||
|
||||
@@ -177,6 +177,7 @@ import { WAMessage, WAMessageReaction } from '@waha/structures/responses.dto';
|
||||
import {
|
||||
MeInfo,
|
||||
MessageCappingData,
|
||||
ReachoutTimelockData,
|
||||
ReachoutTimelockEnforcementType,
|
||||
} from '@waha/structures/sessions.dto';
|
||||
import { EnsureSeconds } from '@waha/utils/timehelper';
|
||||
@@ -572,20 +573,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
this.sock.ev.on('connection.update', async (update) => {
|
||||
const { connection, lastDisconnect, qr, isNewLogin } = update;
|
||||
if (update.reachoutTimeLock) {
|
||||
const timelock = update.reachoutTimeLock;
|
||||
const enforcementType =
|
||||
timelock.enforcementType ?? ReachoutTimelockEnforcementType.DEFAULT;
|
||||
let timeEnforcementEnds: number | null = null;
|
||||
if (timelock.timeEnforcementEnds) {
|
||||
timeEnforcementEnds = EnsureSeconds(
|
||||
timelock.timeEnforcementEnds.getTime(),
|
||||
);
|
||||
}
|
||||
this.reachoutTimelock.update({
|
||||
enforcementType: enforcementType as ReachoutTimelockEnforcementType,
|
||||
isActive: timelock.isActive === true,
|
||||
timeEnforcementEnds: timeEnforcementEnds,
|
||||
});
|
||||
this.updateReachoutTimelockFromState(update.reachoutTimeLock);
|
||||
}
|
||||
if (isNewLogin) {
|
||||
this.restartClient();
|
||||
@@ -1058,6 +1046,30 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
return capping;
|
||||
}
|
||||
|
||||
@Activity()
|
||||
public async fetchReachoutTimelock(): Promise<ReachoutTimelockData> {
|
||||
const state = await this.sock.fetchAccountReachoutTimelock();
|
||||
return this.updateReachoutTimelockFromState(state);
|
||||
}
|
||||
|
||||
private updateReachoutTimelockFromState(timelock: any): ReachoutTimelockData {
|
||||
const enforcementType =
|
||||
timelock.enforcementType ?? ReachoutTimelockEnforcementType.DEFAULT;
|
||||
let timeEnforcementEnds: number | null = null;
|
||||
if (timelock.timeEnforcementEnds) {
|
||||
timeEnforcementEnds = EnsureSeconds(
|
||||
timelock.timeEnforcementEnds.getTime(),
|
||||
);
|
||||
}
|
||||
const data: ReachoutTimelockData = {
|
||||
enforcementType: enforcementType as ReachoutTimelockEnforcementType,
|
||||
isActive: timelock.isActive === true,
|
||||
timeEnforcementEnds: timeEnforcementEnds,
|
||||
};
|
||||
this.reachoutTimelock.update(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Other methods
|
||||
*/
|
||||
|
||||
@@ -130,6 +130,7 @@ import { BrowserTraceQuery } from '@waha/structures/server.debug.dto';
|
||||
import {
|
||||
MeInfo,
|
||||
MessageCappingData,
|
||||
ReachoutTimelockData,
|
||||
ReachoutTimelockEnforcementType,
|
||||
} from '@waha/structures/sessions.dto';
|
||||
import { EnsureSeconds } from '@waha/utils/timehelper';
|
||||
@@ -629,25 +630,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
|
||||
protected listenConnectionEvents() {
|
||||
this.whatsapp.on(Events.REACHOUT_TIMELOCK_UPDATE, (record: any) => {
|
||||
if (!record) {
|
||||
// The app removes the stored record when the enforcement is lifted (or there is none)
|
||||
const current = this.reachoutTimelock.value;
|
||||
if (current?.isActive) {
|
||||
this.reachoutTimelock.update({ ...current, isActive: false });
|
||||
}
|
||||
return;
|
||||
}
|
||||
const enforcementType =
|
||||
record.enforcement_type ?? ReachoutTimelockEnforcementType.DEFAULT;
|
||||
this.reachoutTimelock.update({
|
||||
enforcementType: enforcementType as ReachoutTimelockEnforcementType,
|
||||
// The record only exists while the enforcement is active
|
||||
isActive: true,
|
||||
// The app stores 'time_enforcement_ends' as unix milliseconds
|
||||
timeEnforcementEnds: record.time_enforcement_ends
|
||||
? EnsureSeconds(record.time_enforcement_ends)
|
||||
: null,
|
||||
});
|
||||
this.updateReachoutTimelockFromRecord(record);
|
||||
});
|
||||
|
||||
this.whatsapp.on(Events.MESSAGE_CAPPING_UPDATE, (record: any) => {
|
||||
@@ -917,6 +900,49 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
return capping;
|
||||
}
|
||||
|
||||
@Activity()
|
||||
public async fetchReachoutTimelock(): Promise<ReachoutTimelockData> {
|
||||
const record = await this.whatsapp.fetchReachoutTimelock();
|
||||
if (record === undefined) {
|
||||
throw new NotImplementedByEngineError(
|
||||
'Reachout timelock is not available in the current WhatsApp Web version',
|
||||
);
|
||||
}
|
||||
return this.updateReachoutTimelockFromRecord(record);
|
||||
}
|
||||
|
||||
// Applies the stored 'WAReachoutTimelockState' record (or its removal) to the tracker,
|
||||
// so MeInfo and 'session.status' reflect it, and returns the resulting state
|
||||
private updateReachoutTimelockFromRecord(record: any): ReachoutTimelockData {
|
||||
if (!record) {
|
||||
// The app removes the stored record when the enforcement is lifted (or there is none)
|
||||
const current = this.reachoutTimelock.value;
|
||||
if (current?.isActive) {
|
||||
this.reachoutTimelock.update({ ...current, isActive: false });
|
||||
}
|
||||
return (
|
||||
this.reachoutTimelock.value ?? {
|
||||
enforcementType: ReachoutTimelockEnforcementType.DEFAULT,
|
||||
isActive: false,
|
||||
timeEnforcementEnds: null,
|
||||
}
|
||||
);
|
||||
}
|
||||
const enforcementType =
|
||||
record.enforcement_type ?? ReachoutTimelockEnforcementType.DEFAULT;
|
||||
const timelock: ReachoutTimelockData = {
|
||||
enforcementType: enforcementType as ReachoutTimelockEnforcementType,
|
||||
// The record only exists while the enforcement is active
|
||||
isActive: true,
|
||||
// The app stores 'time_enforcement_ends' as unix milliseconds
|
||||
timeEnforcementEnds: record.time_enforcement_ends
|
||||
? EnsureSeconds(record.time_enforcement_ends)
|
||||
: null,
|
||||
};
|
||||
this.reachoutTimelock.update(timelock);
|
||||
return timelock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Groups methods
|
||||
*/
|
||||
|
||||
@@ -14350,7 +14350,7 @@ __metadata:
|
||||
|
||||
"whatsapp-web.js@github:devlikeapro/whatsapp-web.js#fork-main-2026-06-26":
|
||||
version: 1.34.7
|
||||
resolution: "whatsapp-web.js@https://github.com/devlikeapro/whatsapp-web.js.git#commit=d8f5e287eef6044b78e565f2900d960b5717ab3a"
|
||||
resolution: "whatsapp-web.js@https://github.com/devlikeapro/whatsapp-web.js.git#commit=7d4ce53562068d6610060599c8e8784dc0101b37"
|
||||
dependencies:
|
||||
archiver: "npm:7.0.1"
|
||||
fluent-ffmpeg: "npm:2.1.3"
|
||||
@@ -14367,7 +14367,7 @@ __metadata:
|
||||
optional: true
|
||||
unzipper:
|
||||
optional: true
|
||||
checksum: 10/1ef628f91ca447ac813937e4f0c407f662fe9ae7743f425746e972e07a6852530a644940f06aa21fedea76c1fdf4ef611d4e877c509adc26acfcbe995ea2bbfc
|
||||
checksum: 10/df44673005a9aae09e14986efd4385f77e59b7a91c23bcb07ae60aed392818da7bdb404fa6d1a571da246347c6a05f34e7bd6f672a67ba15ad4bba95fa9ab0ca
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Reference in new issue
Block a user