[core] ChatWoot - contacts pull

fix #1231
This commit is contained in:
devlikepro committed 2025-11-04 14:00:21 +07:00
1 parent cc056dca9c
commit c001d7fed6
43 files changed
+2516 -138

No files matched your search

+1
View File
@@ -83,6 +83,7 @@
"long": "^5.3.2",
"mime-types": "^2.1.27",
"mongodb": "6.9.0",
"ms": "^2.1.3",
"mustache": "^4.2.0",
"nestjs-pino": "^4.1.0",
"node-cache": "5.1.2",
+14
View File
@@ -7,6 +7,12 @@ import { DelayedError } from 'bullmq';
import { PinoLogger } from 'nestjs-pino';
import { Logger } from 'pino';
export interface JobDataTimeout {
timeout?: {
job?: number;
};
}
/**
* Base class for app consumers that provides common functionality
* like mutex locking, logging setup, and error handling.
@@ -27,6 +33,14 @@ export abstract class AppConsumer extends BaseWorkerHost {
});
}
protected signal(job) {
if (job.data?.timeout?.job != null) {
return AbortSignal.timeout(job.data?.timeout?.job);
}
// Aka never abort
return new AbortController().signal;
}
/**
* Executes a job with mutex locking to ensure only one job processes a specific resource at a time.
* @param job The job to process
+3 -3
View File
@@ -60,8 +60,8 @@ base = base.replace(/\/+$/, '');
export function JobLink(job: Job): { text: string; url: string } {
const text = `${job.queueName} => ${job.id}`;
const url = `${base}/jobs/queue/${encodeURIComponent(job.queueName)}/${
job.id
}`;
const queue = encodeURIComponent(job.queueName);
const id = encodeURIComponent(job.id);
const url = `${base}/jobs/queue/${queue}/${id}`;
return { text: text, url: url };
}
+10
View File
@@ -28,6 +28,7 @@ import { ChatWootScheduleService } from './services/ChatWootScheduleService';
import { ChatWootWAHAQueueService } from './services/ChatWootWAHAQueueService';
import { ChatWootConversationCreatedConsumer } from './consumers/inbox/conversation_created';
import { ChatWootConversationStatusChangedConsumer } from '@waha/apps/chatwoot/consumers/inbox/conversation_status_changed';
import { TaskContactsPullConsumer } from '@waha/apps/chatwoot/consumers/task/contacts.pull';
const CONTROLLERS = [ChatwootWebhookController, ChatwootLocalesController];
@@ -40,6 +41,10 @@ const IMPORTS = lodash.flatten([
name: QueueName.SCHEDULED_CHECK_VERSION,
defaultJobOptions: merge(NoRetriesJobOptions, JobRemoveOptions),
}),
RegisterAppQueue({
name: QueueName.TASK_CONTACTS_PULL,
defaultJobOptions: merge(ExponentialRetriesJobOptions, JobRemoveOptions),
}),
RegisterAppQueue({
name: QueueName.WAHA_MESSAGE_ANY,
defaultJobOptions: merge(ExponentialRetriesJobOptions, JobRemoveOptions),
@@ -98,14 +103,19 @@ const PROVIDERS = [
ChatWootConversationCreatedConsumer,
ChatWootConversationStatusChangedConsumer,
ChatWootInboxCommandsConsumer,
// Tasks
TaskContactsPullConsumer,
// WAHA
WAHASessionStatusConsumer,
WAHAMessageAnyConsumer,
WAHAMessageReactionConsumer,
WAHAMessageEditedConsumer,
WAHAMessageRevokedConsumer,
WAHAMessageAckConsumer,
// Scheduled
MessageCleanupConsumer,
CheckVersionConsumer,
// Services
ChatWootWAHAQueueService,
ChatWootQueueService,
ChatWootScheduleService,
+79
View File
@@ -0,0 +1,79 @@
import { CommandContext } from '@waha/apps/chatwoot/cli/types';
import { QueueName } from '@waha/apps/chatwoot/consumers/QueueName';
import {
ContactsPullOptions,
ContactsPullStatusMessage,
} from '@waha/apps/chatwoot/consumers/task/contacts.pull';
import { Job, JobsOptions, Queue } from 'bullmq';
import { ChatWootScheduleService } from '@waha/apps/chatwoot/services/ChatWootScheduleService';
import { ILogger } from '@waha/apps/app_sdk/ILogger';
import { JobDataTimeout } from '@waha/apps/app_sdk/AppConsumer';
export async function ContactsPullStart(
ctx: CommandContext,
options: ContactsPullOptions,
jobOptions: JobsOptions & JobDataTimeout,
) {
const jobId = ChatWootScheduleService.SingleJobId(ctx.app);
const job: Job = await ctx.queues.importContacts.getJob(jobId);
if (job) {
const state = await job.getState();
const done = state === 'completed' || state === 'failed';
if (!done) {
const msg = ctx.l.r('cli.cmd.contacts.pull.already-running');
await ctx.conversation.incoming(msg);
return;
}
// Remove already done job
await ContactsPullRemove(ctx.queues.importContacts, ctx.app, ctx.logger);
}
const opts: JobsOptions = {
...jobOptions,
jobId: jobId,
};
const data = {
app: ctx.app,
session: ctx.session,
timeout: {
job: jobOptions.timeout.job,
},
options: options,
};
await ctx.queues.importContacts.add(QueueName.TASK_CONTACTS_PULL, data, opts);
const msg = ctx.l.r('cli.cmd.contacts.pull.queued', {
batch: options.batch,
});
await ctx.conversation.incoming(msg);
}
export async function ContactsPullStatus(ctx: CommandContext) {
const jobId = ChatWootScheduleService.SingleJobId(ctx.app);
const job: Job | null = await ctx.queues.importContacts.getJob(jobId);
if (!job) {
const msg = ctx.l.r('cli.cmd.contacts.status.not-found');
await ctx.conversation.incoming(msg);
return;
}
const state = await job.getState();
const msg = ContactsPullStatusMessage(ctx.l, job, state);
await ctx.conversation.incoming(msg);
}
export async function ContactsPullRemove(
queue: Queue,
app: string,
logger: ILogger,
): Promise<boolean> {
const jobId = ChatWootScheduleService.SingleJobId(app);
const job: Job = await queue.getJob(jobId);
if (!job) {
logger.info('Pull Contacts job has already been removed');
return false;
}
await job.remove();
return true;
}
+112 -3
View File
@@ -1,7 +1,21 @@
import { Command, CommanderError, OutputConfiguration } from 'commander';
import {
Argument,
Command,
CommanderError,
Option,
OutputConfiguration,
} from 'commander';
import argvSplit from 'string-argv';
import { BufferedOutput } from '@waha/apps/chatwoot/cli/BufferedOutput';
import { buildFormatHelp, fullCommandPath } from '@waha/apps/chatwoot/cli/help';
import { BufferedOutput } from '@waha/apps/chatwoot/cli/utils/BufferedOutput';
import {
buildFormatHelp,
fullCommandPath,
} from '@waha/apps/chatwoot/cli/utils/help';
import {
JobAttemptsOption,
JobTimeoutOption,
ParseMS,
} from '@waha/apps/chatwoot/cli/utils/options';
import { CommandContext } from '@waha/apps/chatwoot/cli/types';
import {
SessionLogout,
@@ -15,6 +29,13 @@ import {
import { ServerReboot, ServerStatus } from '@waha/apps/chatwoot/cli/cmd.server';
import { ChatWootCommandsConfig } from '@waha/apps/chatwoot/dto/config.dto';
import { CommandDisabled } from '@waha/apps/chatwoot/cli/cmd.disabled';
import {
ContactsPullStart,
ContactsPullStatus,
} from '@waha/apps/chatwoot/cli/cmd.contacts';
import { ContactsPullOptions } from '@waha/apps/chatwoot/consumers/task/contacts.pull';
import { JobsOptions } from 'bullmq';
import { JobDataTimeout } from '@waha/apps/app_sdk/AppConsumer';
function BuildProgram(
commands: ChatWootCommandsConfig,
@@ -24,6 +45,7 @@ function BuildProgram(
const l = ctx.l;
const SessionGroup = l.r('cli.cmd.root.sub.session');
const ServerGroup = l.r('cli.cmd.root.sub.server');
const SyncGroup = l.r('cli.cmd.root.sub.sync');
const program = new Command();
program
@@ -31,13 +53,18 @@ function BuildProgram(
.description(l.r('cli.cmd.root.description'))
.exitOverride()
.configureOutput(output)
.showSuggestionAfterError(true)
.commandsGroup(SessionGroup)
.commandsGroup(ServerGroup)
.commandsGroup(SyncGroup)
.configureHelp({
helpWidth: 200,
styleOptionTerm(str: string) {
return `- \`${str}\``;
},
styleArgumentTerm(str: string) {
return `- **${str}**`;
},
styleUsage(str) {
return `**${str}**`;
},
@@ -109,6 +136,88 @@ function BuildProgram(
.helpGroup(SessionGroup)
.action(() => SessionScreenshot(ctx));
//
// Pull Contacts
//
program
.command('contacts')
.alias('/contacts')
.summary(l.r('cli.cmd.contacts.summary'))
.description(l.r('cli.cmd.contacts.description'))
.helpGroup(SyncGroup)
.addArgument(
new Argument(
'[action]',
l.r('cli.cmd.contacts.action.description'),
).choices(['pull', 'status']),
)
.addOption(
new Option('--avatar <mode>', l.r('cli.cmd.contacts.pull.option.avatar'))
.choices(['skip', 'if-missing', 'update'])
.default('if-missing'),
)
.option('--groups', l.r('cli.cmd.contacts.pull.option.groups'))
.option('--no-lids', l.r('cli.cmd.contacts.pull.option.no-lids'))
.option(
'--no-attributes',
l.r('cli.cmd.contacts.pull.option.no-attributes'),
)
.option(
'--batch <number>',
l.r('cli.cmd.contacts.pull.option.batch'),
100 as any,
)
.addOption(new JobAttemptsOption(l, 6))
.addOption(new JobTimeoutOption(l, '10m'))
.addOption(
new Option(
'--delay-contact <duration>',
l.r('cli.cmd.contacts.pull.option.delay-contact'),
)
.argParser(ParseMS)
.default(ParseMS('0.1s'), '0.1s'),
)
.addOption(
new Option(
'--delay-batch <duration>',
l.r('cli.cmd.contacts.pull.option.delay-batch'),
)
.argParser(ParseMS)
.default(ParseMS('1s'), '1s'),
)
.action(async (action, opts, cmd: Command) => {
if (!action) {
cmd.outputHelp();
return;
}
if (action === 'status') {
await ContactsPullStatus(ctx);
return;
}
const options: ContactsPullOptions = {
batch: opts.batch,
avatar: opts.avatar,
attributes: opts.attributes,
contacts: {
lids: opts.lids,
groups: opts.groups,
},
delay: {
contact: opts.delayContact,
batch: opts.delayBatch,
},
};
const jobOptions: JobsOptions & JobDataTimeout = {
attempts: opts.attempts,
timeout: {
job: opts.timeout,
},
};
await ContactsPullStart(ctx, options, jobOptions);
});
//
// Server
//
+6 -1
View File
@@ -2,11 +2,16 @@ import { WAHASelf } from '@waha/apps/chatwoot/session/WAHASelf';
import { Locale } from '@waha/apps/chatwoot/i18n/locale';
import { Conversation } from '../client/Conversation';
import { ILogger } from '@waha/apps/app_sdk/ILogger';
import { Queue } from 'bullmq';
export interface CommandContext {
app: string;
session: string;
logger: ILogger;
l: Locale;
session: string;
waha: WAHASelf;
conversation: Conversation;
queues: {
importContacts: Queue;
};
}
File renamed without changes.
+33
View File
@@ -0,0 +1,33 @@
import * as ms from 'ms';
import { Option } from 'commander';
import { Locale } from '@waha/apps/chatwoot/i18n/locale';
/**
* Parse human string into milliseconds
*/
export function ParseMS(value: string) {
const duration = ms(value);
if (duration == null) throw new Error(`Invalid duration: "${value}"`);
if (duration < 0) throw new Error(`Duration cannot be negative: "${value}"`);
return duration;
}
export function ParseSeconds(value: string): number {
const duration = ParseMS(value);
return Math.floor(duration / 1000);
}
export class JobAttemptsOption extends Option {
constructor(l: Locale, def: number) {
super('--attempts <number>', l.r('cli.cmd.options.job.attempts'));
this.default(def);
}
}
export class JobTimeoutOption extends Option {
constructor(l: Locale, def: string) {
super('--timeout <duration>', l.r('cli.cmd.options.job.timeout'));
this.argParser(ParseMS);
this.default(ParseMS(def));
}
}
@@ -3,7 +3,10 @@ import ChatwootClient, {
public_contact_create_update_payload,
} from '@figuro/chatwoot-sdk';
import { ILogger } from '@waha/apps/app_sdk/ILogger';
import { ContactService } from '@waha/apps/chatwoot/client/ContactService';
import {
AvatarUpdateMode,
ContactService,
} from '@waha/apps/chatwoot/client/ContactService';
import { Conversation } from '@waha/apps/chatwoot/client/Conversation';
import {
ContactIds,
@@ -40,7 +43,7 @@ export class ContactConversationService {
this.cache = CacheForConfig(config);
}
private async upsertByContactInfo(
public async upsertByContactInfo(
contactInfo: ContactInfo,
): Promise<ContactIds> {
const chatId = contactInfo.ChatId();
@@ -50,55 +53,41 @@ export class ContactConversationService {
return this.cache.get(chatId);
}
//
// Find or create contact
//
let contact = await this.contactService.searchByAnyID(chatId);
if (!contact) {
const request = await contactInfo.PublicContactCreate();
contact = await this.contactService.create(chatId, request);
}
let cwContact = await this.contactService.findOrCreateContact(contactInfo);
// Update custom attributes - always
const attributes = await contactInfo.Attributes();
this.logger.info(
`Updating if required contact custom attributes for chat.id: ${chatId}, contact.id: ${contact.data.id}`,
`Updating if required contact custom attributes for chat.id: ${chatId}, contact.id: ${cwContact.data.id}`,
);
const attributes = await contactInfo.Attributes();
await this.contactService.upsertCustomAttributes(
cwContact.data,
attributes,
);
await this.contactService.updateAvatar(
cwContact,
contactInfo,
AvatarUpdateMode.IF_MISSING,
);
await this.contactService.upsertCustomAttributes(contact.data, attributes);
// Update Avatar if nothing, but keep the original one if any
if (!contact.data.thumbnail) {
const avatarUrl = await contactInfo.AvatarUrl().catch((err) => {
this.logger.warn(
`Error getting avatar for chat.id from WhatsApp: ${chatId}`,
);
this.logger.warn(err);
return null;
});
if (avatarUrl) {
this.contactService.updateAvatarUrlSafe(contact.data.id, avatarUrl);
}
}
this.logger.info(
`Using contact for chat.id: ${chatId}, contact.id: ${contact.data.id}, contact.sourceId: ${contact.sourceId}`,
`Using contact for chat.id: ${chatId}, contact.id: ${cwContact.data.id}, contact.sourceId: ${cwContact.sourceId}`,
);
//
// Get or create a conversation for this inbox
//
const conversation = await this.conversationService.upsert({
id: contact.data.id,
sourceId: contact.sourceId,
id: cwContact.data.id,
sourceId: cwContact.sourceId,
});
this.logger.info(
`Using conversation for chat.id: ${chatId}, conversation.id: ${conversation.id}, contact.id: ${contact.sourceId}`,
`Using conversation for chat.id: ${chatId}, conversation.id: ${conversation.id}, contact.id: ${cwContact.sourceId}`,
);
// Save to cache
const ids = {
id: conversation.id,
sourceId: contact.sourceId,
sourceId: cwContact.sourceId,
};
this.cache.set(chatId, ids);
return ids;
@@ -14,12 +14,18 @@ import * as lodash from 'lodash';
import { AttributeKey } from '../const';
import { E164Parser } from '@waha/core/utils/PhoneJidNormalizer';
import { ContactInfo } from '@waha/apps/chatwoot/client/ContactConversationService';
export interface ContactResponse {
data: generic_id & contact;
sourceId: string;
}
export enum AvatarUpdateMode {
IF_MISSING,
ALWAYS,
}
export class ContactService {
constructor(
private config: ChatWootAPIConfig,
@@ -28,6 +34,16 @@ export class ContactService {
private logger: ILogger,
) {}
async findOrCreateContact(contactInfo: ContactInfo) {
const chatId = contactInfo.ChatId();
let contact = await this.searchByAnyID(chatId);
if (!contact) {
const request = await contactInfo.PublicContactCreate();
contact = await this.create(chatId, request);
}
return contact;
}
async searchByAnyID(chatId: string): Promise<ContactResponse | null> {
const payload: any[] = [
{
@@ -136,6 +152,28 @@ export class ContactService {
};
}
public async updateAvatar(
contact: ContactResponse,
contactInfo: ContactInfo,
mode: AvatarUpdateMode,
) {
// Update Avatar if nothing, but keep the original one if any
if (contact.data.thumbnail && mode == AvatarUpdateMode.IF_MISSING) {
return;
}
const chatId = contactInfo.ChatId();
const avatarUrl = await contactInfo.AvatarUrl().catch((err) => {
this.logger.warn(
`Error getting avatar for chat.id from WhatsApp: ${chatId}`,
);
this.logger.warn(err);
return null;
});
if (avatarUrl) {
this.updateAvatarUrlSafe(contact.data.id, avatarUrl);
}
}
public updateAvatarUrlSafe(contactId, avatarUrl: string) {
this.accountAPI.contacts
.update({
+5
View File
@@ -5,6 +5,11 @@ export enum QueueName {
SCHEDULED_MESSAGE_CLEANUP = 'chatwoot.scheduled | message.cleanup',
SCHEDULED_CHECK_VERSION = 'chatwoot.scheduled | check.version',
//
// Task
//
TASK_CONTACTS_PULL = 'chatwoot.task | contacts.pull',
//
// WAHA Events
//
+12 -6
View File
@@ -1,11 +1,11 @@
import { Processor } from '@nestjs/bullmq';
import { InjectQueue, Processor } from '@nestjs/bullmq';
import { JOB_CONCURRENCY } from '@waha/apps/app_sdk/constants';
import { ChatWootInboxMessageConsumer } from '@waha/apps/chatwoot/consumers/inbox/base';
import { QueueName } from '@waha/apps/chatwoot/consumers/QueueName';
import { DIContainer } from '@waha/apps/chatwoot/di/DIContainer';
import { SessionManager } from '@waha/core/abc/manager.abc';
import { RMutexService } from '@waha/modules/rmutex/rmutex.service';
import { Job } from 'bullmq';
import { Job, Queue } from 'bullmq';
import { PinoLogger } from 'nestjs-pino';
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
import { runText } from '@waha/apps/chatwoot/cli';
@@ -17,6 +17,8 @@ export class ChatWootInboxCommandsConsumer extends ChatWootInboxMessageConsumer
protected readonly manager: SessionManager,
log: PinoLogger,
rmutex: RMutexService,
@InjectQueue(QueueName.TASK_CONTACTS_PULL)
private readonly importContactsQueue: Queue,
) {
super(manager, log, rmutex, 'ChatWootInboxCommandsConsumer');
}
@@ -26,19 +28,23 @@ export class ChatWootInboxCommandsConsumer extends ChatWootInboxMessageConsumer
}
protected async Process(container: DIContainer, body, job: Job) {
const session = job.data.session;
const cmd = body.content;
this.logger.info(`Executing command '${cmd}' for session ${session}...`);
this.logger.info(
`Executing command '${cmd}' for session ${job.data.session}...`,
);
const repo = container.ContactConversationService();
const conversation = await repo.InboxNotifications();
const ctx: CommandContext = {
app: job.data.app,
session: job.data.session,
logger: this.logger,
l: container.Locale(),
session: session,
waha: container.WAHASelf(),
conversation: conversation,
queues: {
importContacts: this.importContactsQueue,
},
};
const commands = container.ChatWootConfig().commands;
const output = await runText(commands, ctx, cmd);
@@ -4,7 +4,6 @@ import { HasBeenRetried } from '@waha/apps/app_sdk/JobUtils';
import { MessageType } from '@waha/apps/chatwoot/client/types';
import { ScheduledData } from '@waha/apps/chatwoot/consumers/types';
import { DIContainer } from '@waha/apps/chatwoot/di/DIContainer';
import { ChatWootAppConfig } from '@waha/apps/chatwoot/dto/config.dto';
import { SessionManager } from '@waha/core/abc/manager.abc';
import { RMutexService } from '@waha/modules/rmutex/rmutex.service';
import { Job } from 'bullmq';
+109
View File
@@ -0,0 +1,109 @@
import { AppConsumer } from '@waha/apps/app_sdk/AppConsumer';
import { JobLoggerWrapper } from '@waha/apps/app_sdk/JobLoggerWrapper';
import { HasBeenRetried } from '@waha/apps/app_sdk/JobUtils';
import { MessageType } from '@waha/apps/chatwoot/client/types';
import { ScheduledData } from '@waha/apps/chatwoot/consumers/types';
import { DIContainer } from '@waha/apps/chatwoot/di/DIContainer';
import { SessionManager } from '@waha/core/abc/manager.abc';
import { RMutexService } from '@waha/modules/rmutex/rmutex.service';
import { Job } from 'bullmq';
import { PinoLogger } from 'nestjs-pino';
import { AppRepository } from '../../storage';
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
import { SignalRace } from '@waha/utils/abortable';
/**
* Base class for ChatWoot background task consumers
*/
export abstract class ChatWootTaskConsumer extends AppConsumer {
protected appRepository: AppRepository;
constructor(
protected readonly manager: SessionManager,
log: PinoLogger,
rmutex: RMutexService,
protected readonly consumerName: string,
) {
super('ChatWoot', consumerName, log, rmutex);
}
/**
* Gets the DIContainer for the specified app.
*/
protected async DIContainer(job: Job, appId: string): Promise<DIContainer> {
const knex = this.manager.store.getWAHADatabase();
this.appRepository = new AppRepository(knex);
const logger = new JobLoggerWrapper(job, this.logger);
const app = await this.appRepository.getById(appId);
return new DIContainer(app.pk, app.config, logger, knex);
}
protected abstract ErrorHeaderKey(): TKey | null;
/**
* Get the function that processes the scheduled job
* This method must be implemented by subclasses
*/
protected abstract Process(
container: DIContainer,
job: Job,
signal: AbortSignal,
): Promise<any>;
/**
* Process the job
* This method is called by the queue processor
*/
async processJob(job: Job<ScheduledData, any, string>): Promise<any> {
const signal = this.signal(job);
return SignalRace(this.ProcessAndReportStatus(job, signal), signal);
}
private async ProcessAndReportStatus(job: Job, signal: AbortSignal) {
try {
const container = await this.DIContainer(job, job.data.app);
const result = await this.Process(container, job, signal);
await this.ReportErrorRecovered(job);
return result;
} catch (err) {
this.ReportErrorForJob(job, err).catch((exc) =>
this.logger.error(`Failed to report error for job ${job.id}: ${exc}`),
);
throw err;
}
}
/**
* Report an error for a scheduled job
*/
protected async ReportErrorForJob(job: Job, err: any) {
const container = await this.DIContainer(job, job.data.app);
let header = this.ErrorHeaderKey()
? container.Locale().key(this.ErrorHeaderKey()).render()
: err.message || `${err}`;
header = `${job.queueName}: ${header}`;
const conversation = await container
.ContactConversationService()
.InboxNotifications();
const reporter = container.ChatWootErrorReporter(job);
await reporter.ReportError(conversation, header, MessageType.INCOMING, err);
throw err;
}
protected async ReportErrorRecovered(job: Job) {
if (!HasBeenRetried(job)) {
return;
}
const container = await this.DIContainer(job, job.data.app);
const conversation = await container
.ContactConversationService()
.InboxNotifications();
const reporter = container.ChatWootErrorReporter(job);
await reporter.ReportSucceeded(conversation, MessageType.INCOMING);
}
}
@@ -0,0 +1,245 @@
import { Processor } from '@nestjs/bullmq';
import * as lodash from 'lodash';
import { JOB_CONCURRENCY } from '@waha/apps/app_sdk/constants';
import { QueueName } from '@waha/apps/chatwoot/consumers/QueueName';
import { DIContainer } from '@waha/apps/chatwoot/di/DIContainer';
import { SessionManager } from '@waha/core/abc/manager.abc';
import { RMutexService } from '@waha/modules/rmutex/rmutex.service';
import { Job, JobState } from 'bullmq';
import { PinoLogger } from 'nestjs-pino';
import { ChatWootTaskConsumer } from '@waha/apps/chatwoot/consumers/task/base';
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
import { WAHASessionAPI } from '@waha/apps/chatwoot/session/WAHASelf';
import { WhatsAppContactInfo } from '@waha/apps/chatwoot/contacts/WhatsAppContactInfo';
import { ContactSortField } from '@waha/structures/contacts.dto';
import { SortOrder } from '@waha/structures/pagination.dto';
import {
AvatarUpdateMode,
ContactService,
} from '@waha/apps/chatwoot/client/ContactService';
import { Conversation } from '@waha/apps/chatwoot/client/Conversation';
import { Locale } from '@waha/apps/chatwoot/i18n/locale';
import { ILogger } from '@waha/apps/app_sdk/ILogger';
import { JobLink } from '@waha/apps/app_sdk/JobUtils';
import { sleep } from '@waha/utils/promiseTimeout';
import { isJidGroup, isPnUser, isLidUser } from '@waha/core/utils/jids';
export interface ContactsPullOptions {
batch: number;
avatar: 'skip' | 'if-missing' | 'update';
attributes: boolean;
contacts: {
groups: boolean;
lids: boolean;
};
delay: {
contact: number;
batch: number;
};
}
/**
* Task consumer that pulls WhatsApp contacts into ChatWoot
* Runs in batches to keep the job resumable and observable in ChatWoot
*/
@Processor(QueueName.TASK_CONTACTS_PULL, { concurrency: JOB_CONCURRENCY })
export class TaskContactsPullConsumer extends ChatWootTaskConsumer {
constructor(manager: SessionManager, log: PinoLogger, rmutex: RMutexService) {
super(manager, log, rmutex, TaskContactsPullConsumer.name);
}
protected ErrorHeaderKey(): TKey {
return null;
}
/**
* Process the contact pull job
* Streams batches of contacts from WAHA into ChatWoot storage
*/
protected async Process(
container: DIContainer,
job: Job,
signal?: AbortSignal,
): Promise<any> {
const options: ContactsPullOptions = job.data.options;
const locale = container.Locale();
const waha = container.WAHASelf();
const session = new WAHASessionAPI(job.data.session, waha);
const conversation = await container
.ContactConversationService()
.InboxNotifications();
const handler = new ContactsPullHandler(
signal,
container.Logger(),
conversation,
session,
locale,
container.ContactService(),
);
return await handler.handle(options, job);
}
}
interface Progress {
ok: number;
errors: number;
skipped: number;
}
const NullProgress: Progress = {
ok: 0,
errors: 0,
skipped: 0,
};
class ContactsPullHandler {
constructor(
private signal: AbortSignal,
private logger: ILogger,
private conversation: Conversation,
private session: WAHASessionAPI,
private l: Locale,
private contactService: ContactService,
) {}
async handle(options: ContactsPullOptions, job) {
const batch = options.batch;
let progress = lodash.merge({}, NullProgress, job.progress);
this.logger.info(
`Pulling contacts for session ${job.data.session} with batch size ${batch}...`,
);
if (progress.ok == 0 && progress.errors == 0 && progress.skipped == 0) {
await this.conversation.activity(
this.l.r('task.contacts.started', { progress: progress }),
);
} else {
await this.conversation.activity(
this.l.r('task.contacts.progress', { progress: progress }),
);
}
// fake 'null' first, so it's not empty list
// we overwrite it at the beginning of the loop
let contacts: any[] = [null];
while (contacts.length != 0) {
this.signal.throwIfAborted();
const processed = progress.ok + progress.errors + progress.skipped;
this.logger.info(
`Fetching contacts batch: offset=${processed}, limit=${batch}...`,
);
contacts = await this.session.getContacts(
{
offset: processed,
limit: batch,
sortBy: ContactSortField.ID,
sortOrder: SortOrder.ASC,
},
{ signal: this.signal },
);
for (const contact of contacts) {
try {
const chatId = contact.id;
if (isJidGroup(chatId)) {
if (!options.contacts.groups) {
this.logger.info(`Skipping group contact ${chatId}...`);
progress.skipped += 1;
await job.updateProgress(progress);
continue;
}
} else if (isLidUser(chatId)) {
if (!options.contacts.lids) {
this.logger.info(`Skipping PN contact ${chatId}...`);
progress.skipped += 1;
await job.updateProgress(progress);
continue;
}
this.logger.info(`Skipping LID contact ${chatId}...`);
} else if (!isPnUser(chatId)) {
this.logger.info(`Skipping non-phone-number contact ${chatId}...`);
progress.skipped += 1;
await job.updateProgress(progress);
continue;
}
this.logger.info(`Pulling ${chatId}...`);
await this.pullOneContact(options, chatId);
progress.ok += 1;
} catch (e) {
this.logger.error(`Error pulling contact ${contact.id}: ${e}`);
progress.errors += 1;
continue;
}
// update progress before signal fails
await job.updateProgress(progress);
this.signal.throwIfAborted();
await sleep(options.delay.contact);
}
await this.conversation.activity(
this.l.r('task.contacts.progress', { progress: progress }),
);
await sleep(options.delay.batch);
}
//
// Final report
//
job.progress = progress;
const msg = ContactsPullStatusMessage(this.l, job, 'completed');
await this.conversation.incoming(msg);
}
private async pullOneContact(options: ContactsPullOptions, chatId: string) {
// Contact
const contactInfo = WhatsAppContactInfo(this.session, chatId, this.l);
let cwContact = await this.contactService.findOrCreateContact(contactInfo);
// Attributes
if (options.attributes) {
const attributes = await contactInfo.Attributes();
await this.contactService.upsertCustomAttributes(
cwContact.data,
attributes,
);
}
// Avatar
switch (options.avatar) {
case 'if-missing':
await this.contactService.updateAvatar(
cwContact,
contactInfo,
AvatarUpdateMode.IF_MISSING,
);
break;
case 'update':
await this.contactService.updateAvatar(
cwContact,
contactInfo,
AvatarUpdateMode.ALWAYS,
);
break;
}
}
}
export function ContactsPullStatusMessage(
l: Locale,
job,
state: JobState | 'unknown',
) {
const details = JobLink(job);
job.progress = lodash.merge({}, NullProgress, job.progress);
const payload = {
error: state === 'failed' || job.progress.errors > 0,
state: lodash.capitalize(state),
progress: job.progress,
details: details,
job: {
timestamp: l.FormatTimestampSec(job.timestamp),
processedOn: l.FormatTimestampSec(job.processedOn),
finishedOn: l.FormatTimestampSec(job.finishedOn),
},
};
return l.r('task.contacts.status', payload);
}
+1 -1
View File
@@ -109,7 +109,7 @@ export class DIContainer {
* @returns ContactService instance
*/
@CacheSync()
private ContactService(): ContactService {
public ContactService(): ContactService {
return new ContactService(
this.config,
this.AccountAPI(),
+21 -1
View File
@@ -64,7 +64,22 @@ export class Locale {
return date.toLocaleString(this.locale, options);
}
FormatTimestamp(timestamp: Long | string | number | null): string | null {
FormatDatetimeSec(date: Date | null) {
if (!date) {
return null;
}
const options: any = this.strings['datetime'] || {};
options.second = '2-digit';
options.timeZone = options.timeZone || options.timezone || process.env.TZ;
return date.toLocaleString(this.locale, options);
}
FormatTimestampSec(timestamp: any) {
const date = this.ParseTimestamp(timestamp);
return this.FormatDatetimeSec(date);
}
ParseTimestamp(timestamp: Long | string | number | null): Date | null {
const value = ensureNumber(timestamp);
if (!value) {
return undefined;
@@ -77,6 +92,11 @@ export class Locale {
if (Number.isNaN(date.getTime())) {
return undefined;
}
return date;
}
FormatTimestamp(timestamp: Long | string | number | null): string | null {
const date = this.ParseTimestamp(timestamp);
return this.FormatDatetime(date);
}
}
+90
View File
@@ -117,6 +117,9 @@ app.updated.message: |-
✅ تم تحديث إعدادات جلسة واتساب **{{{name}}}**.
cli.usage.arguments: |-
[الإجراء]
cli.usage.command: |-
[الأمر]
@@ -138,6 +141,9 @@ cli.cmd.root.sub.session: |-
cli.cmd.root.sub.server: |-
🖥️ **الخادم**:
cli.cmd.root.sub.sync: |-
🔄️ **المزامنة**:
cli.cmd.session.status.description: |-
اطّلع على حالة الجلسة الحالية.
@@ -159,6 +165,62 @@ cli.cmd.session.qr.description: |-
cli.cmd.session.screenshot.description: |-
التقط لقطة شاشة للجلسة (متاح فقط مع محرك **WEBJS**).
cli.cmd.contacts.summary: |-
اسحب جهات اتصال واتساب.
cli.cmd.contacts.description: |-
👤 شغِّل `contacts pull` لبدء المزامنة أو `contacts status` لمتابعة التقدم.
cli.cmd.contacts.action.description: |-
استخدم `start` لبدء سحب جهات الاتصال أو `status` لمراجعة المهمة النشطة.
cli.cmd.contacts.pull.description: |-
ابدأ بسحب جهات اتصال واتساب إلى ChatWoot.
cli.cmd.contacts.pull.option.batch: |-
عدد جهات الاتصال التي يتم سحبها في كل دفعة.
cli.cmd.contacts.pull.option.delay-contact: |-
التأخير بين مزامنة كل جهة اتصال لتجنّب حدود معدل سحب الصور.
cli.cmd.contacts.pull.option.delay-batch: |-
التأخير بين دفعات جهات الاتصال.
cli.cmd.contacts.pull.option.avatar: |-
اختر كيفية سحب الصور الرمزية: `skip` أو `if-missing` أو `update`.
cli.cmd.contacts.pull.option.groups: |-
قم بتضمين جهات اتصال مجموعات واتساب في المزامنة.
cli.cmd.contacts.pull.option.no-lids: |-
تخطَّ جهات اتصال واتساب LID (معرّفات الأجهزة المرتبطة JID) في المزامنة.
cli.cmd.contacts.pull.option.no-attributes: |-
لا تسحب سمات جهة الاتصال إذا كانت موجودة بالفعل (هذا يسرّع العملية).
cli.cmd.contacts.pull.already-running: |-
⏳ مهمة **سحب جهات الاتصال** تعمل بالفعل. انتظر حتى تنتهي أو أوقفها قبل بدء واحدة جديدة.
أرسل `contacts status` للاطلاع على التفاصيل.
cli.cmd.contacts.pull.queued: |-
👥 تمت جدولة مهمة **سحب جهات الاتصال**.
سنشارك تحديث التقدم كل **{{batch}}** جهة اتصال.
قد يستغرق سحب جهات الاتصال بعض الوقت، لذا تحقق من آخر حالة في أي وقت باستخدام `contacts status`.
cli.cmd.options.job.attempts: |-
عدد مرات إعادة محاولة المهمة قبل اعتبارها فاشلة.
cli.cmd.options.job.timeout: |-
الحد الأقصى لمدة تشغيل المهمة قبل إعادة المحاولة (يدعم قيمًا مثل `30s` أو `5m` أو `1h`).
cli.cmd.contacts.status.description: |-
اعرض حالة مهمة السحب الحالية.
cli.cmd.contacts.status.not-found: |-
ℹ️ لم يتم العثور على مهمة **سحب جهات الاتصال** نشطة. ابدأ واحدة باستخدام `contacts pull`.
cli.cmd.server.description: |-
أدِر الخادم.
@@ -186,6 +248,34 @@ cli.help.options.defaultGroup: |-
cli.help.globalOptions.title: |-
**الخيارات العامة:**
task.contacts.status: |-
👤 سحب جهات الاتصال: **{{state}}**
- ✅ الناجحة: {{{progress.ok}}} جهة اتصال
- ⚪ تم تخطيها: {{{progress.skipped}}} جهة اتصال
- ⚠️ الأخطاء: {{{progress.errors}}} جهة اتصال
{{#job}}
⌛ الجدول الزمني:
- في قائمة الانتظار: {{{timestamp}}}
{{#processedOn}}
- وقت البدء: {{{processedOn}}}
{{/processedOn}}
{{#finishedOn}}
- وقت الانتهاء: {{{finishedOn}}}
{{/finishedOn}}
{{/job}}
{{#error}}
التفاصيل:
[{{{details.text}}}]({{{details.url}}})
{{/error}}
task.contacts.started: |-
جارٍ سحب جهات الاتصال...
task.contacts.progress: |-
سحب جهات الاتصال: ✅ {{progress.ok}}، ⚪ {{progress.skipped}}، ⚠️ {{progress.errors}}
app.help.reminder: |-
أرسل **help** للحصول على قائمة الأوامر المتاحة.
+90
View File
@@ -117,6 +117,9 @@ app.updated.message: |-
✅ হোয়াটসঅ্যাপ সেশন **{{{name}}}** এর সেটিংস আপডেট করা হয়েছে।
cli.usage.arguments: |-
[অ্যাকশন]
cli.usage.command: |-
[কমান্ড]
@@ -138,6 +141,9 @@ cli.cmd.root.sub.session: |-
cli.cmd.root.sub.server: |-
🖥️ **সার্ভার**:
cli.cmd.root.sub.sync: |-
🔄️ **সিঙ্ক**:
cli.cmd.session.status.description: |-
বর্তমান সেশনের স্ট্যাটাস দেখুন।
@@ -159,6 +165,62 @@ cli.cmd.session.qr.description: |-
cli.cmd.session.screenshot.description: |-
সেশনের স্ক্রিনশট নিন (শুধুমাত্র **WEBJS** ইঞ্জিনে উপলব্ধ)।
cli.cmd.contacts.summary: |-
হোয়াটসঅ্যাপ কনট্যাক্টস আনুন।
cli.cmd.contacts.description: |-
👤 সিঙ্ক শুরু করতে `contacts pull` চালান বা অগ্রগতি দেখতে `contacts status` চালান।
cli.cmd.contacts.action.description: |-
`start` দিয়ে কনট্যাক্ট টানা শুরু করুন বা `status` দিয়ে সক্রিয় কাজ পরীক্ষা করুন।
cli.cmd.contacts.pull.description: |-
হোয়াটসঅ্যাপ কনট্যাক্টস ChatWoot-এ আনা শুরু করুন।
cli.cmd.contacts.pull.option.batch: |-
প্রতি ব্যাচে কত কনট্যাক্ট টানা হবে।
cli.cmd.contacts.pull.option.delay-contact: |-
প্রতিটি কনট্যাক্ট সিঙ্কের মাঝে বিলম্ব, যাতে অ্যাভাটার টানার সময় রেট লিমিট এড়ানো যায়।
cli.cmd.contacts.pull.option.delay-batch: |-
প্রতিটি ব্যাচের মাঝে বিলম্ব।
cli.cmd.contacts.pull.option.avatar: |-
কনট্যাক্টের অ্যাভাটার কীভাবে টানবেন নির্ধারণ করুন: `skip`, `if-missing`, অথবা `update`।
cli.cmd.contacts.pull.option.groups: |-
সিঙ্কে WhatsApp গ্রুপ কনট্যাক্ট অন্তর্ভুক্ত করুন।
cli.cmd.contacts.pull.option.no-lids: |-
সিঙ্কে WhatsApp LID কনট্যাক্ট (লিঙ্কড-ডিভাইস JID) বাদ দিন।
cli.cmd.contacts.pull.option.no-attributes: |-
কনট্যাক্ট আগেই থাকলে তার অ্যাট্রিবিউট টানবেন না (এতে প্রক্রিয়া দ্রুত হয়)।
cli.cmd.contacts.pull.already-running: |-
⏳ **Pull Contacts** কাজটি ইতিমধ্যেই চলছে। এটি শেষ হওয়া পর্যন্ত অপেক্ষা করুন বা নতুন কাজ শুরু করার আগে থামান।
বিস্তারিত জানতে `contacts status` পাঠান।
cli.cmd.contacts.pull.queued: |-
👥 **Pull Contacts** কাজটি কিউতে রয়েছে।
আমরা প্রতি **{{batch}}** কনট্যাক্টে অগ্রগতির আপডেট শেয়ার করব।
কনট্যাক্ট টানতে কিছুটা সময় লাগতে পারে - যেকোনো সময় `contacts status` দিয়ে সর্বশেষ অবস্থা দেখুন।
cli.cmd.options.job.attempts: |-
কোনো কাজ ব্যর্থ ঘোষণা করার আগে কয়বার পুনরায় চেষ্টা করা হবে।
cli.cmd.options.job.timeout: |-
কাজ পুনরায় চেষ্টা করার আগে সর্বোচ্চ চালনার সময় (যেমন `30s`, `5m`, `1h`)।
cli.cmd.contacts.status.description: |-
চলমান টানার কাজের অবস্থা দেখান।
cli.cmd.contacts.status.not-found: |-
ℹ️ কোনো সক্রিয় **Pull Contacts** কাজ পাওয়া যায়নি। `contacts pull` দিয়ে একটি শুরু করুন।
cli.cmd.server.description: |-
সার্ভার পরিচালনা করুন।
@@ -186,6 +248,34 @@ cli.help.options.defaultGroup: |-
cli.help.globalOptions.title: |-
**গ্লোবাল বিকল্পসমূহ:**
task.contacts.status: |-
👤 কনট্যাক্ট টানা: **{{state}}**
- ✅ সফল: {{{progress.ok}}} কনট্যাক্ট
- ⚪ এড়িয়ে গেছে: {{{progress.skipped}}} কনট্যাক্ট
- ⚠️ ত্রুটি: {{{progress.errors}}} কনট্যাক্ট
{{#job}}
⌛ সময়রেখা:
- কিউ হয়েছে: {{{timestamp}}}
{{#processedOn}}
- শুরু হয়েছে: {{{processedOn}}}
{{/processedOn}}
{{#finishedOn}}
- শেষ হয়েছে: {{{finishedOn}}}
{{/finishedOn}}
{{/job}}
{{#error}}
বিস্তারিত:
[{{{details.text}}}]({{{details.url}}})
{{/error}}
task.contacts.started: |-
কনট্যাক্ট টানা হচ্ছে...
task.contacts.progress: |-
কনট্যাক্ট টানা: ✅ {{progress.ok}}, ⚪ {{progress.skipped}}, ⚠️ {{progress.errors}}
app.help.reminder: |-
উপলব্ধ কমান্ডগুলির তালিকা পেতে **help** পাঠান।
+90
View File
@@ -120,6 +120,9 @@ app.updated.message: |-
✅ Einstellungen für WhatsApp-Sitzung **{{{name}}}** wurden aktualisiert.
cli.usage.arguments: |-
[Aktion]
cli.usage.command: |-
[Befehl]
@@ -141,6 +144,9 @@ cli.cmd.root.sub.session: |-
cli.cmd.root.sub.server: |-
🖥️ **Server**:
cli.cmd.root.sub.sync: |-
🔄️ **Synchronisierung**:
cli.cmd.session.status.description: |-
Aktuellen Sitzungsstatus abrufen.
@@ -162,6 +168,62 @@ cli.cmd.session.qr.description: |-
cli.cmd.session.screenshot.description: |-
Einen Screenshot der Sitzung aufnehmen (nur mit der **WEBJS**-Engine verfügbar).
cli.cmd.contacts.summary: |-
WhatsApp-Kontakte abrufen.
cli.cmd.contacts.description: |-
👤 Führen Sie `contacts pull` aus, um eine Synchronisierung zu starten, oder `contacts status`, um den Fortschritt zu prüfen.
cli.cmd.contacts.action.description: |-
Mit `start` den Abruf beginnen oder mit `status` den aktiven Auftrag prüfen.
cli.cmd.contacts.pull.description: |-
Beginnen Sie, WhatsApp-Kontakte in ChatWoot zu importieren.
cli.cmd.contacts.pull.option.batch: |-
Anzahl der Kontakte pro Batch.
cli.cmd.contacts.pull.option.delay-contact: |-
Verzögerung zwischen einzelnen Kontakten, um beim Laden von Avataren Rate Limits zu vermeiden.
cli.cmd.contacts.pull.option.delay-batch: |-
Verzögerung zwischen Kontakt-Batches.
cli.cmd.contacts.pull.option.avatar: |-
Legen Sie fest, wie Avatare geladen werden: `skip`, `if-missing` oder `update`.
cli.cmd.contacts.pull.option.groups: |-
WhatsApp-Gruppenkontakte in die Synchronisierung einbeziehen.
cli.cmd.contacts.pull.option.no-lids: |-
WhatsApp-LID-Kontakte (Linked-Device-JIDs) bei der Synchronisierung überspringen.
cli.cmd.contacts.pull.option.no-attributes: |-
Keine Kontaktattribute laden, wenn der Kontakt bereits existiert (beschleunigt den Vorgang).
cli.cmd.contacts.pull.already-running: |-
⏳ Der Auftrag **Pull Contacts** läuft bereits. Warten Sie, bis er abgeschlossen ist, oder stoppen Sie ihn, bevor Sie einen neuen starten.
Senden Sie `contacts status` für Details.
cli.cmd.contacts.pull.queued: |-
👥 Auftrag **Pull Contacts** in die Warteschlange gestellt.
Wir teilen nach jeweils **{{batch}}** Kontakten ein Fortschrittsupdate.
Das Abrufen kann etwas dauern – prüfen Sie den aktuellen Stand jederzeit mit `contacts status`.
cli.cmd.options.job.attempts: |-
Anzahl der erneuten Versuche, bevor der Auftrag als fehlgeschlagen markiert wird.
cli.cmd.options.job.timeout: |-
Maximale Laufzeit, bevor der Auftrag erneut versucht wird (unterstützt Werte wie `30s`, `5m`, `1h`).
cli.cmd.contacts.status.description: |-
Status des aktuellen Abrufauftrags anzeigen.
cli.cmd.contacts.status.not-found: |-
ℹ️ Kein aktiver **Pull Contacts** Auftrag gefunden. Starten Sie einen mit `contacts pull`.
cli.cmd.server.description: |-
Server verwalten.
@@ -189,6 +251,34 @@ cli.help.options.defaultGroup: |-
cli.help.globalOptions.title: |-
**Globale Optionen:**
task.contacts.status: |-
👤 Kontakte abrufen: **{{state}}**
- ✅ Erfolgreich: {{{progress.ok}}} Kontakte
- ⚪ Übersprungen: {{{progress.skipped}}} Kontakte
- ⚠️ Fehler: {{{progress.errors}}} Kontakte
{{#job}}
⌛ Zeitplan:
- Eingereiht: {{{timestamp}}}
{{#processedOn}}
- Start: {{{processedOn}}}
{{/processedOn}}
{{#finishedOn}}
- Ende: {{{finishedOn}}}
{{/finishedOn}}
{{/job}}
{{#error}}
Details:
[{{{details.text}}}]({{{details.url}}})
{{/error}}
task.contacts.started: |-
Kontakte werden abgerufen...
task.contacts.progress: |-
Kontakte abrufen: ✅ {{progress.ok}}, ⚪ {{progress.skipped}}, ⚠️ {{progress.errors}}
app.help.reminder: |-
Senden Sie **help**, um die Liste der verfügbaren Befehle zu erhalten.
+91 -1
View File
@@ -290,6 +290,9 @@ app.updated.message: |-
✅ Settings for WhatsApp session **{{{name}}}** have been updated.
cli.usage.arguments: |-
[action]
cli.usage.command: |-
[command]
@@ -311,6 +314,9 @@ cli.cmd.root.sub.session: |-
cli.cmd.root.sub.server: |-
🖥️ **Session**:
cli.cmd.root.sub.sync: |-
🔄️ **Sync**:
cli.cmd.session.status.description: |-
Get current session status.
@@ -332,6 +338,62 @@ cli.cmd.session.qr.description: |-
cli.cmd.session.screenshot.description: |-
Capture a session screenshot (available only with the **WEBJS** engine).
cli.cmd.contacts.summary: |-
Pull WhatsApp contacts.
cli.cmd.contacts.description: |-
👤 Run `contacts pull` to launch a sync or `contacts status` to inspect progress.
cli.cmd.contacts.action.description: |-
`start` to launch a contact pull or `status` to inspect the active job.
cli.cmd.contacts.pull.description: |-
Start pulling WhatsApp contacts into ChatWoot.
cli.cmd.contacts.pull.option.batch: |-
Number of contacts to pull in each batch.
cli.cmd.contacts.pull.option.delay-contact: |-
Delay between syncing each contact to avoid rate limiting in pulling avatars.
cli.cmd.contacts.pull.option.delay-batch: |-
Delay between batches of contacts.
cli.cmd.contacts.pull.option.avatar: |-
Choose how to pull avatars for contacts: `skip`, `if-missing`, or `update`.
cli.cmd.contacts.pull.option.groups: |-
Include WhatsApp group contacts in the sync.
cli.cmd.contacts.pull.option.no-lids: |-
Skip WhatsApp LID contacts (linked-device JIDs) in the sync.
cli.cmd.contacts.pull.option.no-attributes: |-
Do not pull contact attributes if contact already exists (it speeds up the process).
cli.cmd.contacts.pull.already-running: |-
⏳ **Pull Contacts** job is already running. Wait for it to finish or stop it before starting a new one.
Send `contacts status` for details.
cli.cmd.contacts.pull.queued: |-
👥 **Pull Contacts** job queued.
We'll share a progress update every **{{batch}}** contacts.
Pulling contacts may take a while - check the latest status anytime with `contacts status`.
cli.cmd.options.job.attempts: |-
Number of times to retry the job before marking it as failed.
cli.cmd.options.job.timeout: |-
Maximum time the job is allowed to run before it is retried (supports values like `30s`, `5m`, `1h`).
cli.cmd.contacts.status.description: |-
Show the status of the current pull job.
cli.cmd.contacts.status.not-found: |-
ℹ️ No active **Pull Contacts** job found. Start one with `contacts pull`.
cli.cmd.server.description: |-
Manage the server.
@@ -345,7 +407,7 @@ cli.cmd.server.reboot.option.force: |-
Force an immediate reboot.
cli.help.usage.title: |-
Usage: send a command to the conversation in the format:
Send a command to the conversation in the format:
cli.help.commands.defaultGroup: |-
**Commands:**
@@ -359,6 +421,34 @@ cli.help.options.defaultGroup: |-
cli.help.globalOptions.title: |-
**Global Options:**
task.contacts.status: |-
👤 Pull Contacts: **{{state}}**
- ✅ Successful: {{{progress.ok}}} contacts
- ⚪ Skipped: {{{progress.skipped}}} contacts
- ⚠️ Errors: {{{progress.errors}}} contacts
{{#job}}
⌛ Timeline:
- Queued: {{{timestamp}}}
{{#processedOn}}
- Start At: {{{processedOn}}}
{{/processedOn}}
{{#finishedOn}}
- Finished At: {{{finishedOn}}}
{{/finishedOn}}
{{/job}}
{{#error}}
Details:
[{{{details.text}}}]({{{details.url}}})
{{/error}}
task.contacts.started: |-
Pulling contacts...
task.contacts.progress: |-
Pull Contacts: ✅ {{progress.ok}}, ⚪ {{progress.skipped}}, ⚠️ {{progress.errors}}
app.help.reminder: |-
Send **help** to get the list of available commands.
+90
View File
@@ -117,6 +117,9 @@ app.updated.message: |-
✅ La configuración para la sesión de WhatsApp **{{{name}}}** ha sido actualizada.
cli.usage.arguments: |-
[acción]
cli.usage.command: |-
[comando]
@@ -138,6 +141,9 @@ cli.cmd.root.sub.session: |-
cli.cmd.root.sub.server: |-
🖥️ **Servidor**:
cli.cmd.root.sub.sync: |-
🔄️ **Sincronización**:
cli.cmd.session.status.description: |-
Obtén el estado actual de la sesión.
@@ -159,6 +165,62 @@ cli.cmd.session.qr.description: |-
cli.cmd.session.screenshot.description: |-
Captura una imagen de la sesión (disponible solo con el motor **WEBJS**).
cli.cmd.contacts.summary: |-
Obtener contactos de WhatsApp.
cli.cmd.contacts.description: |-
👤 Ejecuta `contacts pull` para iniciar una sincronización o `contacts status` para revisar el progreso.
cli.cmd.contacts.action.description: |-
Usa `start` para comenzar la extracción de contactos o `status` para revisar el trabajo activo.
cli.cmd.contacts.pull.description: |-
Empieza a importar contactos de WhatsApp en ChatWoot.
cli.cmd.contacts.pull.option.batch: |-
Cantidad de contactos a extraer en cada lote.
cli.cmd.contacts.pull.option.delay-contact: |-
Retraso entre cada contacto para evitar límites al obtener los avatares.
cli.cmd.contacts.pull.option.delay-batch: |-
Retraso entre los lotes de contactos.
cli.cmd.contacts.pull.option.avatar: |-
Elige cómo obtener los avatares de los contactos: `skip`, `if-missing` o `update`.
cli.cmd.contacts.pull.option.groups: |-
Incluir contactos de grupos de WhatsApp en la sincronización.
cli.cmd.contacts.pull.option.no-lids: |-
Omitir contactos LID de WhatsApp (JID de dispositivo vinculado) en la sincronización.
cli.cmd.contacts.pull.option.no-attributes: |-
No obtengas los atributos del contacto si ya existe (acelera el proceso).
cli.cmd.contacts.pull.already-running: |-
⏳ El trabajo **Pull Contacts** ya se está ejecutando. Espera a que finalice o deténlo antes de iniciar uno nuevo.
Envía `contacts status` para más detalles.
cli.cmd.contacts.pull.queued: |-
👥 Trabajo **Pull Contacts** en cola.
Compartiremos una actualización de progreso cada **{{batch}}** contactos.
La importación puede tardar; consulta el estado más reciente en cualquier momento con `contacts status`.
cli.cmd.options.job.attempts: |-
Número de intentos antes de marcar el trabajo como fallido.
cli.cmd.options.job.timeout: |-
Tiempo máximo que puede ejecutarse el trabajo antes de reintentarlo (admite valores como `30s`, `5m`, `1h`).
cli.cmd.contacts.status.description: |-
Mostrar el estado del trabajo de importación actual.
cli.cmd.contacts.status.not-found: |-
ℹ️ No se encontró ningún trabajo activo de **Pull Contacts**. Inicia uno con `contacts pull`.
cli.cmd.server.description: |-
Administra el servidor.
@@ -186,6 +248,34 @@ cli.help.options.defaultGroup: |-
cli.help.globalOptions.title: |-
**Opciones globales:**
task.contacts.status: |-
👤 Extracción de contactos: **{{state}}**
- ✅ Correctos: {{{progress.ok}}} contactos
- ⚪ Omitidos: {{{progress.skipped}}} contactos
- ⚠️ Errores: {{{progress.errors}}} contactos
{{#job}}
⌛ Cronología:
- En cola: {{{timestamp}}}
{{#processedOn}}
- Inicio: {{{processedOn}}}
{{/processedOn}}
{{#finishedOn}}
- Finalizó: {{{finishedOn}}}
{{/finishedOn}}
{{/job}}
{{#error}}
Detalles:
[{{{details.text}}}]({{{details.url}}})
{{/error}}
task.contacts.started: |-
Extrayendo contactos...
task.contacts.progress: |-
Extracción de contactos: ✅ {{progress.ok}}, ⚪ {{progress.skipped}}, ⚠️ {{progress.errors}}
app.help.reminder: |-
Envía **help** para obtener la lista de comandos disponibles.
+90
View File
@@ -117,6 +117,9 @@ app.updated.message: |-
✅ تنظیمات جلسه واتساپ **{{{name}}}** به‌روزرسانی شده است.
cli.usage.arguments: |-
[اقدام]
cli.usage.command: |-
[دستور]
@@ -138,6 +141,9 @@ cli.cmd.root.sub.session: |-
cli.cmd.root.sub.server: |-
🖥️ **سرور**:
cli.cmd.root.sub.sync: |-
🔄️ **همگام‌سازی**:
cli.cmd.session.status.description: |-
وضعیت فعلی جلسه را دریافت کنید.
@@ -159,6 +165,62 @@ cli.cmd.session.qr.description: |-
cli.cmd.session.screenshot.description: |-
از جلسه اسکرین‌شات بگیرید (فقط با موتور **WEBJS** در دسترس است).
cli.cmd.contacts.summary: |-
دریافت مخاطبان واتساپ.
cli.cmd.contacts.description: |-
👤 `contacts pull` را اجرا کنید تا همگام‌سازی آغاز شود یا `contacts status` را برای بررسی پیشرفت اجرا کنید.
cli.cmd.contacts.action.description: |-
از `start` برای آغاز دریافت مخاطبان یا از `status` برای بررسی کار فعال استفاده کنید.
cli.cmd.contacts.pull.description: |-
دریافت مخاطبان واتساپ در ChatWoot را آغاز کنید.
cli.cmd.contacts.pull.option.batch: |-
تعداد مخاطبانی که در هر دسته دریافت می‌شوند.
cli.cmd.contacts.pull.option.delay-contact: |-
تأخیر بین همگام‌سازی هر مخاطب برای جلوگیری از محدودیت نرخ هنگام دریافت آواتار.
cli.cmd.contacts.pull.option.delay-batch: |-
تأخیر بین دسته‌های مخاطبان.
cli.cmd.contacts.pull.option.avatar: |-
نحوه دریافت آواتار مخاطبان را انتخاب کنید: `skip`، `if-missing` یا `update`.
cli.cmd.contacts.pull.option.groups: |-
مخاطبان گروه‌های واتساپ را در همگام‌سازی قرار دهید.
cli.cmd.contacts.pull.option.no-lids: |-
مخاطبان LID واتساپ (شناسه‌های دستگاه متصل JID) را در همگام‌سازی نادیده بگیرید.
cli.cmd.contacts.pull.option.no-attributes: |-
اگر مخاطب از قبل وجود دارد ویژگی‌های او را دریافت نکنید (فرآیند را سریع‌تر می‌کند).
cli.cmd.contacts.pull.already-running: |-
⏳ کار **Pull Contacts** هم‌اکنون در حال اجراست. صبر کنید تا تمام شود یا قبل از شروع کار جدید آن را متوقف کنید.
برای جزئیات `contacts status` را ارسال کنید.
cli.cmd.contacts.pull.queued: |-
👥 کار **Pull Contacts** در صف قرار گرفت.
پس از هر **{{batch}}** مخاطب یک به‌روزرسانی پیشرفت ارسال می‌کنیم.
دریافت مخاطبان ممکن است زمان‌بر باشد؛ هر زمان با `contacts status` آخرین وضعیت را بررسی کنید.
cli.cmd.options.job.attempts: |-
تعداد دفعات تلاش مجدد پیش از علامت‌گذاری کار به‌عنوان ناموفق.
cli.cmd.options.job.timeout: |-
حداکثر مدت اجرای کار پیش از تلاش دوباره (از مقادیری مانند `30s`، `5m`، `1h` پشتیبانی می‌کند).
cli.cmd.contacts.status.description: |-
وضعیت کار دریافت فعلی را نمایش دهید.
cli.cmd.contacts.status.not-found: |-
ℹ️ هیچ کار فعال **Pull Contacts** یافت نشد. با `contacts pull` یکی آغاز کنید.
cli.cmd.server.description: |-
سرور را مدیریت کنید.
@@ -186,6 +248,34 @@ cli.help.options.defaultGroup: |-
cli.help.globalOptions.title: |-
**گزینه‌های عمومی:**
task.contacts.status: |-
👤 دریافت مخاطبان: **{{state}}**
- ✅ موفق: {{{progress.ok}}} مخاطب
- ⚪ نادیده‌گرفته‌شده: {{{progress.skipped}}} مخاطب
- ⚠️ خطا: {{{progress.errors}}} مخاطب
{{#job}}
⌛ جدول زمانی:
- در صف: {{{timestamp}}}
{{#processedOn}}
- زمان شروع: {{{processedOn}}}
{{/processedOn}}
{{#finishedOn}}
- زمان پایان: {{{finishedOn}}}
{{/finishedOn}}
{{/job}}
{{#error}}
جزئیات:
[{{{details.text}}}]({{{details.url}}})
{{/error}}
task.contacts.started: |-
در حال دریافت مخاطبان...
task.contacts.progress: |-
دریافت مخاطبان: ✅ {{progress.ok}}، ⚪ {{progress.skipped}}، ⚠️ {{progress.errors}}
app.help.reminder: |-
برای دریافت لیست دستورات موجود، **help** را ارسال کنید.
+90
View File
@@ -117,6 +117,9 @@ app.updated.message: |-
✅ Les paramètres de la session WhatsApp **{{{name}}}** ont été mis à jour.
cli.usage.arguments: |-
[action]
cli.usage.command: |-
[commande]
@@ -138,6 +141,9 @@ cli.cmd.root.sub.session: |-
cli.cmd.root.sub.server: |-
🖥️ **Serveur** :
cli.cmd.root.sub.sync: |-
🔄️ **Synchronisation** :
cli.cmd.session.status.description: |-
Obtenez l'état actuel de la session.
@@ -159,6 +165,62 @@ cli.cmd.session.qr.description: |-
cli.cmd.session.screenshot.description: |-
Prenez une capture d'écran de la session (disponible uniquement avec le moteur **WEBJS**).
cli.cmd.contacts.summary: |-
Importer les contacts WhatsApp.
cli.cmd.contacts.description: |-
👤 Exécutez `contacts pull` pour lancer une synchronisation ou `contacts status` pour suivre la progression.
cli.cmd.contacts.action.description: |-
Utilisez `start` pour démarrer l'import de contacts ou `status` pour consulter la tâche active.
cli.cmd.contacts.pull.description: |-
Commencez à importer les contacts WhatsApp dans ChatWoot.
cli.cmd.contacts.pull.option.batch: |-
Nombre de contacts importés par lot.
cli.cmd.contacts.pull.option.delay-contact: |-
Délai entre chaque contact pour éviter les limites lors du chargement des avatars.
cli.cmd.contacts.pull.option.delay-batch: |-
Délai entre les lots de contacts.
cli.cmd.contacts.pull.option.avatar: |-
Choisissez comment charger les avatars des contacts : `skip`, `if-missing` ou `update`.
cli.cmd.contacts.pull.option.groups: |-
Inclure les contacts de groupes WhatsApp dans la synchronisation.
cli.cmd.contacts.pull.option.no-lids: |-
Exclure les contacts LID WhatsApp (JID d'appareil lié) de la synchronisation.
cli.cmd.contacts.pull.option.no-attributes: |-
Ne pas importer les attributs si le contact existe déjà (accélère le processus).
cli.cmd.contacts.pull.already-running: |-
⏳ La tâche **Pull Contacts** est déjà en cours. Attendez qu'elle se termine ou arrêtez-la avant d'en démarrer une nouvelle.
Envoyez `contacts status` pour plus de détails.
cli.cmd.contacts.pull.queued: |-
👥 Tâche **Pull Contacts** mise en file d'attente.
Nous partagerons une mise à jour toutes les **{{batch}}** contacts.
L'import peut prendre du temps — consultez l'état le plus récent à tout moment avec `contacts status`.
cli.cmd.options.job.attempts: |-
Nombre de tentatives avant de marquer la tâche comme échouée.
cli.cmd.options.job.timeout: |-
Durée maximale autorisée avant une nouvelle tentative (accepte `30s`, `5m`, `1h`).
cli.cmd.contacts.status.description: |-
Afficher l'état de la tâche d'import en cours.
cli.cmd.contacts.status.not-found: |-
ℹ️ Aucune tâche **Pull Contacts** active trouvée. Démarrez-en une avec `contacts pull`.
cli.cmd.server.description: |-
Gérez le serveur.
@@ -186,6 +248,34 @@ cli.help.options.defaultGroup: |-
cli.help.globalOptions.title: |-
**Options globales :**
task.contacts.status: |-
👤 Import de contacts : **{{state}}**
- ✅ Réussis : {{{progress.ok}}} contacts
- ⚪ Ignorés : {{{progress.skipped}}} contacts
- ⚠️ Erreurs : {{{progress.errors}}} contacts
{{#job}}
⌛ Chronologie :
- En file d'attente : {{{timestamp}}}
{{#processedOn}}
- Début : {{{processedOn}}}
{{/processedOn}}
{{#finishedOn}}
- Fin : {{{finishedOn}}}
{{/finishedOn}}
{{/job}}
{{#error}}
Détails :
[{{{details.text}}}]({{{details.url}}})
{{/error}}
task.contacts.started: |-
Importation des contacts...
task.contacts.progress: |-
Import de contacts : ✅ {{progress.ok}}, ⚪ {{progress.skipped}}, ⚠️ {{progress.errors}}
app.help.reminder: |-
Envoyez **help** pour obtenir la liste des commandes disponibles.
+90
View File
@@ -117,6 +117,9 @@ app.updated.message: |-
✅ ההגדרות עבור הפעלת וואטסאפ **{{{name}}}** עודכנו.
cli.usage.arguments: |-
[פעולה]
cli.usage.command: |-
[פקודה]
@@ -138,6 +141,9 @@ cli.cmd.root.sub.session: |-
cli.cmd.root.sub.server: |-
🖥️ **שרת**:
cli.cmd.root.sub.sync: |-
🔄️ **סנכרון**:
cli.cmd.session.status.description: |-
קבל את סטטוס ההפעלה הנוכחי.
@@ -159,6 +165,62 @@ cli.cmd.session.qr.description: |-
cli.cmd.session.screenshot.description: |-
צלם צילום מסך של ההפעלה (זמין רק עם מנוע **WEBJS**).
cli.cmd.contacts.summary: |-
משיכת אנשי קשר של וואטסאפ.
cli.cmd.contacts.description: |-
👤 הרץ `contacts pull` כדי להתחיל סנכרון או `contacts status` כדי לבדוק את ההתקדמות.
cli.cmd.contacts.action.description: |-
השתמש ב-`start` כדי להתחיל למשוך אנשי קשר או ב-`status` כדי לבדוק את המשימה הפעילה.
cli.cmd.contacts.pull.description: |-
התחל למשוך אנשי קשר של וואטסאפ אל ChatWoot.
cli.cmd.contacts.pull.option.batch: |-
מספר אנשי הקשר שנמשכים בכל אצווה.
cli.cmd.contacts.pull.option.delay-contact: |-
השהיה בין כל איש קשר כדי להימנע ממגבלות בעת משיכת תמונות הפרופיל.
cli.cmd.contacts.pull.option.delay-batch: |-
השהיה בין אצוות של אנשי קשר.
cli.cmd.contacts.pull.option.avatar: |-
בחר כיצד למשוך תמונות פרופיל: `skip`, `if-missing` או `update`.
cli.cmd.contacts.pull.option.groups: |-
כלול בסנכרון אנשי קשר של קבוצות WhatsApp.
cli.cmd.contacts.pull.option.no-lids: |-
דלג בסנכרון על אנשי קשר מסוג LID של WhatsApp (כתובות JID של מכשירים מקושרים).
cli.cmd.contacts.pull.option.no-attributes: |-
אל תמשוך מאפייני איש קשר אם הוא כבר קיים (מאיץ את התהליך).
cli.cmd.contacts.pull.already-running: |-
⏳ המשימה **Pull Contacts** כבר פועלת. המתן שתסתיים או עצור אותה לפני שמתחילים חדשה.
שלח `contacts status` לקבלת פרטים.
cli.cmd.contacts.pull.queued: |-
👥 המשימה **Pull Contacts** בתור.
נעדכן בהתקדמות כל **{{batch}}** אנשי קשר.
משיכת אנשי קשר יכולה לקחת זמן – בדוק בכל עת את הסטטוס האחרון עם `contacts status`.
cli.cmd.options.job.attempts: |-
מספר ניסיונות החזרה לפני שסימנים את המשימה ככושלת.
cli.cmd.options.job.timeout: |-
משך הזמן המרבי שבו המשימה יכולה לפעול לפני ניסיון נוסף (תומך בערכים כגון `30s`, `5m`, `1h`).
cli.cmd.contacts.status.description: |-
הצגת הסטטוס של משימת המשיכה הנוכחית.
cli.cmd.contacts.status.not-found: |-
ℹ️ לא נמצאה משימת **Pull Contacts** פעילה. התחל אחת עם `contacts pull`.
cli.cmd.server.description: |-
נהל את השרת.
@@ -186,6 +248,34 @@ cli.help.options.defaultGroup: |-
cli.help.globalOptions.title: |-
**אפשרויות גלובליות:**
task.contacts.status: |-
👤 משיכת אנשי קשר: **{{state}}**
- ✅ הצלחות: {{{progress.ok}}} אנשי קשר
- ⚪ שדלגו עליהם: {{{progress.skipped}}} אנשי קשר
- ⚠️ שגיאות: {{{progress.errors}}} אנשי קשר
{{#job}}
⌛ ציר זמן:
- נכנסה לתור: {{{timestamp}}}
{{#processedOn}}
- התחלה: {{{processedOn}}}
{{/processedOn}}
{{#finishedOn}}
- סיום: {{{finishedOn}}}
{{/finishedOn}}
{{/job}}
{{#error}}
פרטים:
[{{{details.text}}}]({{{details.url}}})
{{/error}}
task.contacts.started: |-
אנשי קשר נמשכים...
task.contacts.progress: |-
משיכת אנשי קשר: ✅ {{progress.ok}}, ⚪ {{progress.skipped}}, ⚠️ {{progress.errors}}
app.help.reminder: |-
שלח **help** כדי לקבל את רשימת הפקודות הזמינות.
+90
View File
@@ -117,6 +117,9 @@ app.updated.message: |-
✅ व्हाट्सएप सत्र **{{{name}}}** की सेटिंग्स अपडेट की गई हैं।
cli.usage.arguments: |-
[क्रिया]
cli.usage.command: |-
[कमांड]
@@ -138,6 +141,9 @@ cli.cmd.root.sub.session: |-
cli.cmd.root.sub.server: |-
🖥️ **सर्वर**:
cli.cmd.root.sub.sync: |-
🔄️ **सिंक**:
cli.cmd.session.status.description: |-
वर्तमान सत्र की स्थिति प्राप्त करें।
@@ -159,6 +165,62 @@ cli.cmd.session.qr.description: |-
cli.cmd.session.screenshot.description: |-
सत्र का स्क्रीनशॉट लें (केवल **WEBJS** इंजन के साथ उपलब्ध)।
cli.cmd.contacts.summary: |-
व्हाट्सएप संपर्क प्राप्त करें।
cli.cmd.contacts.description: |-
👤 सिंक शुरू करने के लिए `contacts pull` चलाएँ या प्रगति देखने के लिए `contacts status` चलाएँ।
cli.cmd.contacts.action.description: |-
`start` से संपर्क खींचना शुरू करें या `status` से सक्रिय कार्य जांचें।
cli.cmd.contacts.pull.description: |-
व्हाट्सएप संपर्कों को ChatWoot में लाना शुरू करें।
cli.cmd.contacts.pull.option.batch: |-
प्रत्येक बैच में खींचे जाने वाले संपर्कों की संख्या।
cli.cmd.contacts.pull.option.delay-contact: |-
हर संपर्क के बीच देरी ताकि अवतार खींचते समय रेट लिमिट से बचा जा सके।
cli.cmd.contacts.pull.option.delay-batch: |-
संपर्क बैचों के बीच देरी।
cli.cmd.contacts.pull.option.avatar: |-
संपर्क अवतार कैसे खींचें चुनें: `skip`, `if-missing`, या `update`।
cli.cmd.contacts.pull.option.groups: |-
सिंक में WhatsApp समूह संपर्क शामिल करें।
cli.cmd.contacts.pull.option.no-lids: |-
सिंक में WhatsApp LID संपर्क (linked-device JID) को छोड़ें।
cli.cmd.contacts.pull.option.no-attributes: |-
यदि संपर्क पहले से मौजूद है तो उसके गुण न खींचें (इससे प्रक्रिया तेज होती है)।
cli.cmd.contacts.pull.already-running: |-
⏳ **Pull Contacts** कार्य पहले से ही चल रहा है। इसे समाप्त होने दें या नया शुरू करने से पहले रोकें।
विवरण के लिए `contacts status` भेजें।
cli.cmd.contacts.pull.queued: |-
👥 **Pull Contacts** कार्य कतार में जोड़ा गया।
हम हर **{{batch}}** संपर्क पर प्रगति अपडेट साझा करेंगे।
संपर्क खींचने में समय लग सकता है — किसी भी समय `contacts status` से नवीनतम स्थिति देखें।
cli.cmd.options.job.attempts: |-
कार्य को विफल मानने से पहले पुनः प्रयासों की संख्या।
cli.cmd.options.job.timeout: |-
कार्य को पुनः कोशिश करने से पहले अधिकतम चलने का समय (जैसे `30s`, `5m`, `1h`)।
cli.cmd.contacts.status.description: |-
वर्तमान खींचने वाले कार्य की स्थिति दिखाएँ।
cli.cmd.contacts.status.not-found: |-
ℹ️ कोई सक्रिय **Pull Contacts** कार्य नहीं मिला। `contacts pull` से एक शुरू करें।
cli.cmd.server.description: |-
सर्वर का प्रबंधन करें।
@@ -186,6 +248,34 @@ cli.help.options.defaultGroup: |-
cli.help.globalOptions.title: |-
**ग्लोबल विकल्प:**
task.contacts.status: |-
👤 संपर्क खींचना: **{{state}}**
- ✅ सफल: {{{progress.ok}}} संपर्क
- ⚪ छोड़े गए: {{{progress.skipped}}} संपर्क
- ⚠️ त्रुटियाँ: {{{progress.errors}}} संपर्क
{{#job}}
⌛ समयरेखा:
- कतार में: {{{timestamp}}}
{{#processedOn}}
- शुरू हुआ: {{{processedOn}}}
{{/processedOn}}
{{#finishedOn}}
- समाप्त: {{{finishedOn}}}
{{/finishedOn}}
{{/job}}
{{#error}}
विवरण:
[{{{details.text}}}]({{{details.url}}})
{{/error}}
task.contacts.started: |-
संपर्क खींचे जा रहे हैं...
task.contacts.progress: |-
संपर्क खींचना: ✅ {{progress.ok}}, ⚪ {{progress.skipped}}, ⚠️ {{progress.errors}}
app.help.reminder: |-
उपलब्ध कमांड की सूची प्राप्त करने के लिए **help** भेजें।
+90
View File
@@ -117,6 +117,9 @@ app.updated.message: |-
✅ Pengaturan untuk sesi WhatsApp **{{{name}}}** telah diperbarui.
cli.usage.arguments: |-
[aksi]
cli.usage.command: |-
[perintah]
@@ -138,6 +141,9 @@ cli.cmd.root.sub.session: |-
cli.cmd.root.sub.server: |-
🖥️ **Server**:
cli.cmd.root.sub.sync: |-
🔄️ **Sinkronisasi**:
cli.cmd.session.status.description: |-
Dapatkan status sesi saat ini.
@@ -159,6 +165,62 @@ cli.cmd.session.qr.description: |-
cli.cmd.session.screenshot.description: |-
Ambil tangkapan layar sesi (hanya tersedia dengan mesin **WEBJS**).
cli.cmd.contacts.summary: |-
Tarik kontak WhatsApp.
cli.cmd.contacts.description: |-
👤 Jalankan `contacts pull` untuk memulai sinkronisasi atau `contacts status` untuk melihat progres.
cli.cmd.contacts.action.description: |-
Gunakan `start` untuk mulai menarik kontak atau `status` untuk memeriksa pekerjaan aktif.
cli.cmd.contacts.pull.description: |-
Mulai menarik kontak WhatsApp ke ChatWoot.
cli.cmd.contacts.pull.option.batch: |-
Jumlah kontak yang ditarik di setiap batch.
cli.cmd.contacts.pull.option.delay-contact: |-
Jeda antar tiap kontak untuk menghindari pembatasan saat mengambil avatar.
cli.cmd.contacts.pull.option.delay-batch: |-
Jeda antar batch kontak.
cli.cmd.contacts.pull.option.avatar: |-
Pilih cara mengambil avatar kontak: `skip`, `if-missing`, atau `update`.
cli.cmd.contacts.pull.option.groups: |-
Sertakan kontak grup WhatsApp dalam sinkronisasi.
cli.cmd.contacts.pull.option.no-lids: |-
Lewati kontak LID WhatsApp (JID perangkat terhubung) saat sinkronisasi.
cli.cmd.contacts.pull.option.no-attributes: |-
Jangan ambil atribut kontak jika kontak sudah ada (mempercepat proses).
cli.cmd.contacts.pull.already-running: |-
⏳ Pekerjaan **Pull Contacts** sudah berjalan. Tunggu hingga selesai atau hentikan sebelum memulai yang baru.
Kirim `contacts status` untuk detail.
cli.cmd.contacts.pull.queued: |-
👥 Pekerjaan **Pull Contacts** masuk antrean.
Kami akan membagikan pembaruan progres setiap **{{batch}}** kontak.
Penarikan kontak bisa memakan waktu—periksa status terbaru kapan saja dengan `contacts status`.
cli.cmd.options.job.attempts: |-
Jumlah percobaan ulang sebelum pekerjaan dianggap gagal.
cli.cmd.options.job.timeout: |-
Durasi maksimum pekerjaan sebelum dicoba ulang (mendukung nilai seperti `30s`, `5m`, `1h`).
cli.cmd.contacts.status.description: |-
Tampilkan status pekerjaan penarikan saat ini.
cli.cmd.contacts.status.not-found: |-
ℹ️ Tidak ada pekerjaan **Pull Contacts** aktif. Mulai satu dengan `contacts pull`.
cli.cmd.server.description: |-
Kelola server.
@@ -186,6 +248,34 @@ cli.help.options.defaultGroup: |-
cli.help.globalOptions.title: |-
**Opsi global:**
task.contacts.status: |-
👤 Penarikan kontak: **{{state}}**
- ✅ Berhasil: {{{progress.ok}}} kontak
- ⚪ Dilewati: {{{progress.skipped}}} kontak
- ⚠️ Kesalahan: {{{progress.errors}}} kontak
{{#job}}
⌛ Linimasa:
- Masuk antrean: {{{timestamp}}}
{{#processedOn}}
- Mulai: {{{processedOn}}}
{{/processedOn}}
{{#finishedOn}}
- Selesai: {{{finishedOn}}}
{{/finishedOn}}
{{/job}}
{{#error}}
Detail:
[{{{details.text}}}]({{{details.url}}})
{{/error}}
task.contacts.started: |-
Menarik kontak...
task.contacts.progress: |-
Penarikan kontak: ✅ {{progress.ok}}, ⚪ {{progress.skipped}}, ⚠️ {{progress.errors}}
app.help.reminder: |-
Kirim **help** untuk mendapatkan daftar perintah yang tersedia.
+90
View File
@@ -117,6 +117,9 @@ app.updated.message: |-
✅ ਵਟਸਐਪ ਸੈਸ਼ਨ **{{{name}}}** ਲਈ ਸੈਟਿੰਗਾਂ ਅਪਡੇਟ ਕੀਤੀਆਂ ਗਈਆਂ ਹਨ।
cli.usage.arguments: |-
[ਕਾਰਵਾਈ]
cli.usage.command: |-
[ਕਮਾਂਡ]
@@ -138,6 +141,9 @@ cli.cmd.root.sub.session: |-
cli.cmd.root.sub.server: |-
🖥️ **ਸਰਵਰ**:
cli.cmd.root.sub.sync: |-
🔄️ **ਸਿੰਕ**:
cli.cmd.session.status.description: |-
ਮੌਜੂਦਾ ਸੈਸ਼ਨ ਦੀ ਸਥਿਤੀ ਪ੍ਰਾਪਤ ਕਰੋ।
@@ -159,6 +165,62 @@ cli.cmd.session.qr.description: |-
cli.cmd.session.screenshot.description: |-
ਸੈਸ਼ਨ ਦਾ ਸਕ੍ਰੀਨਸ਼ਾਟ ਲਓ (ਸਿਰਫ਼ **WEBJS** ਇੰਜਨ ਨਾਲ ਉਪਲਬਧ)।
cli.cmd.contacts.summary: |-
ਵਟਸਐਪ ਸੰਪਰਕ ਖਿੱਚੋ।
cli.cmd.contacts.description: |-
👤 ਸਿੰਕ ਸ਼ੁਰੂ ਕਰਨ ਲਈ `contacts pull` ਚਲਾਓ ਜਾਂ ਤਰੱਕੀ ਦੇਖਣ ਲਈ `contacts status` ਚਲਾਓ।
cli.cmd.contacts.action.description: |-
`start` ਨਾਲ ਸੰਪਰਕ ਖਿੱਚਣਾ ਸ਼ੁਰੂ ਕਰੋ ਜਾਂ `status` ਨਾਲ ਸਰਗਰਮ ਕੰਮ ਦੀ ਜਾਂਚ ਕਰੋ।
cli.cmd.contacts.pull.description: |-
ਵਟਸਐਪ ਸੰਪਰਕਾਂ ਨੂੰ ChatWoot ਵਿੱਚ ਲਿਆਉਣਾ ਸ਼ੁਰੂ ਕਰੋ।
cli.cmd.contacts.pull.option.batch: |-
ਹਰ ਬੈਚ ਵਿੱਚ ਖਿੱਚੇ ਜਾਣ ਵਾਲੇ ਸੰਪਰਕਾਂ ਦੀ ਗਿਣਤੀ।
cli.cmd.contacts.pull.option.delay-contact: |-
ਹਰ ਸੰਪਰਕ ਦੇ ਵਿਚਕਾਰ ਦੇਰੀ, ਤਾਂ ਜੋ ਅਵਤਾਰ ਖਿੱਚਦੇ ਸਮੇਂ ਰੇਟ ਲਿਮਟ ਤੋਂ ਬਚਿਆ ਜਾ ਸਕੇ।
cli.cmd.contacts.pull.option.delay-batch: |-
ਸੰਪਰਕ ਬੈਚਾਂ ਦੇ ਵਿਚਕਾਰ ਦੇਰੀ।
cli.cmd.contacts.pull.option.avatar: |-
ਚੁਣੋ ਕਿ ਅਵਤਾਰ ਕਿਵੇਂ ਖਿੱਚਣੇ ਹਨ: `skip`, `if-missing`, ਜਾਂ `update`।
cli.cmd.contacts.pull.option.groups: |-
ਸਿੰਕ ਵਿੱਚ WhatsApp ਗਰੁੱਪ ਸੰਪਰਕ ਸ਼ਾਮਲ ਕਰੋ।
cli.cmd.contacts.pull.option.no-lids: |-
ਸਿੰਕ ਵਿੱਚ WhatsApp LID ਸੰਪਰਕ (ਲਿੰਕਡ-ਡਿਵਾਈਸ JID) ਨੂੰ ਛੱਡੋ।
cli.cmd.contacts.pull.option.no-attributes: |-
ਜੇ ਸੰਪਰਕ ਪਹਿਲਾਂ ਹੀ ਮੌਜੂਦ ਹੈ ਤਾਂ ਉਸਦੇ ਗੁਣ ਨਾ ਖਿੱਚੋ (ਇਸ ਨਾਲ ਪ੍ਰਕਿਰਿਆ ਤੇਜ਼ ਹੁੰਦੀ ਹੈ)।
cli.cmd.contacts.pull.already-running: |-
⏳ **Pull Contacts** ਕੰਮ ਪਹਿਲਾਂ ਹੀ ਚੱਲ ਰਿਹਾ ਹੈ। ਇਸਦੇ ਮੁਕੰਮਲ ਹੋਣ ਦੀ ਉਡੀਕ ਕਰੋ ਜਾਂ ਨਵਾਂ ਸ਼ੁਰੂ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਇਸਨੂੰ ਰੋਕੋ।
ਵੇਰਵਿਆਂ ਲਈ `contacts status` ਭੇਜੋ।
cli.cmd.contacts.pull.queued: |-
👥 **Pull Contacts** ਕੰਮ ਕਤਾਰ ਵਿੱਚ ਲਗਾਇਆ ਗਿਆ ਹੈ।
ਅਸੀਂ ਹਰ **{{batch}}** ਸੰਪਰਕ ਤੋਂ ਬਾਅਦ ਤਰੱਕੀ ਅਪਡੇਟ ਸਾਂਝੇ ਕਰਾਂਗੇ।
ਸੰਪਰਕ ਖਿੱਚਣ ਵਿੱਚ ਸਮਾਂ ਲੱਗ ਸਕਦਾ ਹੈ — ਕਿਸੇ ਵੀ ਵੇਲੇ `contacts status` ਨਾਲ ਤਾਜ਼ਾ ਸਥਿਤੀ ਜਾਂਚੋ।
cli.cmd.options.job.attempts: |-
ਕੰਮ ਨੂੰ ਅਸਫਲ ਘੋਸ਼ਿਤ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ਾਂ ਦੀ ਗਿਣਤੀ।
cli.cmd.options.job.timeout: |-
ਕੰਮ ਨੂੰ ਮੁੜ ਕੋਸ਼ਿਸ਼ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਚੱਲਣ ਦਾ ਵੱਧ ਤੋਂ ਵੱਧ ਸਮਾਂ (ਜਿਵੇਂ `30s`, `5m`, `1h`)।
cli.cmd.contacts.status.description: |-
ਮੌਜੂਦਾ ਖਿੱਚਣ ਵਾਲੇ ਕੰਮ ਦੀ ਸਥਿਤੀ ਵੇਖਾਓ।
cli.cmd.contacts.status.not-found: |-
ℹ️ ਕੋਈ ਸਰਗਰਮ **Pull Contacts** ਕੰਮ ਨਹੀਂ ਮਿਲਿਆ। `contacts pull` ਨਾਲ ਇੱਕ ਸ਼ੁਰੂ ਕਰੋ।
cli.cmd.server.description: |-
ਸਰਵਰ ਦਾ ਪ੍ਰਬੰਧ ਕਰੋ।
@@ -186,6 +248,34 @@ cli.help.options.defaultGroup: |-
cli.help.globalOptions.title: |-
**ਗਲੋਬਲ ਚੋਣਾਂ:**
task.contacts.status: |-
👤 ਸੰਪਰਕ ਖਿੱਚਣਾ: **{{state}}**
- ✅ ਸਫਲ: {{{progress.ok}}} ਸੰਪਰਕ
- ⚪ ਛੱਡੇ ਗਏ: {{{progress.skipped}}} ਸੰਪਰਕ
- ⚠️ ਗਲਤੀਆਂ: {{{progress.errors}}} ਸੰਪਰਕ
{{#job}}
⌛ ਸਮਾਂ-ਰੇਖਾ:
- ਕਤਾਰ ਵਿੱਚ: {{{timestamp}}}
{{#processedOn}}
- ਸ਼ੁਰੂ: {{{processedOn}}}
{{/processedOn}}
{{#finishedOn}}
- ਸਮਾਪਤ: {{{finishedOn}}}
{{/finishedOn}}
{{/job}}
{{#error}}
ਵੇਰਵੇ:
[{{{details.text}}}]({{{details.url}}})
{{/error}}
task.contacts.started: |-
ਸੰਪਰਕ ਖਿੱਚੇ ਜਾ ਰਹੇ ਹਨ...
task.contacts.progress: |-
ਸੰਪਰਕ ਖਿੱਚਣਾ: ✅ {{progress.ok}}, ⚪ {{progress.skipped}}, ⚠️ {{progress.errors}}
app.help.reminder: |-
ਉਪਲਬਧ ਕਮਾਂਡਾਂ ਦੀ ਸੂਚੀ ਪ੍ਰਾਪਤ ਕਰਨ ਲਈ **help** ਭੇਜੋ।
+90
View File
@@ -120,6 +120,9 @@ app.updated.message: |-
✅ As configurações para a sessão do WhatsApp **{{{name}}}** foram atualizadas.
cli.usage.arguments: |-
[ação]
cli.usage.command: |-
[comando]
@@ -141,6 +144,9 @@ cli.cmd.root.sub.session: |-
cli.cmd.root.sub.server: |-
🖥️ **Servidor**:
cli.cmd.root.sub.sync: |-
🔄️ **Sincronização**:
cli.cmd.session.status.description: |-
Obtenha o status atual da sessão.
@@ -162,6 +168,62 @@ cli.cmd.session.qr.description: |-
cli.cmd.session.screenshot.description: |-
Faça uma captura de tela da sessão (disponível apenas com o mecanismo **WEBJS**).
cli.cmd.contacts.summary: |-
Buscar contatos do WhatsApp.
cli.cmd.contacts.description: |-
👤 Execute `contacts pull` para iniciar uma sincronização ou `contacts status` para acompanhar o progresso.
cli.cmd.contacts.action.description: |-
Use `start` para iniciar a busca de contatos ou `status` para conferir o trabalho ativo.
cli.cmd.contacts.pull.description: |-
Comece a importar contatos do WhatsApp para o ChatWoot.
cli.cmd.contacts.pull.option.batch: |-
Quantidade de contatos buscados em cada lote.
cli.cmd.contacts.pull.option.delay-contact: |-
Atraso entre cada contato para evitar limites ao buscar avatares.
cli.cmd.contacts.pull.option.delay-batch: |-
Atraso entre os lotes de contatos.
cli.cmd.contacts.pull.option.avatar: |-
Escolha como buscar os avatares dos contatos: `skip`, `if-missing` ou `update`.
cli.cmd.contacts.pull.option.groups: |-
Incluir contatos de grupo do WhatsApp na sincronização.
cli.cmd.contacts.pull.option.no-lids: |-
Ignorar contatos LID do WhatsApp (JID de dispositivo vinculado) na sincronização.
cli.cmd.contacts.pull.option.no-attributes: |-
Não busque atributos do contato se ele já existir (acelera o processo).
cli.cmd.contacts.pull.already-running: |-
⏳ O trabalho **Pull Contacts** já está em execução. Aguarde terminar ou interrompa-o antes de iniciar um novo.
Envie `contacts status` para obter detalhes.
cli.cmd.contacts.pull.queued: |-
👥 Trabalho **Pull Contacts** enfileirado.
Compartilharemos atualizações a cada **{{batch}}** contatos.
Buscar contatos pode levar algum tempo — verifique o status mais recente a qualquer momento com `contacts status`.
cli.cmd.options.job.attempts: |-
Número de tentativas antes de marcar o trabalho como falho.
cli.cmd.options.job.timeout: |-
Tempo máximo permitido antes de tentar novamente (aceita valores como `30s`, `5m`, `1h`).
cli.cmd.contacts.status.description: |-
Mostrar o status do trabalho de busca atual.
cli.cmd.contacts.status.not-found: |-
ℹ️ Nenhum trabalho ativo **Pull Contacts** encontrado. Inicie um com `contacts pull`.
cli.cmd.server.description: |-
Gerencie o servidor.
@@ -189,6 +251,34 @@ cli.help.options.defaultGroup: |-
cli.help.globalOptions.title: |-
**Opções globais:**
task.contacts.status: |-
👤 Busca de contatos: **{{state}}**
- ✅ Sucesso: {{{progress.ok}}} contatos
- ⚪ Ignorados: {{{progress.skipped}}} contatos
- ⚠️ Erros: {{{progress.errors}}} contatos
{{#job}}
⌛ Linha do tempo:
- Na fila: {{{timestamp}}}
{{#processedOn}}
- Início: {{{processedOn}}}
{{/processedOn}}
{{#finishedOn}}
- Fim: {{{finishedOn}}}
{{/finishedOn}}
{{/job}}
{{#error}}
Detalhes:
[{{{details.text}}}]({{{details.url}}})
{{/error}}
task.contacts.started: |-
Buscando contatos...
task.contacts.progress: |-
Busca de contatos: ✅ {{progress.ok}}, ⚪ {{progress.skipped}}, ⚠️ {{progress.errors}}
app.help.reminder: |-
Envie **help** para obter a lista de comandos disponíveis.
+90
View File
@@ -117,6 +117,9 @@ app.updated.message: |-
✅ Настройки для сессии WhatsApp **{{{name}}}** были обновлены.
cli.usage.arguments: |-
[действие]
cli.usage.command: |-
[команда]
@@ -138,6 +141,9 @@ cli.cmd.root.sub.session: |-
cli.cmd.root.sub.server: |-
🖥️ **Сервер**:
cli.cmd.root.sub.sync: |-
🔄️ **Синхронизация**:
cli.cmd.session.status.description: |-
Получите текущий статус сессии.
@@ -159,6 +165,62 @@ cli.cmd.session.qr.description: |-
cli.cmd.session.screenshot.description: |-
Сделайте снимок экрана сессии (доступно только с движком **WEBJS**).
cli.cmd.contacts.summary: |-
Загрузить контакты WhatsApp.
cli.cmd.contacts.description: |-
👤 Выполните `contacts pull`, чтобы запустить синхронизацию, или `contacts status`, чтобы проверить прогресс.
cli.cmd.contacts.action.description: |-
Используйте `start`, чтобы начать загрузку контактов, или `status`, чтобы проверить активное задание.
cli.cmd.contacts.pull.description: |-
Начните импорт контактов WhatsApp в ChatWoot.
cli.cmd.contacts.pull.option.batch: |-
Количество контактов в каждой партии.
cli.cmd.contacts.pull.option.delay-contact: |-
Задержка между обработкой контактов, чтобы избежать ограничений при загрузке аватаров.
cli.cmd.contacts.pull.option.delay-batch: |-
Задержка между партиями контактов.
cli.cmd.contacts.pull.option.avatar: |-
Выберите, как загружать аватары контактов: `skip`, `if-missing` или `update`.
cli.cmd.contacts.pull.option.groups: |-
Включить контакты групп WhatsApp в синхронизацию.
cli.cmd.contacts.pull.option.no-lids: |-
Пропускать контакты LID WhatsApp (JID привязанного устройства) при синхронизации.
cli.cmd.contacts.pull.option.no-attributes: |-
Не загружать атрибуты контакта, если он уже существует (ускоряет процесс).
cli.cmd.contacts.pull.already-running: |-
⏳ Задание **Pull Contacts** уже выполняется. Подождите его завершения или остановите перед запуском нового.
Отправьте `contacts status` для подробностей.
cli.cmd.contacts.pull.queued: |-
👥 Задание **Pull Contacts** поставлено в очередь.
Мы будем отправлять обновления прогресса каждые **{{batch}}** контактов.
Загрузка контактов может занять время — проверяйте актуальный статус в любое время командой `contacts status`.
cli.cmd.options.job.attempts: |-
Число повторных попыток до пометки задания как неуспешного.
cli.cmd.options.job.timeout: |-
Максимальное время выполнения перед повторной попыткой (поддерживает значения `30s`, `5m`, `1h`).
cli.cmd.contacts.status.description: |-
Показать статус текущего задания загрузки.
cli.cmd.contacts.status.not-found: |-
ℹ️ Активное задание **Pull Contacts** не найдено. Запустите его командой `contacts pull`.
cli.cmd.server.description: |-
Управляйте сервером.
@@ -186,6 +248,34 @@ cli.help.options.defaultGroup: |-
cli.help.globalOptions.title: |-
**Глобальные опции:**
task.contacts.status: |-
👤 Загрузка контактов: **{{state}}**
- ✅ Успешно: {{{progress.ok}}} контактов
- ⚪ Пропущено: {{{progress.skipped}}} контактов
- ⚠️ Ошибки: {{{progress.errors}}} контактов
{{#job}}
⌛ Хронология:
- В очереди: {{{timestamp}}}
{{#processedOn}}
- Начато: {{{processedOn}}}
{{/processedOn}}
{{#finishedOn}}
- Завершено: {{{finishedOn}}}
{{/finishedOn}}
{{/job}}
{{#error}}
Подробности:
[{{{details.text}}}]({{{details.url}}})
{{/error}}
task.contacts.started: |-
Идёт загрузка контактов...
task.contacts.progress: |-
Загрузка контактов: ✅ {{progress.ok}}, ⚪ {{progress.skipped}}, ⚠️ {{progress.errors}}
app.help.reminder: |-
Отправьте **help**, чтобы получить список доступных команд.
+90
View File
@@ -117,6 +117,9 @@ app.updated.message: |-
✅ WhatsApp oturumu **{{{name}}}** için ayarlar güncellendi.
cli.usage.arguments: |-
[eylem]
cli.usage.command: |-
[komut]
@@ -138,6 +141,9 @@ cli.cmd.root.sub.session: |-
cli.cmd.root.sub.server: |-
🖥️ **Sunucu**:
cli.cmd.root.sub.sync: |-
🔄️ **Eşitleme**:
cli.cmd.session.status.description: |-
Geçerli oturum durumunu alın.
@@ -159,6 +165,62 @@ cli.cmd.session.qr.description: |-
cli.cmd.session.screenshot.description: |-
Oturumun ekran görüntüsünü alın (yalnızca **WEBJS** motoru ile kullanılabilir).
cli.cmd.contacts.summary: |-
WhatsApp kişilerini içe aktarın.
cli.cmd.contacts.description: |-
👤 `contacts pull` komutunu çalıştırarak bir eşitleme başlatın veya `contacts status` ile ilerlemeyi kontrol edin.
cli.cmd.contacts.action.description: |-
`start` ile kişi çekmeyi başlatın veya `status` ile etkin görevi kontrol edin.
cli.cmd.contacts.pull.description: |-
WhatsApp kişilerini ChatWoot'a çekmeye başlayın.
cli.cmd.contacts.pull.option.batch: |-
Her partide çekilecek kişi sayısı.
cli.cmd.contacts.pull.option.delay-contact: |-
Avatarları çekerken limitlere takılmamak için her kişi arasında bekleme süresi.
cli.cmd.contacts.pull.option.delay-batch: |-
Kişi partileri arasındaki bekleme süresi.
cli.cmd.contacts.pull.option.avatar: |-
Kişi avatarlarının nasıl çekileceğini seçin: `skip`, `if-missing` veya `update`.
cli.cmd.contacts.pull.option.groups: |-
WhatsApp grup kişilerini senkronizasyona dahil et.
cli.cmd.contacts.pull.option.no-lids: |-
WhatsApp LID kişilerini (bağlı cihaz JID'leri) senkronizasyonda atla.
cli.cmd.contacts.pull.option.no-attributes: |-
Kişi zaten varsa özelliklerini çekmeyin (süreci hızlandırır).
cli.cmd.contacts.pull.already-running: |-
⏳ **Pull Contacts** görevi zaten çalışıyor. Bitmesini bekleyin veya yenisini başlatmadan önce durdurun.
Ayrıntılar için `contacts status` gönderin.
cli.cmd.contacts.pull.queued: |-
👥 **Pull Contacts** görevi sıraya alındı.
Her **{{batch}}** kişide bir ilerleme güncellemesi paylaşacağız.
Kişileri çekmek zaman alabilir — en güncel durumu istediğiniz zaman `contacts status` ile kontrol edin.
cli.cmd.options.job.attempts: |-
Görevi başarısız saymadan önce yapılacak yeniden deneme sayısı.
cli.cmd.options.job.timeout: |-
Görev yeniden denenmeden önce çalışmasına izin verilen en uzun süre (`30s`, `5m`, `1h` gibi değerleri destekler).
cli.cmd.contacts.status.description: |-
Devam eden kişi çekme görevinin durumunu gösterin.
cli.cmd.contacts.status.not-found: |-
ℹ️ Aktif bir **Pull Contacts** görevi bulunamadı. `contacts pull` ile bir tane başlatın.
cli.cmd.server.description: |-
Sunucuyu yönetin.
@@ -186,6 +248,34 @@ cli.help.options.defaultGroup: |-
cli.help.globalOptions.title: |-
**Genel seçenekler:**
task.contacts.status: |-
👤 Kişi çekme: **{{state}}**
- ✅ Başarılı: {{{progress.ok}}} kişi
- ⚪ Atlanan: {{{progress.skipped}}} kişi
- ⚠️ Hatalar: {{{progress.errors}}} kişi
{{#job}}
⌛ Zaman çizelgesi:
- Sıraya alındı: {{{timestamp}}}
{{#processedOn}}
- Başladı: {{{processedOn}}}
{{/processedOn}}
{{#finishedOn}}
- Bitti: {{{finishedOn}}}
{{/finishedOn}}
{{/job}}
{{#error}}
Ayrıntılar:
[{{{details.text}}}]({{{details.url}}})
{{/error}}
task.contacts.started: |-
Kişiler çekiliyor...
task.contacts.progress: |-
Kişi çekme: ✅ {{progress.ok}}, ⚪ {{progress.skipped}}, ⚠️ {{progress.errors}}
app.help.reminder: |-
Kullanılabilir komutların listesini almak için **help** gönderin.
+90
View File
@@ -117,6 +117,9 @@ app.updated.message: |-
✅ Налаштування для сесії WhatsApp **{{{name}}}** були оновлені.
cli.usage.arguments: |-
[дія]
cli.usage.command: |-
[команда]
@@ -138,6 +141,9 @@ cli.cmd.root.sub.session: |-
cli.cmd.root.sub.server: |-
🖥️ **Сервер**:
cli.cmd.root.sub.sync: |-
🔄️ **Синхронізація**:
cli.cmd.session.status.description: |-
Отримайте поточний статус сесії.
@@ -159,6 +165,62 @@ cli.cmd.session.qr.description: |-
cli.cmd.session.screenshot.description: |-
Зробіть знімок екрана сесії (доступно лише з рушієм **WEBJS**).
cli.cmd.contacts.summary: |-
Отримати контакти WhatsApp.
cli.cmd.contacts.description: |-
👤 Запустіть `contacts pull`, щоб почати синхронізацію, або `contacts status`, щоб відстежити прогрес.
cli.cmd.contacts.action.description: |-
Використайте `start`, щоб почати отримання контактів, або `status`, щоб перевірити активне завдання.
cli.cmd.contacts.pull.description: |-
Почніть імпортувати контакти WhatsApp у ChatWoot.
cli.cmd.contacts.pull.option.batch: |-
Кількість контактів у кожній партії.
cli.cmd.contacts.pull.option.delay-contact: |-
Затримка між кожним контактом, щоб уникнути обмежень під час завантаження аватарів.
cli.cmd.contacts.pull.option.delay-batch: |-
Затримка між партіями контактів.
cli.cmd.contacts.pull.option.avatar: |-
Виберіть, як завантажувати аватари контактів: `skip`, `if-missing` чи `update`.
cli.cmd.contacts.pull.option.groups: |-
Додати контакти груп WhatsApp до синхронізації.
cli.cmd.contacts.pull.option.no-lids: |-
Пропускати контакти LID WhatsApp (JID підключеного пристрою) під час синхронізації.
cli.cmd.contacts.pull.option.no-attributes: |-
Не завантажуйте атрибути контакту, якщо він уже існує (це пришвидшує процес).
cli.cmd.contacts.pull.already-running: |-
⏳ Завдання **Pull Contacts** вже виконується. Дочекайтеся завершення або зупиніть його перед запуском нового.
Надішліть `contacts status`, щоб отримати деталі.
cli.cmd.contacts.pull.queued: |-
👥 Завдання **Pull Contacts** поставлено в чергу.
Ми надішлемо оновлення прогресу після кожних **{{batch}}** контактів.
Отримання контактів може зайняти час — перевіряйте актуальний статус будь-коли за допомогою `contacts status`.
cli.cmd.options.job.attempts: |-
Кількість повторних спроб перед позначенням завдання як невдалого.
cli.cmd.options.job.timeout: |-
Максимальний час виконання перед повторною спробою (підтримуються значення `30s`, `5m`, `1h`).
cli.cmd.contacts.status.description: |-
Показати статус поточного завдання отримання.
cli.cmd.contacts.status.not-found: |-
ℹ️ Активне завдання **Pull Contacts** не знайдено. Запустіть його командою `contacts pull`.
cli.cmd.server.description: |-
Керуйте сервером.
@@ -186,6 +248,34 @@ cli.help.options.defaultGroup: |-
cli.help.globalOptions.title: |-
**Глобальні опції:**
task.contacts.status: |-
👤 Отримання контактів: **{{state}}**
- ✅ Успішно: {{{progress.ok}}} контактів
- ⚪ Пропущено: {{{progress.skipped}}} контактів
- ⚠️ Помилки: {{{progress.errors}}} контактів
{{#job}}
⌛ Хронологія:
- У черзі: {{{timestamp}}}
{{#processedOn}}
- Початок: {{{processedOn}}}
{{/processedOn}}
{{#finishedOn}}
- Завершення: {{{finishedOn}}}
{{/finishedOn}}
{{/job}}
{{#error}}
Деталі:
[{{{details.text}}}]({{{details.url}}})
{{/error}}
task.contacts.started: |-
Отримання контактів...
task.contacts.progress: |-
Отримання контактів: ✅ {{progress.ok}}, ⚪ {{progress.skipped}}, ⚠️ {{progress.errors}}
app.help.reminder: |-
Надішліть **help**, щоб отримати список доступних команд.
+90
View File
@@ -117,6 +117,9 @@ app.updated.message: |-
✅ واٹس ایپ سیشن **{{{name}}}** کی ترتیبات اپڈیٹ کر دی گئی ہیں۔
cli.usage.arguments: |-
[عمل]
cli.usage.command: |-
[کمانڈ]
@@ -138,6 +141,9 @@ cli.cmd.root.sub.session: |-
cli.cmd.root.sub.server: |-
🖥️ **سرور**:
cli.cmd.root.sub.sync: |-
🔄️ **ہم وقت سازی**:
cli.cmd.session.status.description: |-
موجودہ سیشن کی حالت حاصل کریں۔
@@ -159,6 +165,62 @@ cli.cmd.session.qr.description: |-
cli.cmd.session.screenshot.description: |-
سیشن کا اسکرین شاٹ لیں (صرف **WEBJS** انجن کے ساتھ دستیاب)۔
cli.cmd.contacts.summary: |-
واٹس ایپ روابط نکالیں۔
cli.cmd.contacts.description: |-
👤 ہم وقت سازی شروع کرنے کے لیے `contacts pull` چلائیں یا پیش رفت دیکھنے کے لیے `contacts status` چلائیں۔
cli.cmd.contacts.action.description: |-
`start` سے روابط نکالنا شروع کریں یا `status` سے فعال کام چیک کریں۔
cli.cmd.contacts.pull.description: |-
واٹس ایپ روابط کو ChatWoot میں لانا شروع کریں۔
cli.cmd.contacts.pull.option.batch: |-
ہر بیچ میں نکالے جانے والے رابطوں کی تعداد۔
cli.cmd.contacts.pull.option.delay-contact: |-
ہر رابطے کے درمیان وقفہ تاکہ اوتار نکالتے وقت ریٹ لمٹس سے بچا جا سکے۔
cli.cmd.contacts.pull.option.delay-batch: |-
رابطوں کے بیچوں کے درمیان وقفہ۔
cli.cmd.contacts.pull.option.avatar: |-
یہ منتخب کریں کہ رابطوں کے اوتار کس طرح نکالے جائیں: `skip`, `if-missing`, یا `update`.
cli.cmd.contacts.pull.option.groups: |-
واٹس ایپ گروپ روابط کو ہم وقت سازی میں شامل کریں۔
cli.cmd.contacts.pull.option.no-lids: |-
واٹس ایپ LID روابط (لنک شدہ ڈیوائس JID) کو ہم وقت سازی کے دوران چھوڑ دیں۔
cli.cmd.contacts.pull.option.no-attributes: |-
اگر رابطہ پہلے سے موجود ہو تو اس کے خواص نہ نکالیں (اس سے عمل تیز ہوتا ہے)۔
cli.cmd.contacts.pull.already-running: |-
⏳ **Pull Contacts** کام پہلے ہی چل رہا ہے۔ اسے مکمل ہونے دیں یا نیا شروع کرنے سے پہلے روک دیں۔
تفصیلات کے لیے `contacts status` بھیجیں۔
cli.cmd.contacts.pull.queued: |-
👥 **Pull Contacts** کام قطار میں لگا دیا گیا ہے۔
ہم ہر **{{batch}}** رابطوں کے بعد پیش رفت کی تازہ کاری شیئر کریں گے۔
روابط نکالنے میں وقت لگ سکتا ہے — کسی بھی وقت `contacts status` کے ذریعے تازہ ترین حالت دیکھیں۔
cli.cmd.options.job.attempts: |-
کام کو ناکام قرار دینے سے پہلے دوبارہ کوششوں کی تعداد۔
cli.cmd.options.job.timeout: |-
کام کو دوبارہ چلانے سے پہلے زیادہ سے زیادہ مدت (جیسے `30s`, `5m`, `1h`)۔
cli.cmd.contacts.status.description: |-
موجودہ نکاسی کے کام کی حالت دکھائیں۔
cli.cmd.contacts.status.not-found: |-
ℹ️ کوئی فعال **Pull Contacts** کام نہیں ملا۔ `contacts pull` سے ایک شروع کریں۔
cli.cmd.server.description: |-
سرور کا انتظام کریں۔
@@ -186,6 +248,34 @@ cli.help.options.defaultGroup: |-
cli.help.globalOptions.title: |-
**عالمی اختیارات:**
task.contacts.status: |-
👤 رابطے نکالنا: **{{state}}**
- ✅ کامیابی: {{{progress.ok}}} روابط
- ⚪ چھوڑے گئے: {{{progress.skipped}}} روابط
- ⚠️ غلطیاں: {{{progress.errors}}} روابط
{{#job}}
⌛ ٹائم لائن:
- قطار میں: {{{timestamp}}}
{{#processedOn}}
- آغاز: {{{processedOn}}}
{{/processedOn}}
{{#finishedOn}}
- اختتام: {{{finishedOn}}}
{{/finishedOn}}
{{/job}}
{{#error}}
تفصیلات:
[{{{details.text}}}]({{{details.url}}})
{{/error}}
task.contacts.started: |-
روابط نکالے جا رہے ہیں...
task.contacts.progress: |-
رابطے نکالنا: ✅ {{progress.ok}}، ⚪ {{progress.skipped}}، ⚠️ {{progress.errors}}
app.help.reminder: |-
دستیاب کمانڈز کی فہرست حاصل کرنے کے لیے **help** بھیجیں۔
+90
View File
@@ -117,6 +117,9 @@ app.updated.message: |-
✅ WhatsApp 会话 **{{{name}}}** 的设置已更新。
cli.usage.arguments: |-
[操作]
cli.usage.command: |-
[命令]
@@ -138,6 +141,9 @@ cli.cmd.root.sub.session: |-
cli.cmd.root.sub.server: |-
🖥️ **服务器**:
cli.cmd.root.sub.sync: |-
🔄️ **同步**:
cli.cmd.session.status.description: |-
获取当前会话状态。
@@ -159,6 +165,62 @@ cli.cmd.session.qr.description: |-
cli.cmd.session.screenshot.description: |-
截取会话截图(仅适用于 **WEBJS** 引擎)。
cli.cmd.contacts.summary: |-
拉取 WhatsApp 联系人。
cli.cmd.contacts.description: |-
👤 运行 `contacts pull` 启动同步,或运行 `contacts status` 查看进度。
cli.cmd.contacts.action.description: |-
使用 `start` 开始拉取联系人,或使用 `status` 查看进行中的任务。
cli.cmd.contacts.pull.description: |-
开始将 WhatsApp 联系人拉取到 ChatWoot。
cli.cmd.contacts.pull.option.batch: |-
每批次拉取的联系人数量。
cli.cmd.contacts.pull.option.delay-contact: |-
在每个联系人之间延迟,以避免拉取头像时触发频率限制。
cli.cmd.contacts.pull.option.delay-batch: |-
在联系人批次之间的延迟。
cli.cmd.contacts.pull.option.avatar: |-
选择如何拉取联系人头像:`skip`、`if-missing` 或 `update`。
cli.cmd.contacts.pull.option.groups: |-
在同步中包含 WhatsApp 群组联系人。
cli.cmd.contacts.pull.option.no-lids: |-
在同步中跳过 WhatsApp LID 联系人(关联设备 JID)。
cli.cmd.contacts.pull.option.no-attributes: |-
如果联系人已存在,则不要拉取其属性(可加快流程)。
cli.cmd.contacts.pull.already-running: |-
⏳ **Pull Contacts** 任务已在运行。请等待完成或先停止再启动新的。
发送 `contacts status` 获取详情。
cli.cmd.contacts.pull.queued: |-
👥 **Pull Contacts** 任务已排队。
我们会每 **{{batch}}** 个联系人更新一次进度。
拉取联系人可能需要一些时间——随时使用 `contacts status` 查看最新状态。
cli.cmd.options.job.attempts: |-
任务标记为失败前的最大重试次数。
cli.cmd.options.job.timeout: |-
任务重新尝试前允许运行的最长时间(支持 `30s`、`5m`、`1h` 等值)。
cli.cmd.contacts.status.description: |-
显示当前拉取任务的状态。
cli.cmd.contacts.status.not-found: |-
ℹ️ 未找到正在运行的 **Pull Contacts** 任务。使用 `contacts pull` 启动一个。
cli.cmd.server.description: |-
管理服务器。
@@ -186,6 +248,34 @@ cli.help.options.defaultGroup: |-
cli.help.globalOptions.title: |-
**全局选项:**
task.contacts.status: |-
👤 联系人拉取:**{{state}}**
- ✅ 成功:{{{progress.ok}}} 位联系人
- ⚪ 跳过:{{{progress.skipped}}} 位联系人
- ⚠️ 错误:{{{progress.errors}}} 位联系人
{{#job}}
⌛ 时间线:
- 排队:{{{timestamp}}}
{{#processedOn}}
- 开始:{{{processedOn}}}
{{/processedOn}}
{{#finishedOn}}
- 完成:{{{finishedOn}}}
{{/finishedOn}}
{{/job}}
{{#error}}
详情:
[{{{details.text}}}]({{{details.url}}})
{{/error}}
task.contacts.started: |-
正在拉取联系人...
task.contacts.progress: |-
联系人拉取:✅ {{progress.ok}},⚪ {{progress.skipped}},⚠️ {{progress.errors}}
app.help.reminder: |-
发送 **help** 获取可用命令列表。
@@ -1,8 +1,10 @@
import { InjectQueue } from '@nestjs/bullmq';
import { Injectable } from '@nestjs/common';
import { Injectable, Logger } from '@nestjs/common';
import { Queue } from 'bullmq';
import { QueueName } from '../consumers/QueueName';
import { InjectPinoLogger, PinoLogger } from 'nestjs-pino';
import { ContactsPullRemove } from '@waha/apps/chatwoot/cli/cmd.contacts';
/**
* Service for scheduling ChatWoot tasks
@@ -11,10 +13,14 @@ import { QueueName } from '../consumers/QueueName';
@Injectable()
export class ChatWootScheduleService {
constructor(
@InjectPinoLogger('DashboardConfigService')
protected logger: PinoLogger,
@InjectQueue(QueueName.SCHEDULED_MESSAGE_CLEANUP)
private readonly messageCleanupQueue: Queue,
@InjectQueue(QueueName.SCHEDULED_CHECK_VERSION)
private readonly checkVersionQueue: Queue,
@InjectQueue(QueueName.TASK_CONTACTS_PULL)
private readonly importContactsQueue: Queue,
) {}
/**
@@ -58,9 +64,27 @@ export class ChatWootScheduleService {
await this.checkVersionQueue.removeJobScheduler(
this.JobId(QueueName.SCHEDULED_CHECK_VERSION, appId),
);
// contacts
ContactsPullRemove(this.importContactsQueue, appId, this.logger).catch(
(reason) => {
// Ignore errors
this.logger.warn(
`Failed to remove "contacts" job for app ${appId}, session ${sessionName}: ${reason}`,
);
},
);
}
/**
* DEPRECATED - for backward compatability
* Use SingleJobId if you want to have single job in the queue by app
*/
private JobId(queue: QueueName, appId: string) {
return `${queue} | ${appId}`;
}
static SingleJobId(appId: string) {
return `${appId}`;
}
}
+184 -81
View File
@@ -12,6 +12,12 @@ import {
import { SessionInfo } from '@waha/structures/sessions.dto';
import axios, { AxiosInstance } from 'axios';
import { Auth } from '@waha/core/auth/config';
import { ContactSortField } from '@waha/structures/contacts.dto';
import { PaginationParams } from '@waha/structures/pagination.dto';
export interface RequestOptions {
signal?: AbortSignal;
}
export class WAHASelf {
public client: AxiosInstance;
@@ -33,57 +39,78 @@ export class WAHASelf {
});
}
async fetch(url: string): Promise<Buffer> {
async fetch(url: string, opts?: RequestOptions): Promise<Buffer> {
const response = await this.client.get(url, {
responseType: 'arraybuffer',
signal: opts?.signal,
});
return Buffer.from(response.data);
}
async qr(session: string): Promise<Buffer> {
async qr(session: string, opts?: RequestOptions): Promise<Buffer> {
const url = `/api/${session}/auth/qr`;
return await this.fetch(url);
return await this.fetch(url, opts);
}
async screenshot(session: string): Promise<Buffer> {
async screenshot(session: string, opts?: RequestOptions): Promise<Buffer> {
const url = `/api/screenshot?session=${session}`;
return await this.fetch(url);
return await this.fetch(url, opts);
}
async restart(session: string): Promise<any> {
async restart(session: string, opts?: RequestOptions): Promise<any> {
const url = `/api/sessions/${session}/restart`;
return await this.client.post(url);
return await this.client.post(url, undefined, { signal: opts?.signal });
}
async logout(session: string): Promise<any> {
async logout(session: string, opts?: RequestOptions): Promise<any> {
const url = `/api/sessions/${session}/logout`;
return await this.client.post(url);
return await this.client.post(url, undefined, { signal: opts?.signal });
}
async stop(session: string): Promise<any> {
async stop(session: string, opts?: RequestOptions): Promise<any> {
const url = `/api/sessions/${session}/stop`;
return await this.client.post(url);
return await this.client.post(url, undefined, { signal: opts?.signal });
}
async get(session: string): Promise<SessionInfo> {
async get(session: string, opts?: RequestOptions): Promise<SessionInfo> {
const url = `/api/sessions/${session}/`;
return await this.client.get(url).then((response) => response.data);
return await this.client
.get(url, { signal: opts?.signal })
.then((response) => response.data);
}
async getContact(session: string, contactId: string) {
async getContacts(
session: string,
page: PaginationParams,
opts?: RequestOptions,
) {
const url = `/api/contacts/all`;
const params = {
session: session,
...page,
sortBy: ContactSortField.ID,
sortOrder: ContactSortField.ID,
};
return await this.client
.get(url, { params: params, signal: opts?.signal })
.then((response) => response.data);
}
async getContact(session: string, contactId: string, opts?: RequestOptions) {
const url = `/api/contacts`;
const params = {
session: session,
contactId: contactId,
};
return await this.client
.get(url, { params: params })
.get(url, { params: params, signal: opts?.signal })
.then((response) => response.data);
}
async contactCheckExists(
session: string,
phone: string,
opts?: RequestOptions,
): Promise<WANumberExistResult> {
const url = `/api/contacts/check-exists`;
const params = {
@@ -91,99 +118,162 @@ export class WAHASelf {
session: session,
};
return await this.client
.get(url, { params: params })
.get(url, { params: params, signal: opts?.signal })
.then((response) => response.data);
}
async getGroup(session: string, groupId: string) {
async getGroup(session: string, groupId: string, opts?: RequestOptions) {
const url = `/api/${session}/groups/${groupId}`;
return await this.client.get(url).then((response) => response.data);
return await this.client
.get(url, { signal: opts?.signal })
.then((response) => response.data);
}
async getChannel(session: string, channelId: string): Promise<Channel> {
async getChannel(
session: string,
channelId: string,
opts?: RequestOptions,
): Promise<Channel> {
const url = `/api/${session}/channels/${channelId}`;
return await this.client.get(url).then((response) => response.data);
return await this.client
.get(url, { signal: opts?.signal })
.then((response) => response.data);
}
async getChatPicture(
session: string,
chatId: string,
opts?: RequestOptions,
): Promise<string | null> {
const url = `/api/${session}/chats/${chatId}/picture`;
return await this.client.get(url).then((response) => response.data?.url);
return await this.client
.get(url, { signal: opts?.signal })
.then((response) => response.data?.url);
}
async sendText(body: MessageTextRequest): Promise<any> {
async sendText(
body: MessageTextRequest,
opts?: RequestOptions,
): Promise<any> {
const url = `/api/sendText`;
return await this.client.post(url, body).then((response) => response.data);
return await this.client
.post(url, body, { signal: opts?.signal })
.then((response) => response.data);
}
async sendImage(body: MessageImageRequest): Promise<any> {
async sendImage(
body: MessageImageRequest,
opts?: RequestOptions,
): Promise<any> {
const url = `/api/sendImage`;
return await this.client.post(url, body).then((response) => response.data);
return await this.client
.post(url, body, { signal: opts?.signal })
.then((response) => response.data);
}
async sendVideo(body: MessageVideoRequest): Promise<any> {
async sendVideo(
body: MessageVideoRequest,
opts?: RequestOptions,
): Promise<any> {
const url = `/api/sendVideo`;
return await this.client.post(url, body).then((response) => response.data);
return await this.client
.post(url, body, { signal: opts?.signal })
.then((response) => response.data);
}
async sendVoice(body: MessageVoiceRequest): Promise<any> {
async sendVoice(
body: MessageVoiceRequest,
opts?: RequestOptions,
): Promise<any> {
const url = `/api/sendVoice`;
return await this.client.post(url, body).then((response) => response.data);
return await this.client
.post(url, body, { signal: opts?.signal })
.then((response) => response.data);
}
async sendFile(body: MessageFileRequest): Promise<any> {
async sendFile(
body: MessageFileRequest,
opts?: RequestOptions,
): Promise<any> {
const url = `/api/sendFile`;
return await this.client.post(url, body).then((response) => response.data);
return await this.client
.post(url, body, { signal: opts?.signal })
.then((response) => response.data);
}
async deleteMessage(session: string, chatId: string, messageId: string) {
async deleteMessage(
session: string,
chatId: string,
messageId: string,
opts?: RequestOptions,
) {
const url = `/api/${session}/chats/${chatId}/messages/${messageId}`;
return await this.client.delete(url).then((response) => response.data);
return await this.client
.delete(url, { signal: opts?.signal })
.then((response) => response.data);
}
async startTyping(body: ChatRequest) {
async startTyping(body: ChatRequest, opts?: RequestOptions) {
const url = `/api/startTyping`;
return await this.client.post(url, body).then((response) => response.data);
return await this.client
.post(url, body, { signal: opts?.signal })
.then((response) => response.data);
}
async stopTyping(body: ChatRequest) {
async stopTyping(body: ChatRequest, opts?: RequestOptions) {
const url = `/api/stopTyping`;
return await this.client.post(url, body);
return await this.client.post(url, body, { signal: opts?.signal });
}
async readMessages(session: string, chatId: string) {
async readMessages(session: string, chatId: string, opts?: RequestOptions) {
const url = `/api/${session}/chats/${chatId}/messages/read`;
return await this.client.post(url).then((response) => response.data);
return await this.client
.post(url, undefined, { signal: opts?.signal })
.then((response) => response.data);
}
async findPNByLid(session: string, lid: string): Promise<string | null> {
async findPNByLid(
session: string,
lid: string,
opts?: RequestOptions,
): Promise<string | null> {
const url = `/api/${session}/lids/${lid}`;
return await this.client.get(url).then((response) => response.data.pn);
return await this.client
.get(url, { signal: opts?.signal })
.then((response) => response.data.pn);
}
async findLIDByPN(session: string, pn: string): Promise<string | null> {
async findLIDByPN(
session: string,
pn: string,
opts?: RequestOptions,
): Promise<string | null> {
const url = `/api/${session}/lids/pn/${pn}`;
return await this.client.get(url).then((response) => response.data.lid);
return await this.client
.get(url, { signal: opts?.signal })
.then((response) => response.data.lid);
}
/**
* Get the server version
* @returns Server version information
*/
async serverVersion(): Promise<any> {
async serverVersion(opts?: RequestOptions): Promise<any> {
const url = `/api/server/version`;
return await this.client.get(url).then((response) => response.data);
return await this.client
.get(url, { signal: opts?.signal })
.then((response) => response.data);
}
/**
* Get the server status
* @returns Server status information
*/
async serverStatus(): Promise<any> {
async serverStatus(opts?: RequestOptions): Promise<any> {
const url = `/api/server/status`;
return await this.client.get(url).then((response) => response.data);
return await this.client
.get(url, { signal: opts?.signal })
.then((response) => response.data);
}
/**
@@ -191,10 +281,13 @@ export class WAHASelf {
* @param force Whether to force reboot (true) or gracefully reboot (false)
* @returns Server stop response
*/
async serverReboot(force: boolean = false): Promise<any> {
async serverReboot(
force: boolean = false,
opts?: RequestOptions,
): Promise<any> {
const url = `/api/server/stop`;
return await this.client
.post(url, { force })
.post(url, { force }, { signal: opts?.signal })
.then((response) => response.data);
}
}
@@ -205,77 +298,87 @@ export class WAHASessionAPI {
private api: WAHASelf,
) {}
getContact(contactId: string): Promise<any> {
return this.api.getContact(this.session, contactId);
getContacts(page: PaginationParams, opts?: RequestOptions): Promise<any> {
return this.api.getContacts(this.session, page, opts);
}
contactCheckExists(phone: string): Promise<WANumberExistResult> {
return this.api.contactCheckExists(this.session, phone);
getContact(contactId: string, opts?: RequestOptions): Promise<any> {
return this.api.getContact(this.session, contactId, opts);
}
getGroup(groupId: string): Promise<any> {
return this.api.getGroup(this.session, groupId);
contactCheckExists(
phone: string,
opts?: RequestOptions,
): Promise<WANumberExistResult> {
return this.api.contactCheckExists(this.session, phone, opts);
}
getChannel(channelId: string): Promise<Channel> {
return this.api.getChannel(this.session, channelId);
getGroup(groupId: string, opts?: RequestOptions): Promise<any> {
return this.api.getGroup(this.session, groupId, opts);
}
getChatPicture(chatId: string): Promise<string | null> {
return this.api.getChatPicture(this.session, chatId);
getChannel(channelId: string, opts?: RequestOptions): Promise<Channel> {
return this.api.getChannel(this.session, channelId, opts);
}
sendText(body: MessageTextRequest): Promise<any> {
getChatPicture(
chatId: string,
opts?: RequestOptions,
): Promise<string | null> {
return this.api.getChatPicture(this.session, chatId, opts);
}
sendText(body: MessageTextRequest, opts?: RequestOptions): Promise<any> {
body.session = this.session;
return this.api.sendText(body);
return this.api.sendText(body, opts);
}
sendImage(body: MessageImageRequest): Promise<any> {
sendImage(body: MessageImageRequest, opts?: RequestOptions): Promise<any> {
body.session = this.session;
return this.api.sendImage(body);
return this.api.sendImage(body, opts);
}
sendVideo(body: MessageVideoRequest): Promise<any> {
sendVideo(body: MessageVideoRequest, opts?: RequestOptions): Promise<any> {
body.session = this.session;
return this.api.sendVideo(body);
return this.api.sendVideo(body, opts);
}
sendVoice(body: MessageVoiceRequest): Promise<any> {
sendVoice(body: MessageVoiceRequest, opts?: RequestOptions): Promise<any> {
body.session = this.session;
return this.api.sendVoice(body);
return this.api.sendVoice(body, opts);
}
sendFile(body: MessageFileRequest): Promise<any> {
sendFile(body: MessageFileRequest, opts?: RequestOptions): Promise<any> {
body.session = this.session;
return this.api.sendFile(body);
return this.api.sendFile(body, opts);
}
deleteMessage(chatId: string, messageId: string) {
return this.api.deleteMessage(this.session, chatId, messageId);
deleteMessage(chatId: string, messageId: string, opts?: RequestOptions) {
return this.api.deleteMessage(this.session, chatId, messageId, opts);
}
startTyping(body: ChatRequest) {
startTyping(body: ChatRequest, opts?: RequestOptions) {
body.session = this.session;
return this.api.startTyping(body);
return this.api.startTyping(body, opts);
}
stopTyping(body: ChatRequest) {
stopTyping(body: ChatRequest, opts?: RequestOptions) {
body.session = this.session;
return this.api.stopTyping(body);
return this.api.stopTyping(body, opts);
}
readMessages(chatId: string) {
return this.api.readMessages(this.session, chatId);
readMessages(chatId: string, opts?: RequestOptions) {
return this.api.readMessages(this.session, chatId, opts);
}
//
// Lids
//
findPNByLid(lid: string) {
return this.api.findPNByLid(this.session, lid);
findPNByLid(lid: string, opts?: RequestOptions) {
return this.api.findPNByLid(this.session, lid, opts);
}
findLIDByPN(pn: string) {
return this.api.findLIDByPN(this.session, pn);
findLIDByPN(pn: string, opts?: RequestOptions) {
return this.api.findLIDByPN(this.session, pn, opts);
}
}
+2 -1
View File
@@ -7,6 +7,7 @@ import {
IgnoreJidConfig,
isJidBroadcast,
isJidNewsletter,
isNullJid,
JidFilter,
} from '@waha/core/utils/jids';
import {
@@ -748,7 +749,7 @@ export abstract class WhatsappSession {
protected async refreshProfilePicture(id: string) {
this.logger.debug(`Refreshing profile picture for id "${id}"...`);
// Have no pictures
if (id === '0@c.us' || id === '0@s.whatsapp.net') {
if (isNullJid(id)) {
return null;
} else if (isJidBroadcast(id)) {
return null;
+17 -4
View File
@@ -29,11 +29,24 @@ export function isLidUser(jid: string) {
return typeof jid === 'string' && jid.endsWith('@lid');
}
export function isNullJid(jid: string) {
if (!jid) {
return false;
}
return jid === '0@c.us' || jid === '0@s.whatsapp.net';
}
export function isPnUser(jid: string) {
return (
typeof jid === 'string' &&
(jid.endsWith('@s.whatsapp.net') || jid.endsWith('@c.us'))
);
if (typeof jid !== 'string') {
return false;
}
if (!jid.endsWith('@s.whatsapp.net') && !jid.endsWith('@c.us')) {
return false;
}
if (isNullJid(jid)) {
return false;
}
return true;
}
export function normalizeJid(jid: string): string {
+1 -1
View File
@@ -30,7 +30,7 @@ export class ContactProfilePictureQuery extends ContactQuery {
refresh?: boolean = false;
}
enum ContactSortField {
export enum ContactSortField {
ID = 'id',
NAME = 'name',
}
+44
View File
@@ -0,0 +1,44 @@
/**
* Convert an AbortSignal into a Promise that rejects when aborted.
*
* Example:
* const signal = AbortSignal.timeout(5000);
* await Promise.race([
* slowOperation(signal),
* SignalToPromise(signal),
* ]);
*/
export function SignalToPromise(signal: AbortSignal): Promise<never> {
signal.throwIfAborted();
return new Promise((_, reject) => {
const onAbort = () => {
signal.removeEventListener('abort', onAbort);
reject(
signal.reason instanceof DOMException
? signal.reason
: new DOMException('The operation was aborted', 'AbortError'),
);
};
signal.addEventListener('abort', onAbort, { once: true });
});
}
/**
* Race an already-started promise against an AbortSignal.
* The promise settles normally unless the signal aborts first.
*
* Example:
* const signal = AbortSignal.any([
* AbortSignal.timeout(10_000),
* controller.signal,
* ]);
*
* await SignalRace(doSomethingAsync(args, signal), signal);
*/
export function SignalRace<T>(
promise: Promise<T>,
signal: AbortSignal,
): Promise<T> {
signal.throwIfAborted();
return Promise.race([promise, SignalToPromise(signal)]);
}
+1
View File
@@ -12596,6 +12596,7 @@ __metadata:
long: ^5.3.2
mime-types: ^2.1.27
mongodb: 6.9.0
ms: ^2.1.3
mustache: ^4.2.0
nestjs-pino: ^4.1.0
node-cache: 5.1.2