Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
34a6e773df | ||
|
|
f0beb03344 | ||
|
|
1ff9a81ab3 | ||
|
|
9b1771c693 | ||
|
|
823e604737 | ||
|
|
97a31bdf17 | ||
|
|
ee7a198ae6 | ||
|
|
f143a51010 | ||
|
|
e483185b2c | ||
|
|
40e1c75746 | ||
|
|
456bd4693c | ||
|
|
5ac9c0f760 | ||
|
|
f621d84b83 | ||
|
|
0dd001e95a | ||
|
|
69b5235db9 | ||
|
|
a673d63b33 | ||
|
|
36485e10f2 | ||
|
|
8a1a062127 | ||
|
|
5961e0de08 | ||
|
|
60bda521a0 | ||
|
|
49f63c0f0d | ||
|
|
33910aedb8 | ||
|
|
3359a81a14 | ||
|
|
e27f850e68 | ||
|
|
07340b8f53 | ||
|
|
2f9dd7faa8 | ||
|
|
3f81fd4a87 | ||
|
|
4dd961fc66 | ||
|
|
0bd70bfb32 | ||
|
|
3ab845d2d6 |
No files matched your search
+1
-1
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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' });
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
+119
-61
@@ -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 { WhatsappSession } from '../core/abc/session.abc';
|
||||
import { parseGroupInviteLink, WhatsappSession } from '../core/abc/session.abc';
|
||||
import {
|
||||
CreateGroupRequest,
|
||||
DescriptionRequest,
|
||||
GroupField,
|
||||
GroupsListFields,
|
||||
GroupsPaginationParams,
|
||||
JoinGroupRequest,
|
||||
JoinGroupResponse,
|
||||
@@ -53,20 +63,19 @@ export class GroupsController {
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
@Query() query: JoinGroupRequest,
|
||||
): Promise<any> {
|
||||
// https://chat.whatsapp.com/123 => 123
|
||||
const code = query.code.split('/').pop();
|
||||
const code = parseGroupInviteLink(query.code);
|
||||
return session.joinInfoGroup(code);
|
||||
}
|
||||
|
||||
@Post('join')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Join group via code' })
|
||||
async joinGroup(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
@Body() request: JoinGroupRequest,
|
||||
): Promise<JoinGroupResponse> {
|
||||
// https://chat.whatsapp.com/123 => 123
|
||||
const code = request.code.split('/').pop();
|
||||
const code = parseGroupInviteLink(request.code);
|
||||
const id = await session.joinGroup(code);
|
||||
return { id: id };
|
||||
}
|
||||
@@ -75,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 }))
|
||||
@@ -101,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
|
||||
@@ -162,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
|
||||
@@ -228,6 +281,7 @@ export class GroupsController {
|
||||
}
|
||||
|
||||
@Post(':id/invite-code/revoke')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@SessionApiParam
|
||||
@GroupIdApiParam
|
||||
@ApiOperation({
|
||||
@@ -253,6 +307,7 @@ export class GroupsController {
|
||||
}
|
||||
|
||||
@Post(':id/participants/add')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@SessionApiParam
|
||||
@GroupIdApiParam
|
||||
@ApiOperation({ summary: 'Add participants' })
|
||||
@@ -265,6 +320,7 @@ export class GroupsController {
|
||||
}
|
||||
|
||||
@Post(':id/participants/remove')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@SessionApiParam
|
||||
@GroupIdApiParam
|
||||
@ApiOperation({
|
||||
@@ -279,6 +335,7 @@ export class GroupsController {
|
||||
}
|
||||
|
||||
@Post(':id/admin/promote')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@SessionApiParam
|
||||
@GroupIdApiParam
|
||||
@ApiOperation({ summary: 'Promote participants to admin users.' })
|
||||
@@ -291,6 +348,7 @@ export class GroupsController {
|
||||
}
|
||||
|
||||
@Post(':id/admin/demote')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@SessionApiParam
|
||||
@GroupIdApiParam
|
||||
@ApiOperation({ summary: 'Demotes participants to regular users.' })
|
||||
|
||||
@@ -138,6 +138,7 @@ export class SwaggerConfiguratorCore {
|
||||
webhooks[event] = {
|
||||
post: {
|
||||
summary: eventMetadata.description,
|
||||
deprecated: eventMetadata.deprecated || false,
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -839,6 +890,18 @@ export function isNewsletter(jid: string) {
|
||||
return jid.endsWith('@newsletter');
|
||||
}
|
||||
|
||||
export function getGroupInviteLink(code: string) {
|
||||
if (code.startsWith('https://')) {
|
||||
return code;
|
||||
}
|
||||
return `https://chat.whatsapp.com/${code}`;
|
||||
}
|
||||
|
||||
export function parseGroupInviteLink(link: string) {
|
||||
// https://chat.whatsapp.com/123 => 123
|
||||
return link.split('/').pop();
|
||||
}
|
||||
|
||||
export function getChannelInviteLink(code: string) {
|
||||
return `https://whatsapp.com/channel/${code}`;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
import {
|
||||
GOWSGroupParticipant,
|
||||
GroupInfoEvent,
|
||||
GroupInfoFull,
|
||||
JoinedGroupEvent,
|
||||
} from '@waha/core/engines/gows/types.group';
|
||||
import { toJID } from '@waha/core/engines/noweb/session.noweb.core';
|
||||
import {
|
||||
GroupId,
|
||||
GroupInfo,
|
||||
GroupParticipant,
|
||||
GroupParticipantRole,
|
||||
} from '@waha/structures/groups.dto';
|
||||
import {
|
||||
GroupParticipantType,
|
||||
GroupV2JoinEvent,
|
||||
GroupV2LeaveEvent,
|
||||
GroupV2ParticipantsEvent,
|
||||
GroupV2UpdateEvent,
|
||||
} from '@waha/structures/groups.events.dto';
|
||||
import { MeInfo } from '@waha/structures/sessions.dto';
|
||||
|
||||
export function ToGroupV2JoinEvent(event: JoinedGroupEvent): GroupV2JoinEvent {
|
||||
return {
|
||||
timestamp: Date.now(),
|
||||
group: ToGroupInfo(event),
|
||||
_data: event,
|
||||
};
|
||||
}
|
||||
|
||||
export function ToGroupV2LeaveEvent(
|
||||
me: MeInfo | null,
|
||||
event: GroupInfoEvent,
|
||||
): GroupV2LeaveEvent | null {
|
||||
if (!event.Leave || event.Leave.length === 0) {
|
||||
return null;
|
||||
}
|
||||
if (!me?.id) {
|
||||
return null;
|
||||
}
|
||||
const jid = toJID(me.id);
|
||||
const left = event.Leave;
|
||||
if (!left.includes(jid)) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
timestamp: Date.now(),
|
||||
group: {
|
||||
id: event.JID,
|
||||
},
|
||||
_data: event,
|
||||
};
|
||||
}
|
||||
|
||||
export function ToGroupV2UpdateEvent(
|
||||
event: GroupInfoEvent,
|
||||
): GroupV2UpdateEvent | null {
|
||||
const group = ToGroupInfoPartial(event);
|
||||
if (!group) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
timestamp: Date.now(),
|
||||
group: group,
|
||||
_data: event,
|
||||
};
|
||||
}
|
||||
|
||||
export function ToGroupV2ParticipantsEvents(
|
||||
event: GroupInfoEvent,
|
||||
): GroupV2ParticipantsEvent[] {
|
||||
const id: GroupId = {
|
||||
id: event.JID,
|
||||
};
|
||||
const events: GroupV2ParticipantsEvent[] = [];
|
||||
const maps = [
|
||||
{ participants: event.Leave, type: GroupParticipantType.LEAVE },
|
||||
{ participants: event.Join, type: GroupParticipantType.JOIN },
|
||||
{ participants: event.Promote, type: GroupParticipantType.PROMOTE },
|
||||
{ participants: event.Demote, type: GroupParticipantType.DEMOTE },
|
||||
];
|
||||
for (const map of maps) {
|
||||
const participants = getParticipants(map.participants, map.type);
|
||||
if (participants) {
|
||||
events.push({
|
||||
group: id,
|
||||
type: map.type,
|
||||
timestamp: Date.now(),
|
||||
participants: participants,
|
||||
_data: event,
|
||||
});
|
||||
}
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
function getParticipants(jids: string[] | null, type: GroupParticipantType) {
|
||||
if (!jids) {
|
||||
return;
|
||||
}
|
||||
let role: GroupParticipantRole;
|
||||
switch (type) {
|
||||
case GroupParticipantType.JOIN:
|
||||
role = GroupParticipantRole.PARTICIPANT;
|
||||
break;
|
||||
case GroupParticipantType.LEAVE:
|
||||
role = GroupParticipantRole.LEFT;
|
||||
break;
|
||||
case GroupParticipantType.PROMOTE:
|
||||
role = GroupParticipantRole.ADMIN;
|
||||
break;
|
||||
case GroupParticipantType.DEMOTE:
|
||||
role = GroupParticipantRole.PARTICIPANT;
|
||||
break;
|
||||
}
|
||||
const participants: GroupParticipant[] = [];
|
||||
for (const jid of jids) {
|
||||
participants.push({
|
||||
id: jid,
|
||||
role: role,
|
||||
});
|
||||
}
|
||||
return participants;
|
||||
}
|
||||
|
||||
function ToGroupInfo(group: GroupInfoFull): GroupInfo {
|
||||
const participants: GroupParticipant[] = extractParticipants(
|
||||
group.Participants,
|
||||
);
|
||||
return {
|
||||
id: group.JID,
|
||||
subject: group.Name,
|
||||
description: group.Topic,
|
||||
invite: undefined,
|
||||
participants: participants,
|
||||
membersCanAddNewMember: group.MemberAddMode === 'all_member_add',
|
||||
membersCanSendMessages: !!group.IsAnnounce,
|
||||
newMembersApprovalRequired: group.IsJoinApprovalRequired,
|
||||
};
|
||||
}
|
||||
|
||||
function ToGroupInfoPartial(group: GroupInfoEvent): GroupInfo | null {
|
||||
const announce: boolean | undefined = group.Announce?.IsAnnounce;
|
||||
let membersCanSendMessages: boolean | undefined;
|
||||
// revert announce or undefined
|
||||
if (announce === undefined) {
|
||||
membersCanSendMessages = undefined;
|
||||
} else {
|
||||
membersCanSendMessages = !announce;
|
||||
}
|
||||
|
||||
const info = {
|
||||
id: group.JID,
|
||||
subject: group.Name?.Name,
|
||||
description: group.Topic?.Topic,
|
||||
invite: group.NewInviteLink || undefined,
|
||||
membersCanAddNewMember: undefined,
|
||||
membersCanSendMessages: membersCanSendMessages,
|
||||
newMembersApprovalRequired:
|
||||
group.MembershipApprovalMode?.IsJoinApprovalRequired,
|
||||
participants: undefined,
|
||||
};
|
||||
// check if all fields are undefined but id - return null
|
||||
for (const key in info) {
|
||||
if (key !== 'id' && info[key] !== undefined) {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function extractParticipants(
|
||||
participants: GOWSGroupParticipant[],
|
||||
): GroupParticipant[] {
|
||||
const result: GroupParticipant[] = [];
|
||||
for (const participant of participants) {
|
||||
let role: GroupParticipantRole;
|
||||
if (participant.IsSuperAdmin) {
|
||||
role = GroupParticipantRole.SUPERADMIN;
|
||||
} else if (participant.IsAdmin) {
|
||||
role = GroupParticipantRole.ADMIN;
|
||||
} else {
|
||||
role = GroupParticipantRole.PARTICIPANT;
|
||||
}
|
||||
result.push({
|
||||
id: participant.JID,
|
||||
role: role,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -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
@@ -86,6 +86,12 @@ import { promisify } from 'util';
|
||||
|
||||
import * as gows from './types';
|
||||
import MessageServiceClient = messages.MessageServiceClient;
|
||||
import {
|
||||
ToGroupV2JoinEvent,
|
||||
ToGroupV2LeaveEvent,
|
||||
ToGroupV2ParticipantsEvents,
|
||||
ToGroupV2UpdateEvent,
|
||||
} from '@waha/core/engines/gows/groups.gows';
|
||||
import {
|
||||
optional,
|
||||
parseJson,
|
||||
@@ -102,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',
|
||||
@@ -116,6 +130,9 @@ enum WhatsMeowEvent {
|
||||
CHAT_PRESENCE = 'events.ChatPresence',
|
||||
PUSH_NAME_SETTING = 'events.PushNameSetting',
|
||||
LOGGED_OUT = 'events.LoggedOut',
|
||||
// Groups
|
||||
GROUP_INFO = 'events.GroupInfo',
|
||||
JOINED_GROUP = 'events.JoinedGroup',
|
||||
}
|
||||
|
||||
const gRPCClientConfig = {
|
||||
@@ -354,6 +371,34 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
|
||||
map((event) => this.toWahaPresences(event.From || event.Chat, [event])),
|
||||
);
|
||||
this.events2.get(WAHAEvents.PRESENCE_UPDATE).switch(presenceUpdates$);
|
||||
//
|
||||
// Group Events
|
||||
//
|
||||
const joinedGroup$ = all$.pipe(onlyEvent(WhatsMeowEvent.JOINED_GROUP));
|
||||
const groupV2Join$ = joinedGroup$.pipe(map(ToGroupV2JoinEvent));
|
||||
this.events2.get(WAHAEvents.GROUP_V2_JOIN).switch(groupV2Join$);
|
||||
|
||||
const groupInfo$ = all$.pipe(onlyEvent(WhatsMeowEvent.GROUP_INFO));
|
||||
|
||||
const groupV2Leave$ = groupInfo$.pipe(
|
||||
map((event) => ToGroupV2LeaveEvent(this.me, event)),
|
||||
filter(Boolean),
|
||||
);
|
||||
this.events2.get(WAHAEvents.GROUP_V2_LEAVE).switch(groupV2Leave$);
|
||||
|
||||
const groupV2Participants$ = groupInfo$.pipe(
|
||||
map(ToGroupV2ParticipantsEvents),
|
||||
mergeMap((events) => events),
|
||||
);
|
||||
this.events2
|
||||
.get(WAHAEvents.GROUP_V2_PARTICIPANTS)
|
||||
.switch(groupV2Participants$);
|
||||
|
||||
const groupV2Update$ = groupInfo$.pipe(
|
||||
map(ToGroupV2UpdateEvent),
|
||||
filter(Boolean),
|
||||
);
|
||||
this.events2.get(WAHAEvents.GROUP_V2_UPDATE).switch(groupV2Update$);
|
||||
}
|
||||
|
||||
async fetchContactProfilePicture(id: string): Promise<string> {
|
||||
@@ -586,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) {
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
// JID just a string alias, we dump it as String in golang
|
||||
type JID = string;
|
||||
|
||||
interface GroupName {
|
||||
Name: string;
|
||||
NameSetAt: string; // ISO string format for time
|
||||
NameSetBy: JID;
|
||||
}
|
||||
|
||||
interface GroupTopic {
|
||||
Topic: string;
|
||||
TopicID: string;
|
||||
TopicSetAt: string;
|
||||
TopicSetBy: JID;
|
||||
TopicDeleted: boolean;
|
||||
}
|
||||
|
||||
interface GroupLocked {
|
||||
IsLocked: boolean;
|
||||
}
|
||||
|
||||
interface GroupAnnounce {
|
||||
IsAnnounce: boolean;
|
||||
AnnounceVersionID: string;
|
||||
}
|
||||
|
||||
interface GroupEphemeral {
|
||||
IsEphemeral: boolean;
|
||||
DisappearingTimer: number;
|
||||
}
|
||||
|
||||
interface GroupMembershipApprovalMode {
|
||||
IsJoinApprovalRequired: boolean;
|
||||
}
|
||||
|
||||
interface GroupDelete {
|
||||
Deleted: boolean;
|
||||
DeleteReason: string;
|
||||
}
|
||||
|
||||
interface GroupLinkTarget {
|
||||
JID: JID;
|
||||
GroupName: GroupName;
|
||||
GroupIsDefaultSub: GroupIsDefaultSub;
|
||||
}
|
||||
|
||||
interface GroupLinkChange {
|
||||
Type: string; // Enum in Go: GroupLinkChangeType
|
||||
UnlinkReason: string; // Enum in Go: GroupUnlinkReason
|
||||
Group: GroupLinkTarget;
|
||||
}
|
||||
|
||||
interface GroupIsDefaultSub {
|
||||
IsDefaultSubGroup: boolean;
|
||||
}
|
||||
|
||||
export interface GroupInfoEvent {
|
||||
JID: JID;
|
||||
Notify: string;
|
||||
Sender?: JID;
|
||||
Timestamp: string;
|
||||
|
||||
Name?: GroupName;
|
||||
Topic?: GroupTopic;
|
||||
Locked?: GroupLocked;
|
||||
Announce?: GroupAnnounce;
|
||||
Ephemeral?: GroupEphemeral;
|
||||
|
||||
MembershipApprovalMode?: GroupMembershipApprovalMode;
|
||||
Delete?: GroupDelete;
|
||||
|
||||
Link?: GroupLinkChange;
|
||||
Unlink?: GroupLinkChange;
|
||||
NewInviteLink?: string;
|
||||
|
||||
PrevParticipantVersionID: string;
|
||||
ParticipantVersionID: string;
|
||||
|
||||
JoinReason: string;
|
||||
|
||||
Join: JID[];
|
||||
Leave: JID[];
|
||||
Promote: JID[];
|
||||
Demote: JID[];
|
||||
}
|
||||
|
||||
export interface GOWSGroupParticipant {
|
||||
JID: JID;
|
||||
IsAdmin: boolean;
|
||||
IsSuperAdmin: boolean;
|
||||
}
|
||||
|
||||
export interface GroupInfoFull {
|
||||
JID: JID;
|
||||
OwnerJID: JID;
|
||||
Name: string; // Name has additional properties here
|
||||
Topic: string; // Topic has additional properties here
|
||||
IsLocked: boolean; // specifies whether the group info can only be edited by admins.
|
||||
IsAnnounce: boolean; // specifies whether only admins can send messages in the group
|
||||
Participants: GOWSGroupParticipant[];
|
||||
MemberAddMode: string; // all_member_add | admin_add
|
||||
IsJoinApprovalRequired: boolean;
|
||||
}
|
||||
|
||||
export interface JoinedGroupEvent extends GroupInfoFull {
|
||||
Reason: string;
|
||||
Type: string;
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
import { GroupMetadata, jidNormalizedUser } from '@adiwajshing/baileys';
|
||||
import type { Contact } from '@adiwajshing/baileys/lib/Types/Contact';
|
||||
import {
|
||||
GroupParticipant as NOWEBGroupParticipant,
|
||||
ParticipantAction,
|
||||
} from '@adiwajshing/baileys/lib/Types/GroupMetadata';
|
||||
import { getGroupInviteLink } from '@waha/core/abc/session.abc';
|
||||
import { toCusFormat } from '@waha/core/engines/noweb/session.noweb.core';
|
||||
import {
|
||||
GroupInfo,
|
||||
GroupParticipant,
|
||||
GroupParticipantRole,
|
||||
} from '@waha/structures/groups.dto';
|
||||
import {
|
||||
GroupParticipantType,
|
||||
GroupV2JoinEvent,
|
||||
GroupV2LeaveEvent,
|
||||
GroupV2ParticipantsEvent,
|
||||
GroupV2UpdateEvent,
|
||||
} from '@waha/structures/groups.events.dto';
|
||||
|
||||
export function ToGroupInfo(group: Partial<GroupMetadata>): GroupInfo {
|
||||
let participants: GroupParticipant[] = undefined;
|
||||
if (group.participants && group.participants.length > 0) {
|
||||
participants = group.participants.map(ToGroupParticipant);
|
||||
}
|
||||
return {
|
||||
id: group.id,
|
||||
subject: group.subject,
|
||||
description: group.desc,
|
||||
invite: group.inviteCode ? getGroupInviteLink(group.inviteCode) : undefined,
|
||||
participants: participants,
|
||||
membersCanAddNewMember: group.memberAddMode,
|
||||
membersCanSendMessages: group.announce,
|
||||
newMembersApprovalRequired: group.joinApprovalMode,
|
||||
};
|
||||
}
|
||||
|
||||
export function ToGroupV2JoinEvent(group: GroupMetadata): GroupV2JoinEvent {
|
||||
return {
|
||||
timestamp: Date.now(),
|
||||
group: ToGroupInfo(group),
|
||||
_data: group,
|
||||
};
|
||||
}
|
||||
|
||||
export function ToGroupParticipant(
|
||||
participant: NOWEBGroupParticipant,
|
||||
): GroupParticipant {
|
||||
let role: GroupParticipantRole = GroupParticipantRole.PARTICIPANT;
|
||||
if (participant.admin === 'admin') {
|
||||
role = GroupParticipantRole.ADMIN;
|
||||
} else if (participant.admin === 'superadmin') {
|
||||
role = GroupParticipantRole.SUPERADMIN;
|
||||
}
|
||||
return {
|
||||
id: toCusFormat(participant.id),
|
||||
role: role,
|
||||
};
|
||||
}
|
||||
|
||||
interface GroupParticipantUpdate {
|
||||
id: string;
|
||||
author: string;
|
||||
participants: string[];
|
||||
action: ParticipantAction;
|
||||
}
|
||||
|
||||
export function ToGroupV2Participants(
|
||||
update: GroupParticipantUpdate,
|
||||
): GroupV2ParticipantsEvent {
|
||||
let role: GroupParticipantRole;
|
||||
let type: GroupParticipantType;
|
||||
switch (update.action) {
|
||||
case 'add':
|
||||
role = GroupParticipantRole.PARTICIPANT;
|
||||
type = GroupParticipantType.JOIN;
|
||||
break;
|
||||
case 'remove':
|
||||
role = GroupParticipantRole.LEFT;
|
||||
type = GroupParticipantType.LEAVE;
|
||||
break;
|
||||
case 'promote':
|
||||
role = GroupParticipantRole.ADMIN;
|
||||
type = GroupParticipantType.PROMOTE;
|
||||
break;
|
||||
case 'demote':
|
||||
role = GroupParticipantRole.ADMIN;
|
||||
type = GroupParticipantType.DEMOTE;
|
||||
break;
|
||||
}
|
||||
|
||||
const participants: GroupParticipant[] = update.participants.map((id) => {
|
||||
return {
|
||||
id: toCusFormat(id),
|
||||
role: role,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
group: {
|
||||
id: toCusFormat(update.id),
|
||||
},
|
||||
type: type,
|
||||
timestamp: Date.now(),
|
||||
participants: participants,
|
||||
_data: update,
|
||||
};
|
||||
}
|
||||
|
||||
export function ToGroupV2UpdateEvent(
|
||||
group: Partial<GroupMetadata>,
|
||||
): GroupV2UpdateEvent {
|
||||
return {
|
||||
timestamp: Date.now(),
|
||||
group: ToGroupInfo(group),
|
||||
_data: group,
|
||||
};
|
||||
}
|
||||
|
||||
export function ToGroupV2LeaveEvent(
|
||||
me: Contact,
|
||||
update: GroupParticipantUpdate,
|
||||
): GroupV2LeaveEvent | null {
|
||||
if (update.action !== 'remove') {
|
||||
return null;
|
||||
}
|
||||
if (!me) {
|
||||
return null;
|
||||
}
|
||||
const meId = jidNormalizedUser(me.id);
|
||||
if (!update.participants.includes(meId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
timestamp: Date.now(),
|
||||
group: {
|
||||
id: toCusFormat(update.id),
|
||||
},
|
||||
_data: update,
|
||||
};
|
||||
}
|
||||
@@ -40,6 +40,12 @@ import { MessageUserReceiptUpdate } from '@adiwajshing/baileys/lib/Types/Message
|
||||
import { isLidUser } from '@adiwajshing/baileys/lib/WABinary/jid-utils';
|
||||
import { Logger as BaileysLogger } from '@adiwajshing/baileys/node_modules/pino';
|
||||
import { UnprocessableEntityException } from '@nestjs/common';
|
||||
import {
|
||||
ToGroupV2JoinEvent,
|
||||
ToGroupV2LeaveEvent,
|
||||
ToGroupV2Participants,
|
||||
ToGroupV2UpdateEvent,
|
||||
} from '@waha/core/engines/noweb/groups.noweb';
|
||||
import { sendButtonMessage } from '@waha/core/engines/noweb/noweb.buttons';
|
||||
import { NowebInMemoryStore } from '@waha/core/engines/noweb/store/NowebInMemoryStore';
|
||||
import { IMediaEngineProcessor } from '@waha/core/media/IMediaEngineProcessor';
|
||||
@@ -80,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';
|
||||
@@ -136,6 +143,7 @@ import {
|
||||
import {
|
||||
CreateGroupRequest,
|
||||
ParticipantsRequest,
|
||||
SettingsSecurityChangeInfo,
|
||||
} from '../../../structures/groups.dto';
|
||||
import {
|
||||
WAHAChatPresences,
|
||||
@@ -377,7 +385,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
this.listenConnectionEvents();
|
||||
this.subscribeEngineEvents2();
|
||||
this.listenContactsUpdatePictureProfile();
|
||||
this.enableAutoRestart();
|
||||
// this.enableAutoRestart();
|
||||
}
|
||||
|
||||
private enableAutoRestart() {
|
||||
@@ -714,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);
|
||||
}
|
||||
@@ -1183,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({});
|
||||
@@ -1191,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);
|
||||
}
|
||||
@@ -1469,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
|
||||
//
|
||||
@@ -1512,12 +1567,51 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
this.events2
|
||||
.get(WAHAEvents.STATE_CHANGE)
|
||||
.switch(fromEvent(this.sock.ev, 'connection.update'));
|
||||
this.events2.get(WAHAEvents.GROUP_JOIN).switch(
|
||||
|
||||
const groupsUpsert$: Observable<GroupMetadata> = fromEvent(
|
||||
this.sock.ev,
|
||||
'groups.upsert',
|
||||
).pipe(
|
||||
// @ts-ignore
|
||||
fromEvent<GroupMetadata[]>(this.sock.ev, 'groups.upsert').pipe(
|
||||
mergeAll(),
|
||||
),
|
||||
mergeAll(),
|
||||
);
|
||||
const groupsUpdate$: Observable<Partial<GroupMetadata>> = fromEvent(
|
||||
this.sock.ev,
|
||||
'groups.update',
|
||||
).pipe(
|
||||
// @ts-ignore
|
||||
mergeAll(),
|
||||
);
|
||||
const groupsParticipantsUpdate$: Observable<any> = fromEvent(
|
||||
this.sock.ev,
|
||||
'group-participants.update',
|
||||
);
|
||||
|
||||
this.events2.get(WAHAEvents.GROUP_JOIN).switch(groupsUpsert$);
|
||||
|
||||
const groupV2Join$ = groupsUpsert$.pipe(
|
||||
map((group) => ToGroupV2JoinEvent(group)),
|
||||
);
|
||||
this.events2.get(WAHAEvents.GROUP_V2_JOIN).switch(groupV2Join$);
|
||||
|
||||
const groupV2Update$ = merge(groupsUpdate$).pipe(map(ToGroupV2UpdateEvent));
|
||||
this.events2.get(WAHAEvents.GROUP_V2_UPDATE).switch(groupV2Update$);
|
||||
|
||||
const groupV2Participants$ = groupsParticipantsUpdate$.pipe(
|
||||
map(ToGroupV2Participants),
|
||||
);
|
||||
this.events2
|
||||
.get(WAHAEvents.GROUP_V2_PARTICIPANTS)
|
||||
.switch(groupV2Participants$);
|
||||
|
||||
const groupV2Leave$ = groupsParticipantsUpdate$.pipe(
|
||||
map((group) =>
|
||||
ToGroupV2LeaveEvent(this.sock?.authState?.creds?.me, group),
|
||||
),
|
||||
filter(Boolean),
|
||||
);
|
||||
this.events2.get(WAHAEvents.GROUP_V2_LEAVE).switch(groupV2Leave$);
|
||||
|
||||
this.events2
|
||||
.get(WAHAEvents.PRESENCE_UPDATE)
|
||||
.switch(
|
||||
@@ -1687,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 {
|
||||
@@ -1719,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
|
||||
@@ -1741,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;
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
import { WebjsClientCore } from '@waha/core/engines/webjs/WebjsClientCore';
|
||||
import {
|
||||
GroupId,
|
||||
GroupInfo,
|
||||
GroupParticipant,
|
||||
GroupParticipantRole,
|
||||
} from '@waha/structures/groups.dto';
|
||||
import {
|
||||
GroupParticipantType,
|
||||
GroupV2JoinEvent,
|
||||
GroupV2LeaveEvent,
|
||||
GroupV2ParticipantsEvent,
|
||||
GroupV2UpdateEvent,
|
||||
} from '@waha/structures/groups.events.dto';
|
||||
import {
|
||||
GroupChat,
|
||||
GroupNotification,
|
||||
GroupNotificationTypes,
|
||||
GroupParticipant as WEBJSGroupParticipant,
|
||||
} from 'whatsapp-web.js';
|
||||
|
||||
function ToGroupInfo(
|
||||
group: GroupChat,
|
||||
invite: string,
|
||||
participants = [],
|
||||
): GroupInfo {
|
||||
// @ts-ignore
|
||||
const groupMetadata = group.groupMetadata;
|
||||
const info: GroupInfo = {
|
||||
// @ts-ignore
|
||||
id: group.id._serialized,
|
||||
subject: group.name,
|
||||
description: group.description,
|
||||
invite: invite,
|
||||
membersCanAddNewMember: groupMetadata.restrict,
|
||||
membersCanSendMessages: groupMetadata.announce,
|
||||
newMembersApprovalRequired: groupMetadata.membershipApprovalMode,
|
||||
participants: participants,
|
||||
};
|
||||
return info;
|
||||
}
|
||||
|
||||
export async function ToGroupV2JoinEvent(
|
||||
client: WebjsClientCore,
|
||||
me: string,
|
||||
notification: GroupNotification,
|
||||
): Promise<GroupV2JoinEvent | null> {
|
||||
if (!notification.recipientIds.includes(me)) {
|
||||
return null;
|
||||
}
|
||||
// @ts-ignore
|
||||
const group: GroupChat = await client.getChatById(notification.id.remote);
|
||||
const invite = await group.getInviteCode();
|
||||
const participants = getParticipants(group.participants);
|
||||
const info: GroupInfo = ToGroupInfo(group, invite, participants);
|
||||
return {
|
||||
timestamp: notification.timestamp,
|
||||
group: info,
|
||||
_data: notification,
|
||||
};
|
||||
}
|
||||
|
||||
function getParticipants(
|
||||
participants: WEBJSGroupParticipant[],
|
||||
): GroupParticipant[] {
|
||||
return participants.map((participant) => {
|
||||
let role: GroupParticipantRole = GroupParticipantRole.PARTICIPANT;
|
||||
if (participant.isSuperAdmin) {
|
||||
role = GroupParticipantRole.SUPERADMIN;
|
||||
} else if (participant.isAdmin) {
|
||||
role = GroupParticipantRole.ADMIN;
|
||||
}
|
||||
|
||||
return {
|
||||
id: participant.id._serialized,
|
||||
role: role,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function ToGroupV2LeaveEvent(
|
||||
me: string,
|
||||
notification: GroupNotification,
|
||||
): GroupV2LeaveEvent | null {
|
||||
if (!notification.recipientIds.includes(me)) {
|
||||
return null;
|
||||
}
|
||||
const group: GroupId = {
|
||||
// @ts-ignore
|
||||
id: notification.id.remote,
|
||||
};
|
||||
return {
|
||||
timestamp: notification.timestamp,
|
||||
group: group,
|
||||
_data: notification,
|
||||
};
|
||||
}
|
||||
|
||||
export async function ToGroupV2UpdateEvent(
|
||||
client: WebjsClientCore,
|
||||
notification: GroupNotification,
|
||||
): Promise<GroupV2UpdateEvent> {
|
||||
// @ts-ignore
|
||||
const group: GroupChat = await client.getChatById(notification.id.remote);
|
||||
const invite = await group.getInviteCode();
|
||||
const info: GroupInfo = ToGroupInfo(group, invite, undefined);
|
||||
return {
|
||||
group: info,
|
||||
timestamp: notification.timestamp,
|
||||
_data: notification,
|
||||
};
|
||||
}
|
||||
|
||||
export function ToGroupV2ParticipantsEvent(
|
||||
notification: GroupNotification,
|
||||
): GroupV2ParticipantsEvent | null {
|
||||
let type: GroupParticipantType;
|
||||
let role: GroupParticipantRole;
|
||||
if (['add', 'invite', 'linked_group_join'].includes(notification.type)) {
|
||||
type = GroupParticipantType.JOIN;
|
||||
role = GroupParticipantRole.PARTICIPANT;
|
||||
} else if (['remove', 'leave'].includes(notification.type)) {
|
||||
type = GroupParticipantType.LEAVE;
|
||||
role = GroupParticipantRole.LEFT;
|
||||
} else if (['promote'].includes(notification.type)) {
|
||||
type = GroupParticipantType.PROMOTE;
|
||||
role = GroupParticipantRole.ADMIN;
|
||||
} else if (['demote'].includes(notification.type)) {
|
||||
type = GroupParticipantType.DEMOTE;
|
||||
role = GroupParticipantRole.PARTICIPANT;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
const participants: GroupParticipant[] = notification.recipientIds.map(
|
||||
(id) => {
|
||||
return {
|
||||
id: id,
|
||||
role: role,
|
||||
};
|
||||
},
|
||||
);
|
||||
const group: GroupId = {
|
||||
// @ts-ignore
|
||||
id: notification.id.remote,
|
||||
};
|
||||
return {
|
||||
group: group,
|
||||
type: type,
|
||||
timestamp: notification.timestamp,
|
||||
participants: participants,
|
||||
_data: notification,
|
||||
};
|
||||
}
|
||||
@@ -3,6 +3,12 @@ import {
|
||||
getChannelInviteLink,
|
||||
WhatsappSession,
|
||||
} from '@waha/core/abc/session.abc';
|
||||
import {
|
||||
ToGroupV2JoinEvent,
|
||||
ToGroupV2LeaveEvent,
|
||||
ToGroupV2ParticipantsEvent,
|
||||
ToGroupV2UpdateEvent,
|
||||
} from '@waha/core/engines/webjs/groups.webjs';
|
||||
import { LocalAuth } from '@waha/core/engines/webjs/LocalAuth';
|
||||
import { WebjsClientCore } from '@waha/core/engines/webjs/WebjsClientCore';
|
||||
import {
|
||||
@@ -78,9 +84,9 @@ import { PaginatorInMemory } from '@waha/utils/Paginator';
|
||||
import { sleep, waitUntil } from '@waha/utils/promiseTimeout';
|
||||
import { SingleDelayedJobRunner } from '@waha/utils/SingleDelayedJobRunner';
|
||||
import * as lodash from 'lodash';
|
||||
import { fromEvent, merge, mergeMap, Observable } from 'rxjs';
|
||||
import { filter, fromEvent, merge, mergeMap, Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import WAWebJS, {
|
||||
import {
|
||||
Call,
|
||||
Channel as WEBJSChannel,
|
||||
Chat,
|
||||
@@ -88,6 +94,7 @@ import WAWebJS, {
|
||||
Contact,
|
||||
Events,
|
||||
GroupChat,
|
||||
GroupNotification,
|
||||
Label as WEBJSLabel,
|
||||
Location,
|
||||
Message,
|
||||
@@ -503,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);
|
||||
}
|
||||
@@ -887,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;
|
||||
}
|
||||
@@ -1033,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),
|
||||
@@ -1233,10 +1248,52 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
//
|
||||
// Groups
|
||||
//
|
||||
const groupJoin$ = fromEvent(this.whatsapp, Events.GROUP_JOIN);
|
||||
this.events2.get(WAHAEvents.GROUP_JOIN).switch(groupJoin$);
|
||||
const groupLeave$ = fromEvent(this.whatsapp, Events.GROUP_LEAVE);
|
||||
this.events2.get(WAHAEvents.GROUP_LEAVE).switch(groupLeave$);
|
||||
const groupJoin$ = fromEvent<GroupNotification>(
|
||||
this.whatsapp,
|
||||
Events.GROUP_JOIN,
|
||||
);
|
||||
this.events2.get(WAHAEvents.GROUP_JOIN).switch(groupJoin$); // v1
|
||||
const groupV2Join$ = groupJoin$.pipe(
|
||||
mergeMap((evt) =>
|
||||
ToGroupV2JoinEvent(this.whatsapp, this.getSessionMeInfo().id, evt),
|
||||
),
|
||||
filter(Boolean),
|
||||
);
|
||||
this.events2.get(WAHAEvents.GROUP_V2_JOIN).switch(groupV2Join$);
|
||||
|
||||
const groupLeave$ = fromEvent<GroupNotification>(
|
||||
this.whatsapp,
|
||||
Events.GROUP_LEAVE,
|
||||
);
|
||||
this.events2.get(WAHAEvents.GROUP_LEAVE).switch(groupLeave$); // v1
|
||||
const groupV2Leave$ = groupLeave$.pipe(
|
||||
map((evt) => ToGroupV2LeaveEvent(this.getSessionMeInfo().id, evt)),
|
||||
filter(Boolean),
|
||||
);
|
||||
this.events2.get(WAHAEvents.GROUP_V2_LEAVE).switch(groupV2Leave$);
|
||||
|
||||
const groupAdminChanged$ = fromEvent(
|
||||
this.whatsapp,
|
||||
Events.GROUP_ADMIN_CHANGED,
|
||||
);
|
||||
const groupV2Participants = merge(
|
||||
groupJoin$,
|
||||
groupLeave$,
|
||||
groupAdminChanged$,
|
||||
).pipe(map(ToGroupV2ParticipantsEvent), filter(Boolean));
|
||||
this.events2
|
||||
.get(WAHAEvents.GROUP_V2_PARTICIPANTS)
|
||||
.switch(groupV2Participants);
|
||||
|
||||
const groupUpdate$ = fromEvent<GroupNotification>(
|
||||
this.whatsapp,
|
||||
Events.GROUP_UPDATE,
|
||||
);
|
||||
const groupV2Update$ = groupUpdate$.pipe(
|
||||
mergeMap((evt) => ToGroupV2UpdateEvent(this.whatsapp, evt)),
|
||||
filter(Boolean),
|
||||
);
|
||||
this.events2.get(WAHAEvents.GROUP_V2_UPDATE).switch(groupV2Update$);
|
||||
|
||||
//
|
||||
// Chats
|
||||
|
||||
+16
-6
@@ -1,5 +1,6 @@
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { WsAdapter } from '@nestjs/platform-ws';
|
||||
import { WAHA_WEBHOOKS } from '@waha/structures/webhooks';
|
||||
import {
|
||||
getNestJSLogLevels,
|
||||
getPinoLogLevel,
|
||||
@@ -15,7 +16,6 @@ import { WhatsappConfigService } from './config.service';
|
||||
import { AppModuleCore } from './core/app.module.core';
|
||||
import { SwaggerConfiguratorCore } from './core/SwaggerConfiguratorCore';
|
||||
import { AllExceptionsFilter } from './nestjs/AllExceptionsFilter';
|
||||
import { WAHA_WEBHOOKS } from './structures/webhooks.dto';
|
||||
import { getWAHAVersion, VERSION, WAHAVersion } from './version';
|
||||
|
||||
const logger: Logger = pino({
|
||||
@@ -23,12 +23,22 @@ const logger: Logger = pino({
|
||||
transport: getPinoTransport(),
|
||||
}).child({ name: 'Bootstrap' });
|
||||
|
||||
logger.info('NODE - Catching unhandled rejection enabled');
|
||||
process.on('unhandledRejection', (reason, promise) => {
|
||||
logger.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
||||
// @ts-ignore
|
||||
logger.error(reason.stack);
|
||||
process.on('uncaughtException', (err) => {
|
||||
logger.error('Uncaught Exception:', err);
|
||||
if (err instanceof Error) {
|
||||
logger.error(err.stack);
|
||||
}
|
||||
});
|
||||
process.on('unhandledRejection', (reason, promise) => {
|
||||
logger.error('Unhandled Rejection at:', promise);
|
||||
if (reason instanceof Error) {
|
||||
logger.error(reason.stack);
|
||||
} else {
|
||||
logger.error('Unhandled rejection reason:', reason);
|
||||
}
|
||||
});
|
||||
logger.info('NODE - Catching unhandled rejections and exceptions enabled');
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
logger.info('SIGINT received');
|
||||
});
|
||||
|
||||
@@ -165,6 +165,8 @@ export class EditMessageRequest {
|
||||
|
||||
@ApiHideProperty()
|
||||
mentions?: string[];
|
||||
|
||||
linkPreview?: boolean = true;
|
||||
}
|
||||
|
||||
export class MessageReplyRequest extends MessageTextRequest {
|
||||
|
||||
@@ -11,6 +11,10 @@ export enum WAHAEvents {
|
||||
STATE_CHANGE = 'state.change',
|
||||
GROUP_JOIN = 'group.join',
|
||||
GROUP_LEAVE = 'group.leave',
|
||||
GROUP_V2_JOIN = 'group.v2.join',
|
||||
GROUP_V2_LEAVE = 'group.v2.leave',
|
||||
GROUP_V2_UPDATE = 'group.v2.update',
|
||||
GROUP_V2_PARTICIPANTS = 'group.v2.participants',
|
||||
PRESENCE_UPDATE = 'presence.update',
|
||||
POLL_VOTE = 'poll.vote',
|
||||
POLL_VOTE_FAILED = 'poll.vote.failed',
|
||||
|
||||
@@ -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',
|
||||
@@ -88,3 +105,69 @@ export class GroupsPaginationParams extends PaginationParams {
|
||||
@IsEnum(GroupSortField)
|
||||
sortBy?: string;
|
||||
}
|
||||
|
||||
export enum GroupParticipantRole {
|
||||
LEFT = 'left',
|
||||
PARTICIPANT = 'participant',
|
||||
ADMIN = 'admin',
|
||||
SUPERADMIN = 'superadmin',
|
||||
}
|
||||
|
||||
export class GroupParticipant {
|
||||
@ApiProperty({
|
||||
example: '123456789@c.us',
|
||||
})
|
||||
id: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: GroupParticipantRole.PARTICIPANT,
|
||||
})
|
||||
role: GroupParticipantRole;
|
||||
}
|
||||
|
||||
export class GroupId {
|
||||
@ApiProperty({
|
||||
example: '123456789@g.us',
|
||||
})
|
||||
id: string;
|
||||
}
|
||||
|
||||
export class GroupInfo {
|
||||
@ApiProperty({
|
||||
example: '123456789@g.us',
|
||||
})
|
||||
id: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 'Group Name',
|
||||
})
|
||||
subject: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 'Group Description',
|
||||
})
|
||||
description: string;
|
||||
|
||||
participants: GroupParticipant[];
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Invite URL',
|
||||
example: 'https://chat.whatsapp.com/1234567890abcdef',
|
||||
})
|
||||
invite?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Members can add new members',
|
||||
})
|
||||
membersCanAddNewMember: boolean;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Members can send messages to the group',
|
||||
})
|
||||
membersCanSendMessages: boolean;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Admin approval required for new members',
|
||||
})
|
||||
newMembersApprovalRequired: boolean;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
import { GroupId, GroupInfo, GroupParticipant } from './groups.dto';
|
||||
|
||||
/**
|
||||
* Happens when you join or are added to a group.
|
||||
*/
|
||||
export class GroupV2JoinEvent {
|
||||
@ApiProperty({
|
||||
description: 'Unix timestamp',
|
||||
example: 1666943582,
|
||||
})
|
||||
timestamp: number;
|
||||
|
||||
group: GroupInfo;
|
||||
|
||||
_data: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Happens when you leave or are removed from a group.
|
||||
*/
|
||||
export class GroupV2LeaveEvent {
|
||||
@ApiProperty({
|
||||
description: 'Unix timestamp',
|
||||
example: 1666943582,
|
||||
})
|
||||
timestamp: number;
|
||||
|
||||
group: GroupId;
|
||||
|
||||
_data: any;
|
||||
}
|
||||
|
||||
export class GroupV2UpdateEvent {
|
||||
@ApiProperty({
|
||||
description: 'Unix timestamp',
|
||||
example: 1666943582,
|
||||
})
|
||||
timestamp: number;
|
||||
|
||||
group: Partial<GroupInfo>;
|
||||
|
||||
_data: any;
|
||||
}
|
||||
|
||||
export enum GroupParticipantType {
|
||||
JOIN = 'join',
|
||||
LEAVE = 'leave',
|
||||
PROMOTE = 'promote',
|
||||
DEMOTE = 'demote',
|
||||
}
|
||||
|
||||
export class GroupV2ParticipantsEvent {
|
||||
group: GroupId;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Type of the event',
|
||||
})
|
||||
type: GroupParticipantType;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Unix timestamp',
|
||||
example: 1666943582,
|
||||
})
|
||||
timestamp: number;
|
||||
|
||||
participants: GroupParticipant[];
|
||||
|
||||
_data: any;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { WAHAEvents } from '@waha/structures/enums.dto';
|
||||
import {
|
||||
GroupV2JoinEvent,
|
||||
GroupV2LeaveEvent,
|
||||
GroupV2ParticipantsEvent,
|
||||
GroupV2UpdateEvent,
|
||||
} from '@waha/structures/groups.events.dto';
|
||||
import { WAHAWebhook } from '@waha/structures/webhooks.dto';
|
||||
|
||||
export class WebhookGroupV2Join extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'When you joined or were added to a group',
|
||||
})
|
||||
event = WAHAEvents.GROUP_V2_JOIN;
|
||||
|
||||
payload: GroupV2JoinEvent;
|
||||
}
|
||||
|
||||
export class WebhookGroupV2Leave extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'When you left or were removed from a group',
|
||||
})
|
||||
event = WAHAEvents.GROUP_V2_LEAVE;
|
||||
|
||||
payload: GroupV2LeaveEvent;
|
||||
}
|
||||
|
||||
export class WebhookGroupV2Update extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'When group info is updated',
|
||||
})
|
||||
event = WAHAEvents.GROUP_V2_UPDATE;
|
||||
|
||||
payload: GroupV2UpdateEvent;
|
||||
}
|
||||
|
||||
export class WebhookGroupV2Participants extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'When participants changed - join, leave, promote to admin',
|
||||
})
|
||||
event = WAHAEvents.GROUP_V2_PARTICIPANTS;
|
||||
|
||||
payload: GroupV2ParticipantsEvent;
|
||||
}
|
||||
@@ -91,6 +91,7 @@ export class PollVotePayload {
|
||||
export class WAMessageRevokedBody {
|
||||
after?: WAMessage;
|
||||
before?: WAMessage;
|
||||
_data?: any;
|
||||
}
|
||||
|
||||
export class WASessionStatusBody {
|
||||
@@ -148,14 +149,14 @@ export class WAHAWebhookSessionStatus extends WAHAWebhook {
|
||||
payload: WASessionStatusBody;
|
||||
}
|
||||
|
||||
class WAHAWebhookMessage extends WAHAWebhook {
|
||||
export class WAHAWebhookMessage extends WAHAWebhook {
|
||||
@ApiProperty({ description: 'Incoming message.' })
|
||||
event = WAHAEvents.MESSAGE;
|
||||
|
||||
payload: WAMessage;
|
||||
}
|
||||
|
||||
class WAHAWebhookMessageReaction extends WAHAWebhook {
|
||||
export class WAHAWebhookMessageReaction extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description:
|
||||
'The event is triggered when a user reacts or removes a reaction.',
|
||||
@@ -165,7 +166,7 @@ class WAHAWebhookMessageReaction extends WAHAWebhook {
|
||||
payload: WAMessageReaction;
|
||||
}
|
||||
|
||||
class WAHAWebhookMessageAny extends WAHAWebhook {
|
||||
export class WAHAWebhookMessageAny extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'Fired on all message creations, including your own.',
|
||||
})
|
||||
@@ -174,7 +175,7 @@ class WAHAWebhookMessageAny extends WAHAWebhook {
|
||||
payload: WAMessage;
|
||||
}
|
||||
|
||||
class WAHAWebhookMessageAck extends WAHAWebhook {
|
||||
export class WAHAWebhookMessageAck extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description:
|
||||
'Receive events when server or recipient gets the message, read or played it.',
|
||||
@@ -184,7 +185,7 @@ class WAHAWebhookMessageAck extends WAHAWebhook {
|
||||
payload: WAMessageAckBody;
|
||||
}
|
||||
|
||||
class WAHAWebhookMessageRevoked extends WAHAWebhook {
|
||||
export class WAHAWebhookMessageRevoked extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description:
|
||||
'The event is triggered when a user, whether it be you or any other participant, ' +
|
||||
@@ -195,34 +196,37 @@ class WAHAWebhookMessageRevoked extends WAHAWebhook {
|
||||
payload: WAMessageRevokedBody;
|
||||
}
|
||||
|
||||
class WAHAWebhookStateChange extends WAHAWebhook {
|
||||
export class WAHAWebhookStateChange extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'It’s an internal engine’s state, not session status.',
|
||||
deprecated: true,
|
||||
})
|
||||
event = WAHAEvents.STATE_CHANGE;
|
||||
|
||||
payload: any;
|
||||
}
|
||||
|
||||
class WAHAWebhookGroupJoin extends WAHAWebhook {
|
||||
export class WAHAWebhookGroupJoin extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'Some one join a group.',
|
||||
deprecated: true,
|
||||
})
|
||||
event = WAHAEvents.GROUP_JOIN;
|
||||
|
||||
payload: any;
|
||||
}
|
||||
|
||||
class WAHAWebhookGroupLeave extends WAHAWebhook {
|
||||
export class WAHAWebhookGroupLeave extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'Some one left a group.',
|
||||
deprecated: true,
|
||||
})
|
||||
event = WAHAEvents.GROUP_LEAVE;
|
||||
|
||||
payload: any;
|
||||
}
|
||||
|
||||
class WAHAWebhookPresenceUpdate extends WAHAWebhook {
|
||||
export class WAHAWebhookPresenceUpdate extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'The most recent presence information for a chat.',
|
||||
})
|
||||
@@ -231,7 +235,7 @@ class WAHAWebhookPresenceUpdate extends WAHAWebhook {
|
||||
payload: WAHAChatPresences;
|
||||
}
|
||||
|
||||
class WAHAWebhookPollVote extends WAHAWebhook {
|
||||
export class WAHAWebhookPollVote extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'With this event, you receive new votes for the poll sent.',
|
||||
})
|
||||
@@ -240,7 +244,7 @@ class WAHAWebhookPollVote extends WAHAWebhook {
|
||||
payload: PollVotePayload;
|
||||
}
|
||||
|
||||
class WAHAWebhookPollVoteFailed extends WAHAWebhook {
|
||||
export class WAHAWebhookPollVoteFailed extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description:
|
||||
'There may be cases when it fails to decrypt a vote from the user. ' +
|
||||
@@ -251,7 +255,7 @@ class WAHAWebhookPollVoteFailed extends WAHAWebhook {
|
||||
payload: PollVotePayload;
|
||||
}
|
||||
|
||||
class WAHAWebhookChatArchive extends WAHAWebhook {
|
||||
export class WAHAWebhookChatArchive extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description:
|
||||
'The event is triggered when the chat is archived or unarchived',
|
||||
@@ -261,7 +265,7 @@ class WAHAWebhookChatArchive extends WAHAWebhook {
|
||||
payload: ChatArchiveEvent;
|
||||
}
|
||||
|
||||
class WAHAWebhookCallReceived extends WAHAWebhook {
|
||||
export class WAHAWebhookCallReceived extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description:
|
||||
'The event is triggered when the call is received by the user.',
|
||||
@@ -271,7 +275,7 @@ class WAHAWebhookCallReceived extends WAHAWebhook {
|
||||
payload: CallData;
|
||||
}
|
||||
|
||||
class WAHAWebhookCallAccepted extends WAHAWebhook {
|
||||
export class WAHAWebhookCallAccepted extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description:
|
||||
'The event is triggered when the call is accepted by the user.',
|
||||
@@ -281,7 +285,7 @@ class WAHAWebhookCallAccepted extends WAHAWebhook {
|
||||
payload: CallData;
|
||||
}
|
||||
|
||||
class WAHAWebhookCallRejected extends WAHAWebhook {
|
||||
export class WAHAWebhookCallRejected extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description:
|
||||
'The event is triggered when the call is rejected by the user.',
|
||||
@@ -291,7 +295,7 @@ class WAHAWebhookCallRejected extends WAHAWebhook {
|
||||
payload: CallData;
|
||||
}
|
||||
|
||||
class WAHAWebhookLabelUpsert extends WAHAWebhook {
|
||||
export class WAHAWebhookLabelUpsert extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'The event is triggered when a label is created or updated',
|
||||
})
|
||||
@@ -300,7 +304,7 @@ class WAHAWebhookLabelUpsert extends WAHAWebhook {
|
||||
payload: Label;
|
||||
}
|
||||
|
||||
class WAHAWebhookLabelDeleted extends WAHAWebhook {
|
||||
export class WAHAWebhookLabelDeleted extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'The event is triggered when a label is deleted',
|
||||
})
|
||||
@@ -309,7 +313,7 @@ class WAHAWebhookLabelDeleted extends WAHAWebhook {
|
||||
payload: Label;
|
||||
}
|
||||
|
||||
class WAHAWebhookLabelChatAdded extends WAHAWebhook {
|
||||
export class WAHAWebhookLabelChatAdded extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'The event is triggered when a label is added to a chat',
|
||||
})
|
||||
@@ -318,7 +322,7 @@ class WAHAWebhookLabelChatAdded extends WAHAWebhook {
|
||||
payload: LabelChatAssociation;
|
||||
}
|
||||
|
||||
class WAHAWebhookLabelChatDeleted extends WAHAWebhook {
|
||||
export class WAHAWebhookLabelChatDeleted extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'The event is triggered when a label is deleted from a chat',
|
||||
})
|
||||
@@ -332,7 +336,7 @@ export class EnginePayload {
|
||||
data: any;
|
||||
}
|
||||
|
||||
class WAHAWebhookEngineEvent extends WAHAWebhook {
|
||||
export class WAHAWebhookEngineEvent extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'Internal engine event.',
|
||||
})
|
||||
@@ -340,28 +344,3 @@ class WAHAWebhookEngineEvent extends WAHAWebhook {
|
||||
|
||||
payload: EnginePayload;
|
||||
}
|
||||
|
||||
const WAHA_WEBHOOKS = [
|
||||
WAHAWebhookSessionStatus,
|
||||
WAHAWebhookMessage,
|
||||
WAHAWebhookMessageReaction,
|
||||
WAHAWebhookMessageAny,
|
||||
WAHAWebhookMessageAck,
|
||||
WAHAWebhookMessageRevoked,
|
||||
WAHAWebhookStateChange,
|
||||
WAHAWebhookGroupJoin,
|
||||
WAHAWebhookGroupLeave,
|
||||
WAHAWebhookPresenceUpdate,
|
||||
WAHAWebhookPollVote,
|
||||
WAHAWebhookPollVoteFailed,
|
||||
WAHAWebhookChatArchive,
|
||||
WAHAWebhookCallReceived,
|
||||
WAHAWebhookCallAccepted,
|
||||
WAHAWebhookCallRejected,
|
||||
WAHAWebhookLabelUpsert,
|
||||
WAHAWebhookLabelDeleted,
|
||||
WAHAWebhookLabelChatAdded,
|
||||
WAHAWebhookLabelChatDeleted,
|
||||
WAHAWebhookEngineEvent,
|
||||
];
|
||||
export { WAHA_WEBHOOKS };
|
||||
@@ -0,0 +1,58 @@
|
||||
import {
|
||||
WebhookGroupV2Join,
|
||||
WebhookGroupV2Leave,
|
||||
WebhookGroupV2Participants,
|
||||
WebhookGroupV2Update,
|
||||
} from '@waha/structures/groups.webhooks.dto';
|
||||
import {
|
||||
WAHAWebhookCallAccepted,
|
||||
WAHAWebhookCallReceived,
|
||||
WAHAWebhookCallRejected,
|
||||
WAHAWebhookChatArchive,
|
||||
WAHAWebhookEngineEvent,
|
||||
WAHAWebhookGroupJoin,
|
||||
WAHAWebhookGroupLeave,
|
||||
WAHAWebhookLabelChatAdded,
|
||||
WAHAWebhookLabelChatDeleted,
|
||||
WAHAWebhookLabelDeleted,
|
||||
WAHAWebhookLabelUpsert,
|
||||
WAHAWebhookMessage,
|
||||
WAHAWebhookMessageAck,
|
||||
WAHAWebhookMessageAny,
|
||||
WAHAWebhookMessageReaction,
|
||||
WAHAWebhookMessageRevoked,
|
||||
WAHAWebhookPollVote,
|
||||
WAHAWebhookPollVoteFailed,
|
||||
WAHAWebhookPresenceUpdate,
|
||||
WAHAWebhookSessionStatus,
|
||||
WAHAWebhookStateChange,
|
||||
} from '@waha/structures/webhooks.dto';
|
||||
|
||||
const WAHA_WEBHOOKS = [
|
||||
WAHAWebhookSessionStatus,
|
||||
WAHAWebhookMessage,
|
||||
WAHAWebhookMessageReaction,
|
||||
WAHAWebhookMessageAny,
|
||||
WAHAWebhookMessageAck,
|
||||
WAHAWebhookMessageRevoked,
|
||||
WebhookGroupV2Join,
|
||||
WebhookGroupV2Leave,
|
||||
WebhookGroupV2Update,
|
||||
WebhookGroupV2Participants,
|
||||
WAHAWebhookPresenceUpdate,
|
||||
WAHAWebhookPollVote,
|
||||
WAHAWebhookPollVoteFailed,
|
||||
WAHAWebhookChatArchive,
|
||||
WAHAWebhookCallReceived,
|
||||
WAHAWebhookCallAccepted,
|
||||
WAHAWebhookCallRejected,
|
||||
WAHAWebhookLabelUpsert,
|
||||
WAHAWebhookLabelDeleted,
|
||||
WAHAWebhookLabelChatAdded,
|
||||
WAHAWebhookLabelChatDeleted,
|
||||
WAHAWebhookEngineEvent,
|
||||
WAHAWebhookGroupJoin,
|
||||
WAHAWebhookGroupLeave,
|
||||
WAHAWebhookStateChange,
|
||||
];
|
||||
export { WAHA_WEBHOOKS };
|
||||
+1
-1
@@ -33,7 +33,7 @@ export function getEngineName(): string {
|
||||
}
|
||||
|
||||
export const VERSION: WAHAEnvironment = {
|
||||
version: '2025.2.3',
|
||||
version: '2025.2.8',
|
||||
engine: getEngineName(),
|
||||
tier: getWAHAVersion(),
|
||||
browser: getBrowserExecutablePath(),
|
||||
|
||||
+2
-2
@@ -2,11 +2,11 @@
|
||||
"waha": {
|
||||
"gows": {
|
||||
"repo": "devlikeapro/gows",
|
||||
"ref": "8b31d6b88166e0962ca47a3dfbd256a42f09dbc1"
|
||||
"ref": "v1.0.0"
|
||||
},
|
||||
"dashboard": {
|
||||
"repo": "devlikeapro/dashboard",
|
||||
"ref": "8c455ec6436398000a26df2a853a1d7dbe8567df"
|
||||
"ref": "d6cd941f8d7621f401305e03ad2c1db8dc0a64f0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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=6394c30b4011b688d041b604abe27e05a6ef1d03"
|
||||
resolution: "@adiwajshing/baileys@https://github.com/devlikeapro/Baileys.git#commit=dcc5d0ea0827c0d57ceda4f94b9cdb260760b77e"
|
||||
dependencies:
|
||||
"@adiwajshing/keyed-db": ^0.2.4
|
||||
"@hapi/boom": ^9.1.3
|
||||
@@ -40,7 +40,7 @@ __metadata:
|
||||
optional: true
|
||||
sharp:
|
||||
optional: true
|
||||
checksum: 84f85787f1c02fe19c3d5f413292fb867584dd2576a5426b231f0d3d5aaed121a7bb9d32e52145dbb332880d3f58acf4323783cba6577c1489e4cf694c22b912
|
||||
checksum: 24e16d0b11dbb83a562e333fcc11f9489d4f3c6735c12f726eead983dd3bbc0f4dbce9c486d849b26ddcb0e2be68972d205d21837aabfcb42ebd8b115958f053
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in new issue
Block a user