@bprotsyk/aso-core 2.1.224 → 2.1.225
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/lib/app/app.d.ts +237 -363
- package/lib/app/app.js +93 -176
- package/lib/general/cloudflare-domain.d.ts +1 -0
- package/lib/general/domain.d.ts +82 -0
- package/lib/general/domain.js +5 -0
- package/lib/index.d.ts +5 -2
- package/lib/index.js +21 -17
- package/lib/keitaro/keitaro-campaign.d.ts +0 -1
- package/lib/keitaro/keitaro-offer.d.ts +3 -14
- package/lib/network/keitaro/http.js +1 -1
- package/lib/network/keitaro/keitaro-service.d.ts +37 -51
- package/lib/network/keitaro/keitaro-service.js +442 -192
- package/lib/offers/list.d.ts +18 -18
- package/lib/offers/offer.d.ts +4 -0
- package/lib/offers/offer.js +1 -0
- package/lib/panel/app/upsert-flash-app-request.d.ts +14 -14
- package/lib/server/server.d.ts +140 -0
- package/lib/server/server.js +36 -0
- package/lib/utils/keitaro-utils.d.ts +1 -1
- package/lib/utils/keitaro-utils.js +9 -722
- package/package.json +1 -1
- package/src/app/app.ts +316 -308
- package/src/general/cloudflare-domain.ts +1 -0
- package/src/general/domain.ts +8 -0
- package/src/index.ts +27 -30
- package/src/keitaro/keitaro-campaign.ts +0 -1
- package/src/keitaro/keitaro-offer.ts +3 -14
- package/src/network/keitaro/http.ts +1 -1
- package/src/network/keitaro/keitaro-service.ts +533 -232
- package/src/offers/offer.ts +2 -0
- package/src/panel/app/upsert-flash-app-request.ts +48 -0
- package/src/server/server.ts +57 -0
- package/src/utils/keitaro-utils.ts +24 -0
|
@@ -1,20 +1,13 @@
|
|
|
1
1
|
import { IKeitaroStream } from "../../keitaro/keitaro-stream";
|
|
2
|
-
import { IOffer } from "../../offers/offer"
|
|
2
|
+
import { IOffer } from "../../offers/offer"
|
|
3
3
|
import keitaroApi from "./http"
|
|
4
4
|
import { IKeitaroCampaign } from "../../keitaro/keitaro-campaign";
|
|
5
5
|
import { IKeitaroDomain } from "../../keitaro/keitaro-domain";
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
DIRECT_TEMPLATE_CAMPAIGN_ID
|
|
12
|
-
} from "../../app/app";
|
|
13
|
-
import { IKeitaroOffer } from "../../keitaro/keitaro-offer";
|
|
14
|
-
|
|
15
|
-
// Default group IDs
|
|
16
|
-
const DEFAULT_OFFERS_GROUP_ID = 107;
|
|
17
|
-
const DEFAULT_CAMPAIGNS_GROUP_ID = 106;
|
|
6
|
+
import { EPlatform, getPlatformName, IApp } from "../../app/app";
|
|
7
|
+
import { TRAFFIC_SOURCE_ID_FLASH_AI, prepareOWCampaignParameters } from "../../utils/keitaro-utils";
|
|
8
|
+
import { convertMillisToDate, getTimestampsForTodayAndYesterday } from "../../utils/general";
|
|
9
|
+
import { IKeitaroOffer } from "index";
|
|
10
|
+
import { IKeitaroClicksRequest, IKeitaroClicksRangeRequest, KeitaroClicksInterval } from "../../keitaro/keitaro-clicks";
|
|
18
11
|
|
|
19
12
|
export interface IKeitaroOffersFilter {
|
|
20
13
|
keitaroId?: number,
|
|
@@ -22,9 +15,9 @@ export interface IKeitaroOffersFilter {
|
|
|
22
15
|
caption?: string
|
|
23
16
|
}
|
|
24
17
|
|
|
25
|
-
// Basic campaign operations
|
|
26
18
|
async function getStreamsByCampaignId(campaignId: number): Promise<IKeitaroStream[]> {
|
|
27
19
|
const { data: streams } = await keitaroApi.get<IKeitaroStream[]>(`campaigns/${campaignId}/streams`);
|
|
20
|
+
|
|
28
21
|
return streams
|
|
29
22
|
}
|
|
30
23
|
|
|
@@ -33,213 +26,450 @@ async function getAllCampaigns(): Promise<IKeitaroCampaign[]> {
|
|
|
33
26
|
return campaigns
|
|
34
27
|
}
|
|
35
28
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
async function cloneStreams(originalCampaignId: number, streamPositionsToClone: number[], campaignRegExp: RegExp): Promise<void> {
|
|
32
|
+
// Get the original campaign's streams
|
|
33
|
+
const originalStreams = await getStreamsByCampaignId(originalCampaignId)
|
|
34
|
+
|
|
35
|
+
// Filter the original streams by the given stream positions to clone
|
|
36
|
+
const streamsToClone = originalStreams.filter(stream => streamPositionsToClone.includes(stream.position));
|
|
37
|
+
|
|
38
|
+
// Get a list of all campaigns
|
|
39
|
+
const allCampaigns = await getAllCampaigns()
|
|
40
|
+
|
|
41
|
+
// Filter the campaigns by the given RegExp
|
|
42
|
+
const matchingCampaigns = allCampaigns.filter(campaign => campaignRegExp.exec(campaign.name) && campaign.id != originalCampaignId);
|
|
43
|
+
// console.log(matchingCampaigns)
|
|
44
|
+
|
|
45
|
+
// For each matching campaign, clone the streams
|
|
46
|
+
for (const matchingCampaign of matchingCampaigns) {
|
|
47
|
+
for (const streamToClone of streamsToClone) {
|
|
48
|
+
await keitaroApi.post('streams', {
|
|
49
|
+
name: streamToClone.name,
|
|
50
|
+
campaign_id: matchingCampaign.id,
|
|
51
|
+
schema: streamToClone.schema,
|
|
52
|
+
type: streamToClone.type,
|
|
53
|
+
action_type: streamToClone.action_type,
|
|
54
|
+
weight: 100,
|
|
55
|
+
offers: streamToClone.offers.map((offer) => {
|
|
56
|
+
return {
|
|
57
|
+
offer_id: offer.offer_id,
|
|
58
|
+
share: 100
|
|
59
|
+
}
|
|
60
|
+
}),
|
|
61
|
+
filters: streamToClone.filters.map((filter) => {
|
|
62
|
+
return {
|
|
63
|
+
name: filter.name,
|
|
64
|
+
mode: filter.mode,
|
|
65
|
+
payload: filter.payload
|
|
66
|
+
}
|
|
67
|
+
}),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
39
71
|
}
|
|
40
72
|
|
|
41
73
|
async function updateCampaign(id: number, payload: Partial<IKeitaroCampaign>) {
|
|
42
74
|
await keitaroApi.put(`campaigns/${id}`, payload)
|
|
43
75
|
}
|
|
44
76
|
|
|
45
|
-
async function
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
77
|
+
async function createStreamForMatchingCampaigns(streamPayloads: { payload: any, offerId: string }[], campaignRegExp: RegExp, avoidGroup?: number) {
|
|
78
|
+
// Get all campaigns once
|
|
79
|
+
const allCampaigns = await getAllCampaigns();
|
|
80
|
+
const matchingCampaigns = allCampaigns.filter(campaign =>
|
|
81
|
+
(avoidGroup ? campaign.group_id != avoidGroup : true) &&
|
|
82
|
+
campaignRegExp.exec(campaign.name)
|
|
83
|
+
);
|
|
49
84
|
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
85
|
+
// Get all streams for matching campaigns once
|
|
86
|
+
const campaignStreams = new Map<number, IKeitaroStream[]>();
|
|
87
|
+
for (const campaign of matchingCampaigns) {
|
|
88
|
+
campaignStreams.set(campaign.id, await getStreamsByCampaignId(campaign.id));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Process each campaign
|
|
92
|
+
for (const matchingCampaign of matchingCampaigns) {
|
|
93
|
+
const streams = campaignStreams.get(matchingCampaign.id) || [];
|
|
94
|
+
|
|
95
|
+
// Process each stream payload
|
|
96
|
+
for (const { payload, offerId } of streamPayloads) {
|
|
97
|
+
const identicalStream = streams.find(stream => stream.name.includes(offerId));
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
if (identicalStream) {
|
|
101
|
+
await keitaroApi.put(`streams/${identicalStream.id}`, {
|
|
102
|
+
campaign_id: matchingCampaign.id,
|
|
103
|
+
...payload
|
|
104
|
+
});
|
|
105
|
+
} else {
|
|
106
|
+
await keitaroApi.post('streams', {
|
|
107
|
+
campaign_id: matchingCampaign.id,
|
|
108
|
+
...payload
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error(`Error updating stream ${offerId} (${payload.name}) in campaign ${matchingCampaign.id} (${matchingCampaign.name}):`, error);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
54
116
|
}
|
|
55
117
|
|
|
56
|
-
// Offer operations
|
|
57
118
|
async function getAllOffers(): Promise<IKeitaroOffer[]> {
|
|
58
|
-
const { data: offers } = await keitaroApi.get<IKeitaroOffer[]>('offers')
|
|
119
|
+
const { data: offers } = await keitaroApi.get<IKeitaroOffer[]>('offers')
|
|
120
|
+
|
|
59
121
|
return offers
|
|
60
122
|
}
|
|
61
123
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
action_type: 'http',
|
|
72
|
-
action_payload: offer.link,
|
|
73
|
-
affiliate_network_id: 0, // Can be set if we have partner mapping
|
|
74
|
-
payout_value: 0,
|
|
75
|
-
payout_currency: 'USD',
|
|
76
|
-
payout_type: 'CPA',
|
|
77
|
-
state: offer.hidden ? 'disabled' : 'active',
|
|
78
|
-
country: [offer.geo],
|
|
79
|
-
offer_type: 'external',
|
|
80
|
-
payout_auto: true,
|
|
81
|
-
payout_upsell: true,
|
|
82
|
-
};
|
|
83
|
-
const { data: keitaroOffer } = await keitaroApi.post<IKeitaroOffer>('offers', payload);
|
|
84
|
-
return keitaroOffer;
|
|
124
|
+
|
|
125
|
+
async function findKeitaroOffers(filter: IKeitaroOffersFilter): Promise<IKeitaroOffer[]> {
|
|
126
|
+
let offers = await getAllOffers()
|
|
127
|
+
|
|
128
|
+
if (filter.caption) offers = offers.filter((o) => o.name.includes(filter.caption!))
|
|
129
|
+
if (filter.keitaroId) offers = offers.filter((o) => o.id == filter.keitaroId!)
|
|
130
|
+
if (filter.name) offers = offers.filter((o) => o.name.includes(filter.name!))
|
|
131
|
+
|
|
132
|
+
return offers
|
|
85
133
|
}
|
|
86
134
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
campaign_id: campaignId
|
|
92
|
-
});
|
|
93
|
-
return stream;
|
|
135
|
+
async function getOfferByKeitaroId(id: number): Promise<IKeitaroOffer> {
|
|
136
|
+
const { data: offer } = await keitaroApi.get<IKeitaroOffer>(`offers/${id}`)
|
|
137
|
+
|
|
138
|
+
return offer
|
|
94
139
|
}
|
|
95
140
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
141
|
+
async function updateOffer(offer: any): Promise<IKeitaroOffer> {
|
|
142
|
+
const { data: o } = await keitaroApi.put<IKeitaroOffer>(`offers/${offer.id}`, offer)
|
|
143
|
+
|
|
144
|
+
return o
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function createStreamPartialPayload(keitaroOfferId: number, offerName: string, offerId: string, offerGeo: string) {
|
|
148
|
+
return {
|
|
149
|
+
name: `${offerName} ${offerGeo} (${offerId})`,
|
|
150
|
+
schema: "landings",
|
|
151
|
+
type: "regular",
|
|
152
|
+
action_type: "http",
|
|
153
|
+
weight: 100,
|
|
154
|
+
offers: [{
|
|
155
|
+
offer_id: keitaroOfferId,
|
|
156
|
+
share: 100,
|
|
157
|
+
state: "active"
|
|
158
|
+
}],
|
|
159
|
+
filters: [{
|
|
160
|
+
name: "sub_id_15",
|
|
161
|
+
mode: "accept",
|
|
162
|
+
payload: [offerId]
|
|
163
|
+
}],
|
|
164
|
+
}
|
|
105
165
|
}
|
|
106
166
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
* @param offer - Our internal IOffer object
|
|
110
|
-
* @returns Result with Keitaro offer ID and distribution stats
|
|
111
|
-
*/
|
|
112
|
-
async function addOfferToKeitaro(offer: IOffer): Promise<{
|
|
113
|
-
keitaroOfferId: number;
|
|
114
|
-
success: number;
|
|
115
|
-
failed: number;
|
|
116
|
-
campaigns: string[]
|
|
117
|
-
}> {
|
|
118
|
-
// First, create the offer in Keitaro
|
|
119
|
-
const keitaroOffer = await createOfferInKeitaro(offer);
|
|
120
|
-
console.log(`Created Keitaro offer #${keitaroOffer.id}: ${keitaroOffer.name}`);
|
|
167
|
+
async function addOffersToKeitaro(offers: IOffer[], affiliateId: number, links: string[], avoidGroup?: number, groupId?: number) {
|
|
168
|
+
const allOffers = await getAllOffers();
|
|
121
169
|
|
|
122
|
-
|
|
123
|
-
const offerwallCampaigns = await getOfferwallCampaigns();
|
|
124
|
-
let success = 0;
|
|
125
|
-
let failed = 0;
|
|
126
|
-
const campaignNames: string[] = [];
|
|
170
|
+
const streamPayloads = [];
|
|
127
171
|
|
|
128
|
-
for (
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
offer_id: keitaroOffer.id,
|
|
150
|
-
share: 100,
|
|
151
|
-
state: 'active'
|
|
152
|
-
}],
|
|
153
|
-
filters: [{
|
|
154
|
-
name: 'country',
|
|
155
|
-
mode: 'accept',
|
|
156
|
-
payload: [offer.geo]
|
|
157
|
-
}]
|
|
158
|
-
});
|
|
172
|
+
for (let i = 0; i < offers.length; i++) {
|
|
173
|
+
const offer = offers[i];
|
|
174
|
+
const link = links[i];
|
|
175
|
+
|
|
176
|
+
const identicalOffer = allOffers.find(o => o.name.includes(offer.name));
|
|
177
|
+
|
|
178
|
+
let keitaroOfferId;
|
|
179
|
+
if (identicalOffer) {
|
|
180
|
+
keitaroOfferId = identicalOffer.id;
|
|
181
|
+
} else {
|
|
182
|
+
const offerPayload = {
|
|
183
|
+
name: `${offer.caption} ${offer.geo} (${offer.name})`,
|
|
184
|
+
group_id: groupId || 1,
|
|
185
|
+
action_payload: link,
|
|
186
|
+
affiliate_network_id: affiliateId,
|
|
187
|
+
country: [offer.geo],
|
|
188
|
+
action_type: "http",
|
|
189
|
+
offer_type: "external",
|
|
190
|
+
payout_auto: true,
|
|
191
|
+
payout_upsell: true,
|
|
192
|
+
};
|
|
159
193
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
} catch (error) {
|
|
163
|
-
console.error(`Failed to add offer to campaign ${campaign.name}:`, error);
|
|
164
|
-
failed++;
|
|
194
|
+
const { data: keitaroOffer } = await keitaroApi.post('offers', offerPayload);
|
|
195
|
+
keitaroOfferId = keitaroOffer.id;
|
|
165
196
|
}
|
|
197
|
+
|
|
198
|
+
streamPayloads.push({
|
|
199
|
+
payload: createStreamPartialPayload(
|
|
200
|
+
keitaroOfferId,
|
|
201
|
+
offer.caption,
|
|
202
|
+
offer.name,
|
|
203
|
+
offer.geo
|
|
204
|
+
),
|
|
205
|
+
offerId: offer.name
|
|
206
|
+
});
|
|
166
207
|
}
|
|
167
208
|
|
|
168
|
-
|
|
209
|
+
await createStreamForMatchingCampaigns(streamPayloads, /◈/, avoidGroup);
|
|
169
210
|
}
|
|
170
211
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
return
|
|
175
|
-
{ length: 8 },
|
|
176
|
-
() => chars.charAt(Math.floor(Math.random() * chars.length))
|
|
177
|
-
).join('');
|
|
212
|
+
async function createCampaign(campaignData: Partial<IKeitaroCampaign>): Promise<IKeitaroCampaign> {
|
|
213
|
+
let { data: campaign } = await keitaroApi.post(`/campaigns`, campaignData)
|
|
214
|
+
|
|
215
|
+
return campaign
|
|
178
216
|
}
|
|
179
217
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
return campaignToKeitaroData(existingCampaign);
|
|
218
|
+
async function getCampaignById(id: number): Promise<IKeitaroCampaign> {
|
|
219
|
+
const { data: campaign } = await keitaroApi.get(`/campaigns/${id}`);
|
|
220
|
+
return campaign
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export async function upsertStreamToCampaign(campaign: IKeitaroCampaign, stream: Partial<IKeitaroStream>) {
|
|
224
|
+
let streams = await getStreamsByCampaignId(campaign.id)
|
|
225
|
+
|
|
226
|
+
let identicalStream = streams.find((s) => stream.name == s.name)
|
|
227
|
+
|
|
228
|
+
if (identicalStream) {
|
|
229
|
+
console.log(`Found identical! Name: ${stream.name}`)
|
|
230
|
+
await keitaroApi.put(`streams/${identicalStream.id}`, {
|
|
231
|
+
campaign_id: campaign.id,
|
|
232
|
+
...stream
|
|
233
|
+
});
|
|
234
|
+
} else {
|
|
235
|
+
await keitaroApi.post('streams', {
|
|
236
|
+
campaign_id: campaign.id,
|
|
237
|
+
...stream
|
|
238
|
+
});
|
|
202
239
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async function cloneOWCampaign(app: IApp, platform?: EPlatform): Promise<IKeitaroCampaign | any> {
|
|
243
|
+
let name = `#${app.id} [◈]`
|
|
244
|
+
let platformName = platform ? getPlatformName(platform) : null;
|
|
245
|
+
const platformCampaignName = `#${app.id} [◈] (${platformName})`;
|
|
246
|
+
const generateAlias = () => {
|
|
247
|
+
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
248
|
+
return Array.from(
|
|
249
|
+
{ length: 8 },
|
|
250
|
+
() => chars.charAt(Math.floor(Math.random() * chars.length))
|
|
251
|
+
).join('');
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
let allCampaigns = await getAllCampaigns();
|
|
255
|
+
|
|
256
|
+
let matchingCampaign: IKeitaroCampaign[] = [];
|
|
257
|
+
|
|
258
|
+
if (platform && platform !== EPlatform.GENERAL) {
|
|
259
|
+
// Шукаємо кампанію з платформою
|
|
260
|
+
matchingCampaign = allCampaigns.filter((c) =>
|
|
261
|
+
c.name.includes(`#${app.id}`) &&
|
|
262
|
+
c.name.includes(`[◈]`) &&
|
|
263
|
+
c.name.includes(`(${platformName})`)
|
|
264
|
+
);
|
|
265
|
+
} else {
|
|
266
|
+
// Шукаємо кампанію без платформи або з суфіксом
|
|
267
|
+
matchingCampaign = allCampaigns.filter((c) =>
|
|
268
|
+
c.name.includes(`#${app.id}`) &&
|
|
269
|
+
c.name.includes(`[◈]`) &&
|
|
270
|
+
!c.name.includes('(')
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (matchingCampaign.length > 0) return matchingCampaign[0];
|
|
275
|
+
|
|
276
|
+
const originalCampaign: IKeitaroCampaign = await getCampaignById(2673);
|
|
277
|
+
const maxPosition = Math.max(...allCampaigns.map(c => c.position || 0));
|
|
278
|
+
const originalStreams = await getStreamsByCampaignId(2673);
|
|
279
|
+
|
|
280
|
+
let allDomains = await KeitaroService.getDomains(true);
|
|
281
|
+
if (!allDomains) {
|
|
282
|
+
throw Error(`Failed to get all domains list`);
|
|
212
283
|
}
|
|
284
|
+
|
|
213
285
|
const domain = allDomains[Math.floor(Math.random() * allDomains.length)];
|
|
214
|
-
|
|
215
|
-
|
|
286
|
+
const maxGroupId = Math.max(...allCampaigns.map(c => c.group_id || 0));
|
|
287
|
+
|
|
288
|
+
let alias = generateAlias();
|
|
289
|
+
|
|
290
|
+
let payload: Partial<IKeitaroCampaign> = {
|
|
291
|
+
// Унікальні поля
|
|
292
|
+
name: platformName ? platformCampaignName : name,
|
|
293
|
+
alias: alias,
|
|
294
|
+
domain_id: domain.id,
|
|
295
|
+
position: [maxPosition + 100],
|
|
296
|
+
group_id: maxGroupId + 1,
|
|
297
|
+
traffic_source_id: TRAFFIC_SOURCE_ID_FLASH_AI,
|
|
298
|
+
parameters: prepareOWCampaignParameters(app),
|
|
299
|
+
|
|
300
|
+
// Неунікальні поля з оригінальної кампанії
|
|
301
|
+
type: originalCampaign.type,
|
|
302
|
+
state: originalCampaign.state,
|
|
303
|
+
cost_type: originalCampaign.cost_type,
|
|
304
|
+
cost_value: originalCampaign.cost_value,
|
|
305
|
+
cost_currency: originalCampaign.cost_currency,
|
|
306
|
+
uniqueness_period: originalCampaign.uniqueness_period,
|
|
307
|
+
cookies_ttl: originalCampaign.cookies_ttl,
|
|
308
|
+
notes: originalCampaign.notes,
|
|
309
|
+
collect_clicks: originalCampaign.collect_clicks,
|
|
310
|
+
uniqueness_type: originalCampaign.uniqueness_type,
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
const newCampaign: IKeitaroCampaign = await createCampaign(payload);
|
|
314
|
+
|
|
315
|
+
for (const stream of originalStreams) {
|
|
316
|
+
await keitaroApi.post('streams', {
|
|
317
|
+
name: stream.name,
|
|
318
|
+
campaign_id: newCampaign.id,
|
|
319
|
+
schema: stream.schema,
|
|
320
|
+
type: stream.type,
|
|
321
|
+
action_type: stream.action_type,
|
|
322
|
+
weight: stream.weight,
|
|
323
|
+
offers: stream.offers.map((offer) => ({
|
|
324
|
+
offer_id: offer.offer_id,
|
|
325
|
+
share: offer.share,
|
|
326
|
+
state: offer.state
|
|
327
|
+
})),
|
|
328
|
+
filters: stream.filters.map((filter) => ({
|
|
329
|
+
name: filter.name,
|
|
330
|
+
mode: filter.mode,
|
|
331
|
+
payload: filter.payload
|
|
332
|
+
})),
|
|
333
|
+
position: stream.position,
|
|
334
|
+
state: stream.state
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
await keitaroApi.put(`/campaigns/${newCampaign.id}`, {
|
|
338
|
+
group_id: originalCampaign.group_id
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
const updatedCampaign = await getCampaignById(newCampaign.id);
|
|
342
|
+
|
|
343
|
+
return updatedCampaign;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
async function cloneDirectCampaign(app: IApp, platform?: EPlatform, addDefaultStreams?: boolean): Promise<IKeitaroCampaign | any> {
|
|
347
|
+
let name = `#${app.id} [➥]`
|
|
348
|
+
let platformName = platform ? getPlatformName(platform) : null;
|
|
349
|
+
const platformCampaignName = `#${app.id} [➥] (${platformName})`;
|
|
350
|
+
const generateAlias = () => {
|
|
351
|
+
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
352
|
+
return Array.from(
|
|
353
|
+
{ length: 8 },
|
|
354
|
+
() => chars.charAt(Math.floor(Math.random() * chars.length))
|
|
355
|
+
).join('');
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
let allCampaigns = await getAllCampaigns();
|
|
359
|
+
let matchingCampaign: IKeitaroCampaign[] = [];
|
|
360
|
+
|
|
361
|
+
if (platform && platform !== EPlatform.GENERAL) {
|
|
362
|
+
// Для конкретної платформи
|
|
363
|
+
const platformName = getPlatformName(platform);
|
|
364
|
+
matchingCampaign = allCampaigns.filter((c) => {
|
|
365
|
+
// Точна перевірка ID з межами слів
|
|
366
|
+
const idPattern = new RegExp(`#${app.id}\\b`);
|
|
367
|
+
const hasId = idPattern.test(c.name);
|
|
368
|
+
// Перевіряємо наявність стрілки (може бути в комбінації з іншими символами)
|
|
369
|
+
const hasArrow = c.name.includes('➥');
|
|
370
|
+
// Перевіряємо наявність платформи в дужках в кінці назви
|
|
371
|
+
const platformPattern = new RegExp(`\\(.*${platformName}.*\\)\\s*$`);
|
|
372
|
+
const hasPlatform = platformPattern.test(c.name);
|
|
373
|
+
return hasId && hasArrow && hasPlatform;
|
|
374
|
+
});
|
|
375
|
+
} else {
|
|
376
|
+
// Для General платформи
|
|
377
|
+
matchingCampaign = allCampaigns.filter((c) => {
|
|
378
|
+
// Точна перевірка ID з межами слів
|
|
379
|
+
const idPattern = new RegExp(`#${app.id}\\b`);
|
|
380
|
+
const hasId = idPattern.test(c.name);
|
|
381
|
+
// Перевіряємо наявність стрілки (може бути в комбінації з іншими символами)
|
|
382
|
+
const hasArrow = c.name.includes('➥');
|
|
383
|
+
// Перевіряємо відсутність платформи в дужках в кінці
|
|
384
|
+
// Дозволяємо будь-які інші дужки (як для гео або опису)
|
|
385
|
+
const noPlatformAtEnd = !/\(.*(?:iOS|Android|Desktop|Mobile).*\)\s*$/.test(c.name);
|
|
386
|
+
return hasId && hasArrow && noPlatformAtEnd;
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (matchingCampaign.length > 0) {
|
|
391
|
+
const existingCampaign = matchingCampaign[0];
|
|
392
|
+
|
|
393
|
+
// Якщо addDefaultStreams === true, додаємо потоки до існуючої кампанії
|
|
394
|
+
if (addDefaultStreams) {
|
|
395
|
+
const originalStreams = await getStreamsByCampaignId(3175);
|
|
396
|
+
const existingStreams = await getStreamsByCampaignId(existingCampaign.id);
|
|
397
|
+
|
|
398
|
+
for (const stream of originalStreams) {
|
|
399
|
+
// Перевіряємо чи потік вже існує
|
|
400
|
+
const existingStream = existingStreams.find(s => s.name === stream.name);
|
|
401
|
+
if (!existingStream) {
|
|
402
|
+
await keitaroApi.post('streams', {
|
|
403
|
+
name: stream.name,
|
|
404
|
+
campaign_id: existingCampaign.id,
|
|
405
|
+
schema: stream.schema,
|
|
406
|
+
type: stream.type,
|
|
407
|
+
action_type: stream.action_type,
|
|
408
|
+
weight: stream.weight,
|
|
409
|
+
offers: stream.offers.map((offer) => ({
|
|
410
|
+
offer_id: offer.offer_id,
|
|
411
|
+
share: offer.share,
|
|
412
|
+
state: offer.state
|
|
413
|
+
})),
|
|
414
|
+
filters: stream.filters.map((filter) => ({
|
|
415
|
+
name: filter.name,
|
|
416
|
+
mode: filter.mode,
|
|
417
|
+
payload: filter.payload
|
|
418
|
+
})),
|
|
419
|
+
position: stream.position,
|
|
420
|
+
state: stream.state
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return existingCampaign;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const originalCampaign: IKeitaroCampaign = await getCampaignById(3175);
|
|
216
430
|
const maxPosition = Math.max(...allCampaigns.map(c => c.position || 0));
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
431
|
+
const originalStreams = await getStreamsByCampaignId(3175);
|
|
432
|
+
|
|
433
|
+
let allDomains = await KeitaroService.getDomains(true);
|
|
434
|
+
if (!allDomains) {
|
|
435
|
+
throw Error(`Failed to get all domains list`);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const domain = allDomains[Math.floor(Math.random() * allDomains.length)];
|
|
439
|
+
const maxGroupId = Math.max(...allCampaigns.map(c => c.group_id || 0));
|
|
440
|
+
|
|
441
|
+
let alias = generateAlias();
|
|
442
|
+
|
|
443
|
+
let payload: Partial<IKeitaroCampaign> = {
|
|
444
|
+
// Унікальні поля
|
|
445
|
+
name: platformName ? platformCampaignName : name,
|
|
221
446
|
alias: alias,
|
|
222
447
|
domain_id: domain.id,
|
|
223
448
|
position: [maxPosition + 100],
|
|
224
|
-
group_id:
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
449
|
+
group_id: maxGroupId + 1,
|
|
450
|
+
traffic_source_id: TRAFFIC_SOURCE_ID_FLASH_AI,
|
|
451
|
+
parameters: prepareOWCampaignParameters(app),
|
|
452
|
+
source: originalCampaign.source,
|
|
453
|
+
bind_visitors: "slo",
|
|
454
|
+
// Неунікальні поля з оригінальної кампанії
|
|
455
|
+
type: originalCampaign.type,
|
|
456
|
+
state: originalCampaign.state,
|
|
457
|
+
cost_type: originalCampaign.cost_type,
|
|
458
|
+
cost_value: originalCampaign.cost_value,
|
|
459
|
+
cost_currency: originalCampaign.cost_currency,
|
|
460
|
+
uniqueness_period: originalCampaign.uniqueness_period,
|
|
461
|
+
cookies_ttl: originalCampaign.cookies_ttl,
|
|
462
|
+
notes: originalCampaign.notes,
|
|
463
|
+
collect_clicks: originalCampaign.collect_clicks,
|
|
464
|
+
uniqueness_type: originalCampaign.uniqueness_type,
|
|
236
465
|
};
|
|
466
|
+
|
|
467
|
+
const newCampaign: IKeitaroCampaign = await createCampaign(payload);
|
|
468
|
+
|
|
469
|
+
// Додаємо потоки: всі якщо addDefaultStreams === true, інакше тільки перший
|
|
470
|
+
const streamsToAdd = addDefaultStreams ? originalStreams : originalStreams.slice(0, 1);
|
|
237
471
|
|
|
238
|
-
|
|
239
|
-
const newCampaign = await createCampaign(payload);
|
|
240
|
-
|
|
241
|
-
// Clone streams from template
|
|
242
|
-
for (const stream of templateStreams) {
|
|
472
|
+
for (const stream of streamsToAdd) {
|
|
243
473
|
await keitaroApi.post('streams', {
|
|
244
474
|
name: stream.name,
|
|
245
475
|
campaign_id: newCampaign.id,
|
|
@@ -261,62 +491,133 @@ async function cloneCampaignForApp(app: IApp, integrationType: IntegrationType):
|
|
|
261
491
|
state: stream.state
|
|
262
492
|
});
|
|
263
493
|
}
|
|
264
|
-
|
|
265
|
-
|
|
494
|
+
await keitaroApi.put(`/campaigns/${newCampaign.id}`, {
|
|
495
|
+
group_id: originalCampaign.group_id
|
|
496
|
+
});
|
|
497
|
+
|
|
266
498
|
const updatedCampaign = await getCampaignById(newCampaign.id);
|
|
499
|
+
|
|
500
|
+
return updatedCampaign;
|
|
267
501
|
|
|
268
|
-
return campaignToKeitaroData(updatedCampaign);
|
|
269
502
|
}
|
|
270
503
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
async function cloneDCampaign(app: IApp): Promise<IKeitaroCampaign> {
|
|
509
|
+
let name = `D #${app.id} (${app.bundle})`
|
|
510
|
+
|
|
511
|
+
let allCampaigns = await getAllCampaigns()
|
|
512
|
+
let matchingCampaign = allCampaigns.filter((c) => c.name.includes(`D #${app.id}`))
|
|
513
|
+
if (matchingCampaign.length > 0) return matchingCampaign[0]
|
|
514
|
+
|
|
515
|
+
const { data: campaigns } = await keitaroApi.post(`/campaigns/2693/clone`);
|
|
516
|
+
|
|
517
|
+
if (campaigns.length == 0) throw Error("Campaign cloning falied")
|
|
518
|
+
|
|
519
|
+
let clonedCampaign: IKeitaroCampaign = campaigns[0]
|
|
520
|
+
|
|
521
|
+
let allDomains = await KeitaroService.getDomains(true)
|
|
522
|
+
if (!allDomains) {
|
|
523
|
+
throw Error(`Failed to get all domains list`)
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
const domain = allDomains[Math.floor(Math.random() * allDomains.length)];
|
|
527
|
+
|
|
528
|
+
let payload: Partial<IKeitaroCampaign> = {
|
|
529
|
+
name: name,
|
|
530
|
+
traffic_source_id: TRAFFIC_SOURCE_ID_FLASH_AI,
|
|
531
|
+
domain_id: domain.id,
|
|
532
|
+
group_id: 93,
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const { data: campaign } = await keitaroApi.put(`/campaigns/${clonedCampaign.id}`, payload)
|
|
536
|
+
|
|
537
|
+
return campaign
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
async function changeCampaignsGroup(fromId: number, toId: number, exceptForCampaignIds: number[], onlyForCampaignIds?: number[]) {
|
|
541
|
+
let campaigns = await getAllCampaigns()
|
|
542
|
+
const matchingCampaigns = campaigns.filter(campaign => campaign.group_id == fromId
|
|
543
|
+
&& exceptForCampaignIds.length > 0 ? !exceptForCampaignIds.includes(campaign.id) : onlyForCampaignIds?.includes(campaign.id))
|
|
544
|
+
|
|
545
|
+
for (let campaign of matchingCampaigns) {
|
|
546
|
+
await updateCampaign(campaign.id, { group_id: toId })
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Domain
|
|
551
|
+
async function getDomains(onlyActive?: boolean): Promise<IKeitaroDomain[]> {
|
|
552
|
+
const { data: domains } = await keitaroApi.get<IKeitaroDomain[]>(`/domains`)
|
|
553
|
+
return onlyActive ? domains.filter((d) => d.network_status == "active") : domains
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
async function getProfitForTimeRange(from: number, to: number): Promise<number> {
|
|
557
|
+
let fromString = convertMillisToDate(from)
|
|
558
|
+
let toString = convertMillisToDate(to)
|
|
559
|
+
|
|
560
|
+
let { data: data } = await keitaroApi.post(`/report/build`, {
|
|
561
|
+
range: {
|
|
562
|
+
from: fromString,
|
|
563
|
+
to: toString,
|
|
564
|
+
timezone: 'Europe/Kyiv'
|
|
565
|
+
},
|
|
566
|
+
metrics: ['profit']
|
|
567
|
+
})
|
|
568
|
+
|
|
569
|
+
return data.rows[0].profit
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// async function getProfitForTodayAndYesterday(): Promise<any> {
|
|
573
|
+
// let timestamps = getTimestampsForTodayAndYesterday()
|
|
574
|
+
// let today = await KeitaroService.getProfitForTimeRange(timestamps.today.start, timestamps.today.end)
|
|
575
|
+
// let yesterday = await KeitaroService.getProfitForTimeRange(timestamps.yesterday.start, timestamps.yesterday.end)
|
|
576
|
+
|
|
577
|
+
// return {
|
|
578
|
+
// today: today,
|
|
579
|
+
// yesterday: yesterday
|
|
580
|
+
// }
|
|
581
|
+
// }
|
|
582
|
+
|
|
583
|
+
async function fixBrokenClickCosts(startDate: string, endDate: string): Promise<void> {
|
|
584
|
+
// Get all campaigns to update
|
|
585
|
+
const campaigns = await getAllCampaigns();
|
|
586
|
+
const campaignIds = campaigns.map(campaign => campaign.id);
|
|
587
|
+
|
|
588
|
+
// Prepare the payload for the API request
|
|
589
|
+
const payload = {
|
|
590
|
+
campaign_ids: campaignIds,
|
|
591
|
+
costs: [{
|
|
592
|
+
start_date: startDate,
|
|
593
|
+
end_date: endDate,
|
|
594
|
+
cost: 0,
|
|
595
|
+
filters: {}
|
|
596
|
+
}],
|
|
597
|
+
timezone: "Europe/Kyiv",
|
|
598
|
+
currency: "USD",
|
|
599
|
+
only_campaign_uniques: 0
|
|
293
600
|
};
|
|
601
|
+
|
|
602
|
+
// Make the API request to update costs
|
|
603
|
+
await keitaroApi.post('/clicks/update_costs', payload);
|
|
294
604
|
}
|
|
295
605
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
cloneCampaignForApp,
|
|
303
|
-
getOfferwallCampaigns,
|
|
304
|
-
|
|
305
|
-
// Stream operations
|
|
306
|
-
getStreamsByCampaignId,
|
|
307
|
-
createStream,
|
|
308
|
-
|
|
309
|
-
// Domain operations
|
|
310
|
-
getDomains,
|
|
311
|
-
|
|
312
|
-
// Offer operations
|
|
313
|
-
getAllOffers,
|
|
314
|
-
createOfferInKeitaro,
|
|
315
|
-
addOfferToKeitaro,
|
|
606
|
+
async function getClicks(request: IKeitaroClicksRequest | KeitaroClicksInterval) {
|
|
607
|
+
const defaultRequest: IKeitaroClicksRequest = typeof request === 'string' ? {
|
|
608
|
+
range: { interval: request }
|
|
609
|
+
} : {
|
|
610
|
+
...request
|
|
611
|
+
};
|
|
316
612
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
campaignToKeitaroData,
|
|
613
|
+
const { data } = await keitaroApi.post('/clicks/log', defaultRequest);
|
|
614
|
+
return data.rows;
|
|
320
615
|
}
|
|
321
616
|
|
|
322
|
-
|
|
617
|
+
|
|
618
|
+
export const KeitaroService = {
|
|
619
|
+
getStreamsByCampaignId, updateCampaign, getAllCampaigns, getAllOffers, cloneStreams, addOffersToKeitaro, getOfferByKeitaroId, getDomains, createCampaign, getCampaignById, upsertStreamToCampaign, cloneOWCampaign,
|
|
620
|
+
updateOffer, changeCampaignsGroup, getProfitForTimeRange, getClicks,
|
|
621
|
+
// getProfitForTodayAndYesterday,
|
|
622
|
+
cloneDCampaign, findKeitaroOffers, fixBrokenClickCosts, cloneDirectCampaign
|
|
623
|
+
}
|