@bprotsyk/aso-core 2.1.115 → 2.1.116
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README-keitaro-clo-geos.md +131 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +3 -1
- package/lib/keitaro/keitaro-clo-geos.d.ts +40 -0
- package/lib/keitaro/keitaro-clo-geos.js +303 -0
- package/lib/network/keitaro/traffle/traffle-keitaro-service.js +1 -0
- package/package.json +1 -1
- package/src/index.ts +2 -1
- package/src/keitaro/keitaro-clo-geos.ts +371 -0
- package/src/network/keitaro/traffle/traffle-keitaro-service.ts +1 -0
- package/test-keitaro-clo-geos.js +93 -0
- package/test-keitaro.js +7 -7
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Keitaro CLO Geos Sync Service
|
|
2
|
+
|
|
3
|
+
Сервіс для синхронізації гео-даних між Keitaro CLO потоками та додатками платформи.
|
|
4
|
+
|
|
5
|
+
## Опис
|
|
6
|
+
|
|
7
|
+
Цей сервіс автоматично:
|
|
8
|
+
1. Отримує всі кампанії з Keitaro
|
|
9
|
+
2. Ідентифікує кампанії, які належать нашим додаткам
|
|
10
|
+
3. Порівнює гео з CLO потоків Keitaro з гео в додатках
|
|
11
|
+
4. Повертає список додатків, які потребують оновлення гео
|
|
12
|
+
|
|
13
|
+
## Основні функції
|
|
14
|
+
|
|
15
|
+
### `syncKeitaroCLOGeosWithApps(apps: IApp[])`
|
|
16
|
+
Основна функція синхронізації. Повертає загальний результат з усіма додатками та їх статусом.
|
|
17
|
+
|
|
18
|
+
### `getAppGeoSyncDetails(appId: number, platform: string, apps: IApp[])`
|
|
19
|
+
Отримує детальну інформацію про синхронізацію для конкретного додатку та платформи.
|
|
20
|
+
|
|
21
|
+
### `getAppsNeedingGeoUpdate(apps: IApp[])`
|
|
22
|
+
Повертає тільки список додатків, які потребують оновлення гео.
|
|
23
|
+
|
|
24
|
+
## Інтерфейси
|
|
25
|
+
|
|
26
|
+
### `IGeoSyncResult`
|
|
27
|
+
```typescript
|
|
28
|
+
interface IGeoSyncResult {
|
|
29
|
+
appId: number; // ID додатку
|
|
30
|
+
platform: string; // Платформа (@, hw, sm, rs, xm, ap, tg, am)
|
|
31
|
+
keitaroGeos: string[]; // Гео з Keitaro CLO потоку
|
|
32
|
+
appGeos: string[]; // Поточні гео в додатку
|
|
33
|
+
missingGeos: string[]; // Гео, які відсутні в додатку
|
|
34
|
+
needsUpdate: boolean; // Чи потребує додаток оновлення
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### `IGeoUpdateData`
|
|
39
|
+
```typescript
|
|
40
|
+
interface IGeoUpdateData {
|
|
41
|
+
appId: number; // ID додатку
|
|
42
|
+
platform: string; // Платформа
|
|
43
|
+
newGeos: string[]; // Нові гео (поточні + відсутні)
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### `IGeoSyncSummary`
|
|
48
|
+
```typescript
|
|
49
|
+
interface IGeoSyncSummary {
|
|
50
|
+
totalApps: number; // Загальна кількість додатків
|
|
51
|
+
appsNeedingUpdate: number; // Кількість додатків, що потребують оновлення
|
|
52
|
+
updates: IGeoUpdateData[]; // Список оновлень
|
|
53
|
+
errors: string[]; // Список помилок
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Використання
|
|
58
|
+
|
|
59
|
+
### Базовий приклад
|
|
60
|
+
```typescript
|
|
61
|
+
import { KeitaroCLOGeosService } from './keitaro/keitaro-clo-geos';
|
|
62
|
+
|
|
63
|
+
// Список ваших додатків
|
|
64
|
+
const apps = [/* ваші додатки */];
|
|
65
|
+
|
|
66
|
+
// Синхронізація всіх додатків
|
|
67
|
+
const summary = await KeitaroCLOGeosService.syncKeitaroCLOGeosWithApps(apps);
|
|
68
|
+
|
|
69
|
+
console.log(`Потребують оновлення: ${summary.appsNeedingUpdate}`);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Отримання конкретного додатку
|
|
73
|
+
```typescript
|
|
74
|
+
// Деталі для додатку 1628 на платформі @
|
|
75
|
+
const details = await KeitaroCLOGeosService.getAppGeoSyncDetails(1628, '@', apps);
|
|
76
|
+
|
|
77
|
+
if (details?.needsUpdate) {
|
|
78
|
+
console.log(`Додаток ${details.appId} потребує оновлення гео`);
|
|
79
|
+
console.log(`Відсутні гео: ${details.missingGeos.join(', ')}`);
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Тільки додатки, що потребують оновлення
|
|
84
|
+
```typescript
|
|
85
|
+
const updates = await KeitaroCLOGeosService.getAppsNeedingGeoUpdate(apps);
|
|
86
|
+
|
|
87
|
+
updates.forEach(update => {
|
|
88
|
+
console.log(`Додаток ${update.appId} (${update.platform}): ${update.newGeos.join(', ')}`);
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Логіка ідентифікації кампаній
|
|
93
|
+
|
|
94
|
+
Сервіс ідентифікує кампанії як "наші" за наступними критеріями:
|
|
95
|
+
|
|
96
|
+
1. **ID додатку** в назві кампанії (наприклад: `#1628`, `[1628]`)
|
|
97
|
+
2. **Bundle ID** або **Domain** в назві кампанії
|
|
98
|
+
3. **Платформа** визначається за суфіксами:
|
|
99
|
+
- `(hw)` або `huawei` → `hw`
|
|
100
|
+
- `(sm)` або `samsung` → `sm`
|
|
101
|
+
- `(rs)` або `rustore` → `rs`
|
|
102
|
+
- `(xm)` або `xiaomi` → `xm`
|
|
103
|
+
- `(ap)` або `apkpure` → `ap`
|
|
104
|
+
- `(tg)` або `telegram` → `tg`
|
|
105
|
+
- `(am)` або `amazon` → `am`
|
|
106
|
+
- Без суфіксу → `@` (General)
|
|
107
|
+
|
|
108
|
+
## CLO потоки
|
|
109
|
+
|
|
110
|
+
Сервіс шукає CLO потоки в кампаніях та аналізує їх фільтри за країною. Гео беруться з фільтра `country` в `payload`.
|
|
111
|
+
|
|
112
|
+
## Тестування
|
|
113
|
+
|
|
114
|
+
Для тестування використовуйте файл `test-keitaro-clo-geos.js`:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
node test-keitaro-clo-geos.js
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Залежності
|
|
121
|
+
|
|
122
|
+
- `axios` - для HTTP запитів до Keitaro API
|
|
123
|
+
- `../network/keitaro/http` - конфігурація Keitaro API
|
|
124
|
+
- `../app/app` - інтерфейси додатків та платформ
|
|
125
|
+
|
|
126
|
+
## Примітки
|
|
127
|
+
|
|
128
|
+
- Сервіс автоматично обробляє помилки та логує їх
|
|
129
|
+
- Гео порівнюються без урахування регістру
|
|
130
|
+
- Повертаються тільки додатки, де гео Keitaro не співпадає з гео додатку
|
|
131
|
+
- Для кожного додатку формується масив нових гео (поточні + відсутні)
|
package/lib/index.d.ts
CHANGED
|
@@ -25,3 +25,4 @@ export { IDomain, IDomainSetupResult, DomainStatus, DomainTarget, CONST_CLOUFLAR
|
|
|
25
25
|
export { INamecheapDomain, INamecheapBuyRequest, INamecheapContactInfo, NamecheapBuyRequestSchema, INamecheapGetDomainsResult, INamecheapBuyResult } from "./general/namecheap-domain";
|
|
26
26
|
export { NginxTemplate } from "./templates/nginx-template";
|
|
27
27
|
export { TraffleKeitaroService } from "./network/keitaro/traffle/traffle-keitaro-service";
|
|
28
|
+
export { KeitaroCLOGeosService } from "./keitaro/keitaro-clo-geos";
|
package/lib/index.js
CHANGED
|
@@ -23,7 +23,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
23
23
|
return result;
|
|
24
24
|
};
|
|
25
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
-
exports.TraffleKeitaroService = exports.NginxTemplate = exports.NamecheapBuyRequestSchema = exports.getDomainTargetByIp = exports.CONST_CLOUFLARE_STATUS_READY = exports.DomainTarget = exports.DomainStatus = exports.ICloudflareDomainType = exports.ICloudflareDomainStatus = exports.KeitaroUtils = exports.KeitaroService = exports.ShapeDiv = exports.PanelUserSchema = exports.PanelUserAccessScope = exports.AlternativeOnActivityResult = exports.AlternativeOnBackPressed = exports.AlternativeNavigation = exports.AlternativeStorageType = exports.AlternativeNetworkTool = exports.AlternativeLogicType = exports.AlternativeSourceType = exports.AlternativeLayoutType = exports.AppType = exports.AppStatus = exports.EPlatform = exports.PlugType = exports.AppSchema = exports.IntegrationVersion = exports.SectionsListSchema = exports.DefaultSectionId = exports.OffersSectionSchema = exports.IOfferType = void 0;
|
|
26
|
+
exports.KeitaroCLOGeosService = exports.TraffleKeitaroService = exports.NginxTemplate = exports.NamecheapBuyRequestSchema = exports.getDomainTargetByIp = exports.CONST_CLOUFLARE_STATUS_READY = exports.DomainTarget = exports.DomainStatus = exports.ICloudflareDomainType = exports.ICloudflareDomainStatus = exports.KeitaroUtils = exports.KeitaroService = exports.ShapeDiv = exports.PanelUserSchema = exports.PanelUserAccessScope = exports.AlternativeOnActivityResult = exports.AlternativeOnBackPressed = exports.AlternativeNavigation = exports.AlternativeStorageType = exports.AlternativeNetworkTool = exports.AlternativeLogicType = exports.AlternativeSourceType = exports.AlternativeLayoutType = exports.AppType = exports.AppStatus = exports.EPlatform = exports.PlugType = exports.AppSchema = exports.IntegrationVersion = exports.SectionsListSchema = exports.DefaultSectionId = exports.OffersSectionSchema = exports.IOfferType = void 0;
|
|
27
27
|
var offer_1 = require("./offers/offer");
|
|
28
28
|
Object.defineProperty(exports, "IOfferType", { enumerable: true, get: function () { return offer_1.IOfferType; } });
|
|
29
29
|
var section_1 = require("./offers/section");
|
|
@@ -70,3 +70,5 @@ var nginx_template_1 = require("./templates/nginx-template");
|
|
|
70
70
|
Object.defineProperty(exports, "NginxTemplate", { enumerable: true, get: function () { return nginx_template_1.NginxTemplate; } });
|
|
71
71
|
var traffle_keitaro_service_1 = require("./network/keitaro/traffle/traffle-keitaro-service");
|
|
72
72
|
Object.defineProperty(exports, "TraffleKeitaroService", { enumerable: true, get: function () { return traffle_keitaro_service_1.TraffleKeitaroService; } });
|
|
73
|
+
var keitaro_clo_geos_1 = require("./keitaro/keitaro-clo-geos");
|
|
74
|
+
Object.defineProperty(exports, "KeitaroCLOGeosService", { enumerable: true, get: function () { return keitaro_clo_geos_1.KeitaroCLOGeosService; } });
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { IApp } from "../app/app";
|
|
2
|
+
export interface IGeoSyncResult {
|
|
3
|
+
appId: number;
|
|
4
|
+
platform: string;
|
|
5
|
+
keitaroGeos: string[];
|
|
6
|
+
appGeos: string[];
|
|
7
|
+
missingGeos: string[];
|
|
8
|
+
needsUpdate: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface IGeoUpdateData {
|
|
11
|
+
appId: number;
|
|
12
|
+
platform: string;
|
|
13
|
+
newGeos: string[];
|
|
14
|
+
}
|
|
15
|
+
export interface IGeoSyncSummary {
|
|
16
|
+
totalApps: number;
|
|
17
|
+
appsNeedingUpdate: number;
|
|
18
|
+
updates: IGeoUpdateData[];
|
|
19
|
+
errors: string[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Основна функція синхронізації гео між Keitaro та додатками
|
|
23
|
+
*/
|
|
24
|
+
export declare function syncKeitaroCLOGeosWithApps(apps: IApp[]): Promise<IGeoSyncSummary>;
|
|
25
|
+
/**
|
|
26
|
+
* Отримує детальну інформацію про синхронізацію для конкретного додатку
|
|
27
|
+
*/
|
|
28
|
+
export declare function getAppGeoSyncDetails(appId: number, platform: string, apps: IApp[]): Promise<IGeoSyncResult | null>;
|
|
29
|
+
/**
|
|
30
|
+
* Отримує всі додатки, які потребують оновлення гео
|
|
31
|
+
*/
|
|
32
|
+
export declare function getAppsNeedingGeoUpdate(apps: IApp[]): Promise<IGeoUpdateData[]>;
|
|
33
|
+
/**
|
|
34
|
+
* Експортуємо основні функції
|
|
35
|
+
*/
|
|
36
|
+
export declare const KeitaroCLOGeosService: {
|
|
37
|
+
syncKeitaroCLOGeosWithApps: typeof syncKeitaroCLOGeosWithApps;
|
|
38
|
+
getAppGeoSyncDetails: typeof getAppGeoSyncDetails;
|
|
39
|
+
getAppsNeedingGeoUpdate: typeof getAppsNeedingGeoUpdate;
|
|
40
|
+
};
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.KeitaroCLOGeosService = exports.getAppsNeedingGeoUpdate = exports.getAppGeoSyncDetails = exports.syncKeitaroCLOGeosWithApps = void 0;
|
|
7
|
+
const http_1 = __importDefault(require("../network/keitaro/traffle/http"));
|
|
8
|
+
/**
|
|
9
|
+
* Отримує всі кампанії з Keitaro
|
|
10
|
+
*/
|
|
11
|
+
async function getAllKeitaroCampaigns() {
|
|
12
|
+
try {
|
|
13
|
+
const { data: campaigns } = await http_1.default.get('campaigns');
|
|
14
|
+
return campaigns;
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
console.error('Помилка отримання кампаній з Keitaro:', error);
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Отримує всі потоки для конкретної кампанії
|
|
23
|
+
*/
|
|
24
|
+
async function getStreamsByCampaignId(campaignId) {
|
|
25
|
+
try {
|
|
26
|
+
const { data: streams } = await http_1.default.get(`campaigns/${campaignId}/streams`);
|
|
27
|
+
return streams;
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
console.error(`Помилка отримання потоків для кампанії ${campaignId}:`, error);
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Отримує гео з CLO потоку кампанії
|
|
36
|
+
*/
|
|
37
|
+
async function getCLOGeosFromCampaign(campaignId) {
|
|
38
|
+
try {
|
|
39
|
+
const streams = await getStreamsByCampaignId(campaignId);
|
|
40
|
+
console.log(`Отримано ${streams.length} потоків для кампанії ${campaignId}`);
|
|
41
|
+
const cloStream = streams.find(stream => stream.name === "CLO");
|
|
42
|
+
if (!cloStream) {
|
|
43
|
+
console.log(`CLO потік не знайдено для кампанії ${campaignId}`);
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
console.log(`Знайдено CLO потік:`, {
|
|
47
|
+
id: cloStream.id,
|
|
48
|
+
name: cloStream.name,
|
|
49
|
+
filters: cloStream.filters
|
|
50
|
+
});
|
|
51
|
+
if (!cloStream.filters || cloStream.filters.length === 0) {
|
|
52
|
+
console.log(`CLO потік не має фільтрів для кампанії ${campaignId}`);
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
// Шукаємо фільтр за країною в CLO потоці
|
|
56
|
+
const countryFilter = cloStream.filters.find(filter => filter.name === "country");
|
|
57
|
+
if (!countryFilter) {
|
|
58
|
+
console.log(`Фільтр country не знайдено в CLO потоці кампанії ${campaignId}`);
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
console.log(`Знайдено фільтр country:`, countryFilter);
|
|
62
|
+
if (!countryFilter.payload || !Array.isArray(countryFilter.payload)) {
|
|
63
|
+
console.log(`Фільтр country не має payload або payload не є масивом для кампанії ${campaignId}`);
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
console.log(`Гео з CLO потоку кампанії ${campaignId}:`, countryFilter.payload);
|
|
67
|
+
return countryFilter.payload;
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
console.error(`Помилка отримання CLO гео для кампанії ${campaignId}:`, error);
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Визначає платформу за назвою кампанії
|
|
76
|
+
*/
|
|
77
|
+
function detectPlatformFromCampaignName(campaignName) {
|
|
78
|
+
const name = campaignName.toLowerCase();
|
|
79
|
+
if (name.includes('(hw)') || name.includes('huawei')) {
|
|
80
|
+
return 'hw';
|
|
81
|
+
}
|
|
82
|
+
else if (name.includes('(sm)') || name.includes('samsung')) {
|
|
83
|
+
return 'sm';
|
|
84
|
+
}
|
|
85
|
+
else if (name.includes('(rs)') || name.includes('rustore')) {
|
|
86
|
+
return 'rs';
|
|
87
|
+
}
|
|
88
|
+
else if (name.includes('(xm)') || name.includes('xiaomi')) {
|
|
89
|
+
return 'xm';
|
|
90
|
+
}
|
|
91
|
+
else if (name.includes('(ap)') || name.includes('apkpure')) {
|
|
92
|
+
return 'ap';
|
|
93
|
+
}
|
|
94
|
+
else if (name.includes('(tg)') || name.includes('telegram')) {
|
|
95
|
+
return 'tg';
|
|
96
|
+
}
|
|
97
|
+
else if (name.includes('(am)') || name.includes('amazon')) {
|
|
98
|
+
return 'am';
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
return '@'; // General platform
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Знаходить додаток за group кампанії
|
|
106
|
+
*/
|
|
107
|
+
function findAppByGroup(group, apps) {
|
|
108
|
+
const groupId = parseInt(group);
|
|
109
|
+
if (isNaN(groupId))
|
|
110
|
+
return null;
|
|
111
|
+
return apps.find(app => app.id === groupId) || null;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Основна функція синхронізації гео між Keitaro та додатками
|
|
115
|
+
*/
|
|
116
|
+
async function syncKeitaroCLOGeosWithApps(apps) {
|
|
117
|
+
console.log(`Починаємо синхронізацію гео для ${apps.length} додатків...`);
|
|
118
|
+
const summary = {
|
|
119
|
+
totalApps: apps.length,
|
|
120
|
+
appsNeedingUpdate: 0,
|
|
121
|
+
updates: [],
|
|
122
|
+
errors: []
|
|
123
|
+
};
|
|
124
|
+
try {
|
|
125
|
+
// Отримуємо всі кампанії з Keitaro
|
|
126
|
+
const allCampaigns = await getAllKeitaroCampaigns();
|
|
127
|
+
console.log(`Отримано ${allCampaigns.length} кампаній з Keitaro`);
|
|
128
|
+
// Діагностика: виводимо всі кампанії з group
|
|
129
|
+
console.log('\n=== Діагностика кампаній ===');
|
|
130
|
+
const campaignsWithGroup = allCampaigns.filter(c => c.group);
|
|
131
|
+
console.log(`Кампанії з group: ${campaignsWithGroup.length}`);
|
|
132
|
+
// Виводимо перші 10 кампаній з group для діагностики
|
|
133
|
+
campaignsWithGroup.slice(0, 10).forEach(campaign => {
|
|
134
|
+
console.log(` ID: ${campaign.id}, Group: "${campaign.group}", Name: "${campaign.name}"`);
|
|
135
|
+
});
|
|
136
|
+
if (campaignsWithGroup.length > 10) {
|
|
137
|
+
console.log(` ... та ще ${campaignsWithGroup.length - 10} кампаній`);
|
|
138
|
+
}
|
|
139
|
+
// Діагностика: шукаємо кампанії з нашими bundle
|
|
140
|
+
console.log('\n=== Пошук кампаній за bundle ===');
|
|
141
|
+
apps.forEach(app => {
|
|
142
|
+
const campaignsWithBundle = allCampaigns.filter(c => c.name.toLowerCase().includes(app.bundle.toLowerCase()));
|
|
143
|
+
console.log(`Кампанії з bundle "${app.bundle}": ${campaignsWithBundle.length}`);
|
|
144
|
+
campaignsWithBundle.forEach(campaign => {
|
|
145
|
+
console.log(` ID: ${campaign.id}, Group: "${campaign.group}", Name: "${campaign.name}"`);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
// Створюємо мапу додатків за ID для швидкого пошуку
|
|
149
|
+
const appsMap = new Map();
|
|
150
|
+
apps.forEach(app => appsMap.set(app.id, app));
|
|
151
|
+
console.log(`\nШукаємо додатки з ID: ${Array.from(appsMap.keys()).join(', ')}`);
|
|
152
|
+
// Фільтруємо кампанії, які мають group відповідний до наших додатків
|
|
153
|
+
const ourCampaigns = allCampaigns.filter(campaign => {
|
|
154
|
+
const hasGroup = campaign.group && appsMap.has(parseInt(campaign.group));
|
|
155
|
+
if (hasGroup) {
|
|
156
|
+
console.log(`✅ Знайдено нашу кампанію за group: ID=${campaign.id}, name="${campaign.name}", group="${campaign.group}"`);
|
|
157
|
+
}
|
|
158
|
+
return hasGroup;
|
|
159
|
+
});
|
|
160
|
+
// Додатково шукаємо кампанії за bundle в назві
|
|
161
|
+
const campaignsByBundle = allCampaigns.filter(campaign => {
|
|
162
|
+
return apps.some(app => campaign.name.toLowerCase().includes(app.bundle.toLowerCase()));
|
|
163
|
+
});
|
|
164
|
+
console.log(`\nЗнайдено ${campaignsByBundle.length} кампаній за bundle`);
|
|
165
|
+
campaignsByBundle.forEach(campaign => {
|
|
166
|
+
const matchingApp = apps.find(app => campaign.name.toLowerCase().includes(app.bundle.toLowerCase()));
|
|
167
|
+
if (matchingApp) {
|
|
168
|
+
console.log(`✅ Знайдено кампанію за bundle: ID=${campaign.id}, name="${campaign.name}", bundle="${matchingApp.bundle}"`);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
// Об'єднуємо результати
|
|
172
|
+
const allOurCampaigns = [...ourCampaigns, ...campaignsByBundle];
|
|
173
|
+
const uniqueCampaigns = allOurCampaigns.filter((campaign, index, self) => index === self.findIndex(c => c.id === campaign.id));
|
|
174
|
+
console.log(`\nВсього знайдено ${uniqueCampaigns.length} унікальних кампаній наших додатків`);
|
|
175
|
+
// Обробляємо кожну кампанію
|
|
176
|
+
for (const campaign of uniqueCampaigns) {
|
|
177
|
+
try {
|
|
178
|
+
const group = campaign.group;
|
|
179
|
+
if (!group)
|
|
180
|
+
continue;
|
|
181
|
+
const app = appsMap.get(parseInt(group));
|
|
182
|
+
if (!app) {
|
|
183
|
+
console.warn(`Додаток з ID ${group} не знайдено в списку додатків`);
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
console.log(`\nОбробляємо кампанію ${campaign.id} для додатку ${group}:`);
|
|
187
|
+
console.log(` Назва: ${campaign.name}`);
|
|
188
|
+
console.log(` Group: ${campaign.group}`);
|
|
189
|
+
// Визначаємо платформу за назвою кампанії
|
|
190
|
+
const platform = detectPlatformFromCampaignName(campaign.name);
|
|
191
|
+
console.log(` Визначена платформа: ${platform}`);
|
|
192
|
+
// Отримуємо гео з CLO потоку кампанії
|
|
193
|
+
console.log(` Отримуємо гео з CLO потоку...`);
|
|
194
|
+
const keitaroGeos = await getCLOGeosFromCampaign(campaign.id);
|
|
195
|
+
console.log(` Keitaro CLO гео: ${keitaroGeos.join(', ')}`);
|
|
196
|
+
// Отримуємо гео з додатку для конкретної платформи
|
|
197
|
+
const platformData = app.platforms[platform];
|
|
198
|
+
const appGeos = platformData?.geo || [];
|
|
199
|
+
console.log(` App гео для платформи ${platform}: ${appGeos.join(', ')}`);
|
|
200
|
+
// Порівнюємо гео
|
|
201
|
+
const missingGeos = keitaroGeos.filter(geo => !appGeos.includes(geo));
|
|
202
|
+
const needsUpdate = missingGeos.length > 0;
|
|
203
|
+
console.log(` Відсутні гео: ${missingGeos.join(', ')}`);
|
|
204
|
+
console.log(` Потребує оновлення: ${needsUpdate}`);
|
|
205
|
+
if (needsUpdate) {
|
|
206
|
+
summary.appsNeedingUpdate++;
|
|
207
|
+
const updateData = {
|
|
208
|
+
appId: parseInt(group),
|
|
209
|
+
platform: platform,
|
|
210
|
+
newGeos: [...appGeos, ...missingGeos]
|
|
211
|
+
};
|
|
212
|
+
summary.updates.push(updateData);
|
|
213
|
+
console.log(`✅ Додаток ${group} (${platform}) потребує оновлення гео:`);
|
|
214
|
+
console.log(` Кампанія: ${campaign.name}`);
|
|
215
|
+
console.log(` Keitaro гео: ${keitaroGeos.join(', ')}`);
|
|
216
|
+
console.log(` App гео: ${appGeos.join(', ')}`);
|
|
217
|
+
console.log(` Відсутні гео: ${missingGeos.join(', ')}`);
|
|
218
|
+
console.log(` Нові гео: ${updateData.newGeos.join(', ')}`);
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
console.log(`✅ Додаток ${group} (${platform}) не потребує оновлення гео`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
const errorMsg = `Помилка обробки кампанії ${campaign.id} (${campaign.name}): ${error}`;
|
|
226
|
+
console.error(errorMsg);
|
|
227
|
+
summary.errors.push(errorMsg);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
console.log(`\nСинхронізація завершена. ${summary.appsNeedingUpdate} додатків потребують оновлення гео.`);
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
const errorMsg = `Критична помилка синхронізації: ${error}`;
|
|
234
|
+
console.error(errorMsg);
|
|
235
|
+
summary.errors.push(errorMsg);
|
|
236
|
+
}
|
|
237
|
+
return summary;
|
|
238
|
+
}
|
|
239
|
+
exports.syncKeitaroCLOGeosWithApps = syncKeitaroCLOGeosWithApps;
|
|
240
|
+
/**
|
|
241
|
+
* Отримує детальну інформацію про синхронізацію для конкретного додатку
|
|
242
|
+
*/
|
|
243
|
+
async function getAppGeoSyncDetails(appId, platform, apps) {
|
|
244
|
+
try {
|
|
245
|
+
const app = apps.find(a => a.id === appId);
|
|
246
|
+
if (!app) {
|
|
247
|
+
console.error(`Додаток з ID ${appId} не знайдено`);
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
const platformData = app.platforms[platform];
|
|
251
|
+
if (!platformData) {
|
|
252
|
+
console.error(`Платформа ${platform} не знайдена для додатку ${appId}`);
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
// Отримуємо всі кампанії та шукаємо нашу за group
|
|
256
|
+
const allCampaigns = await getAllKeitaroCampaigns();
|
|
257
|
+
const ourCampaign = allCampaigns.find(campaign => campaign.group === appId.toString());
|
|
258
|
+
if (!ourCampaign) {
|
|
259
|
+
console.error(`Кампанія для додатку ${appId} (group: ${appId}) не знайдена в Keitaro`);
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
console.log(`Знайдено кампанію для додатку ${appId}:`, {
|
|
263
|
+
id: ourCampaign.id,
|
|
264
|
+
name: ourCampaign.name,
|
|
265
|
+
group: ourCampaign.group
|
|
266
|
+
});
|
|
267
|
+
// Отримуємо гео з CLO потоку
|
|
268
|
+
const keitaroGeos = await getCLOGeosFromCampaign(ourCampaign.id);
|
|
269
|
+
const appGeos = platformData.geo || [];
|
|
270
|
+
// Порівнюємо гео
|
|
271
|
+
const missingGeos = keitaroGeos.filter(geo => !appGeos.includes(geo));
|
|
272
|
+
const needsUpdate = missingGeos.length > 0;
|
|
273
|
+
return {
|
|
274
|
+
appId,
|
|
275
|
+
platform,
|
|
276
|
+
keitaroGeos,
|
|
277
|
+
appGeos,
|
|
278
|
+
missingGeos,
|
|
279
|
+
needsUpdate
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
catch (error) {
|
|
283
|
+
console.error(`Помилка отримання деталей синхронізації для додатку ${appId}:`, error);
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
exports.getAppGeoSyncDetails = getAppGeoSyncDetails;
|
|
288
|
+
/**
|
|
289
|
+
* Отримує всі додатки, які потребують оновлення гео
|
|
290
|
+
*/
|
|
291
|
+
async function getAppsNeedingGeoUpdate(apps) {
|
|
292
|
+
const summary = await syncKeitaroCLOGeosWithApps(apps);
|
|
293
|
+
return summary.updates;
|
|
294
|
+
}
|
|
295
|
+
exports.getAppsNeedingGeoUpdate = getAppsNeedingGeoUpdate;
|
|
296
|
+
/**
|
|
297
|
+
* Експортуємо основні функції
|
|
298
|
+
*/
|
|
299
|
+
exports.KeitaroCLOGeosService = {
|
|
300
|
+
syncKeitaroCLOGeosWithApps,
|
|
301
|
+
getAppGeoSyncDetails,
|
|
302
|
+
getAppsNeedingGeoUpdate
|
|
303
|
+
};
|
|
@@ -702,6 +702,7 @@ async function cloneTraffleCampaign(app, platform, addDefaultStreams) {
|
|
|
702
702
|
cost_value: originalCampaign.cost_value,
|
|
703
703
|
cost_currency: originalCampaign.cost_currency,
|
|
704
704
|
uniqueness_period: originalCampaign.uniqueness_period,
|
|
705
|
+
uniqueness_method: originalCampaign.uniqueness_method,
|
|
705
706
|
cookies_ttl: originalCampaign.cookies_ttl,
|
|
706
707
|
notes: originalCampaign.notes,
|
|
707
708
|
collect_clicks: originalCampaign.collect_clicks,
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -32,4 +32,5 @@ export { IDomain, IDomainSetupResult, DomainStatus, DomainTarget, CONST_CLOUFLAR
|
|
|
32
32
|
export { INamecheapDomain, INamecheapBuyRequest, INamecheapContactInfo, NamecheapBuyRequestSchema, INamecheapGetDomainsResult, INamecheapBuyResult } from "./general/namecheap-domain"
|
|
33
33
|
export { NginxTemplate } from "./templates/nginx-template";
|
|
34
34
|
|
|
35
|
-
export { TraffleKeitaroService } from "./network/keitaro/traffle/traffle-keitaro-service"
|
|
35
|
+
export { TraffleKeitaroService } from "./network/keitaro/traffle/traffle-keitaro-service"
|
|
36
|
+
export { KeitaroCLOGeosService } from "./keitaro/keitaro-clo-geos"
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import keitaroApi from "../network/keitaro/traffle/http";
|
|
2
|
+
import { IKeitaroCampaign } from "./keitaro-campaign";
|
|
3
|
+
import { IKeitaroStream } from "./keitaro-stream";
|
|
4
|
+
import { IApp, IPlatformParams, EPlatform } from "../app/app";
|
|
5
|
+
|
|
6
|
+
// Інтерфейс для результату синхронізації гео
|
|
7
|
+
export interface IGeoSyncResult {
|
|
8
|
+
appId: number;
|
|
9
|
+
platform: string;
|
|
10
|
+
keitaroGeos: string[];
|
|
11
|
+
appGeos: string[];
|
|
12
|
+
missingGeos: string[];
|
|
13
|
+
needsUpdate: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Інтерфейс для оновлення гео в додатку
|
|
17
|
+
export interface IGeoUpdateData {
|
|
18
|
+
appId: number;
|
|
19
|
+
platform: string;
|
|
20
|
+
newGeos: string[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Інтерфейс для загального результату синхронізації
|
|
24
|
+
export interface IGeoSyncSummary {
|
|
25
|
+
totalApps: number;
|
|
26
|
+
appsNeedingUpdate: number;
|
|
27
|
+
updates: IGeoUpdateData[];
|
|
28
|
+
errors: string[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Отримує всі кампанії з Keitaro
|
|
33
|
+
*/
|
|
34
|
+
async function getAllKeitaroCampaigns(): Promise<IKeitaroCampaign[]> {
|
|
35
|
+
try {
|
|
36
|
+
const { data: campaigns } = await keitaroApi.get<IKeitaroCampaign[]>('campaigns');
|
|
37
|
+
return campaigns;
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error('Помилка отримання кампаній з Keitaro:', error);
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Отримує всі потоки для конкретної кампанії
|
|
46
|
+
*/
|
|
47
|
+
async function getStreamsByCampaignId(campaignId: number): Promise<IKeitaroStream[]> {
|
|
48
|
+
try {
|
|
49
|
+
const { data: streams } = await keitaroApi.get<IKeitaroStream[]>(`campaigns/${campaignId}/streams`);
|
|
50
|
+
return streams;
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error(`Помилка отримання потоків для кампанії ${campaignId}:`, error);
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Отримує гео з CLO потоку кампанії
|
|
59
|
+
*/
|
|
60
|
+
async function getCLOGeosFromCampaign(campaignId: number): Promise<string[]> {
|
|
61
|
+
try {
|
|
62
|
+
const streams = await getStreamsByCampaignId(campaignId);
|
|
63
|
+
console.log(`Отримано ${streams.length} потоків для кампанії ${campaignId}`);
|
|
64
|
+
|
|
65
|
+
const cloStream = streams.find(stream => stream.name === "CLO");
|
|
66
|
+
if (!cloStream) {
|
|
67
|
+
console.log(`CLO потік не знайдено для кампанії ${campaignId}`);
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
console.log(`Знайдено CLO потік:`, {
|
|
72
|
+
id: cloStream.id,
|
|
73
|
+
name: cloStream.name,
|
|
74
|
+
filters: cloStream.filters
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
if (!cloStream.filters || cloStream.filters.length === 0) {
|
|
78
|
+
console.log(`CLO потік не має фільтрів для кампанії ${campaignId}`);
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Шукаємо фільтр за країною в CLO потоці
|
|
83
|
+
const countryFilter = cloStream.filters.find(filter => filter.name === "country");
|
|
84
|
+
if (!countryFilter) {
|
|
85
|
+
console.log(`Фільтр country не знайдено в CLO потоці кампанії ${campaignId}`);
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
console.log(`Знайдено фільтр country:`, countryFilter);
|
|
90
|
+
|
|
91
|
+
if (!countryFilter.payload || !Array.isArray(countryFilter.payload)) {
|
|
92
|
+
console.log(`Фільтр country не має payload або payload не є масивом для кампанії ${campaignId}`);
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
console.log(`Гео з CLO потоку кампанії ${campaignId}:`, countryFilter.payload);
|
|
97
|
+
return countryFilter.payload;
|
|
98
|
+
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error(`Помилка отримання CLO гео для кампанії ${campaignId}:`, error);
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Визначає платформу за назвою кампанії
|
|
107
|
+
*/
|
|
108
|
+
function detectPlatformFromCampaignName(campaignName: string): string {
|
|
109
|
+
const name = campaignName.toLowerCase();
|
|
110
|
+
|
|
111
|
+
if (name.includes('(hw)') || name.includes('huawei')) {
|
|
112
|
+
return 'hw';
|
|
113
|
+
} else if (name.includes('(sm)') || name.includes('samsung')) {
|
|
114
|
+
return 'sm';
|
|
115
|
+
} else if (name.includes('(rs)') || name.includes('rustore')) {
|
|
116
|
+
return 'rs';
|
|
117
|
+
} else if (name.includes('(xm)') || name.includes('xiaomi')) {
|
|
118
|
+
return 'xm';
|
|
119
|
+
} else if (name.includes('(ap)') || name.includes('apkpure')) {
|
|
120
|
+
return 'ap';
|
|
121
|
+
} else if (name.includes('(tg)') || name.includes('telegram')) {
|
|
122
|
+
return 'tg';
|
|
123
|
+
} else if (name.includes('(am)') || name.includes('amazon')) {
|
|
124
|
+
return 'am';
|
|
125
|
+
} else {
|
|
126
|
+
return '@'; // General platform
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Знаходить додаток за group кампанії
|
|
132
|
+
*/
|
|
133
|
+
function findAppByGroup(group: string, apps: IApp[]): IApp | null {
|
|
134
|
+
const groupId = parseInt(group);
|
|
135
|
+
if (isNaN(groupId)) return null;
|
|
136
|
+
return apps.find(app => app.id === groupId) || null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Основна функція синхронізації гео між Keitaro та додатками
|
|
141
|
+
*/
|
|
142
|
+
export async function syncKeitaroCLOGeosWithApps(apps: IApp[]): Promise<IGeoSyncSummary> {
|
|
143
|
+
console.log(`Починаємо синхронізацію гео для ${apps.length} додатків...`);
|
|
144
|
+
|
|
145
|
+
const summary: IGeoSyncSummary = {
|
|
146
|
+
totalApps: apps.length,
|
|
147
|
+
appsNeedingUpdate: 0,
|
|
148
|
+
updates: [],
|
|
149
|
+
errors: []
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
// Отримуємо всі кампанії з Keitaro
|
|
154
|
+
const allCampaigns = await getAllKeitaroCampaigns();
|
|
155
|
+
console.log(`Отримано ${allCampaigns.length} кампаній з Keitaro`);
|
|
156
|
+
|
|
157
|
+
// Діагностика: виводимо всі кампанії з group
|
|
158
|
+
console.log('\n=== Діагностика кампаній ===');
|
|
159
|
+
const campaignsWithGroup = allCampaigns.filter(c => c.group);
|
|
160
|
+
console.log(`Кампанії з group: ${campaignsWithGroup.length}`);
|
|
161
|
+
|
|
162
|
+
// Виводимо перші 10 кампаній з group для діагностики
|
|
163
|
+
campaignsWithGroup.slice(0, 10).forEach(campaign => {
|
|
164
|
+
console.log(` ID: ${campaign.id}, Group: "${campaign.group}", Name: "${campaign.name}"`);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
if (campaignsWithGroup.length > 10) {
|
|
168
|
+
console.log(` ... та ще ${campaignsWithGroup.length - 10} кампаній`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Діагностика: шукаємо кампанії з нашими bundle
|
|
172
|
+
console.log('\n=== Пошук кампаній за bundle ===');
|
|
173
|
+
apps.forEach(app => {
|
|
174
|
+
const campaignsWithBundle = allCampaigns.filter(c =>
|
|
175
|
+
c.name.toLowerCase().includes(app.bundle.toLowerCase())
|
|
176
|
+
);
|
|
177
|
+
console.log(`Кампанії з bundle "${app.bundle}": ${campaignsWithBundle.length}`);
|
|
178
|
+
campaignsWithBundle.forEach(campaign => {
|
|
179
|
+
console.log(` ID: ${campaign.id}, Group: "${campaign.group}", Name: "${campaign.name}"`);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// Створюємо мапу додатків за ID для швидкого пошуку
|
|
184
|
+
const appsMap = new Map<number, IApp>();
|
|
185
|
+
apps.forEach(app => appsMap.set(app.id, app));
|
|
186
|
+
|
|
187
|
+
console.log(`\nШукаємо додатки з ID: ${Array.from(appsMap.keys()).join(', ')}`);
|
|
188
|
+
|
|
189
|
+
// Фільтруємо кампанії, які мають group відповідний до наших додатків
|
|
190
|
+
const ourCampaigns = allCampaigns.filter(campaign => {
|
|
191
|
+
const hasGroup = campaign.group && appsMap.has(parseInt(campaign.group));
|
|
192
|
+
if (hasGroup) {
|
|
193
|
+
console.log(`✅ Знайдено нашу кампанію за group: ID=${campaign.id}, name="${campaign.name}", group="${campaign.group}"`);
|
|
194
|
+
}
|
|
195
|
+
return hasGroup;
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Додатково шукаємо кампанії за bundle в назві
|
|
199
|
+
const campaignsByBundle = allCampaigns.filter(campaign => {
|
|
200
|
+
return apps.some(app =>
|
|
201
|
+
campaign.name.toLowerCase().includes(app.bundle.toLowerCase())
|
|
202
|
+
);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
console.log(`\nЗнайдено ${campaignsByBundle.length} кампаній за bundle`);
|
|
206
|
+
campaignsByBundle.forEach(campaign => {
|
|
207
|
+
const matchingApp = apps.find(app =>
|
|
208
|
+
campaign.name.toLowerCase().includes(app.bundle.toLowerCase())
|
|
209
|
+
);
|
|
210
|
+
if (matchingApp) {
|
|
211
|
+
console.log(`✅ Знайдено кампанію за bundle: ID=${campaign.id}, name="${campaign.name}", bundle="${matchingApp.bundle}"`);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Об'єднуємо результати
|
|
216
|
+
const allOurCampaigns = [...ourCampaigns, ...campaignsByBundle];
|
|
217
|
+
const uniqueCampaigns = allOurCampaigns.filter((campaign, index, self) =>
|
|
218
|
+
index === self.findIndex(c => c.id === campaign.id)
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
console.log(`\nВсього знайдено ${uniqueCampaigns.length} унікальних кампаній наших додатків`);
|
|
222
|
+
|
|
223
|
+
// Обробляємо кожну кампанію
|
|
224
|
+
for (const campaign of uniqueCampaigns) {
|
|
225
|
+
try {
|
|
226
|
+
const group = campaign.group;
|
|
227
|
+
if (!group) continue;
|
|
228
|
+
|
|
229
|
+
const app = appsMap.get(parseInt(group));
|
|
230
|
+
if (!app) {
|
|
231
|
+
console.warn(`Додаток з ID ${group} не знайдено в списку додатків`);
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
console.log(`\nОбробляємо кампанію ${campaign.id} для додатку ${group}:`);
|
|
236
|
+
console.log(` Назва: ${campaign.name}`);
|
|
237
|
+
console.log(` Group: ${campaign.group}`);
|
|
238
|
+
|
|
239
|
+
// Визначаємо платформу за назвою кампанії
|
|
240
|
+
const platform = detectPlatformFromCampaignName(campaign.name);
|
|
241
|
+
console.log(` Визначена платформа: ${platform}`);
|
|
242
|
+
|
|
243
|
+
// Отримуємо гео з CLO потоку кампанії
|
|
244
|
+
console.log(` Отримуємо гео з CLO потоку...`);
|
|
245
|
+
const keitaroGeos = await getCLOGeosFromCampaign(campaign.id);
|
|
246
|
+
console.log(` Keitaro CLO гео: ${keitaroGeos.join(', ')}`);
|
|
247
|
+
|
|
248
|
+
// Отримуємо гео з додатку для конкретної платформи
|
|
249
|
+
const platformData = app.platforms[platform as EPlatform];
|
|
250
|
+
const appGeos = platformData?.geo || [];
|
|
251
|
+
console.log(` App гео для платформи ${platform}: ${appGeos.join(', ')}`);
|
|
252
|
+
|
|
253
|
+
// Порівнюємо гео
|
|
254
|
+
const missingGeos = keitaroGeos.filter(geo => !appGeos.includes(geo));
|
|
255
|
+
const needsUpdate = missingGeos.length > 0;
|
|
256
|
+
|
|
257
|
+
console.log(` Відсутні гео: ${missingGeos.join(', ')}`);
|
|
258
|
+
console.log(` Потребує оновлення: ${needsUpdate}`);
|
|
259
|
+
|
|
260
|
+
if (needsUpdate) {
|
|
261
|
+
summary.appsNeedingUpdate++;
|
|
262
|
+
|
|
263
|
+
const updateData: IGeoUpdateData = {
|
|
264
|
+
appId: parseInt(group),
|
|
265
|
+
platform: platform,
|
|
266
|
+
newGeos: [...appGeos, ...missingGeos]
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
summary.updates.push(updateData);
|
|
270
|
+
|
|
271
|
+
console.log(`✅ Додаток ${group} (${platform}) потребує оновлення гео:`);
|
|
272
|
+
console.log(` Кампанія: ${campaign.name}`);
|
|
273
|
+
console.log(` Keitaro гео: ${keitaroGeos.join(', ')}`);
|
|
274
|
+
console.log(` App гео: ${appGeos.join(', ')}`);
|
|
275
|
+
console.log(` Відсутні гео: ${missingGeos.join(', ')}`);
|
|
276
|
+
console.log(` Нові гео: ${updateData.newGeos.join(', ')}`);
|
|
277
|
+
} else {
|
|
278
|
+
console.log(`✅ Додаток ${group} (${platform}) не потребує оновлення гео`);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
} catch (error) {
|
|
282
|
+
const errorMsg = `Помилка обробки кампанії ${campaign.id} (${campaign.name}): ${error}`;
|
|
283
|
+
console.error(errorMsg);
|
|
284
|
+
summary.errors.push(errorMsg);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
console.log(`\nСинхронізація завершена. ${summary.appsNeedingUpdate} додатків потребують оновлення гео.`);
|
|
289
|
+
|
|
290
|
+
} catch (error) {
|
|
291
|
+
const errorMsg = `Критична помилка синхронізації: ${error}`;
|
|
292
|
+
console.error(errorMsg);
|
|
293
|
+
summary.errors.push(errorMsg);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return summary;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Отримує детальну інформацію про синхронізацію для конкретного додатку
|
|
301
|
+
*/
|
|
302
|
+
export async function getAppGeoSyncDetails(appId: number, platform: string, apps: IApp[]): Promise<IGeoSyncResult | null> {
|
|
303
|
+
try {
|
|
304
|
+
const app = apps.find(a => a.id === appId);
|
|
305
|
+
if (!app) {
|
|
306
|
+
console.error(`Додаток з ID ${appId} не знайдено`);
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const platformData = app.platforms[platform as EPlatform];
|
|
311
|
+
if (!platformData) {
|
|
312
|
+
console.error(`Платформа ${platform} не знайдена для додатку ${appId}`);
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Отримуємо всі кампанії та шукаємо нашу за group
|
|
317
|
+
const allCampaigns = await getAllKeitaroCampaigns();
|
|
318
|
+
const ourCampaign = allCampaigns.find(campaign =>
|
|
319
|
+
campaign.group === appId.toString()
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
if (!ourCampaign) {
|
|
323
|
+
console.error(`Кампанія для додатку ${appId} (group: ${appId}) не знайдена в Keitaro`);
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
console.log(`Знайдено кампанію для додатку ${appId}:`, {
|
|
328
|
+
id: ourCampaign.id,
|
|
329
|
+
name: ourCampaign.name,
|
|
330
|
+
group: ourCampaign.group
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// Отримуємо гео з CLO потоку
|
|
334
|
+
const keitaroGeos = await getCLOGeosFromCampaign(ourCampaign.id);
|
|
335
|
+
const appGeos = platformData.geo || [];
|
|
336
|
+
|
|
337
|
+
// Порівнюємо гео
|
|
338
|
+
const missingGeos = keitaroGeos.filter(geo => !appGeos.includes(geo));
|
|
339
|
+
const needsUpdate = missingGeos.length > 0;
|
|
340
|
+
|
|
341
|
+
return {
|
|
342
|
+
appId,
|
|
343
|
+
platform,
|
|
344
|
+
keitaroGeos,
|
|
345
|
+
appGeos,
|
|
346
|
+
missingGeos,
|
|
347
|
+
needsUpdate
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
} catch (error) {
|
|
351
|
+
console.error(`Помилка отримання деталей синхронізації для додатку ${appId}:`, error);
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Отримує всі додатки, які потребують оновлення гео
|
|
358
|
+
*/
|
|
359
|
+
export async function getAppsNeedingGeoUpdate(apps: IApp[]): Promise<IGeoUpdateData[]> {
|
|
360
|
+
const summary = await syncKeitaroCLOGeosWithApps(apps);
|
|
361
|
+
return summary.updates;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Експортуємо основні функції
|
|
366
|
+
*/
|
|
367
|
+
export const KeitaroCLOGeosService = {
|
|
368
|
+
syncKeitaroCLOGeosWithApps,
|
|
369
|
+
getAppGeoSyncDetails,
|
|
370
|
+
getAppsNeedingGeoUpdate
|
|
371
|
+
};
|
|
@@ -844,6 +844,7 @@ async function cloneTraffleCampaign(app: IApp, platform: EPlatform, addDefaultSt
|
|
|
844
844
|
cost_value: originalCampaign.cost_value,
|
|
845
845
|
cost_currency: originalCampaign.cost_currency,
|
|
846
846
|
uniqueness_period: originalCampaign.uniqueness_period,
|
|
847
|
+
uniqueness_method: originalCampaign.uniqueness_method,
|
|
847
848
|
cookies_ttl: originalCampaign.cookies_ttl,
|
|
848
849
|
notes: originalCampaign.notes,
|
|
849
850
|
collect_clicks: originalCampaign.collect_clicks,
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
const { KeitaroCLOGeosService } = require('./lib/keitaro/keitaro-clo-geos');
|
|
2
|
+
|
|
3
|
+
// Тестові дані додатків
|
|
4
|
+
const testApps = [
|
|
5
|
+
{
|
|
6
|
+
id: 9999,
|
|
7
|
+
bundle: "com.test.app2",
|
|
8
|
+
name: "Test App 2",
|
|
9
|
+
domainParams: {
|
|
10
|
+
name: "test.com"
|
|
11
|
+
},
|
|
12
|
+
platforms: {
|
|
13
|
+
'@': {
|
|
14
|
+
geo: ["US"],
|
|
15
|
+
enabled: true
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: 9998,
|
|
21
|
+
bundle: "com.test.app222",
|
|
22
|
+
name: "Test App 222",
|
|
23
|
+
domainParams: {
|
|
24
|
+
name: "test2.com"
|
|
25
|
+
},
|
|
26
|
+
platforms: {
|
|
27
|
+
'@': {
|
|
28
|
+
geo: ["UA"],
|
|
29
|
+
enabled: true
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
async function testGeoSync() {
|
|
36
|
+
try {
|
|
37
|
+
console.log('=== Тестування синхронізації гео Keitaro CLO ===\n');
|
|
38
|
+
|
|
39
|
+
// Тест 1: Отримання додатків, які потребують оновлення гео
|
|
40
|
+
console.log('1. Отримання додатків, які потребують оновлення гео...');
|
|
41
|
+
const appsNeedingUpdate = await KeitaroCLOGeosService.getAppsNeedingGeoUpdate(testApps);
|
|
42
|
+
console.log(`Результат: ${appsNeedingUpdate.length} додатків потребують оновлення\n`);
|
|
43
|
+
|
|
44
|
+
// Тест 2: Детальна синхронізація
|
|
45
|
+
console.log('2. Повна синхронізація гео...');
|
|
46
|
+
const syncSummary = await KeitaroCLOGeosService.syncKeitaroCLOGeosWithApps(testApps);
|
|
47
|
+
console.log('Результат синхронізації:');
|
|
48
|
+
console.log(`- Всього додатків: ${syncSummary.totalApps}`);
|
|
49
|
+
console.log(`- Потребують оновлення: ${syncSummary.appsNeedingUpdate}`);
|
|
50
|
+
console.log(`- Помилки: ${syncSummary.errors.length}\n`);
|
|
51
|
+
|
|
52
|
+
// Тест 3: Деталі для конкретного додатку (використовуємо наявний ID 9999)
|
|
53
|
+
console.log('3. Деталі синхронізації для додатку 9999...');
|
|
54
|
+
const appDetails = await KeitaroCLOGeosService.getAppGeoSyncDetails(9999, '@', testApps);
|
|
55
|
+
if (appDetails) {
|
|
56
|
+
// console.log('Деталі додатку:');
|
|
57
|
+
// console.log(`- App ID: ${appDetails.appId}`);
|
|
58
|
+
// console.log(`- Платформа: ${appDetails.platform}`);
|
|
59
|
+
// console.log(`- Keitaro гео: ${appDetails.keitaroGeos.join(', ')}`);
|
|
60
|
+
// console.log(`- App гео: ${appDetails.appGeos.join(', ')}`);
|
|
61
|
+
// console.log(`- Відсутні гео: ${appDetails.missingGeos.join(', ')}`);
|
|
62
|
+
// console.log(`- Потребує оновлення: ${appDetails.needsUpdate}\n`);
|
|
63
|
+
console.log(`appDetails: ${JSON.stringify(appDetails)}`);
|
|
64
|
+
} else {
|
|
65
|
+
console.log('Додаток не знайдено або помилка\n');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
// Виводимо всі оновлення
|
|
71
|
+
if (syncSummary.updates.length > 0) {
|
|
72
|
+
console.log('5. Список оновлень гео:');
|
|
73
|
+
syncSummary.updates.forEach((update, index) => {
|
|
74
|
+
console.log(`${index + 1}. Додаток ${update.appId} (${update.platform}):`);
|
|
75
|
+
console.log(` Нові гео: ${update.newGeos.join(', ')}`);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Виводимо помилки, якщо є
|
|
80
|
+
if (syncSummary.errors.length > 0) {
|
|
81
|
+
console.log('\n6. Помилки:');
|
|
82
|
+
syncSummary.errors.forEach((error, index) => {
|
|
83
|
+
console.log(`${index + 1}. ${error}`);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error('Помилка тестування:', error);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Запускаємо тест
|
|
93
|
+
testGeoSync();
|
package/test-keitaro.js
CHANGED
|
@@ -5,12 +5,12 @@ async function testClone() {
|
|
|
5
5
|
try {
|
|
6
6
|
// Тестові дані для створення кампанії з trackingParams
|
|
7
7
|
const testApp = {
|
|
8
|
-
id:
|
|
9
|
-
bundle: "com.test.
|
|
8
|
+
id: 9998,
|
|
9
|
+
bundle: "com.test.app222",
|
|
10
10
|
name: "Test App",
|
|
11
11
|
platforms: {
|
|
12
12
|
'@': {
|
|
13
|
-
geo: ["UA"
|
|
13
|
+
geo: ["UA"],
|
|
14
14
|
appsflyerParams: {
|
|
15
15
|
apiToken: "test_token",
|
|
16
16
|
devKey: "test_dev_key"
|
|
@@ -20,11 +20,11 @@ async function testClone() {
|
|
|
20
20
|
keitaroData: {
|
|
21
21
|
trackingCampaignAlias: "test123",
|
|
22
22
|
trackingCampaignId: 9999,
|
|
23
|
-
trackingCampaignName: "[
|
|
24
|
-
trackingDomainName: "
|
|
23
|
+
trackingCampaignName: "[9998] Test App ET [com.test.app222] [Android] test2.com",
|
|
24
|
+
trackingDomainName: "test2.com",
|
|
25
25
|
campingToken: "test_token_123",
|
|
26
26
|
trackingParams: {
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
advertising_id: "old_advertising_id",
|
|
29
29
|
appsflyer_device_id: "old_device_id"
|
|
30
30
|
}
|
|
@@ -33,7 +33,7 @@ async function testClone() {
|
|
|
33
33
|
}
|
|
34
34
|
},
|
|
35
35
|
domainParams: {
|
|
36
|
-
name: "
|
|
36
|
+
name: "test2.com"
|
|
37
37
|
}
|
|
38
38
|
};
|
|
39
39
|
|