Compare commits

...
15 Commits
Author SHA1 Message Date
devlikepro d745279fec [core] 2025.4.1
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-04-09 16:05:03 +07:00
devlikepro df34c9f2fd [core] Up GOWS 2025-04-09 16:05:03 +07:00
devlikepro 37da9aabe3 [core] Add POST /api/send/link-review
fix #880
fix #596
2025-04-09 16:05:02 +07:00
devlikepro 3a2205147c [core] API - deprecate /sendLinkPreview 2025-04-09 16:05:02 +07:00
devlikepro 5c6acaf10a [core] NOWEB - filter out sender key distribution msg
fix #891
2025-04-09 16:05:02 +07:00
devlikepro d916f2dea5 [core] NOWEB - return error in case of malformed xml 2025-04-09 16:05:01 +07:00
devlikepro 254a1aedcb [core] NOWEB - send reaction to channel
fix #889
2025-04-09 16:05:01 +07:00
devlikepro dce3175388 [core] GOWS - fix sending reaction to DM/Groups for other participants 2025-04-09 16:05:01 +07:00
devlikepro e2b6b45418 [core] GOWS - send reaction to channel
fix #889
2025-04-09 16:05:01 +07:00
devlikepro e210d1e9fc [core] 2025.3.3
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
2025-03-31 15:39:56 +07:00
devlikepro df0f449e13 [core] use isJidBroadcast 2025-03-31 15:39:56 +07:00
devlikepro 895105c900 [core] Ignore @broadcast for picture
fix #878
fix #881
2025-03-31 15:39:56 +07:00
devlikepro 209538709b [core] Up Dashboard 2025-03-31 15:39:56 +07:00
devlikepro ef7c35286b [core] NOWEB - revert presence optimizations 2025-03-31 15:39:56 +07:00
devlikepro 70af0828cb [core] NOWEB - cache presence for 1 minute 2025-03-31 15:39:56 +07:00
15 changed files with 723 additions and 42 deletions

No files matched your search

+27 -6
View File
@@ -23,6 +23,7 @@ import {
MessageFileRequest,
MessageForwardRequest,
MessageImageRequest,
MessageLinkCustomPreviewRequest,
MessageLinkPreviewRequest,
MessageLocationRequest,
MessagePollRequest,
@@ -95,6 +96,25 @@ export class ChattingController {
return whatsapp.sendVideo(request);
}
@Post('/send/link-custom-preview')
@ApiOperation({
summary: 'Send a text message with a CUSTOM link preview.',
description:
'You can use regular /api/sendText if you wanna send auto-generated link preview.',
})
@UsePipes(new WAHAValidationPipe())
async sendLinkCustomPreview(
@Body() request: MessageLinkCustomPreviewRequest,
): Promise<any> {
const whatsapp = await this.manager.getWorkingSession(request.session);
if (!request.text.includes(request.preview.url)) {
throw new Error(
'"text" must include the URL provided in the "preview.url"',
);
}
return whatsapp.sendLinkCustomPreview(request);
}
@Post('/sendButtons')
@ApiOperation({
summary: 'Send buttons (interactive message)',
@@ -166,12 +186,6 @@ export class ChattingController {
return whatsapp.sendLocation(request);
}
@Post('/sendLinkPreview')
async sendLinkPreview(@Body() request: MessageLinkPreviewRequest) {
const whatsapp = await this.manager.getWorkingSession(request.session);
return whatsapp.sendLinkPreview(request);
}
@Post('/sendContactVcard')
async sendContactVcard(@Body() request: MessageContactVcardRequest) {
const whatsapp = await this.manager.getWorkingSession(request.session);
@@ -236,4 +250,11 @@ export class ChattingController {
const whatsapp = await this.manager.getWorkingSession(request.session);
return whatsapp.reply(request);
}
@Post('/sendLinkPreview')
@ApiOperation({ deprecated: true })
async sendLinkPreview_DEPRECATED(@Body() request: MessageLinkPreviewRequest) {
const whatsapp = await this.manager.getWorkingSession(request.session);
return whatsapp.sendLinkPreview(request);
}
}
+11 -1
View File
@@ -1,3 +1,4 @@
import { isJidBroadcast } from '@adiwajshing/baileys/lib/WABinary/jid-utils';
import {
Channel,
ChannelListResult,
@@ -51,6 +52,7 @@ import {
MessageFileRequest,
MessageForwardRequest,
MessageImageRequest,
MessageLinkCustomPreviewRequest,
MessageLinkPreviewRequest,
MessageLocationRequest,
MessagePollRequest,
@@ -420,6 +422,12 @@ export abstract class WhatsappSession {
throw new NotImplementedByEngineError();
}
sendLinkCustomPreview(
request: MessageLinkCustomPreviewRequest,
): Promise<any> {
throw new NotImplementedByEngineError();
}
abstract forwardMessage(request: MessageForwardRequest): Promise<WAMessage>;
abstract sendImage(request: MessageImageRequest);
@@ -597,7 +605,9 @@ export abstract class WhatsappSession {
protected async refreshProfilePicture(id: string) {
let fn: Promise<string>;
if (isNewsletter(id)) {
if (isJidBroadcast(id)) {
return null;
} else if (isNewsletter(id)) {
fn = this.channelsGetChannel(id).then(
(channel: Channel) => channel.picture || channel.preview,
);
+162
View File
@@ -2535,6 +2535,142 @@ export namespace messages {
return Media.deserialize(bytes);
}
}
export class LinkPreview extends pb_1.Message {
#one_of_decls: number[][] = [];
constructor(data?: any[] | {
url?: string;
title?: string;
description?: string;
image?: Uint8Array;
}) {
super();
pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [], this.#one_of_decls);
if (!Array.isArray(data) && typeof data == "object") {
if ("url" in data && data.url != undefined) {
this.url = data.url;
}
if ("title" in data && data.title != undefined) {
this.title = data.title;
}
if ("description" in data && data.description != undefined) {
this.description = data.description;
}
if ("image" in data && data.image != undefined) {
this.image = data.image;
}
}
}
get url() {
return pb_1.Message.getFieldWithDefault(this, 1, "") as string;
}
set url(value: string) {
pb_1.Message.setField(this, 1, value);
}
get title() {
return pb_1.Message.getFieldWithDefault(this, 2, "") as string;
}
set title(value: string) {
pb_1.Message.setField(this, 2, value);
}
get description() {
return pb_1.Message.getFieldWithDefault(this, 3, "") as string;
}
set description(value: string) {
pb_1.Message.setField(this, 3, value);
}
get image() {
return pb_1.Message.getFieldWithDefault(this, 4, new Uint8Array(0)) as Uint8Array;
}
set image(value: Uint8Array) {
pb_1.Message.setField(this, 4, value);
}
static fromObject(data: {
url?: string;
title?: string;
description?: string;
image?: Uint8Array;
}): LinkPreview {
const message = new LinkPreview({});
if (data.url != null) {
message.url = data.url;
}
if (data.title != null) {
message.title = data.title;
}
if (data.description != null) {
message.description = data.description;
}
if (data.image != null) {
message.image = data.image;
}
return message;
}
toObject() {
const data: {
url?: string;
title?: string;
description?: string;
image?: Uint8Array;
} = {};
if (this.url != null) {
data.url = this.url;
}
if (this.title != null) {
data.title = this.title;
}
if (this.description != null) {
data.description = this.description;
}
if (this.image != null) {
data.image = this.image;
}
return data;
}
serialize(): Uint8Array;
serialize(w: pb_1.BinaryWriter): void;
serialize(w?: pb_1.BinaryWriter): Uint8Array | void {
const writer = w || new pb_1.BinaryWriter();
if (this.url.length)
writer.writeString(1, this.url);
if (this.title.length)
writer.writeString(2, this.title);
if (this.description.length)
writer.writeString(3, this.description);
if (this.image.length)
writer.writeBytes(4, this.image);
if (!w)
return writer.getResultBuffer();
}
static deserialize(bytes: Uint8Array | pb_1.BinaryReader): LinkPreview {
const reader = bytes instanceof pb_1.BinaryReader ? bytes : new pb_1.BinaryReader(bytes), message = new LinkPreview();
while (reader.nextField()) {
if (reader.isEndGroup())
break;
switch (reader.getFieldNumber()) {
case 1:
message.url = reader.readString();
break;
case 2:
message.title = reader.readString();
break;
case 3:
message.description = reader.readString();
break;
case 4:
message.image = reader.readBytes();
break;
default: reader.skipField();
}
}
return message;
}
serializeBinary(): Uint8Array {
return this.serialize();
}
static deserializeBinary(bytes: Uint8Array): LinkPreview {
return LinkPreview.deserialize(bytes);
}
}
export class MessageRequest extends pb_1.Message {
#one_of_decls: number[][] = [];
constructor(data?: any[] | {
@@ -2549,6 +2685,7 @@ export namespace messages {
replyTo?: string;
id?: string;
participants?: string[];
preview?: LinkPreview;
}) {
super();
pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [11], this.#one_of_decls);
@@ -2586,6 +2723,9 @@ export namespace messages {
if ("participants" in data && data.participants != undefined) {
this.participants = data.participants;
}
if ("preview" in data && data.preview != undefined) {
this.preview = data.preview;
}
}
}
get session() {
@@ -2666,6 +2806,15 @@ export namespace messages {
set participants(value: string[]) {
pb_1.Message.setField(this, 11, value);
}
get preview() {
return pb_1.Message.getWrapperField(this, LinkPreview, 12) as LinkPreview;
}
set preview(value: LinkPreview) {
pb_1.Message.setWrapperField(this, 12, value);
}
get has_preview() {
return pb_1.Message.getField(this, 12) != null;
}
static fromObject(data: {
session?: ReturnType<typeof Session.prototype.toObject>;
jid?: string;
@@ -2678,6 +2827,7 @@ export namespace messages {
replyTo?: string;
id?: string;
participants?: string[];
preview?: ReturnType<typeof LinkPreview.prototype.toObject>;
}): MessageRequest {
const message = new MessageRequest({});
if (data.session != null) {
@@ -2713,6 +2863,9 @@ export namespace messages {
if (data.participants != null) {
message.participants = data.participants;
}
if (data.preview != null) {
message.preview = LinkPreview.fromObject(data.preview);
}
return message;
}
toObject() {
@@ -2728,6 +2881,7 @@ export namespace messages {
replyTo?: string;
id?: string;
participants?: string[];
preview?: ReturnType<typeof LinkPreview.prototype.toObject>;
} = {};
if (this.session != null) {
data.session = this.session.toObject();
@@ -2762,6 +2916,9 @@ export namespace messages {
if (this.participants != null) {
data.participants = this.participants;
}
if (this.preview != null) {
data.preview = this.preview.toObject();
}
return data;
}
serialize(): Uint8Array;
@@ -2790,6 +2947,8 @@ export namespace messages {
writer.writeString(10, this.id);
if (this.participants.length)
writer.writeRepeatedString(11, this.participants);
if (this.has_preview)
writer.writeMessage(12, this.preview, () => this.preview.serialize(writer));
if (!w)
return writer.getResultBuffer();
}
@@ -2832,6 +2991,9 @@ export namespace messages {
case 11:
pb_1.Message.addToRepeatedField(message, 11, reader.readString());
break;
case 12:
reader.readMessage(message.preview, () => message.preview = LinkPreview.deserialize(reader));
break;
default: reader.skipField();
}
}
+318 -1
View File
@@ -45,6 +45,7 @@ goog.exportSymbol('proto.messages.JidRequest', null, global);
goog.exportSymbol('proto.messages.JidStringRequest', null, global);
goog.exportSymbol('proto.messages.Json', null, global);
goog.exportSymbol('proto.messages.JsonList', null, global);
goog.exportSymbol('proto.messages.LinkPreview', null, global);
goog.exportSymbol('proto.messages.LogLevel', null, global);
goog.exportSymbol('proto.messages.MarkReadRequest', null, global);
goog.exportSymbol('proto.messages.Media', null, global);
@@ -662,6 +663,27 @@ if (goog.DEBUG && !COMPILED) {
*/
proto.messages.Media.displayName = 'proto.messages.Media';
}
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
* server response, or constructed directly in Javascript. The array is used
* in place and becomes part of the constructed object. It is not cloned.
* If no data is provided, the constructed object will be empty, but still
* valid.
* @extends {jspb.Message}
* @constructor
*/
proto.messages.LinkPreview = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.messages.LinkPreview, jspb.Message);
if (goog.DEBUG && !COMPILED) {
/**
* @public
* @override
*/
proto.messages.LinkPreview.displayName = 'proto.messages.LinkPreview';
}
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
@@ -6338,6 +6360,250 @@ proto.messages.Media.prototype.setFilename = function(value) {
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto.
* Field names that are reserved in JavaScript and will be renamed to pb_name.
* Optional fields that are not set will be set to undefined.
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
* For the list of reserved names please see:
* net/proto2/compiler/js/internal/generator.cc#kKeyword.
* @param {boolean=} opt_includeInstance Deprecated. whether to include the
* JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @return {!Object}
*/
proto.messages.LinkPreview.prototype.toObject = function(opt_includeInstance) {
return proto.messages.LinkPreview.toObject(opt_includeInstance, this);
};
/**
* Static version of the {@see toObject} method.
* @param {boolean|undefined} includeInstance Deprecated. Whether to include
* the JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @param {!proto.messages.LinkPreview} msg The msg instance to transform.
* @return {!Object}
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.messages.LinkPreview.toObject = function(includeInstance, msg) {
var f, obj = {
url: jspb.Message.getFieldWithDefault(msg, 1, ""),
title: jspb.Message.getFieldWithDefault(msg, 2, ""),
description: jspb.Message.getFieldWithDefault(msg, 3, ""),
image: msg.getImage_asB64()
};
if (includeInstance) {
obj.$jspbMessageInstance = msg;
}
return obj;
};
}
/**
* Deserializes binary data (in protobuf wire format).
* @param {jspb.ByteSource} bytes The bytes to deserialize.
* @return {!proto.messages.LinkPreview}
*/
proto.messages.LinkPreview.deserializeBinary = function(bytes) {
var reader = new jspb.BinaryReader(bytes);
var msg = new proto.messages.LinkPreview;
return proto.messages.LinkPreview.deserializeBinaryFromReader(msg, reader);
};
/**
* Deserializes binary data (in protobuf wire format) from the
* given reader into the given message object.
* @param {!proto.messages.LinkPreview} msg The message object to deserialize into.
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
* @return {!proto.messages.LinkPreview}
*/
proto.messages.LinkPreview.deserializeBinaryFromReader = function(msg, reader) {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
var field = reader.getFieldNumber();
switch (field) {
case 1:
var value = /** @type {string} */ (reader.readString());
msg.setUrl(value);
break;
case 2:
var value = /** @type {string} */ (reader.readString());
msg.setTitle(value);
break;
case 3:
var value = /** @type {string} */ (reader.readString());
msg.setDescription(value);
break;
case 4:
var value = /** @type {!Uint8Array} */ (reader.readBytes());
msg.setImage(value);
break;
default:
reader.skipField();
break;
}
}
return msg;
};
/**
* Serializes the message to binary data (in protobuf wire format).
* @return {!Uint8Array}
*/
proto.messages.LinkPreview.prototype.serializeBinary = function() {
var writer = new jspb.BinaryWriter();
proto.messages.LinkPreview.serializeBinaryToWriter(this, writer);
return writer.getResultBuffer();
};
/**
* Serializes the given message to binary data (in protobuf wire
* format), writing to the given BinaryWriter.
* @param {!proto.messages.LinkPreview} message
* @param {!jspb.BinaryWriter} writer
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.messages.LinkPreview.serializeBinaryToWriter = function(message, writer) {
var f = undefined;
f = message.getUrl();
if (f.length > 0) {
writer.writeString(
1,
f
);
}
f = message.getTitle();
if (f.length > 0) {
writer.writeString(
2,
f
);
}
f = message.getDescription();
if (f.length > 0) {
writer.writeString(
3,
f
);
}
f = message.getImage_asU8();
if (f.length > 0) {
writer.writeBytes(
4,
f
);
}
};
/**
* optional string url = 1;
* @return {string}
*/
proto.messages.LinkPreview.prototype.getUrl = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
};
/**
* @param {string} value
* @return {!proto.messages.LinkPreview} returns this
*/
proto.messages.LinkPreview.prototype.setUrl = function(value) {
return jspb.Message.setProto3StringField(this, 1, value);
};
/**
* optional string title = 2;
* @return {string}
*/
proto.messages.LinkPreview.prototype.getTitle = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ""));
};
/**
* @param {string} value
* @return {!proto.messages.LinkPreview} returns this
*/
proto.messages.LinkPreview.prototype.setTitle = function(value) {
return jspb.Message.setProto3StringField(this, 2, value);
};
/**
* optional string description = 3;
* @return {string}
*/
proto.messages.LinkPreview.prototype.getDescription = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, ""));
};
/**
* @param {string} value
* @return {!proto.messages.LinkPreview} returns this
*/
proto.messages.LinkPreview.prototype.setDescription = function(value) {
return jspb.Message.setProto3StringField(this, 3, value);
};
/**
* optional bytes image = 4;
* @return {!(string|Uint8Array)}
*/
proto.messages.LinkPreview.prototype.getImage = function() {
return /** @type {!(string|Uint8Array)} */ (jspb.Message.getFieldWithDefault(this, 4, ""));
};
/**
* optional bytes image = 4;
* This is a type-conversion wrapper around `getImage()`
* @return {string}
*/
proto.messages.LinkPreview.prototype.getImage_asB64 = function() {
return /** @type {string} */ (jspb.Message.bytesAsB64(
this.getImage()));
};
/**
* optional bytes image = 4;
* Note that Uint8Array is not supported on all browsers.
* @see http://caniuse.com/Uint8Array
* This is a type-conversion wrapper around `getImage()`
* @return {!Uint8Array}
*/
proto.messages.LinkPreview.prototype.getImage_asU8 = function() {
return /** @type {!Uint8Array} */ (jspb.Message.bytesAsU8(
this.getImage()));
};
/**
* @param {!(string|Uint8Array)} value
* @return {!proto.messages.LinkPreview} returns this
*/
proto.messages.LinkPreview.prototype.setImage = function(value) {
return jspb.Message.setProto3BytesField(this, 4, value);
};
/**
* List of repeated fields within this message type.
* @private {!Array<number>}
@@ -6386,7 +6652,8 @@ proto.messages.MessageRequest.toObject = function(includeInstance, msg) {
linkpreviewhighquality: jspb.Message.getBooleanFieldWithDefault(msg, 8, false),
replyto: jspb.Message.getFieldWithDefault(msg, 9, ""),
id: jspb.Message.getFieldWithDefault(msg, 10, ""),
participantsList: (f = jspb.Message.getRepeatedField(msg, 11)) == null ? undefined : f
participantsList: (f = jspb.Message.getRepeatedField(msg, 11)) == null ? undefined : f,
preview: (f = msg.getPreview()) && proto.messages.LinkPreview.toObject(includeInstance, f)
};
if (includeInstance) {
@@ -6471,6 +6738,11 @@ proto.messages.MessageRequest.deserializeBinaryFromReader = function(msg, reader
var value = /** @type {string} */ (reader.readString());
msg.addParticipants(value);
break;
case 12:
var value = new proto.messages.LinkPreview;
reader.readMessage(value,proto.messages.LinkPreview.deserializeBinaryFromReader);
msg.setPreview(value);
break;
default:
reader.skipField();
break;
@@ -6581,6 +6853,14 @@ proto.messages.MessageRequest.serializeBinaryToWriter = function(message, writer
f
);
}
f = message.getPreview();
if (f != null) {
writer.writeMessage(
12,
f,
proto.messages.LinkPreview.serializeBinaryToWriter
);
}
};
@@ -6877,6 +7157,43 @@ proto.messages.MessageRequest.prototype.clearParticipantsList = function() {
};
/**
* optional LinkPreview preview = 12;
* @return {?proto.messages.LinkPreview}
*/
proto.messages.MessageRequest.prototype.getPreview = function() {
return /** @type{?proto.messages.LinkPreview} */ (
jspb.Message.getWrapperField(this, proto.messages.LinkPreview, 12));
};
/**
* @param {?proto.messages.LinkPreview|undefined} value
* @return {!proto.messages.MessageRequest} returns this
*/
proto.messages.MessageRequest.prototype.setPreview = function(value) {
return jspb.Message.setWrapperField(this, 12, value);
};
/**
* Clears the message field making it undefined.
* @return {!proto.messages.MessageRequest} returns this
*/
proto.messages.MessageRequest.prototype.clearPreview = function() {
return this.setPreview(undefined);
};
/**
* Returns whether this field is set.
* @return {boolean}
*/
proto.messages.MessageRequest.prototype.hasPreview = function() {
return jspb.Message.getField(this, 12) != null;
};
+11 -5
View File
@@ -2,7 +2,6 @@ import {
getContentType,
getUrlFromDirectPath,
isJidGroup,
isJidStatusBroadcast,
jidNormalizedUser,
} from '@adiwajshing/baileys';
import * as grpc from '@grpc/grpc-js';
@@ -64,10 +63,10 @@ import {
ChatRequest,
CheckNumberStatusQuery,
EditMessageRequest,
MessageButtonReply,
MessageFileRequest,
MessageForwardRequest,
MessageImageRequest,
MessageLinkCustomPreviewRequest,
MessageLocationRequest,
MessageReactionRequest,
MessageReplyRequest,
@@ -130,6 +129,7 @@ import { promisify } from 'util';
import * as gows from './types';
import MessageServiceClient = messages.MessageServiceClient;
import { isJidBroadcast } from '@adiwajshing/baileys/lib/WABinary/jid-utils';
import { ReplyToMessage } from '@waha/structures/message.dto';
enum WhatsMeowEvent {
@@ -642,11 +642,11 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
}
protected messageResponse(jid, data) {
const message = parseJson(data.message);
const message = data.message ? parseJson(data.message) : null;
const id = buildMessageId({
ID: data.id,
IsFromMe: true,
IsGroup: isJidGroup(jid) || isJidStatusBroadcast(jid),
IsGroup: isJidGroup(jid) || isJidBroadcast(jid),
Chat: jid,
Sender: this.me.id,
});
@@ -692,6 +692,12 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
throw new AvailableInPlusVersion();
}
sendLinkCustomPreview(
request: MessageLinkCustomPreviewRequest,
): Promise<any> {
throw new AvailableInPlusVersion();
}
reply(request: MessageReplyRequest) {
throw new Error('Method not implemented.');
}
@@ -920,7 +926,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
jid: key.remoteJid,
messageId: key.id,
reaction: request.reaction,
sender: key.fromMe ? this.me.id : key.participant,
sender: key.fromMe ? this.me.id : key.participant || key.remoteJid,
});
const response = await promisify(this.client.SendReaction)(message);
const data = response.toObject();
+2 -1
View File
@@ -7,7 +7,8 @@ export class PresenceProxy {
constructor() {
this.cache = new NodeCache({
stdTTL: 60 * 60, // 1 hour
stdTTL: 60, // 1 minute
checkperiod: 60, // 1 minute
useClones: false,
});
+46 -18
View File
@@ -10,8 +10,6 @@ import makeWASocket, {
getContentType,
getKeyAuthor,
isJidGroup,
isJidNewsletter,
isJidStatusBroadcast,
isJidUser,
isRealMessage,
jidNormalizedUser,
@@ -38,7 +36,10 @@ import {
LabelAssociationType,
} from '@adiwajshing/baileys/lib/Types/LabelAssociation';
import { MessageUserReceiptUpdate } from '@adiwajshing/baileys/lib/Types/Message';
import { isLidUser } from '@adiwajshing/baileys/lib/WABinary/jid-utils';
import {
isJidBroadcast,
isLidUser,
} from '@adiwajshing/baileys/lib/WABinary/jid-utils';
import { Logger as BaileysLogger } from '@adiwajshing/baileys/node_modules/pino';
import { UnprocessableEntityException } from '@nestjs/common';
import {
@@ -96,6 +97,7 @@ import {
MessageFileRequest,
MessageForwardRequest,
MessageImageRequest,
MessageLinkCustomPreviewRequest,
MessageLinkPreviewRequest,
MessageLocationRequest,
MessagePollRequest,
@@ -823,6 +825,12 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
throw new AvailableInPlusVersion();
}
sendLinkCustomPreview(
request: MessageLinkCustomPreviewRequest,
): Promise<any> {
throw new AvailableInPlusVersion();
}
protected async uploadMedia(
file: RemoteFile | BinaryFile,
type,
@@ -970,13 +978,34 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
async setReaction(request: MessageReactionRequest) {
const key = parseMessageIdSerialized(request.messageId);
const reactionMessage = {
react: {
text: request.reaction,
key: key,
},
};
return this.sock.sendMessage(key.remoteJid, reactionMessage);
if (isNewsletter(key.remoteJid)) {
let serverId = Number(key.id);
if (!serverId) {
const msg = await this.store.getMessageById(key.remoteJid, key.id);
if (msg) {
// @ts-ignore
serverId = Number(msg.key.server_id);
}
}
if (!serverId) {
throw new UnprocessableEntityException(
`Unable to get server id for channel message '${key.id}'`,
);
}
return this.sock.newsletterReactMessage(
key.remoteJid,
serverId.toString(),
request.reaction,
);
} else {
const reactionMessage = {
react: {
text: request.reaction,
key: key,
},
};
return this.sock.sendMessage(key.remoteJid, reactionMessage);
}
}
async setStar(request: MessageStarRequest) {
@@ -1196,12 +1225,6 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
public async fetchContactProfilePicture(id: string) {
const contact = this.ensureSuffix(id);
if (isJidNewsletter(id)) {
return null;
}
if (isJidStatusBroadcast(id)) {
return null;
}
try {
const url = await this.sock.profilePictureUrl(contact, 'image');
return url;
@@ -1943,6 +1966,8 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
proto.Message.ProtocolMessage.Type.EPHEMERAL_SYNC_RESPONSE
)
return;
// Ignore key distribution messages
if (message.message.senderKeyDistributionMessage) return;
if (downloadMedia) {
try {
@@ -2335,7 +2360,7 @@ export function toCusFormat(remoteJid) {
if (isJidGroup(remoteJid)) {
return remoteJid;
}
if (isJidStatusBroadcast(remoteJid)) {
if (isJidBroadcast(remoteJid)) {
return remoteJid;
}
if (isLidUser(remoteJid)) {
@@ -2366,12 +2391,15 @@ export function toJID(chatId) {
if (isJidGroup(chatId)) {
return chatId;
}
if (isJidStatusBroadcast(chatId)) {
if (isJidBroadcast(chatId)) {
return chatId;
}
if (isNewsletter(chatId)) {
return chatId;
}
if (isLidUser(chatId)) {
return chatId;
}
const number = chatId.split('@')[0];
return number + '@s.whatsapp.net';
}
@@ -7,7 +7,6 @@ import makeWASocket, {
} from '@adiwajshing/baileys';
import { Label } from '@adiwajshing/baileys/lib/Types/Label';
import { BadRequestException } from '@nestjs/common';
import { PresenceProxy } from '@waha/core/engines/noweb/PresenceProxy';
import { GetChatMessagesFilter } from '@waha/structures/chats.dto';
import { PaginationParams } from '@waha/structures/pagination.dto';
import { PaginatorInMemory } from '@waha/utils/Paginator';
@@ -27,7 +26,7 @@ export class NowebInMemoryStore implements INowebStore {
constructor() {
this.store = makeInMemoryStore({ logger: logger });
const presences = new PresenceProxy().proxy;
const presences = {};
this.store.presences = presences;
// Adjust inline even handler
this.store.setPresences(presences);
@@ -18,7 +18,6 @@ import {
LabelAssociation,
LabelAssociationType,
} from '@adiwajshing/baileys/lib/Types/LabelAssociation';
import { PresenceProxy } from '@waha/core/engines/noweb/PresenceProxy';
import { IGroupRepository } from '@waha/core/engines/noweb/store/IGroupRepository';
import { ILabelAssociationRepository } from '@waha/core/engines/noweb/store/ILabelAssociationsRepository';
import { ILabelsRepository } from '@waha/core/engines/noweb/store/ILabelsRepository';
@@ -72,7 +71,7 @@ export class NowebPersistentStore implements INowebStore {
this.messagesRepo = storage.getMessagesRepository();
this.labelsRepo = storage.getLabelsRepository();
this.labelAssociationsRepo = storage.getLabelAssociationRepository();
this.presences = new PresenceProxy().proxy;
this.presences = {};
this.lock = new AsyncLock({ maxPending: Infinity });
}
+28
View File
@@ -0,0 +1,28 @@
import {
registerDecorator,
ValidationArguments,
ValidationOptions,
} from 'class-validator';
export function IsFileType(validationOptions?: ValidationOptions) {
return function (object: any, propertyName: string) {
registerDecorator({
name: 'isFileType',
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
validator: {
validate(value: any, _args: ValidationArguments) {
return (
value &&
typeof value === 'object' &&
(typeof value.data === 'string' || typeof value.url === 'string')
);
},
defaultMessage() {
return 'must contain either "data" or "url".';
},
},
});
};
}
+80 -1
View File
@@ -4,8 +4,17 @@ import {
ApiProperty,
getSchemaPath,
} from '@nestjs/swagger';
import { IsFileType } from '@waha/nestjs/validation/IsFileType';
import { GetChatMessagesQuery } from '@waha/structures/chats.dto';
import { IsNotEmpty, IsString } from 'class-validator';
import { plainToInstance, Transform, Type } from 'class-transformer';
import {
IsBoolean,
IsNotEmpty,
IsOptional,
IsString,
IsUrl,
ValidateNested,
} from 'class-validator';
import {
SessionBaseRequest,
@@ -14,6 +23,9 @@ import {
} from './base.dto';
import {
BinaryFile,
FileContent,
FileType,
FileURL,
RemoteFile,
VideoBinaryFile,
VideoRemoteFile,
@@ -162,6 +174,73 @@ export class MessageTextRequest extends ChatRequest {
linkPreviewHighQuality?: boolean = false;
}
@ApiExtraModels(FileURL, FileContent)
export class LinkPreviewData {
@IsNotEmpty()
@IsUrl({
protocols: ['http', 'https'],
require_protocol: true,
require_tld: false,
})
url: string = 'https://github.com/';
@IsString()
title: string = 'Your Title';
@IsString()
description: string = 'Check this out, amazing!';
@IsOptional()
@ValidateNested()
@Transform(
({ value }) => {
if (value?.url) {
return plainToInstance(FileURL, value);
}
if (value?.data) {
return plainToInstance(FileContent, value);
}
return value;
},
{ toClassOnly: true },
)
@IsFileType({ message: 'Image must contain either "data" or "url".' })
@ApiProperty({
oneOf: [
{ $ref: getSchemaPath(FileURL) },
{ $ref: getSchemaPath(FileContent) },
],
example: {
url:
process.env.WHATSAPP_SWAGGER_JPG_EXAMPLE_URL ||
'https://github.com/devlikeapro/waha/raw/core/examples/waha.jpg',
},
})
image?: FileType;
}
export class MessageLinkCustomPreviewRequest extends ChatRequest {
@IsString()
@ApiProperty({
description:
'The text to send. MUST include the URL provided in preview.url',
})
text: string = 'Check this out! https://github.com/';
@IsBoolean()
@IsOptional()
linkPreviewHighQuality?: boolean = true;
@ValidateNested()
@Type(() => LinkPreviewData)
preview: LinkPreviewData;
@ReplyToProperty()
@IsString()
@IsOptional()
reply_to?: string;
}
export class EditMessageRequest {
text: string = 'Hello, world!';
+31
View File
@@ -1,4 +1,5 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsBase64, IsNotEmpty, IsString, IsUrl } from 'class-validator';
export class Base64File {
mimetype: string;
@@ -117,3 +118,33 @@ export class VideoRemoteFile {
})
url: string;
}
export class FileURL {
@IsString()
@IsNotEmpty()
@IsUrl({
protocols: ['http', 'https'],
require_protocol: true,
require_tld: false,
})
@ApiProperty({
example:
process.env.WHATSAPP_SWAGGER_JPG_EXAMPLE_URL ||
'https://github.com/devlikeapro/waha/raw/core/examples/waha.jpg',
})
url?: string;
}
export class FileContent {
@IsString()
@IsNotEmpty()
@IsBase64()
@ApiProperty({
description: 'Base64-encoded data of the file',
example:
'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 Line truncated
})
data?: string;
}
export type FileType = FileURL | FileContent;
+1 -1
View File
@@ -33,7 +33,7 @@ export function getEngineName(): string {
}
export const VERSION: WAHAEnvironment = {
version: '2025.3.2',
version: '2025.4.1',
engine: getEngineName(),
tier: getWAHAVersion(),
browser:
+2 -2
View File
@@ -2,11 +2,11 @@
"waha": {
"gows": {
"repo": "devlikeapro/gows",
"ref": "v1.0.2"
"ref": "v1.0.3"
},
"dashboard": {
"repo": "devlikeapro/dashboard",
"ref": "4dd30223fb0c5bb9bd8f168d0ff47633aa4a3047"
"ref": "0e7e2e7edc715b0c30fff89c10ee2d2fc3402d2d"
}
}
}
+2 -2
View File
@@ -7,7 +7,7 @@ __metadata:
"@adiwajshing/baileys@github:devlikeapro/Baileys#fork-master-channels-6712":
version: 6.7.12
resolution: "@adiwajshing/baileys@https://github.com/devlikeapro/Baileys.git#commit=a6b6fde036f35043326bea8cd7d46644f4222dd6"
resolution: "@adiwajshing/baileys@https://github.com/devlikeapro/Baileys.git#commit=4e51470a1e1f6d56b3e48feabf886b80c0a75bd0"
dependencies:
"@adiwajshing/keyed-db": ^0.2.4
"@hapi/boom": ^9.1.3
@@ -40,7 +40,7 @@ __metadata:
optional: true
sharp:
optional: true
checksum: f463ff68a64860ae729720c06d7759f76444dcecbfc81a24ce1d47b5988b68f9fb3ec5d266b7deed4239ac955a744301aeceb1797c2e452e31a2871ea973a361
checksum: d2d5fe4f4ed2a519fd30c486b079978fac1894b4ad195d337387acc841110f663e4525b89336f8dcccc63e6e7b54f4542e00006e7d42ee196dbce7b7d790b7fc
languageName: node
linkType: hard