Compare commits

..
24 Commits
Author SHA1 Message Date
devlikepro 34a6e773df [core] 2025.2.8
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-02-28 10:22:33 +07:00
devlikepro f0beb03344 [core] Link preview on edit
fix #833
2025-02-28 10:22:33 +07:00
devlikepro 1ff9a81ab3 [core] Fix channels API
fix #832
2025-02-28 10:22:33 +07:00
devlikepro 9b1771c693 [core] 2025.2.7
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-02-27 14:34:23 +07:00
devlikepro 823e604737 [core] GOWS v1.0.0 2025-02-27 14:34:23 +07:00
devlikepro 97a31bdf17 [core] NOWEB - ignore EPHEMERAL_SYNC_RESPONSE 2025-02-27 14:34:22 +07:00
devlikepro ee7a198ae6 [core] GOWS - startTyping/stopTyping 2025-02-27 14:34:22 +07:00
devlikepro f143a51010 [core] group id in picture 2025-02-27 14:34:21 +07:00
devlikepro e483185b2c [core] fields.exclude optional 2025-02-27 14:34:21 +07:00
devlikepro 40e1c75746 [core] GOWS - v1.0.0-dev.1 2025-02-27 14:34:21 +07:00
devlikepro 456bd4693c [core] GOWS - fetch proto from ref with no v 2025-02-27 14:34:21 +07:00
devlikepro 5ac9c0f760 [core] NOWEB - no auto restart 2025-02-27 14:34:20 +07:00
devlikepro f621d84b83 [core] Exclude groups participants
fix #242
2025-02-27 14:34:20 +07:00
devlikepro 0dd001e95a [core] GOWS Groups API 2025-02-27 14:34:19 +07:00
devlikepro 69b5235db9 [core] Groups API - return 200 on POST in not create API 2025-02-27 14:34:19 +07:00
devlikepro a673d63b33 [core] Groups Picture API - Get, Update, Remove
fix #802
2025-02-27 14:34:18 +07:00
devlikepro 36485e10f2 [core] NOWEB add admin only / messages admin settings API
fix #695
fix #808
2025-02-27 14:34:18 +07:00
devlikepro 8a1a062127 [core] Place Channels Search API at the end 2025-02-27 14:34:18 +07:00
devlikepro 5961e0de08 [core] Fix group.participants undefined
fix #810
2025-02-27 14:34:18 +07:00
devlikepro 60bda521a0 [core] Wait until session is running and check if exist after 2025-02-27 14:34:18 +07:00
devlikepro 49f63c0f0d [core] 2025.2.6
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
fix #820
2025-02-21 10:06:27 +07:00
devlikepro 33910aedb8 [core] 2025.2.5
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-02-19 09:20:27 +07:00
devlikepro 3359a81a14 [core] NOWEB add message.revoked event
fix #813
2025-02-19 09:20:26 +07:00
devlikepro e27f850e68 [core] NOWEB Fix body extract from messages
fix #813
2025-02-19 09:20:26 +07:00
19 changed files with 3534 additions and 155 deletions

No files matched your search

+1 -1
View File
@@ -66,7 +66,7 @@ RUN \
elif [ "$ARCH" = "aarch64" ]; then ARCH="arm64"; \
else echo "Unsupported architecture: $ARCH" && exit 1; fi && \
mkdir -p /go/gows/bin && \
wget -O /go/gows/bin/gows https://github.com/${GOWS_GITHUB_REPO}/releases/download/v.${GOWS_SHA}/gows-${ARCH} && \
wget -O /go/gows/bin/gows https://github.com/${GOWS_GITHUB_REPO}/releases/download/${GOWS_SHA}/gows-${ARCH} && \
chmod +x /go/gows/bin/gows
+1 -1
View File
@@ -53,7 +53,7 @@ function cleanDirectory(directory, suffix) {
// Helper function to download files
async function downloadFiles(repo, ref, directory) {
for (const file of PROTO_FILES) {
const url = `https://github.com/${repo}/releases/download/v.${ref}/${file}`;
const url = `https://github.com/${repo}/releases/download/${ref}/${file}`;
const filePath = path.join(directory, file);
try {
const response = await axios.get(url, { responseType: 'arraybuffer' });
+78 -78
View File
@@ -49,84 +49,6 @@ export class ChannelsController {
private channelsInfoService: ChannelsInfoServiceCore,
) {}
@Post('/search/by-view')
@HttpCode(HttpStatus.OK)
@SessionApiParam
@UsePipes(new WAHAValidationPipe())
@ApiOperation({ summary: 'Search for channels (by view)' })
async searchByView(
@WorkingSessionParam session: WhatsappSession,
@Body() request: ChannelSearchByView,
): Promise<ChannelListResult> {
return session.searchChannelsByView(request);
}
@Post('/search/by-text')
@HttpCode(HttpStatus.OK)
@SessionApiParam
@UsePipes(new WAHAValidationPipe())
@ApiOperation({ summary: 'Search for channels (by text)' })
async searchByText(
@WorkingSessionParam session: WhatsappSession,
@Body() request: ChannelSearchByText,
): Promise<ChannelListResult> {
return session.searchChannelsByText(request);
}
@Get('/search/views')
@SessionApiParam
@ApiOperation({ summary: 'Get list of views for channel search' })
getSearchViews(): Promise<ChannelView[]> {
return this.channelsInfoService.getViews();
}
@Get('/search/countries')
@SessionApiParam
@ApiOperation({ summary: 'Get list of countries for channel search' })
getSearchCountries(): Promise<ChannelCountry[]> {
return this.channelsInfoService.getCountries();
}
@Get('/search/categories')
@SessionApiParam
@ApiOperation({ summary: 'Get list of categories for channel search' })
getSearchCategories(): Promise<ChannelCategory[]> {
return this.channelsInfoService.getCategories();
}
@Get(':id/messages/preview')
@SessionApiParam
@UsePipes(new WAHAValidationPipe())
@ApiParam({
name: 'id',
description: 'Channel id or invite code',
required: true,
type: 'string',
schema: {
default: '0029Va4K0PZ5a245NkngBA2M',
},
})
@ApiOperation({
summary: 'Preview channel messages',
description:
'You can use either ' +
'invite code (https://www.whatsapp.com/channel/123) or (123)' +
'OR' +
'Channel ID (123@newsletter).',
})
async previewChannelMessages(
@WorkingSessionParam session: WhatsappSession,
@Param('id') code: string,
@Query() query: PreviewChannelMessages,
): Promise<ChannelMessage[]> {
if (isNewsletter(code)) {
const channel = await session.channelsGetChannel(code);
code = parseChannelInviteLink(channel.invite);
}
const inviteCode = parseChannelInviteLink(code);
return session.previewChannelMessages(inviteCode, query);
}
@Get('')
@SessionApiParam
@ApiOperation({ summary: 'Get list of know channels' })
@@ -179,6 +101,39 @@ export class ChannelsController {
}
}
@Get(':id/messages/preview')
@SessionApiParam
@UsePipes(new WAHAValidationPipe())
@ApiParam({
name: 'id',
description: 'Channel id or invite code',
required: true,
type: 'string',
schema: {
default: '0029Va4K0PZ5a245NkngBA2M',
},
})
@ApiOperation({
summary: 'Preview channel messages',
description:
'You can use either ' +
'invite code (https://www.whatsapp.com/channel/123) or (123)' +
'OR' +
'Channel ID (123@newsletter).',
})
async previewChannelMessages(
@WorkingSessionParam session: WhatsappSession,
@Param('id') code: string,
@Query() query: PreviewChannelMessages,
): Promise<ChannelMessage[]> {
if (isNewsletter(code)) {
const channel = await session.channelsGetChannel(code);
code = parseChannelInviteLink(channel.invite);
}
const inviteCode = parseChannelInviteLink(code);
return session.previewChannelMessages(inviteCode, query);
}
@Post(':id/follow')
@SessionApiParam
@NewsletterIdApiParam
@@ -222,4 +177,49 @@ export class ChannelsController {
): Promise<void> {
return session.channelsUnmuteChannel(id);
}
@Post('/search/by-view')
@HttpCode(HttpStatus.OK)
@SessionApiParam
@UsePipes(new WAHAValidationPipe())
@ApiOperation({ summary: 'Search for channels (by view)' })
async searchByView(
@WorkingSessionParam session: WhatsappSession,
@Body() request: ChannelSearchByView,
): Promise<ChannelListResult> {
return session.searchChannelsByView(request);
}
@Post('/search/by-text')
@HttpCode(HttpStatus.OK)
@SessionApiParam
@UsePipes(new WAHAValidationPipe())
@ApiOperation({ summary: 'Search for channels (by text)' })
async searchByText(
@WorkingSessionParam session: WhatsappSession,
@Body() request: ChannelSearchByText,
): Promise<ChannelListResult> {
return session.searchChannelsByText(request);
}
@Get('/search/views')
@SessionApiParam
@ApiOperation({ summary: 'Get list of views for channel search' })
getSearchViews(): Promise<ChannelView[]> {
return this.channelsInfoService.getViews();
}
@Get('/search/countries')
@SessionApiParam
@ApiOperation({ summary: 'Get list of countries for channel search' })
getSearchCountries(): Promise<ChannelCountry[]> {
return this.channelsInfoService.getCountries();
}
@Get('/search/categories')
@SessionApiParam
@ApiOperation({ summary: 'Get list of categories for channel search' })
getSearchCategories(): Promise<ChannelCategory[]> {
return this.channelsInfoService.getCategories();
}
}
+116 -56
View File
@@ -3,6 +3,8 @@ import {
Controller,
Delete,
Get,
HttpCode,
HttpStatus,
Param,
Post,
Put,
@@ -16,12 +18,20 @@ import {
SessionApiParam,
WorkingSessionParam,
} from '@waha/nestjs/params/SessionApiParam';
import { Result } from '@waha/structures/base.dto';
import {
ChatPictureQuery,
ChatPictureResponse,
} from '@waha/structures/chats.dto';
import { ProfilePictureRequest } from '@waha/structures/profile.dto';
import { SessionManager } from '../core/abc/manager.abc';
import { parseGroupInviteLink, WhatsappSession } from '../core/abc/session.abc';
import {
CreateGroupRequest,
DescriptionRequest,
GroupField,
GroupsListFields,
GroupsPaginationParams,
JoinGroupRequest,
JoinGroupResponse,
@@ -58,6 +68,7 @@ export class GroupsController {
}
@Post('join')
@HttpCode(HttpStatus.OK)
@SessionApiParam
@ApiOperation({ summary: 'Join group via code' })
async joinGroup(
@@ -73,14 +84,18 @@ export class GroupsController {
@SessionApiParam
@ApiOperation({ summary: 'Get all groups.' })
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
getGroups(
async getGroups(
@WorkingSessionParam session: WhatsappSession,
@Query() pagination: GroupsPaginationParams,
@Query() fields: GroupsListFields,
) {
return session.getGroups(pagination);
let groups: any = await session.getGroups(pagination);
groups = session.filterGroupsFields(groups, fields);
return groups;
}
@Post('refresh')
@HttpCode(HttpStatus.OK)
@SessionApiParam
@ApiOperation({ summary: 'Refresh groups from the server.' })
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
@@ -99,6 +114,100 @@ export class GroupsController {
return session.getGroup(id);
}
@Delete(':id')
@SessionApiParam
@GroupIdApiParam
@ApiOperation({ summary: 'Delete the group.' })
deleteGroup(
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
) {
return session.deleteGroup(id);
}
@Post(':id/leave')
@HttpCode(HttpStatus.OK)
@SessionApiParam
@GroupIdApiParam
@ApiOperation({ summary: 'Leave the group.' })
leaveGroup(
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
) {
return session.leaveGroup(id);
}
@Get(':id/picture')
@SessionApiParam
@GroupIdApiParam
@ApiOperation({ summary: 'Get group picture' })
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
async getChatPicture(
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
@Query() query: ChatPictureQuery,
): Promise<ChatPictureResponse> {
const url = await session.getContactProfilePicture(id, query.refresh);
return { url: url };
}
@Put(':id/picture')
@SessionApiParam
@GroupIdApiParam
@ApiOperation({ summary: 'Set group picture' })
async setPicture(
@Param('id') id: string,
@WorkingSessionParam session: WhatsappSession,
@Body() request: ProfilePictureRequest,
): Promise<Result> {
const success = await session.updateGroupPicture(id, request.file);
return { success: success ?? true };
}
@Delete(':id/picture')
@SessionApiParam
@GroupIdApiParam
@ApiOperation({ summary: 'Delete group picture' })
async deletePicture(
@Param('id') id: string,
@WorkingSessionParam session: WhatsappSession,
): Promise<Result> {
const success = await session.updateGroupPicture(id, null);
return { success: success || true };
}
@Put(':id/description')
@ApiOperation({
summary: 'Updates the group description.',
description:
'Returns "true" if the subject was properly updated. This can return "false" if the user does not have the necessary permissions.',
})
@SessionApiParam
@GroupIdApiParam
setDescription(
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
@Body() request: DescriptionRequest,
) {
return session.setDescription(id, request.description);
}
@Put(':id/subject')
@SessionApiParam
@GroupIdApiParam
@ApiOperation({
summary: 'Updates the group subject',
description:
'Returns "true" if the subject was properly updated. This can return "false" if the user does not have the necessary permissions.',
})
setSubject(
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
@Body() request: SubjectRequest,
) {
return session.setSubject(id, request.subject);
}
@Put(':id/settings/security/info-admin-only')
@SessionApiParam
@GroupIdApiParam
@@ -160,60 +269,6 @@ export class GroupsController {
return session.getMessagesAdminsOnly(id);
}
@Delete(':id')
@SessionApiParam
@GroupIdApiParam
@ApiOperation({ summary: 'Delete the group.' })
deleteGroup(
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
) {
return session.deleteGroup(id);
}
@Post(':id/leave')
@SessionApiParam
@GroupIdApiParam
@ApiOperation({ summary: 'Leave the group.' })
leaveGroup(
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
) {
return session.leaveGroup(id);
}
@Put(':id/description')
@ApiOperation({
summary: 'Updates the group description.',
description:
'Returns "true" if the subject was properly updated. This can return "false" if the user does not have the necessary permissions.',
})
@SessionApiParam
@GroupIdApiParam
setDescription(
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
@Body() request: DescriptionRequest,
) {
return session.setDescription(id, request.description);
}
@Put(':id/subject')
@SessionApiParam
@GroupIdApiParam
@ApiOperation({
summary: 'Updates the group subject',
description:
'Returns "true" if the subject was properly updated. This can return "false" if the user does not have the necessary permissions.',
})
setSubject(
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
@Body() request: SubjectRequest,
) {
return session.setSubject(id, request.subject);
}
@Get(':id/invite-code')
@SessionApiParam
@GroupIdApiParam
@@ -226,6 +281,7 @@ export class GroupsController {
}
@Post(':id/invite-code/revoke')
@HttpCode(HttpStatus.OK)
@SessionApiParam
@GroupIdApiParam
@ApiOperation({
@@ -251,6 +307,7 @@ export class GroupsController {
}
@Post(':id/participants/add')
@HttpCode(HttpStatus.OK)
@SessionApiParam
@GroupIdApiParam
@ApiOperation({ summary: 'Add participants' })
@@ -263,6 +320,7 @@ export class GroupsController {
}
@Post(':id/participants/remove')
@HttpCode(HttpStatus.OK)
@SessionApiParam
@GroupIdApiParam
@ApiOperation({
@@ -277,6 +335,7 @@ export class GroupsController {
}
@Post(':id/admin/promote')
@HttpCode(HttpStatus.OK)
@SessionApiParam
@GroupIdApiParam
@ApiOperation({ summary: 'Promote participants to admin users.' })
@@ -289,6 +348,7 @@ export class GroupsController {
}
@Post(':id/admin/demote')
@HttpCode(HttpStatus.OK)
@SessionApiParam
@GroupIdApiParam
@ApiOperation({ summary: 'Demotes participants to regular users.' })
+28 -1
View File
@@ -46,8 +46,10 @@ export abstract class SessionManager
protected sessionWorkerRepository: ISessionWorkerRepository;
private lock: any;
WAIT_SESSION_RUNNING_INTERVAL = 500;
WAIT_SESSION_RUNNING_TIMEOUT = 5_000;
WAIT_STATUS_INTERVAL = 500;
WAIT_STATUS_TIMEOUT = 5_000;
WAIT_STATUS_TIMEOUT = 10_000;
LOCK_TIMEOUT = 10_000;
protected constructor(
@@ -148,6 +150,31 @@ export abstract class SessionManager
sessionName: string,
expected: WAHASessionStatus[],
): Promise<WhatsappSession> {
const running = await waitUntil(
async () => this.isRunning(sessionName),
this.WAIT_SESSION_RUNNING_INTERVAL,
this.WAIT_SESSION_RUNNING_TIMEOUT,
);
if (!running) {
const exists = await this.exists(sessionName);
if (!exists) {
throw new UnprocessableEntityException({
error: `Session "${sessionName}" does not exist`,
session: sessionName,
});
}
const msg = {
error:
'Session status is not as expected. Try again later or restart the session',
session: sessionName,
status: 'STOPPED',
expected: expected,
};
throw new UnprocessableEntityException(msg);
}
const session = this.getSession(sessionName);
const valid = await waitUntil(
async () => expected.includes(session.status),
+54 -3
View File
@@ -71,6 +71,8 @@ import {
} from '../../structures/enums.dto';
import {
CreateGroupRequest,
GroupField,
GroupsListFields,
GroupsPaginationParams,
ParticipantsRequest,
SettingsSecurityChangeInfo,
@@ -360,10 +362,10 @@ export abstract class WhatsappSession {
// Refresh profile picture after update
setTimeout(() => {
this.logger.info('Refreshing my profile picture after update...');
this.logger.debug('Refreshing my profile picture after update...');
this.refreshMyProfilePicture()
.then(() => {
this.logger.info('Refreshed my profile picture after update');
this.logger.debug('Refreshed my profile picture after update');
})
.catch((err) => {
this.logger.error('Error refreshing my profile picture after update');
@@ -621,10 +623,22 @@ export abstract class WhatsappSession {
throw new NotImplementedByEngineError();
}
public getGroups(pagination: PaginationParams) {
public getGroups(pagination: PaginationParams): Promise<any> {
throw new NotImplementedByEngineError();
}
public filterGroupsFields(data: any, fields: GroupsListFields) {
const groups: any[] = Array.isArray(data) ? data : Object.values(data);
if (fields.exclude?.includes(GroupField.PARTICIPANTS)) {
groups.forEach((group) => this.removeGroupsFieldParticipant(group));
}
return data;
}
protected removeGroupsFieldParticipant(group: any) {
return;
}
public refreshGroups(): Promise<boolean> {
throw new NotImplementedByEngineError();
}
@@ -661,6 +675,43 @@ export abstract class WhatsappSession {
throw new NotImplementedByEngineError();
}
public async updateGroupPicture(
id: string,
file: BinaryFile | RemoteFile | null,
): Promise<boolean> {
if (file) {
await this.setGroupPicture(id, file);
} else {
await this.deleteGroupPicture(id);
}
// Refresh picture after update
setTimeout(() => {
this.logger.debug('Refreshing group profile picture after update...');
this.refreshProfilePicture(id)
.then(() => {
this.logger.debug('Refreshed group profile picture after update');
})
.catch((err) => {
this.logger.error('Error refreshing my profile picture after update');
this.logger.error(err, err.stack);
});
}, 3_000);
return true;
}
protected setGroupPicture(
id: string,
file: BinaryFile | RemoteFile,
): Promise<boolean> {
throw new NotImplementedByEngineError();
}
protected deleteGroupPicture(id: string): Promise<boolean> {
throw new NotImplementedByEngineError();
}
public setSubject(id, description) {
throw new NotImplementedByEngineError();
}
File diff suppressed because it is too large. Load diff
+258
View File
@@ -37,6 +37,17 @@ function deserialize_messages_CheckPhonesResponse(buffer_arg) {
return gows_pb.CheckPhonesResponse.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_CreateGroupRequest(arg) {
if (!(arg instanceof gows_pb.CreateGroupRequest)) {
throw new Error('Expected argument of type messages.CreateGroupRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_CreateGroupRequest(buffer_arg) {
return gows_pb.CreateGroupRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_CreateNewsletterRequest(arg) {
if (!(arg instanceof gows_pb.CreateNewsletterRequest)) {
throw new Error('Expected argument of type messages.CreateNewsletterRequest');
@@ -147,6 +158,50 @@ function deserialize_messages_GetNewsletterMessagesByInviteRequest(buffer_arg) {
return gows_pb.GetNewsletterMessagesByInviteRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_GroupCodeRequest(arg) {
if (!(arg instanceof gows_pb.GroupCodeRequest)) {
throw new Error('Expected argument of type messages.GroupCodeRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_GroupCodeRequest(buffer_arg) {
return gows_pb.GroupCodeRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_JidBoolRequest(arg) {
if (!(arg instanceof gows_pb.JidBoolRequest)) {
throw new Error('Expected argument of type messages.JidBoolRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_JidBoolRequest(buffer_arg) {
return gows_pb.JidBoolRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_JidRequest(arg) {
if (!(arg instanceof gows_pb.JidRequest)) {
throw new Error('Expected argument of type messages.JidRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_JidRequest(buffer_arg) {
return gows_pb.JidRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_JidStringRequest(arg) {
if (!(arg instanceof gows_pb.JidStringRequest)) {
throw new Error('Expected argument of type messages.JidStringRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_JidStringRequest(buffer_arg) {
return gows_pb.JidStringRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_Json(arg) {
if (!(arg instanceof gows_pb.Json)) {
throw new Error('Expected argument of type messages.Json');
@@ -290,6 +345,17 @@ function deserialize_messages_NewsletterToggleMuteRequest(buffer_arg) {
return gows_pb.NewsletterToggleMuteRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_OptionalString(arg) {
if (!(arg instanceof gows_pb.OptionalString)) {
throw new Error('Expected argument of type messages.OptionalString');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_OptionalString(buffer_arg) {
return gows_pb.OptionalString.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_PairCodeRequest(arg) {
if (!(arg instanceof gows_pb.PairCodeRequest)) {
throw new Error('Expected argument of type messages.PairCodeRequest');
@@ -411,6 +477,17 @@ function deserialize_messages_SessionStateResponse(buffer_arg) {
return gows_pb.SessionStateResponse.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_SetPictureRequest(arg) {
if (!(arg instanceof gows_pb.SetPictureRequest)) {
throw new Error('Expected argument of type messages.SetPictureRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_SetPictureRequest(buffer_arg) {
return gows_pb.SetPictureRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_SetProfilePictureRequest(arg) {
if (!(arg instanceof gows_pb.SetProfilePictureRequest)) {
throw new Error('Expected argument of type messages.SetProfilePictureRequest');
@@ -444,6 +521,17 @@ function deserialize_messages_SubscribePresenceRequest(buffer_arg) {
return gows_pb.SubscribePresenceRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_UpdateParticipantsRequest(arg) {
if (!(arg instanceof gows_pb.UpdateParticipantsRequest)) {
throw new Error('Expected argument of type messages.UpdateParticipantsRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_UpdateParticipantsRequest(buffer_arg) {
return gows_pb.UpdateParticipantsRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
//
// Events
@@ -559,6 +647,176 @@ setProfileName: {
responseDeserialize: deserialize_messages_Empty,
},
//
// Groups
//
fetchGroups: {
path: '/messages.MessageService/FetchGroups',
requestStream: false,
responseStream: false,
requestType: gows_pb.Session,
responseType: gows_pb.Empty,
requestSerialize: serialize_messages_Session,
requestDeserialize: deserialize_messages_Session,
responseSerialize: serialize_messages_Empty,
responseDeserialize: deserialize_messages_Empty,
},
getGroups: {
path: '/messages.MessageService/GetGroups',
requestStream: false,
responseStream: false,
requestType: gows_pb.Session,
responseType: gows_pb.JsonList,
requestSerialize: serialize_messages_Session,
requestDeserialize: deserialize_messages_Session,
responseSerialize: serialize_messages_JsonList,
responseDeserialize: deserialize_messages_JsonList,
},
getGroupInfo: {
path: '/messages.MessageService/GetGroupInfo',
requestStream: false,
responseStream: false,
requestType: gows_pb.JidRequest,
responseType: gows_pb.Json,
requestSerialize: serialize_messages_JidRequest,
requestDeserialize: deserialize_messages_JidRequest,
responseSerialize: serialize_messages_Json,
responseDeserialize: deserialize_messages_Json,
},
createGroup: {
path: '/messages.MessageService/CreateGroup',
requestStream: false,
responseStream: false,
requestType: gows_pb.CreateGroupRequest,
responseType: gows_pb.Json,
requestSerialize: serialize_messages_CreateGroupRequest,
requestDeserialize: deserialize_messages_CreateGroupRequest,
responseSerialize: serialize_messages_Json,
responseDeserialize: deserialize_messages_Json,
},
leaveGroup: {
path: '/messages.MessageService/LeaveGroup',
requestStream: false,
responseStream: false,
requestType: gows_pb.JidRequest,
responseType: gows_pb.Empty,
requestSerialize: serialize_messages_JidRequest,
requestDeserialize: deserialize_messages_JidRequest,
responseSerialize: serialize_messages_Empty,
responseDeserialize: deserialize_messages_Empty,
},
getGroupInviteLink: {
path: '/messages.MessageService/GetGroupInviteLink',
requestStream: false,
responseStream: false,
requestType: gows_pb.JidRequest,
responseType: gows_pb.OptionalString,
requestSerialize: serialize_messages_JidRequest,
requestDeserialize: deserialize_messages_JidRequest,
responseSerialize: serialize_messages_OptionalString,
responseDeserialize: deserialize_messages_OptionalString,
},
revokeGroupInviteLink: {
path: '/messages.MessageService/RevokeGroupInviteLink',
requestStream: false,
responseStream: false,
requestType: gows_pb.JidRequest,
responseType: gows_pb.OptionalString,
requestSerialize: serialize_messages_JidRequest,
requestDeserialize: deserialize_messages_JidRequest,
responseSerialize: serialize_messages_OptionalString,
responseDeserialize: deserialize_messages_OptionalString,
},
getGroupInfoFromLink: {
path: '/messages.MessageService/GetGroupInfoFromLink',
requestStream: false,
responseStream: false,
requestType: gows_pb.GroupCodeRequest,
responseType: gows_pb.Json,
requestSerialize: serialize_messages_GroupCodeRequest,
requestDeserialize: deserialize_messages_GroupCodeRequest,
responseSerialize: serialize_messages_Json,
responseDeserialize: deserialize_messages_Json,
},
joinGroupWithLink: {
path: '/messages.MessageService/JoinGroupWithLink',
requestStream: false,
responseStream: false,
requestType: gows_pb.GroupCodeRequest,
responseType: gows_pb.Json,
requestSerialize: serialize_messages_GroupCodeRequest,
requestDeserialize: deserialize_messages_GroupCodeRequest,
responseSerialize: serialize_messages_Json,
responseDeserialize: deserialize_messages_Json,
},
setGroupName: {
path: '/messages.MessageService/SetGroupName',
requestStream: false,
responseStream: false,
requestType: gows_pb.JidStringRequest,
responseType: gows_pb.Empty,
requestSerialize: serialize_messages_JidStringRequest,
requestDeserialize: deserialize_messages_JidStringRequest,
responseSerialize: serialize_messages_Empty,
responseDeserialize: deserialize_messages_Empty,
},
setGroupDescription: {
path: '/messages.MessageService/SetGroupDescription',
requestStream: false,
responseStream: false,
requestType: gows_pb.JidStringRequest,
responseType: gows_pb.Empty,
requestSerialize: serialize_messages_JidStringRequest,
requestDeserialize: deserialize_messages_JidStringRequest,
responseSerialize: serialize_messages_Empty,
responseDeserialize: deserialize_messages_Empty,
},
setGroupPicture: {
path: '/messages.MessageService/SetGroupPicture',
requestStream: false,
responseStream: false,
requestType: gows_pb.SetPictureRequest,
responseType: gows_pb.Empty,
requestSerialize: serialize_messages_SetPictureRequest,
requestDeserialize: deserialize_messages_SetPictureRequest,
responseSerialize: serialize_messages_Empty,
responseDeserialize: deserialize_messages_Empty,
},
setGroupLocked: {
path: '/messages.MessageService/SetGroupLocked',
requestStream: false,
responseStream: false,
requestType: gows_pb.JidBoolRequest,
responseType: gows_pb.Empty,
requestSerialize: serialize_messages_JidBoolRequest,
requestDeserialize: deserialize_messages_JidBoolRequest,
responseSerialize: serialize_messages_Empty,
responseDeserialize: deserialize_messages_Empty,
},
// change info only by admins
setGroupAnnounce: {
path: '/messages.MessageService/SetGroupAnnounce',
requestStream: false,
responseStream: false,
requestType: gows_pb.JidBoolRequest,
responseType: gows_pb.Empty,
requestSerialize: serialize_messages_JidBoolRequest,
requestDeserialize: deserialize_messages_JidBoolRequest,
responseSerialize: serialize_messages_Empty,
responseDeserialize: deserialize_messages_Empty,
},
// send messages only by admins
updateGroupParticipants: {
path: '/messages.MessageService/UpdateGroupParticipants',
requestStream: false,
responseStream: false,
requestType: gows_pb.UpdateParticipantsRequest,
responseType: gows_pb.JsonList,
requestSerialize: serialize_messages_UpdateParticipantsRequest,
requestDeserialize: deserialize_messages_UpdateParticipantsRequest,
responseSerialize: serialize_messages_JsonList,
responseDeserialize: deserialize_messages_JsonList,
},
//
// Actions
//
sendMessage: {
File diff suppressed because it is too large. Load diff
+210 -3
View File
@@ -21,7 +21,10 @@ import {
toCusFormat,
toJID,
} from '@waha/core/engines/noweb/session.noweb.core';
import { AvailableInPlusVersion } from '@waha/core/exceptions';
import {
AvailableInPlusVersion,
NotImplementedByEngineError,
} from '@waha/core/exceptions';
import { IMediaEngineProcessor } from '@waha/core/media/IMediaEngineProcessor';
import { QR } from '@waha/core/QR';
import {
@@ -105,7 +108,15 @@ import {
} from '@waha/structures/chats.dto';
import { ContactQuery } from '@waha/structures/contacts.dto';
import { BinaryFile, RemoteFile } from '@waha/structures/files.dto';
import {
CreateGroupRequest,
GroupSortField,
Participant,
ParticipantsRequest,
SettingsSecurityChangeInfo,
} from '@waha/structures/groups.dto';
import { PaginationParams, SortOrder } from '@waha/structures/pagination.dto';
import { PaginatorInMemory } from '@waha/utils/Paginator';
enum WhatsMeowEvent {
CONNECTED = 'gows.ConnectedEventData',
@@ -620,11 +631,207 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
}
startTyping(chat: ChatRequest) {
throw new Error('Method not implemented.');
return this.setPresence(WAHAPresenceStatus.TYPING, chat.chatId);
}
stopTyping(chat: ChatRequest) {
throw new Error('Method not implemented.');
return this.setPresence(WAHAPresenceStatus.PAUSED, chat.chatId);
}
/**
* Group methods
*/
public async createGroup(request: CreateGroupRequest) {
const req = new messages.CreateGroupRequest({
session: this.session,
name: request.name,
participants: request.participants.map((p) => toJID(p.id)),
});
const response = await promisify(this.client.CreateGroup)(req);
const data = parseJson(response);
return data;
}
public async joinInfoGroup(code: string): Promise<any> {
const req = new messages.GroupCodeRequest({
session: this.session,
code: code,
});
const response = await promisify(this.client.GetGroupInfoFromLink)(req);
const data = parseJson(response);
return data;
}
public async joinGroup(code: string): Promise<string> {
const req = new messages.GroupCodeRequest({
session: this.session,
code: code,
});
const response = await promisify(this.client.JoinGroupWithLink)(req);
const data = parseJson(response);
return data.jid;
}
public async getGroups(pagination: PaginationParams) {
const req = this.session;
const response = await promisify(this.client.GetGroups)(req);
const data = parseJsonList(response);
switch (pagination.sortBy) {
case GroupSortField.ID:
pagination.sortBy = 'JID';
break;
case GroupSortField.SUBJECT:
pagination.sortBy = 'Name';
break;
}
const paginator = new PaginatorInMemory(pagination);
return paginator.apply(data);
}
protected removeGroupsFieldParticipant(group: any) {
delete group.Participants;
}
public async refreshGroups(): Promise<boolean> {
const req = this.session;
await promisify(this.client.FetchGroups)(req);
return true;
}
public async getGroup(id) {
const req = new messages.JidRequest({
session: this.session,
jid: id,
});
const response = await promisify(this.client.GetGroupInfo)(req);
const data = parseJson(response);
return data;
}
public async getInfoAdminsOnly(id): Promise<SettingsSecurityChangeInfo> {
const group = await this.getGroup(id);
return {
adminsOnly: group.IsLocked,
};
}
public async setInfoAdminsOnly(id, value) {
const req = new messages.JidBoolRequest({
session: this.session,
jid: id,
value: value,
});
await promisify(this.client.SetGroupLocked)(req);
return;
}
public async getMessagesAdminsOnly(id): Promise<SettingsSecurityChangeInfo> {
const group = await this.getGroup(id);
return {
adminsOnly: group.IsAnnounce,
};
}
public async setMessagesAdminsOnly(id, value) {
const req = new messages.JidBoolRequest({
session: this.session,
jid: id,
value: value,
});
await promisify(this.client.SetGroupAnnounce)(req);
return;
}
public deleteGroup(id) {
throw new NotImplementedByEngineError();
}
public async leaveGroup(id) {
const req = new messages.JidRequest({
session: this.session,
jid: id,
});
await promisify(this.client.LeaveGroup)(req);
}
public async setDescription(id, description) {
const req = new messages.JidStringRequest({
session: this.session,
jid: id,
value: description,
});
await promisify(this.client.SetGroupDescription)(req);
}
public async setSubject(id, description) {
const req = new messages.JidStringRequest({
session: this.session,
jid: id,
value: description,
});
await promisify(this.client.SetGroupName)(req);
}
public async getInviteCode(id): Promise<string> {
const req = new messages.JidRequest({
session: this.session,
jid: id,
});
const response = await promisify(this.client.GetGroupInviteLink)(req);
const data = response.toObject();
return data.value;
}
public async revokeInviteCode(id): Promise<string> {
const req = new messages.JidRequest({
session: this.session,
jid: id,
});
const response = await promisify(this.client.RevokeGroupInviteLink)(req);
const data = response.toObject();
return data.value;
}
public async getParticipants(id) {
const group = await this.getGroup(id);
return group.Participants;
}
private async updateParticipants(
id: string,
participants: Array<Participant>,
action: messages.ParticipantAction,
): Promise<any> {
const jids = participants.map((p) => toJID(p.id));
const req = new messages.UpdateParticipantsRequest({
session: this.session,
jid: id,
participants: jids,
action: action,
});
const response = await promisify(this.client.UpdateGroupParticipants)(req);
const data = parseJsonList(response);
return data;
}
public addParticipants(id: string, request: ParticipantsRequest) {
const action = messages.ParticipantAction.ADD;
return this.updateParticipants(id, request.participants, action);
}
public removeParticipants(id, request: ParticipantsRequest) {
const action = messages.ParticipantAction.REMOVE;
return this.updateParticipants(id, request.participants, action);
}
public promoteParticipantsToAdmin(id, request: ParticipantsRequest) {
const action = messages.ParticipantAction.PROMOTE;
return this.updateParticipants(id, request.participants, action);
}
public demoteParticipantsToUser(id, request: ParticipantsRequest) {
const action = messages.ParticipantAction.DEMOTE;
return this.updateParticipants(id, request.participants, action);
}
async setReaction(request: MessageReactionRequest) {
+74 -7
View File
@@ -86,6 +86,7 @@ import {
PollVote,
PollVotePayload,
WAMessageAckBody,
WAMessageRevokedBody,
} from '@waha/structures/webhooks.dto';
import { LoggerBuilder } from '@waha/utils/logging';
import { sleep, waitUntil } from '@waha/utils/promiseTimeout';
@@ -142,6 +143,7 @@ import {
import {
CreateGroupRequest,
ParticipantsRequest,
SettingsSecurityChangeInfo,
} from '../../../structures/groups.dto';
import {
WAHAChatPresences,
@@ -383,7 +385,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
this.listenConnectionEvents();
this.subscribeEngineEvents2();
this.listenContactsUpdatePictureProfile();
this.enableAutoRestart();
// this.enableAutoRestart();
}
private enableAutoRestart() {
@@ -720,6 +722,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
text: request.text,
mentions: request.mentions?.map(toJID),
edit: key,
linkPreview: this.getLinkPreview(request),
};
return this.sock.sendMessage(jid, message);
}
@@ -1189,6 +1192,10 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return lodash.keyBy(groups, 'id');
}
protected removeGroupsFieldParticipant(group: any) {
delete group.participants;
}
public async refreshGroups(): Promise<boolean> {
this.store.resetGroupsCache();
await this.store.getGroups({});
@@ -1197,13 +1204,37 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
public async getGroup(id) {
const groups = await this.getGroups({});
return groups[id];
const group = groups[id];
if (!group) {
throw new Error(`Group with id '${id}' not found`);
}
return group;
}
public async deleteGroup(id) {
throw new NotImplementedByEngineError();
}
public async getInfoAdminsOnly(id): Promise<SettingsSecurityChangeInfo> {
const group = await this.getGroup(id);
return { adminsOnly: group.restrict };
}
public async setInfoAdminsOnly(id, value) {
const setting = value ? 'locked' : 'unlocked';
return await this.sock.groupSettingUpdate(id, setting);
}
public async getMessagesAdminsOnly(id): Promise<SettingsSecurityChangeInfo> {
const group = await this.getGroup(id);
return { adminsOnly: group.announce };
}
public async setMessagesAdminsOnly(id, value) {
const setting = value ? 'announcement' : 'not_announcement';
return await this.sock.groupSettingUpdate(id, setting);
}
public async leaveGroup(id) {
return this.sock.groupLeave(id);
}
@@ -1475,6 +1506,24 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
this.events2.get(WAHAEvents.MESSAGE).switch(messagesFromOthers$);
this.events2.get(WAHAEvents.MESSAGE_ANY).switch(messagesFromAll$);
const messagesRevoked$ = messagesUpsert$.pipe(
// @ts-ignore
filter(
(message) =>
message.message.protocolMessage?.type ===
proto.Message.ProtocolMessage.Type.REVOKE,
),
mergeMap(async (message): Promise<WAMessageRevokedBody> => {
const afterMessage = await this.toWAMessage(message);
return {
after: afterMessage,
before: null,
_data: message,
};
}),
);
this.events2.get(WAHAEvents.MESSAGE_REVOKED).switch(messagesRevoked$);
//
// Message Reactions
//
@@ -1732,6 +1781,17 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
if (message.message.reactionMessage) return;
// Ignore poll votes, we have dedicated handler for that
if (message.message.pollUpdateMessage) return;
// Ignore revoke, we have a dedicated handler for that
if (
message.message.protocolMessage?.type ===
proto.Message.ProtocolMessage.Type.REVOKE
)
return;
if (
message.message.protocolMessage?.type ===
proto.Message.ProtocolMessage.Type.EPHEMERAL_SYNC_RESPONSE
)
return;
if (downloadMedia) {
try {
@@ -1764,7 +1824,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
timestamp: ensureNumber(message.messageTimestamp),
from: toCusFormat(fromToParticipant.from),
fromMe: message.key.fromMe,
body: body,
body: body || null,
to: toCusFormat(fromToParticipant.to),
participant: toCusFormat(fromToParticipant.participant),
// Media
@@ -1786,21 +1846,28 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
if (!message) {
return null;
}
let body = message.conversation;
const content = extractMessageContent(message);
if (!content) {
return null;
}
let body = content.conversation || null;
if (!body) {
// Some of the messages have no conversation, but instead have text in extendedTextMessage
// https://github.com/devlikeapro/waha/issues/90
body = message.extendedTextMessage?.text;
body = content.extendedTextMessage?.text;
}
if (!body) {
// Populate from caption
const mediaContent = extractMediaContent(message);
const mediaContent = extractMediaContent(content);
// @ts-ignore - AudioMessage doesn't have caption field
body = mediaContent?.caption;
}
// Response for buttons
if (!body) {
body = message.templateButtonReplyMessage?.selectedDisplayText;
body = content.templateButtonReplyMessage?.selectedDisplayText;
}
if (!body) {
body = content.buttonsResponseMessage?.selectedDisplayText;
}
return body;
}
@@ -269,6 +269,9 @@ export class NowebPersistentStore implements INowebStore {
if (!group) {
group = { id: id, participants: [] } as GroupMetadata;
}
if (!group.participants) {
group.participants = [];
}
const participantsById = new DefaultMap<string, GroupParticipant>((key) => {
return { id: key, admin: null } as GroupParticipant;
+9 -1
View File
@@ -510,6 +510,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
const options = {
// It's fine to sent just ids instead of Contact object
mentions: request.mentions as unknown as string[],
linkPreview: request.linkPreview,
};
return message.edit(request.text, options);
}
@@ -894,6 +895,13 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
return paginator.apply(groups);
}
protected removeGroupsFieldParticipant(group: any) {
delete group.groupMetadata?.participants;
delete group.groupMetadata?.pendingParticipants;
delete group.groupMetadata?.pastParticipants;
delete group.groupMetadata?.membershipApprovalRequests;
}
public async refreshGroups(): Promise<boolean> {
return true;
}
@@ -1040,7 +1048,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
// Exclude GUEST, browser saves the data
// when we search channels or getting messages
channels = channels.filter((channel) => channel.role === 'GUEST');
channels = channels.filter((channel) => channel.role !== 'GUEST');
const promises = channels.map(async (channel) =>
this.whatsapp.getProfilePicUrl(channel.id),
+2
View File
@@ -165,6 +165,8 @@ export class EditMessageRequest {
@ApiHideProperty()
mentions?: string[];
linkPreview?: boolean = true;
}
export class MessageReplyRequest extends MessageTextRequest {
+17
View File
@@ -74,6 +74,23 @@ export class JoinGroupResponse {
id: string;
}
export enum GroupField {
NONE = '',
PARTICIPANTS = 'participants',
}
export class GroupsListFields {
@IsOptional()
@ApiProperty({
description: 'Exclude fields',
enum: GroupField,
isArray: true,
required: false,
})
@IsEnum(GroupField, { each: true })
exclude: string[];
}
export enum GroupSortField {
ID = 'id',
SUBJECT = 'subject',
+1
View File
@@ -91,6 +91,7 @@ export class PollVotePayload {
export class WAMessageRevokedBody {
after?: WAMessage;
before?: WAMessage;
_data?: any;
}
export class WASessionStatusBody {
+1 -1
View File
@@ -33,7 +33,7 @@ export function getEngineName(): string {
}
export const VERSION: WAHAEnvironment = {
version: '2025.2.4',
version: '2025.2.8',
engine: getEngineName(),
tier: getWAHAVersion(),
browser: getBrowserExecutablePath(),
+1 -1
View File
@@ -2,7 +2,7 @@
"waha": {
"gows": {
"repo": "devlikeapro/gows",
"ref": "8b31d6b88166e0962ca47a3dfbd256a42f09dbc1"
"ref": "v1.0.0"
},
"dashboard": {
"repo": "devlikeapro/dashboard",
+2 -2
View File
@@ -12931,7 +12931,7 @@ __metadata:
"whatsapp-web.js@github:devlikeapro/whatsapp-web.js#fork-main-channels":
version: 1.26.0
resolution: "whatsapp-web.js@https://github.com/devlikeapro/whatsapp-web.js.git#commit=5b4dd6dec206905e3aaa7b0461d7c6d4cc3126a8"
resolution: "whatsapp-web.js@https://github.com/devlikeapro/whatsapp-web.js.git#commit=bb4525a80667d83122108a03d936e41cefd179a4"
dependencies:
"@pedroslopez/moduleraid": ^5.0.2
archiver: ^5.3.1
@@ -12949,7 +12949,7 @@ __metadata:
optional: true
unzipper:
optional: true
checksum: 441e54f450ed9ea5d5756c1ca4fd592e498ad79247d2c6508aee2a5c1b2608a9eb3f6360234f2f0ce563edb187a3d453d41773e745da9bebf657f575b9fa66b4
checksum: a06d30c9f52a1ff0268e4e98e3c0d80112a5dc8a9445d0df460fac3cbe0168b6f1c5ad494518b1e9a98acb54da8c46a2f6644e9f41b1b2987771fac101747c55
languageName: node
linkType: hard