@bprotsyk/aso-core 2.1.180 → 2.1.181

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.
@@ -37,6 +37,7 @@ declare function getTraffleAffiliateNetworks(): Promise<IAffiliateNetwork[]>;
37
37
  declare function deleteOfferById(id: number): Promise<any>;
38
38
  declare function addOffersToTraffleKeitaro(offers: ITraffleOffer[]): Promise<void>;
39
39
  declare function updateOfferLinkById(id: number, action_payload: string | object): Promise<any>;
40
+ declare function getAllCampaigns(): Promise<IKeitaroCampaign[]>;
40
41
  declare function TrafleKeitaroParameters(parameters: any): Promise<{
41
42
  parameters: {
42
43
  keyword: {
@@ -133,6 +134,10 @@ declare function TrafleKeitaroParameters(parameters: any): Promise<{
133
134
  } | null>;
134
135
  declare function cloneTraffleCampaign(app: IApp, platform: EPlatform, addDefaultStreams?: boolean): Promise<IKeitaroCampaign | any>;
135
136
  export declare function updateStreamsForApp(app: IApp, platform: EPlatform): Promise<boolean>;
137
+ export declare function updateCampaignNamesToNewPattern(requireConfirmation?: boolean): Promise<{
138
+ updated: number;
139
+ failed: number;
140
+ }>;
136
141
  export declare const TraffleKeitaroService: {
137
142
  addOffersToTraffleKeitaro: typeof addOffersToTraffleKeitaro;
138
143
  getTraffleOffersGroups: typeof getTraffleOffersGroups;
@@ -140,9 +145,11 @@ export declare const TraffleKeitaroService: {
140
145
  createGroup: typeof createGroup;
141
146
  deleteOfferById: typeof deleteOfferById;
142
147
  getAllOffers: typeof getAllOffers;
148
+ getAllCampaigns: typeof getAllCampaigns;
143
149
  updateOfferLinkById: typeof updateOfferLinkById;
144
150
  cloneTraffleCampaign: typeof cloneTraffleCampaign;
145
151
  TrafleKeitaroParameters: typeof TrafleKeitaroParameters;
146
152
  updateStreamsForApp: typeof updateStreamsForApp;
153
+ updateCampaignNamesToNewPattern: typeof updateCampaignNamesToNewPattern;
147
154
  };
148
155
  export {};
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.TraffleKeitaroService = exports.updateStreamsForApp = exports.ITraffleTrafficType = void 0;
6
+ exports.TraffleKeitaroService = exports.updateCampaignNamesToNewPattern = exports.updateStreamsForApp = exports.ITraffleTrafficType = void 0;
7
7
  const http_1 = __importDefault(require("./http"));
8
8
  const app_1 = require("../../../app/app");
9
9
  const openai_1 = require("../../../network/openai/openai");
@@ -290,6 +290,24 @@ async function checkAndUpdateCampaignParameters(existingCampaign, app, platform)
290
290
  return await updateCampaignWithAppParams(existingCampaign, app, platform);
291
291
  }
292
292
  console.log("Parameters are up to date, no update needed");
293
+ // Always add postback even if no parameter update is needed
294
+ try {
295
+ console.log(`[Traffle] Adding postback to campaign ${existingCampaign.id} (no parameter update needed)`);
296
+ const postbackUrl = `https://traffle-tech.com/lander/appsflyer-s2s/index.php?status={status}&bundle_id={sub_id_16}&advertising_id={sub_id_17}&appsflyer_device_id={sub_id_18}&appsflyer_dev_key={sub_id_19}`;
297
+ await http_1.default.put(`/campaigns/${existingCampaign.id}`, {
298
+ postbacks: [{
299
+ id: 0,
300
+ campaign_id: existingCampaign.id,
301
+ method: "GET",
302
+ statuses: ["lead", "sale", "rejected", "rebill"],
303
+ url: postbackUrl
304
+ }]
305
+ });
306
+ console.log(`[Traffle] Postback added to campaign ${existingCampaign.id}`);
307
+ }
308
+ catch (error) {
309
+ console.error(`[Traffle] Failed to add postback to campaign ${existingCampaign.id}:`, error);
310
+ }
293
311
  return false;
294
312
  }
295
313
  return false;
@@ -465,6 +483,7 @@ async function updateAppWithCampaignParams(existingCampaign, app, platform) {
465
483
  }
466
484
  }
467
485
  async function cloneTraffleCampaign(app, platform, addDefaultStreams) {
486
+ console.log(`[Traffle] cloneTraffleCampaign called for app ${app.id}, platform ${platform}, addDefaultStreams: ${addDefaultStreams}`);
468
487
  const ORIGINAL_CLONE_CAMPAIGN_ID = 1925;
469
488
  const appsflyerAvailability = app.platforms[platform].appsflyerParams?.apiToken ? "[AF]" : "";
470
489
  let name = `[${app.id}] [${app.bundle}] ${app.name} [Volodymyr Vinnik] [El Trafico] [Android] ${appsflyerAvailability} ${app.domainParams.name}`;
@@ -593,6 +612,24 @@ async function cloneTraffleCampaign(app, platform, addDefaultStreams) {
593
612
  }
594
613
  }
595
614
  }
615
+ // Add postback to existing campaign
616
+ try {
617
+ console.log(`[Traffle] Adding postback to existing campaign ${existingCampaign.id}`);
618
+ const postbackUrl = `https://traffle-tech.com/lander/appsflyer-s2s/index.php?status={status}&bundle_id={sub_id_16}&advertising_id={sub_id_17}&appsflyer_device_id={sub_id_18}&appsflyer_dev_key={sub_id_19}`;
619
+ await http_1.default.put(`/campaigns/${existingCampaign.id}`, {
620
+ postbacks: [{
621
+ id: 0,
622
+ campaign_id: existingCampaign.id,
623
+ method: "GET",
624
+ statuses: ["lead", "sale", "rejected", "rebill"],
625
+ url: postbackUrl
626
+ }]
627
+ });
628
+ console.log(`[Traffle] Postback added to campaign ${existingCampaign.id}`);
629
+ }
630
+ catch (error) {
631
+ console.error(`[Traffle] Failed to add postback to campaign ${existingCampaign.id}:`, error);
632
+ }
596
633
  console.log(existingCampaign);
597
634
  return existingCampaign;
598
635
  }
@@ -720,6 +757,24 @@ async function cloneTraffleCampaign(app, platform, addDefaultStreams) {
720
757
  await createStreamWithoutIds(stream, newCampaign.id);
721
758
  }
722
759
  const updatedCampaign = await getCampaignById(newCampaign.id);
760
+ // Add postback to new campaign
761
+ try {
762
+ console.log(`[Traffle] Adding postback to new campaign ${updatedCampaign.id}`);
763
+ const postbackUrl = `https://traffle-tech.com/lander/appsflyer-s2s/index.php?status={status}&bundle_id={sub_id_16}&advertising_id={sub_id_17}&appsflyer_device_id={sub_id_18}&appsflyer_dev_key={sub_id_19}`;
764
+ await http_1.default.put(`/campaigns/${updatedCampaign.id}`, {
765
+ postbacks: [{
766
+ id: 0,
767
+ campaign_id: updatedCampaign.id,
768
+ method: "GET",
769
+ statuses: ["lead", "sale", "rejected", "rebill"],
770
+ url: postbackUrl
771
+ }]
772
+ });
773
+ console.log(`[Traffle] Postback added to campaign ${updatedCampaign.id}`);
774
+ }
775
+ catch (error) {
776
+ console.error(`[Traffle] Failed to add postback to campaign ${updatedCampaign.id}:`, error);
777
+ }
723
778
  return updatedCampaign;
724
779
  }
725
780
  async function findCampaignForAppPlatform(app, platform) {
@@ -840,6 +895,183 @@ async function updateStreamsForApp(app, platform) {
840
895
  }
841
896
  }
842
897
  exports.updateStreamsForApp = updateStreamsForApp;
898
+ async function findCampaignsWithOldNamingPattern() {
899
+ try {
900
+ const allCampaigns = await getAllCampaigns();
901
+ // Pattern: [id] app_name [bundle] [platform] domain.com (flexible - ID is optional)
902
+ // We want to find campaigns that DON'T have [Volodymyr Vinnik] in them
903
+ const oldPatternCampaigns = allCampaigns.filter(campaign => {
904
+ const name = campaign.name;
905
+ // Check if it matches the old pattern: BAN_prefix [id] app_name [bundle] [platform] domain (flexible)
906
+ const oldPattern = /^(BAN[_\w.]*\s+)?(\[(\d+)\]\s+)?([^[]+)\s+\[([^\]]+)\]\s+\[([^\]]+)\]\s+(.+)$/;
907
+ const match = name.match(oldPattern);
908
+ if (!match)
909
+ return false;
910
+ // Check if it doesn't already have [Volodymyr Vinnik]
911
+ return !name.includes('[Volodymyr Vinnik]');
912
+ });
913
+ console.log(`Found ${oldPatternCampaigns.length} campaigns with old naming pattern`);
914
+ return oldPatternCampaigns;
915
+ }
916
+ catch (error) {
917
+ console.error("Error finding campaigns with old naming pattern:", error);
918
+ return [];
919
+ }
920
+ }
921
+ async function updateCampaignNameToNewPattern(campaign, requireConfirmation = false) {
922
+ try {
923
+ const name = campaign.name;
924
+ // Parse the old pattern: BAN_prefix [id] app_name [bundle] [platform] domain (flexible - BAN and ID are optional)
925
+ const oldPattern = /^(BAN[_\w.]*\s+)?(\[(\d+)\]\s+)?([^[]+)\s+\[([^\]]+)\]\s+\[([^\]]+)\]\s+(.+)$/;
926
+ const match = name.match(oldPattern);
927
+ if (!match) {
928
+ console.log(`Campaign ${campaign.id} doesn't match old pattern: ${name}`);
929
+ return false;
930
+ }
931
+ const [, banPrefix, , idFromName, appName, bundle, platform, domain] = match;
932
+ // Create new name based on what exists in original name
933
+ let newName;
934
+ if (banPrefix && idFromName) {
935
+ // Original had BAN and ID: BAN_prefix [id] [bundle] app_name [Volodymyr Vinnik] [El Trafico] [platform] domain
936
+ newName = `${banPrefix.trim()} [${idFromName}] [${bundle}] ${appName.trim()} [Volodymyr Vinnik] [El Trafico] [${platform}] ${domain}`;
937
+ }
938
+ else if (banPrefix && !idFromName) {
939
+ // Original had BAN but no ID: BAN_prefix [bundle] app_name [Volodymyr Vinnik] [El Trafico] [platform] domain
940
+ newName = `${banPrefix.trim()} [${bundle}] ${appName.trim()} [Volodymyr Vinnik] [El Trafico] [${platform}] ${domain}`;
941
+ }
942
+ else if (!banPrefix && idFromName) {
943
+ // Original had ID but no BAN: [id] [bundle] app_name [Volodymyr Vinnik] [El Trafico] [platform] domain
944
+ newName = `[${idFromName}] [${bundle}] ${appName.trim()} [Volodymyr Vinnik] [El Trafico] [${platform}] ${domain}`;
945
+ }
946
+ else {
947
+ // Original had neither BAN nor ID: [bundle] app_name [Volodymyr Vinnik] [El Trafico] [platform] domain
948
+ newName = `[${bundle}] ${appName.trim()} [Volodymyr Vinnik] [El Trafico] [${platform}] ${domain}`;
949
+ }
950
+ if (requireConfirmation) {
951
+ console.log(`\nCampaign ${campaign.id}:`);
952
+ console.log(`OLD: "${name}"`);
953
+ console.log(`NEW: "${newName}"`);
954
+ console.log(`\nDo you want to update this campaign? (y/n/s to skip all remaining):`);
955
+ const confirmed = await confirmUpdate();
956
+ if (!confirmed) {
957
+ console.log(`Skipped campaign ${campaign.id}`);
958
+ return false;
959
+ }
960
+ }
961
+ // Update the campaign
962
+ await http_1.default.put(`/campaigns/${campaign.id}`, { name: newName });
963
+ console.log(`Updated campaign ${campaign.id}: "${name}" → "${newName}"`);
964
+ return true;
965
+ }
966
+ catch (error) {
967
+ console.error(`Failed to update campaign ${campaign.id}:`, error);
968
+ return false;
969
+ }
970
+ }
971
+ // Helper function to handle user confirmation
972
+ async function confirmUpdate() {
973
+ const readline = require('readline');
974
+ const rl = readline.createInterface({
975
+ input: process.stdin,
976
+ output: process.stdout
977
+ });
978
+ return new Promise((resolve) => {
979
+ rl.question('', (answer) => {
980
+ rl.close();
981
+ const lowerAnswer = answer.toLowerCase().trim();
982
+ if (lowerAnswer === 's') {
983
+ // Skip all remaining
984
+ console.log("Skipping all remaining updates...");
985
+ process.exit(0);
986
+ }
987
+ resolve(lowerAnswer === 'y' || lowerAnswer === 'yes');
988
+ });
989
+ });
990
+ }
991
+ async function updateCampaignNamesToNewPattern(requireConfirmation = false) {
992
+ try {
993
+ console.log("Starting campaign name update process...");
994
+ const oldPatternCampaigns = await findCampaignsWithOldNamingPattern();
995
+ if (oldPatternCampaigns.length === 0) {
996
+ console.log("No campaigns found with old naming pattern");
997
+ return { updated: 0, failed: 0 };
998
+ }
999
+ console.log(`Found ${oldPatternCampaigns.length} campaigns to potentially update`);
1000
+ if (requireConfirmation) {
1001
+ console.log("Interactive mode: You will be asked to confirm each update");
1002
+ }
1003
+ let updated = 0;
1004
+ let failed = 0;
1005
+ for (const campaign of oldPatternCampaigns) {
1006
+ const success = await updateCampaignNameToNewPattern(campaign, requireConfirmation);
1007
+ if (success) {
1008
+ updated++;
1009
+ }
1010
+ else {
1011
+ failed++;
1012
+ }
1013
+ }
1014
+ console.log(`Campaign name update completed: ${updated} updated, ${failed} failed`);
1015
+ return { updated, failed };
1016
+ }
1017
+ catch (error) {
1018
+ console.error("Failed to update campaign names:", error);
1019
+ return { updated: 0, failed: 0 };
1020
+ }
1021
+ }
1022
+ exports.updateCampaignNamesToNewPattern = updateCampaignNamesToNewPattern;
1023
+ async function ensurePostbacksForCampaign(campaignId, appId, bundle) {
1024
+ try {
1025
+ console.log(`[Traffle] Starting ensurePostbacksForCampaign for campaign ${campaignId}, app ${appId}, bundle ${bundle}`);
1026
+ const postbackUrl = `https://traffle-tech.com/lander/appsflyer-s2s/index.php?status={status}&bundle_id={sub_id_16}&advertising_id={sub_id_17}&appsflyer_device_id={sub_id_18}&appsflyer_dev_key={sub_id_19}`;
1027
+ // Check if postback already exists
1028
+ console.log(`[Traffle] Getting existing postbacks for campaign ${campaignId}`);
1029
+ const existingPostbacks = await getCampaignPostbacks(campaignId);
1030
+ console.log(`[Traffle] Existing postbacks:`, existingPostbacks);
1031
+ const postbackExists = existingPostbacks?.some(postback => postback.url === postbackUrl &&
1032
+ postback.method === "GET" &&
1033
+ postback.statuses?.includes("lead"));
1034
+ if (postbackExists) {
1035
+ console.log(`[Traffle] AppsFlyer postback already exists for Traffle campaign ${campaignId}, skipping`);
1036
+ return;
1037
+ }
1038
+ const postbackData = {
1039
+ id: 0,
1040
+ campaign_id: campaignId,
1041
+ method: "GET",
1042
+ statuses: ["lead", "sale", "rejected", "rebill"],
1043
+ url: postbackUrl
1044
+ };
1045
+ console.log(`[Traffle] Adding AppsFlyer postback to Traffle campaign ${campaignId}:`, postbackData);
1046
+ // Use Traffle API to add postback via campaign update
1047
+ console.log(`[Traffle] Making PUT request to /campaigns/${campaignId} with postbacks:`, [postbackData]);
1048
+ const response = await http_1.default.put(`/campaigns/${campaignId}`, {
1049
+ postbacks: [postbackData]
1050
+ });
1051
+ console.log(`[Traffle] Postback creation response:`, response.status, response.data?.postbacks);
1052
+ if (response.status === 200) {
1053
+ console.log(`[Traffle] Successfully added AppsFlyer postback to Traffle campaign ${campaignId}`);
1054
+ }
1055
+ }
1056
+ catch (error) {
1057
+ console.error(`Failed to add AppsFlyer postback to Traffle campaign ${campaignId}:`, error);
1058
+ }
1059
+ }
1060
+ async function getCampaignPostbacks(campaignId) {
1061
+ try {
1062
+ console.log(`[Traffle] Getting postbacks for campaign ${campaignId}`);
1063
+ const response = await http_1.default.get(`/campaigns/${campaignId}`);
1064
+ console.log(`[Traffle] Campaign ${campaignId} response:`, response.status, response.data?.postbacks);
1065
+ if (response.status === 200 && response.data) {
1066
+ return response.data.postbacks || [];
1067
+ }
1068
+ return null;
1069
+ }
1070
+ catch (error) {
1071
+ console.error(`[Traffle] Failed to get postbacks for Traffle campaign ${campaignId}:`, error);
1072
+ return null;
1073
+ }
1074
+ }
843
1075
  exports.TraffleKeitaroService = {
844
- addOffersToTraffleKeitaro, getTraffleOffersGroups, getTraffleAffiliateNetworks, createGroup, deleteOfferById, getAllOffers, updateOfferLinkById, cloneTraffleCampaign, TrafleKeitaroParameters, updateStreamsForApp
1076
+ addOffersToTraffleKeitaro, getTraffleOffersGroups, getTraffleAffiliateNetworks, createGroup, deleteOfferById, getAllOffers, getAllCampaigns, updateOfferLinkById, cloneTraffleCampaign, TrafleKeitaroParameters, updateStreamsForApp, updateCampaignNamesToNewPattern
845
1077
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bprotsyk/aso-core",
3
- "version": "2.1.180",
3
+ "version": "2.1.181",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "scripts": {
@@ -378,6 +378,27 @@ async function checkAndUpdateCampaignParameters(existingCampaign: IKeitaroCampai
378
378
  }
379
379
 
380
380
  console.log("Parameters are up to date, no update needed");
381
+
382
+ // Always add postback even if no parameter update is needed
383
+ try {
384
+ console.log(`[Traffle] Adding postback to campaign ${existingCampaign.id} (no parameter update needed)`);
385
+ const postbackUrl = `https://traffle-tech.com/lander/appsflyer-s2s/index.php?status={status}&bundle_id={sub_id_16}&advertising_id={sub_id_17}&appsflyer_device_id={sub_id_18}&appsflyer_dev_key={sub_id_19}`;
386
+
387
+ await keitaroApi.put(`/campaigns/${existingCampaign.id}`, {
388
+ postbacks: [{
389
+ id: 0,
390
+ campaign_id: existingCampaign.id,
391
+ method: "GET",
392
+ statuses: ["lead", "sale", "rejected", "rebill"],
393
+ url: postbackUrl
394
+ }]
395
+ });
396
+
397
+ console.log(`[Traffle] Postback added to campaign ${existingCampaign.id}`);
398
+ } catch (error) {
399
+ console.error(`[Traffle] Failed to add postback to campaign ${existingCampaign.id}:`, error);
400
+ }
401
+
381
402
  return false;
382
403
  }
383
404
 
@@ -572,6 +593,7 @@ async function updateAppWithCampaignParams(existingCampaign: IKeitaroCampaign, a
572
593
  }
573
594
 
574
595
  async function cloneTraffleCampaign(app: IApp, platform: EPlatform, addDefaultStreams?: boolean): Promise<IKeitaroCampaign | any> {
596
+ console.log(`[Traffle] cloneTraffleCampaign called for app ${app.id}, platform ${platform}, addDefaultStreams: ${addDefaultStreams}`)
575
597
 
576
598
 
577
599
 
@@ -722,6 +744,26 @@ async function cloneTraffleCampaign(app: IApp, platform: EPlatform, addDefaultSt
722
744
  }
723
745
  }
724
746
 
747
+ // Add postback to existing campaign
748
+ try {
749
+ console.log(`[Traffle] Adding postback to existing campaign ${existingCampaign.id}`);
750
+ const postbackUrl = `https://traffle-tech.com/lander/appsflyer-s2s/index.php?status={status}&bundle_id={sub_id_16}&advertising_id={sub_id_17}&appsflyer_device_id={sub_id_18}&appsflyer_dev_key={sub_id_19}`;
751
+
752
+ await keitaroApi.put(`/campaigns/${existingCampaign.id}`, {
753
+ postbacks: [{
754
+ id: 0,
755
+ campaign_id: existingCampaign.id,
756
+ method: "GET",
757
+ statuses: ["lead", "sale", "rejected", "rebill"],
758
+ url: postbackUrl
759
+ }]
760
+ });
761
+
762
+ console.log(`[Traffle] Postback added to campaign ${existingCampaign.id}`);
763
+ } catch (error) {
764
+ console.error(`[Traffle] Failed to add postback to campaign ${existingCampaign.id}:`, error);
765
+ }
766
+
725
767
  console.log(existingCampaign)
726
768
  return existingCampaign;
727
769
  }
@@ -870,6 +912,27 @@ async function cloneTraffleCampaign(app: IApp, platform: EPlatform, addDefaultSt
870
912
  }
871
913
 
872
914
  const updatedCampaign = await getCampaignById(newCampaign.id);
915
+
916
+ // Add postback to new campaign
917
+ try {
918
+ console.log(`[Traffle] Adding postback to new campaign ${updatedCampaign.id}`);
919
+ const postbackUrl = `https://traffle-tech.com/lander/appsflyer-s2s/index.php?status={status}&bundle_id={sub_id_16}&advertising_id={sub_id_17}&appsflyer_device_id={sub_id_18}&appsflyer_dev_key={sub_id_19}`;
920
+
921
+ await keitaroApi.put(`/campaigns/${updatedCampaign.id}`, {
922
+ postbacks: [{
923
+ id: 0,
924
+ campaign_id: updatedCampaign.id,
925
+ method: "GET",
926
+ statuses: ["lead", "sale", "rejected", "rebill"],
927
+ url: postbackUrl
928
+ }]
929
+ });
930
+
931
+ console.log(`[Traffle] Postback added to campaign ${updatedCampaign.id}`);
932
+ } catch (error) {
933
+ console.error(`[Traffle] Failed to add postback to campaign ${updatedCampaign.id}:`, error);
934
+ }
935
+
873
936
  return updatedCampaign;
874
937
  }
875
938
 
@@ -1001,6 +1064,214 @@ export async function updateStreamsForApp(app: IApp, platform: EPlatform): Promi
1001
1064
  }
1002
1065
  }
1003
1066
 
1067
+ async function findCampaignsWithOldNamingPattern(): Promise<IKeitaroCampaign[]> {
1068
+ try {
1069
+ const allCampaigns = await getAllCampaigns();
1070
+
1071
+ // Pattern: [id] app_name [bundle] [platform] domain.com (flexible - ID is optional)
1072
+ // We want to find campaigns that DON'T have [Volodymyr Vinnik] in them
1073
+ const oldPatternCampaigns = allCampaigns.filter(campaign => {
1074
+ const name = campaign.name;
1075
+
1076
+ // Check if it matches the old pattern: BAN_prefix [id] app_name [bundle] [platform] domain (flexible)
1077
+ const oldPattern = /^(BAN[_\w.]*\s+)?(\[(\d+)\]\s+)?([^[]+)\s+\[([^\]]+)\]\s+\[([^\]]+)\]\s+(.+)$/;
1078
+ const match = name.match(oldPattern);
1079
+
1080
+ if (!match) return false;
1081
+
1082
+ // Check if it doesn't already have [Volodymyr Vinnik]
1083
+ return !name.includes('[Volodymyr Vinnik]');
1084
+ });
1085
+
1086
+ console.log(`Found ${oldPatternCampaigns.length} campaigns with old naming pattern`);
1087
+ return oldPatternCampaigns;
1088
+ } catch (error) {
1089
+ console.error("Error finding campaigns with old naming pattern:", error);
1090
+ return [];
1091
+ }
1092
+ }
1093
+
1094
+ async function updateCampaignNameToNewPattern(campaign: IKeitaroCampaign, requireConfirmation: boolean = false): Promise<boolean> {
1095
+ try {
1096
+ const name = campaign.name;
1097
+
1098
+ // Parse the old pattern: BAN_prefix [id] app_name [bundle] [platform] domain (flexible - BAN and ID are optional)
1099
+ const oldPattern = /^(BAN[_\w.]*\s+)?(\[(\d+)\]\s+)?([^[]+)\s+\[([^\]]+)\]\s+\[([^\]]+)\]\s+(.+)$/;
1100
+ const match = name.match(oldPattern);
1101
+
1102
+ if (!match) {
1103
+ console.log(`Campaign ${campaign.id} doesn't match old pattern: ${name}`);
1104
+ return false;
1105
+ }
1106
+
1107
+ const [, banPrefix, , idFromName, appName, bundle, platform, domain] = match;
1108
+
1109
+ // Create new name based on what exists in original name
1110
+ let newName: string;
1111
+ if (banPrefix && idFromName) {
1112
+ // Original had BAN and ID: BAN_prefix [id] [bundle] app_name [Volodymyr Vinnik] [El Trafico] [platform] domain
1113
+ newName = `${banPrefix.trim()} [${idFromName}] [${bundle}] ${appName.trim()} [Volodymyr Vinnik] [El Trafico] [${platform}] ${domain}`;
1114
+ } else if (banPrefix && !idFromName) {
1115
+ // Original had BAN but no ID: BAN_prefix [bundle] app_name [Volodymyr Vinnik] [El Trafico] [platform] domain
1116
+ newName = `${banPrefix.trim()} [${bundle}] ${appName.trim()} [Volodymyr Vinnik] [El Trafico] [${platform}] ${domain}`;
1117
+ } else if (!banPrefix && idFromName) {
1118
+ // Original had ID but no BAN: [id] [bundle] app_name [Volodymyr Vinnik] [El Trafico] [platform] domain
1119
+ newName = `[${idFromName}] [${bundle}] ${appName.trim()} [Volodymyr Vinnik] [El Trafico] [${platform}] ${domain}`;
1120
+ } else {
1121
+ // Original had neither BAN nor ID: [bundle] app_name [Volodymyr Vinnik] [El Trafico] [platform] domain
1122
+ newName = `[${bundle}] ${appName.trim()} [Volodymyr Vinnik] [El Trafico] [${platform}] ${domain}`;
1123
+ }
1124
+
1125
+ if (requireConfirmation) {
1126
+ console.log(`\nCampaign ${campaign.id}:`);
1127
+ console.log(`OLD: "${name}"`);
1128
+ console.log(`NEW: "${newName}"`);
1129
+ console.log(`\nDo you want to update this campaign? (y/n/s to skip all remaining):`);
1130
+
1131
+ const confirmed = await confirmUpdate();
1132
+ if (!confirmed) {
1133
+ console.log(`Skipped campaign ${campaign.id}`);
1134
+ return false;
1135
+ }
1136
+ }
1137
+
1138
+ // Update the campaign
1139
+ await keitaroApi.put(`/campaigns/${campaign.id}`, { name: newName });
1140
+ console.log(`Updated campaign ${campaign.id}: "${name}" → "${newName}"`);
1141
+
1142
+ return true;
1143
+ } catch (error) {
1144
+ console.error(`Failed to update campaign ${campaign.id}:`, error);
1145
+ return false;
1146
+ }
1147
+ }
1148
+
1149
+ // Helper function to handle user confirmation
1150
+ async function confirmUpdate(): Promise<boolean> {
1151
+ const readline = require('readline');
1152
+ const rl = readline.createInterface({
1153
+ input: process.stdin,
1154
+ output: process.stdout
1155
+ });
1156
+
1157
+ return new Promise((resolve) => {
1158
+ rl.question('', (answer: string) => {
1159
+ rl.close();
1160
+ const lowerAnswer = answer.toLowerCase().trim();
1161
+ if (lowerAnswer === 's') {
1162
+ // Skip all remaining
1163
+ console.log("Skipping all remaining updates...");
1164
+ process.exit(0);
1165
+ }
1166
+ resolve(lowerAnswer === 'y' || lowerAnswer === 'yes');
1167
+ });
1168
+ });
1169
+ }
1170
+
1171
+ export async function updateCampaignNamesToNewPattern(requireConfirmation: boolean = false): Promise<{ updated: number, failed: number }> {
1172
+ try {
1173
+ console.log("Starting campaign name update process...");
1174
+
1175
+ const oldPatternCampaigns = await findCampaignsWithOldNamingPattern();
1176
+
1177
+ if (oldPatternCampaigns.length === 0) {
1178
+ console.log("No campaigns found with old naming pattern");
1179
+ return { updated: 0, failed: 0 };
1180
+ }
1181
+
1182
+ console.log(`Found ${oldPatternCampaigns.length} campaigns to potentially update`);
1183
+
1184
+ if (requireConfirmation) {
1185
+ console.log("Interactive mode: You will be asked to confirm each update");
1186
+ }
1187
+
1188
+ let updated = 0;
1189
+ let failed = 0;
1190
+
1191
+ for (const campaign of oldPatternCampaigns) {
1192
+ const success = await updateCampaignNameToNewPattern(campaign, requireConfirmation);
1193
+ if (success) {
1194
+ updated++;
1195
+ } else {
1196
+ failed++;
1197
+ }
1198
+ }
1199
+
1200
+ console.log(`Campaign name update completed: ${updated} updated, ${failed} failed`);
1201
+ return { updated, failed };
1202
+ } catch (error) {
1203
+ console.error("Failed to update campaign names:", error);
1204
+ return { updated: 0, failed: 0 };
1205
+ }
1206
+ }
1207
+
1208
+ async function ensurePostbacksForCampaign(campaignId: number, appId: number, bundle: string): Promise<void> {
1209
+ try {
1210
+ console.log(`[Traffle] Starting ensurePostbacksForCampaign for campaign ${campaignId}, app ${appId}, bundle ${bundle}`)
1211
+
1212
+ const postbackUrl = `https://traffle-tech.com/lander/appsflyer-s2s/index.php?status={status}&bundle_id={sub_id_16}&advertising_id={sub_id_17}&appsflyer_device_id={sub_id_18}&appsflyer_dev_key={sub_id_19}`
1213
+
1214
+ // Check if postback already exists
1215
+ console.log(`[Traffle] Getting existing postbacks for campaign ${campaignId}`)
1216
+ const existingPostbacks = await getCampaignPostbacks(campaignId)
1217
+ console.log(`[Traffle] Existing postbacks:`, existingPostbacks)
1218
+
1219
+ const postbackExists = existingPostbacks?.some(postback =>
1220
+ postback.url === postbackUrl &&
1221
+ postback.method === "GET" &&
1222
+ postback.statuses?.includes("lead")
1223
+ )
1224
+
1225
+ if (postbackExists) {
1226
+ console.log(`[Traffle] AppsFlyer postback already exists for Traffle campaign ${campaignId}, skipping`)
1227
+ return
1228
+ }
1229
+
1230
+ const postbackData = {
1231
+ id: 0, // Will be set by Traffle
1232
+ campaign_id: campaignId,
1233
+ method: "GET",
1234
+ statuses: ["lead", "sale", "rejected", "rebill"],
1235
+ url: postbackUrl
1236
+ }
1237
+
1238
+ console.log(`[Traffle] Adding AppsFlyer postback to Traffle campaign ${campaignId}:`, postbackData)
1239
+
1240
+ // Use Traffle API to add postback via campaign update
1241
+ console.log(`[Traffle] Making PUT request to /campaigns/${campaignId} with postbacks:`, [postbackData])
1242
+
1243
+ const response = await keitaroApi.put(`/campaigns/${campaignId}`, {
1244
+ postbacks: [postbackData]
1245
+ })
1246
+
1247
+ console.log(`[Traffle] Postback creation response:`, response.status, response.data?.postbacks)
1248
+
1249
+ if (response.status === 200) {
1250
+ console.log(`[Traffle] Successfully added AppsFlyer postback to Traffle campaign ${campaignId}`)
1251
+ }
1252
+ } catch (error) {
1253
+ console.error(`Failed to add AppsFlyer postback to Traffle campaign ${campaignId}:`, error)
1254
+ }
1255
+ }
1256
+
1257
+ async function getCampaignPostbacks(campaignId: number): Promise<any[] | null> {
1258
+ try {
1259
+ console.log(`[Traffle] Getting postbacks for campaign ${campaignId}`)
1260
+ const response = await keitaroApi.get(`/campaigns/${campaignId}`)
1261
+
1262
+ console.log(`[Traffle] Campaign ${campaignId} response:`, response.status, response.data?.postbacks)
1263
+
1264
+ if (response.status === 200 && response.data) {
1265
+ return response.data.postbacks || []
1266
+ }
1267
+
1268
+ return null
1269
+ } catch (error) {
1270
+ console.error(`[Traffle] Failed to get postbacks for Traffle campaign ${campaignId}:`, error)
1271
+ return null
1272
+ }
1273
+ }
1274
+
1004
1275
  export const TraffleKeitaroService = {
1005
- addOffersToTraffleKeitaro, getTraffleOffersGroups, getTraffleAffiliateNetworks, createGroup, deleteOfferById, getAllOffers, updateOfferLinkById, cloneTraffleCampaign, TrafleKeitaroParameters, updateStreamsForApp
1276
+ addOffersToTraffleKeitaro, getTraffleOffersGroups, getTraffleAffiliateNetworks, createGroup, deleteOfferById, getAllOffers, getAllCampaigns, updateOfferLinkById, cloneTraffleCampaign, TrafleKeitaroParameters, updateStreamsForApp, updateCampaignNamesToNewPattern
1006
1277
  }