1 parent
cc056dca9c
commit
c001d7fed6
43 files changed
+2516
-138
No files matched your search
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
//
|
||||
|
||||
@@ -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.
File renamed without changes.
@@ -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,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
|
||||
//
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -109,7 +109,7 @@ export class DIContainer {
|
||||
* @returns ContactService instance
|
||||
*/
|
||||
@CacheSync()
|
||||
private ContactService(): ContactService {
|
||||
public ContactService(): ContactService {
|
||||
return new ContactService(
|
||||
this.config,
|
||||
this.AccountAPI(),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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** للحصول على قائمة الأوامر المتاحة.
|
||||
|
||||
|
||||
@@ -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** পাঠান।
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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** را ارسال کنید.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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** כדי לקבל את רשימת הפקודות הזמינות.
|
||||
|
||||
|
||||
@@ -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** भेजें।
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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** ਭੇਜੋ।
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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**, чтобы получить список доступных команд.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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**, щоб отримати список доступних команд.
|
||||
|
||||
|
||||
@@ -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** بھیجیں۔
|
||||
|
||||
|
||||
@@ -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}`;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -30,7 +30,7 @@ export class ContactProfilePictureQuery extends ContactQuery {
|
||||
refresh?: boolean = false;
|
||||
}
|
||||
|
||||
enum ContactSortField {
|
||||
export enum ContactSortField {
|
||||
ID = 'id',
|
||||
NAME = 'name',
|
||||
}
|
||||
|
||||
@@ -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)]);
|
||||
}
|
||||
Reference in new issue
Block a user