@open-loyalty/mcp-server 1.3.6 → 1.4.1
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/dist/instructions.d.ts +1 -1
- package/dist/instructions.js +18 -5
- package/dist/tools/achievement/index.js +10 -10
- package/dist/tools/achievement/schemas.js +16 -8
- package/dist/tools/reward/handlers.d.ts +3 -3
- package/dist/tools/reward/handlers.js +54 -8
- package/dist/tools/reward/index.d.ts +4 -8
- package/dist/tools/reward/index.js +13 -5
- package/dist/tools/reward/schemas.d.ts +3 -7
- package/dist/tools/reward/schemas.js +16 -8
- package/dist/tools/tierset.d.ts +1 -1
- package/dist/tools/tierset.js +49 -25
- package/dist/tools/transaction.js +5 -2
- package/dist/tools/wallet-type.js +27 -16
- package/dist/types/schemas/admin.d.ts +6 -6
- package/dist/types/schemas/role.d.ts +4 -4
- package/dist/types/schemas/wallet-type.js +7 -5
- package/package.json +1 -1
- package/dist/prompts/fan-engagement-setup.d.ts +0 -107
- package/dist/prompts/fan-engagement-setup.js +0 -492
- package/dist/tools/achievement.d.ts +0 -1017
- package/dist/tools/achievement.js +0 -354
- package/dist/tools/campaign.d.ts +0 -1800
- package/dist/tools/campaign.js +0 -737
- package/dist/tools/member.d.ts +0 -366
- package/dist/tools/member.js +0 -352
- package/dist/tools/reward.d.ts +0 -279
- package/dist/tools/reward.js +0 -361
- package/dist/tools/segment.d.ts +0 -816
- package/dist/tools/segment.js +0 -333
- package/dist/workflows/app-login-streak.d.ts +0 -39
- package/dist/workflows/app-login-streak.js +0 -298
- package/dist/workflows/early-arrival.d.ts +0 -33
- package/dist/workflows/early-arrival.js +0 -148
- package/dist/workflows/index.d.ts +0 -101
- package/dist/workflows/index.js +0 -208
- package/dist/workflows/match-attendance.d.ts +0 -45
- package/dist/workflows/match-attendance.js +0 -308
- package/dist/workflows/sportsbar-visit.d.ts +0 -41
- package/dist/workflows/sportsbar-visit.js +0 -284
- package/dist/workflows/vod-watching.d.ts +0 -43
- package/dist/workflows/vod-watching.js +0 -326
|
@@ -1,308 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Match Attendance Workflow
|
|
3
|
-
*
|
|
4
|
-
* Creates a complete match attendance reward program including:
|
|
5
|
-
* - Base campaign for per-match coin rewards
|
|
6
|
-
* - Achievements for attendance milestones (6, 12 matches, etc.)
|
|
7
|
-
* - Bonus campaigns triggered by achievement completion
|
|
8
|
-
*/
|
|
9
|
-
import { campaignCreate, campaignList } from "../tools/campaign/handlers.js";
|
|
10
|
-
import { achievementCreate, achievementList } from "../tools/achievement.js";
|
|
11
|
-
import { badgeList } from "../tools/badge.js";
|
|
12
|
-
import { formatOLDate, DEFAULTS } from "../prompts/fan-engagement-setup.js";
|
|
13
|
-
// ============================================================================
|
|
14
|
-
// Workflow Implementation
|
|
15
|
-
// ============================================================================
|
|
16
|
-
/**
|
|
17
|
-
* Execute the match attendance workflow
|
|
18
|
-
*/
|
|
19
|
-
export async function executeMatchAttendanceWorkflow(config = {}) {
|
|
20
|
-
const result = {
|
|
21
|
-
success: false,
|
|
22
|
-
achievements: [],
|
|
23
|
-
errors: [],
|
|
24
|
-
summary: "",
|
|
25
|
-
};
|
|
26
|
-
// Merge with defaults
|
|
27
|
-
const cfg = {
|
|
28
|
-
coinsPerMatch: config.coinsPerMatch ?? DEFAULTS.matchAttendance.coinsPerMatch,
|
|
29
|
-
milestones: config.milestones ?? DEFAULTS.matchAttendance.milestones,
|
|
30
|
-
badgeNames: config.badgeNames ?? Object.values(DEFAULTS.matchAttendance.badges),
|
|
31
|
-
milestoneBonuses: config.milestoneBonuses ?? Object.values(DEFAULTS.matchAttendance.milestoneBonuses),
|
|
32
|
-
limitPerDay: config.limitPerDay ?? DEFAULTS.matchAttendance.limitPerDay,
|
|
33
|
-
seasonStart: config.seasonStart ?? DEFAULTS.seasonDates.start,
|
|
34
|
-
seasonEnd: config.seasonEnd ?? DEFAULTS.seasonDates.end,
|
|
35
|
-
};
|
|
36
|
-
try {
|
|
37
|
-
// Step 1: Create base match attendance campaign
|
|
38
|
-
const baseCampaignResult = await createBaseCampaign(cfg);
|
|
39
|
-
if (baseCampaignResult.error) {
|
|
40
|
-
result.errors.push(baseCampaignResult.error);
|
|
41
|
-
}
|
|
42
|
-
else {
|
|
43
|
-
result.baseCampaignId = baseCampaignResult.campaignId;
|
|
44
|
-
}
|
|
45
|
-
// Step 2: Get available badges for achievements
|
|
46
|
-
const badges = await getAvailableBadges();
|
|
47
|
-
// Step 3: Create achievements and bonus campaigns for each milestone
|
|
48
|
-
for (let i = 0; i < cfg.milestones.length; i++) {
|
|
49
|
-
const milestone = cfg.milestones[i];
|
|
50
|
-
const badgeName = cfg.badgeNames[i] || `Milestone ${milestone}`;
|
|
51
|
-
const bonus = cfg.milestoneBonuses[i] || 500;
|
|
52
|
-
const achievementResult = await createMilestoneAchievement(milestone, badgeName, badges, cfg.seasonStart, cfg.seasonEnd);
|
|
53
|
-
if (achievementResult.error) {
|
|
54
|
-
result.errors.push(achievementResult.error);
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
const achievementEntry = {
|
|
58
|
-
achievementId: achievementResult.achievementId,
|
|
59
|
-
milestone,
|
|
60
|
-
badgeName,
|
|
61
|
-
};
|
|
62
|
-
// Create bonus campaign for this achievement
|
|
63
|
-
if (achievementResult.achievementId) {
|
|
64
|
-
const bonusCampaignResult = await createAchievementBonusCampaign(achievementResult.achievementId, milestone, bonus, cfg.seasonStart, cfg.seasonEnd);
|
|
65
|
-
if (bonusCampaignResult.error) {
|
|
66
|
-
result.errors.push(bonusCampaignResult.error);
|
|
67
|
-
}
|
|
68
|
-
else {
|
|
69
|
-
achievementEntry.bonusCampaignId = bonusCampaignResult.campaignId;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
result.achievements.push(achievementEntry);
|
|
73
|
-
}
|
|
74
|
-
// Step 4: Verify setup
|
|
75
|
-
const verification = await verifySetup(result);
|
|
76
|
-
if (verification.warnings.length > 0) {
|
|
77
|
-
result.errors.push(...verification.warnings);
|
|
78
|
-
}
|
|
79
|
-
// Build summary
|
|
80
|
-
result.success = result.baseCampaignId !== undefined && result.errors.length === 0;
|
|
81
|
-
result.summary = buildSummary(cfg, result);
|
|
82
|
-
}
|
|
83
|
-
catch (error) {
|
|
84
|
-
result.errors.push(`Workflow error: ${error instanceof Error ? error.message : String(error)}`);
|
|
85
|
-
}
|
|
86
|
-
return result;
|
|
87
|
-
}
|
|
88
|
-
// ============================================================================
|
|
89
|
-
// Helper Functions
|
|
90
|
-
// ============================================================================
|
|
91
|
-
async function createBaseCampaign(cfg) {
|
|
92
|
-
try {
|
|
93
|
-
const response = await campaignCreate({
|
|
94
|
-
type: "direct",
|
|
95
|
-
trigger: "custom_event",
|
|
96
|
-
event: "match_attendance",
|
|
97
|
-
translations: {
|
|
98
|
-
en: {
|
|
99
|
-
name: "Match Attendance Reward",
|
|
100
|
-
description: `Earn ${cfg.coinsPerMatch} coins for each match you attend`,
|
|
101
|
-
},
|
|
102
|
-
},
|
|
103
|
-
activity: {
|
|
104
|
-
startsAt: formatOLDate(cfg.seasonStart),
|
|
105
|
-
endsAt: formatOLDate(cfg.seasonEnd),
|
|
106
|
-
},
|
|
107
|
-
rules: [
|
|
108
|
-
{
|
|
109
|
-
name: "Award coins for match attendance",
|
|
110
|
-
effects: [
|
|
111
|
-
{
|
|
112
|
-
effect: "give_points",
|
|
113
|
-
pointsRule: { fixedValue: cfg.coinsPerMatch },
|
|
114
|
-
},
|
|
115
|
-
],
|
|
116
|
-
},
|
|
117
|
-
],
|
|
118
|
-
limits: {
|
|
119
|
-
executionsPerMember: {
|
|
120
|
-
value: cfg.limitPerDay,
|
|
121
|
-
interval: { type: "days", value: 1 },
|
|
122
|
-
},
|
|
123
|
-
},
|
|
124
|
-
active: true,
|
|
125
|
-
});
|
|
126
|
-
return { campaignId: response.campaignId };
|
|
127
|
-
}
|
|
128
|
-
catch (error) {
|
|
129
|
-
return {
|
|
130
|
-
error: `Failed to create base campaign: ${error instanceof Error ? error.message : String(error)}`,
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
async function getAvailableBadges() {
|
|
135
|
-
try {
|
|
136
|
-
const response = await badgeList({});
|
|
137
|
-
const badgeMap = new Map();
|
|
138
|
-
for (const badge of response.badges) {
|
|
139
|
-
if (badge.name && badge.badgeTypeId) {
|
|
140
|
-
badgeMap.set(badge.name.toLowerCase(), badge.badgeTypeId);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
return badgeMap;
|
|
144
|
-
}
|
|
145
|
-
catch {
|
|
146
|
-
// Return empty map if badges can't be fetched - achievements will be created without badges
|
|
147
|
-
return new Map();
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
async function createMilestoneAchievement(milestone, badgeName, badges, seasonStart, seasonEnd) {
|
|
151
|
-
try {
|
|
152
|
-
// Find badge ID by name (case-insensitive)
|
|
153
|
-
const badgeTypeId = badges.get(badgeName.toLowerCase());
|
|
154
|
-
// Determine the goal value (for percentage, calculate based on assumed total matches)
|
|
155
|
-
let periodGoal;
|
|
156
|
-
let achievementName;
|
|
157
|
-
let achievementDescription;
|
|
158
|
-
if (typeof milestone === "string" && milestone.includes("%")) {
|
|
159
|
-
// Percentage milestone (e.g., "80%")
|
|
160
|
-
const percentage = parseInt(milestone.replace("%", ""), 10);
|
|
161
|
-
// Assume ~19 home matches per season (typical for major football leagues)
|
|
162
|
-
periodGoal = Math.ceil(19 * (percentage / 100));
|
|
163
|
-
achievementName = `${milestone} Match Attendance`;
|
|
164
|
-
achievementDescription = `Attend ${milestone} of all home matches this season`;
|
|
165
|
-
}
|
|
166
|
-
else {
|
|
167
|
-
periodGoal = typeof milestone === "number" ? milestone : parseInt(milestone, 10);
|
|
168
|
-
achievementName = `Attend ${periodGoal} Matches`;
|
|
169
|
-
achievementDescription = `Be at ${periodGoal} home games this season`;
|
|
170
|
-
}
|
|
171
|
-
const achievementPayload = {
|
|
172
|
-
translations: {
|
|
173
|
-
en: {
|
|
174
|
-
name: achievementName,
|
|
175
|
-
description: achievementDescription,
|
|
176
|
-
},
|
|
177
|
-
},
|
|
178
|
-
active: true,
|
|
179
|
-
activity: {
|
|
180
|
-
startsAt: formatOLDate(seasonStart),
|
|
181
|
-
endsAt: formatOLDate(seasonEnd),
|
|
182
|
-
},
|
|
183
|
-
rules: [
|
|
184
|
-
{
|
|
185
|
-
trigger: "custom_event",
|
|
186
|
-
event: "match_attendance",
|
|
187
|
-
completeRule: {
|
|
188
|
-
periodGoal,
|
|
189
|
-
},
|
|
190
|
-
},
|
|
191
|
-
],
|
|
192
|
-
};
|
|
193
|
-
// Add badge if found
|
|
194
|
-
if (badgeTypeId) {
|
|
195
|
-
achievementPayload.badgeTypeId = badgeTypeId;
|
|
196
|
-
}
|
|
197
|
-
const response = await achievementCreate(achievementPayload);
|
|
198
|
-
return { achievementId: response.achievementId };
|
|
199
|
-
}
|
|
200
|
-
catch (error) {
|
|
201
|
-
return {
|
|
202
|
-
error: `Failed to create achievement for ${milestone}: ${error instanceof Error ? error.message : String(error)}`,
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
async function createAchievementBonusCampaign(achievementId, milestone, bonusCoins, seasonStart, seasonEnd) {
|
|
207
|
-
try {
|
|
208
|
-
const milestoneLabel = typeof milestone === "string" ? milestone : `${milestone} Matches`;
|
|
209
|
-
const response = await campaignCreate({
|
|
210
|
-
type: "direct",
|
|
211
|
-
trigger: "achievement",
|
|
212
|
-
translations: {
|
|
213
|
-
en: {
|
|
214
|
-
name: `${milestoneLabel} Achievement Bonus`,
|
|
215
|
-
description: `Bonus ${bonusCoins} coins for completing the ${milestoneLabel} achievement`,
|
|
216
|
-
},
|
|
217
|
-
},
|
|
218
|
-
activity: {
|
|
219
|
-
startsAt: formatOLDate(seasonStart),
|
|
220
|
-
endsAt: formatOLDate(seasonEnd),
|
|
221
|
-
},
|
|
222
|
-
rules: [
|
|
223
|
-
{
|
|
224
|
-
name: `Bonus for completing ${milestoneLabel}`,
|
|
225
|
-
effects: [
|
|
226
|
-
{
|
|
227
|
-
effect: "give_points",
|
|
228
|
-
pointsRule: { fixedValue: bonusCoins },
|
|
229
|
-
},
|
|
230
|
-
],
|
|
231
|
-
conditions: [
|
|
232
|
-
{
|
|
233
|
-
operator: "is_equal",
|
|
234
|
-
attribute: "achievement.achievementId",
|
|
235
|
-
data: { value: achievementId },
|
|
236
|
-
},
|
|
237
|
-
],
|
|
238
|
-
},
|
|
239
|
-
],
|
|
240
|
-
active: true,
|
|
241
|
-
});
|
|
242
|
-
return { campaignId: response.campaignId };
|
|
243
|
-
}
|
|
244
|
-
catch (error) {
|
|
245
|
-
return {
|
|
246
|
-
error: `Failed to create bonus campaign for ${milestone}: ${error instanceof Error ? error.message : String(error)}`,
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
async function verifySetup(result) {
|
|
251
|
-
const warnings = [];
|
|
252
|
-
try {
|
|
253
|
-
// Verify campaigns exist
|
|
254
|
-
if (result.baseCampaignId) {
|
|
255
|
-
const campaigns = await campaignList({ active: true });
|
|
256
|
-
const found = campaigns.campaigns.some((c) => c.campaignId === result.baseCampaignId);
|
|
257
|
-
if (!found) {
|
|
258
|
-
warnings.push("Base campaign created but not found in active campaigns list");
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
// Verify achievements exist
|
|
262
|
-
if (result.achievements.length > 0) {
|
|
263
|
-
const achievements = await achievementList({ active: true });
|
|
264
|
-
for (const ach of result.achievements) {
|
|
265
|
-
const found = achievements.achievements.some((a) => a.achievementId === ach.achievementId);
|
|
266
|
-
if (!found) {
|
|
267
|
-
warnings.push(`Achievement ${ach.milestone} created but not found in active achievements list`);
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
catch (error) {
|
|
273
|
-
warnings.push(`Verification error: ${error instanceof Error ? error.message : String(error)}`);
|
|
274
|
-
}
|
|
275
|
-
return { warnings };
|
|
276
|
-
}
|
|
277
|
-
function buildSummary(cfg, result) {
|
|
278
|
-
const lines = [];
|
|
279
|
-
if (result.baseCampaignId) {
|
|
280
|
-
lines.push(`Fans will earn ${cfg.coinsPerMatch} coins per match attended`);
|
|
281
|
-
}
|
|
282
|
-
if (result.achievements.length > 0) {
|
|
283
|
-
lines.push(`\nMilestone achievements created:`);
|
|
284
|
-
for (const ach of result.achievements) {
|
|
285
|
-
const bonusText = ach.bonusCampaignId
|
|
286
|
-
? ` (+ ${cfg.milestoneBonuses[result.achievements.indexOf(ach)] || 500} coin bonus)`
|
|
287
|
-
: "";
|
|
288
|
-
lines.push(`- ${ach.milestone} matches: ${ach.badgeName} badge${bonusText}`);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
lines.push(`\nSeason: ${cfg.seasonStart} to ${cfg.seasonEnd}`);
|
|
292
|
-
lines.push(`Anti-fraud: Max ${cfg.limitPerDay} attendance${cfg.limitPerDay > 1 ? "s" : ""} per day`);
|
|
293
|
-
if (result.errors.length > 0) {
|
|
294
|
-
lines.push(`\nWarnings/Errors:`);
|
|
295
|
-
for (const error of result.errors) {
|
|
296
|
-
lines.push(`- ${error}`);
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
return lines.join("\n");
|
|
300
|
-
}
|
|
301
|
-
// ============================================================================
|
|
302
|
-
// Exports
|
|
303
|
-
// ============================================================================
|
|
304
|
-
export const matchAttendanceWorkflow = {
|
|
305
|
-
id: "match-attendance",
|
|
306
|
-
name: "Match Attendance Campaign",
|
|
307
|
-
execute: executeMatchAttendanceWorkflow,
|
|
308
|
-
};
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sports Bar Visit Workflow
|
|
3
|
-
*
|
|
4
|
-
* Creates a campaign to reward fans for visiting the sports bar,
|
|
5
|
-
* with optional achievement for visiting multiple times.
|
|
6
|
-
*/
|
|
7
|
-
export interface SportsbarVisitConfig {
|
|
8
|
-
/** Coins awarded per visit */
|
|
9
|
-
coinsPerVisit: number;
|
|
10
|
-
/** Whether to create achievement for multiple visits */
|
|
11
|
-
createAchievement: boolean;
|
|
12
|
-
/** Number of visits for achievement milestone */
|
|
13
|
-
visitMilestone: number;
|
|
14
|
-
/** Bonus coins for completing the achievement */
|
|
15
|
-
milestoneBonus: number;
|
|
16
|
-
/** Maximum visits counted per day (anti-abuse) */
|
|
17
|
-
limitPerDay: number;
|
|
18
|
-
/** Badge name for achievement (optional) */
|
|
19
|
-
badgeName?: string;
|
|
20
|
-
/** Season start date (ISO format) */
|
|
21
|
-
seasonStart: string;
|
|
22
|
-
/** Season end date (ISO format) */
|
|
23
|
-
seasonEnd: string;
|
|
24
|
-
}
|
|
25
|
-
export interface SportsbarVisitResult {
|
|
26
|
-
success: boolean;
|
|
27
|
-
visitCampaignId?: string;
|
|
28
|
-
achievementId?: string;
|
|
29
|
-
bonusCampaignId?: string;
|
|
30
|
-
errors: string[];
|
|
31
|
-
summary: string;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Execute the sports bar visit workflow
|
|
35
|
-
*/
|
|
36
|
-
export declare function executeSportsbarVisitWorkflow(config?: Partial<SportsbarVisitConfig>): Promise<SportsbarVisitResult>;
|
|
37
|
-
export declare const sportsbarVisitWorkflow: {
|
|
38
|
-
id: string;
|
|
39
|
-
name: string;
|
|
40
|
-
execute: typeof executeSportsbarVisitWorkflow;
|
|
41
|
-
};
|
|
@@ -1,284 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sports Bar Visit Workflow
|
|
3
|
-
*
|
|
4
|
-
* Creates a campaign to reward fans for visiting the sports bar,
|
|
5
|
-
* with optional achievement for visiting multiple times.
|
|
6
|
-
*/
|
|
7
|
-
import { campaignCreate, campaignList } from "../tools/campaign/handlers.js";
|
|
8
|
-
import { achievementCreate, achievementList } from "../tools/achievement.js";
|
|
9
|
-
import { badgeList } from "../tools/badge.js";
|
|
10
|
-
import { formatOLDate, DEFAULTS } from "../prompts/fan-engagement-setup.js";
|
|
11
|
-
// ============================================================================
|
|
12
|
-
// Workflow Implementation
|
|
13
|
-
// ============================================================================
|
|
14
|
-
/**
|
|
15
|
-
* Execute the sports bar visit workflow
|
|
16
|
-
*/
|
|
17
|
-
export async function executeSportsbarVisitWorkflow(config = {}) {
|
|
18
|
-
const result = {
|
|
19
|
-
success: false,
|
|
20
|
-
errors: [],
|
|
21
|
-
summary: "",
|
|
22
|
-
};
|
|
23
|
-
// Merge with defaults
|
|
24
|
-
const cfg = {
|
|
25
|
-
coinsPerVisit: config.coinsPerVisit ?? DEFAULTS.sportsbarVisit.coinsPerVisit,
|
|
26
|
-
createAchievement: config.createAchievement ?? true,
|
|
27
|
-
visitMilestone: config.visitMilestone ?? DEFAULTS.sportsbarVisit.visitMilestone,
|
|
28
|
-
milestoneBonus: config.milestoneBonus ?? DEFAULTS.sportsbarVisit.milestoneBonus,
|
|
29
|
-
limitPerDay: config.limitPerDay ?? DEFAULTS.sportsbarVisit.limitPerDay,
|
|
30
|
-
badgeName: config.badgeName ?? "Sports Bar Regular",
|
|
31
|
-
seasonStart: config.seasonStart ?? DEFAULTS.seasonDates.start,
|
|
32
|
-
seasonEnd: config.seasonEnd ?? DEFAULTS.seasonDates.end,
|
|
33
|
-
};
|
|
34
|
-
try {
|
|
35
|
-
// Step 1: Create base visit campaign
|
|
36
|
-
const visitCampaignResult = await createVisitCampaign(cfg);
|
|
37
|
-
if (visitCampaignResult.error) {
|
|
38
|
-
result.errors.push(visitCampaignResult.error);
|
|
39
|
-
}
|
|
40
|
-
else {
|
|
41
|
-
result.visitCampaignId = visitCampaignResult.campaignId;
|
|
42
|
-
}
|
|
43
|
-
// Step 2: Create achievement and bonus campaign if enabled
|
|
44
|
-
if (cfg.createAchievement) {
|
|
45
|
-
// Get available badges
|
|
46
|
-
const badges = await getAvailableBadges();
|
|
47
|
-
const achievementResult = await createVisitAchievement(cfg, badges);
|
|
48
|
-
if (achievementResult.error) {
|
|
49
|
-
result.errors.push(achievementResult.error);
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
result.achievementId = achievementResult.achievementId;
|
|
53
|
-
// Create bonus campaign for achievement
|
|
54
|
-
if (achievementResult.achievementId) {
|
|
55
|
-
const bonusCampaignResult = await createAchievementBonusCampaign(achievementResult.achievementId, cfg);
|
|
56
|
-
if (bonusCampaignResult.error) {
|
|
57
|
-
result.errors.push(bonusCampaignResult.error);
|
|
58
|
-
}
|
|
59
|
-
else {
|
|
60
|
-
result.bonusCampaignId = bonusCampaignResult.campaignId;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
// Step 3: Verify setup
|
|
66
|
-
const verification = await verifySetup(result);
|
|
67
|
-
if (verification.warnings.length > 0) {
|
|
68
|
-
result.errors.push(...verification.warnings);
|
|
69
|
-
}
|
|
70
|
-
// Determine success
|
|
71
|
-
result.success = result.visitCampaignId !== undefined && result.errors.length === 0;
|
|
72
|
-
// Build summary
|
|
73
|
-
result.summary = buildSummary(cfg, result);
|
|
74
|
-
}
|
|
75
|
-
catch (error) {
|
|
76
|
-
result.errors.push(`Workflow error: ${error instanceof Error ? error.message : String(error)}`);
|
|
77
|
-
}
|
|
78
|
-
return result;
|
|
79
|
-
}
|
|
80
|
-
// ============================================================================
|
|
81
|
-
// Helper Functions
|
|
82
|
-
// ============================================================================
|
|
83
|
-
async function createVisitCampaign(cfg) {
|
|
84
|
-
try {
|
|
85
|
-
const response = await campaignCreate({
|
|
86
|
-
type: "direct",
|
|
87
|
-
trigger: "custom_event",
|
|
88
|
-
event: "sportsbar_visit",
|
|
89
|
-
translations: {
|
|
90
|
-
en: {
|
|
91
|
-
name: "Sports Bar Visit Reward",
|
|
92
|
-
description: `Earn ${cfg.coinsPerVisit} coins for each visit to the sports bar`,
|
|
93
|
-
},
|
|
94
|
-
},
|
|
95
|
-
activity: {
|
|
96
|
-
startsAt: formatOLDate(cfg.seasonStart),
|
|
97
|
-
endsAt: formatOLDate(cfg.seasonEnd),
|
|
98
|
-
},
|
|
99
|
-
rules: [
|
|
100
|
-
{
|
|
101
|
-
name: "Award coins for sports bar visit",
|
|
102
|
-
effects: [
|
|
103
|
-
{
|
|
104
|
-
effect: "give_points",
|
|
105
|
-
pointsRule: { fixedValue: cfg.coinsPerVisit },
|
|
106
|
-
},
|
|
107
|
-
],
|
|
108
|
-
},
|
|
109
|
-
],
|
|
110
|
-
limits: {
|
|
111
|
-
executionsPerMember: {
|
|
112
|
-
value: cfg.limitPerDay,
|
|
113
|
-
interval: { type: "days", value: 1 },
|
|
114
|
-
},
|
|
115
|
-
},
|
|
116
|
-
active: true,
|
|
117
|
-
});
|
|
118
|
-
return { campaignId: response.campaignId };
|
|
119
|
-
}
|
|
120
|
-
catch (error) {
|
|
121
|
-
return {
|
|
122
|
-
error: `Failed to create visit campaign: ${error instanceof Error ? error.message : String(error)}`,
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
async function getAvailableBadges() {
|
|
127
|
-
try {
|
|
128
|
-
const response = await badgeList({});
|
|
129
|
-
const badgeMap = new Map();
|
|
130
|
-
for (const badge of response.badges) {
|
|
131
|
-
if (badge.name && badge.badgeTypeId) {
|
|
132
|
-
badgeMap.set(badge.name.toLowerCase(), badge.badgeTypeId);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
return badgeMap;
|
|
136
|
-
}
|
|
137
|
-
catch {
|
|
138
|
-
return new Map();
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
async function createVisitAchievement(cfg, badges) {
|
|
142
|
-
try {
|
|
143
|
-
const badgeTypeId = cfg.badgeName
|
|
144
|
-
? badges.get(cfg.badgeName.toLowerCase())
|
|
145
|
-
: undefined;
|
|
146
|
-
const achievementPayload = {
|
|
147
|
-
translations: {
|
|
148
|
-
en: {
|
|
149
|
-
name: `${cfg.visitMilestone} Sports Bar Visits`,
|
|
150
|
-
description: `Visit the sports bar ${cfg.visitMilestone} times this season`,
|
|
151
|
-
},
|
|
152
|
-
},
|
|
153
|
-
active: true,
|
|
154
|
-
activity: {
|
|
155
|
-
startsAt: formatOLDate(cfg.seasonStart),
|
|
156
|
-
endsAt: formatOLDate(cfg.seasonEnd),
|
|
157
|
-
},
|
|
158
|
-
rules: [
|
|
159
|
-
{
|
|
160
|
-
trigger: "custom_event",
|
|
161
|
-
event: "sportsbar_visit",
|
|
162
|
-
completeRule: {
|
|
163
|
-
periodGoal: cfg.visitMilestone,
|
|
164
|
-
},
|
|
165
|
-
},
|
|
166
|
-
],
|
|
167
|
-
};
|
|
168
|
-
if (badgeTypeId) {
|
|
169
|
-
achievementPayload.badgeTypeId = badgeTypeId;
|
|
170
|
-
}
|
|
171
|
-
const response = await achievementCreate(achievementPayload);
|
|
172
|
-
return { achievementId: response.achievementId };
|
|
173
|
-
}
|
|
174
|
-
catch (error) {
|
|
175
|
-
return {
|
|
176
|
-
error: `Failed to create visit achievement: ${error instanceof Error ? error.message : String(error)}`,
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
async function createAchievementBonusCampaign(achievementId, cfg) {
|
|
181
|
-
try {
|
|
182
|
-
const response = await campaignCreate({
|
|
183
|
-
type: "direct",
|
|
184
|
-
trigger: "achievement",
|
|
185
|
-
translations: {
|
|
186
|
-
en: {
|
|
187
|
-
name: `Sports Bar ${cfg.visitMilestone} Visits Bonus`,
|
|
188
|
-
description: `Bonus ${cfg.milestoneBonus} coins for visiting the sports bar ${cfg.visitMilestone} times`,
|
|
189
|
-
},
|
|
190
|
-
},
|
|
191
|
-
activity: {
|
|
192
|
-
startsAt: formatOLDate(cfg.seasonStart),
|
|
193
|
-
endsAt: formatOLDate(cfg.seasonEnd),
|
|
194
|
-
},
|
|
195
|
-
rules: [
|
|
196
|
-
{
|
|
197
|
-
name: `Bonus for ${cfg.visitMilestone} visits`,
|
|
198
|
-
effects: [
|
|
199
|
-
{
|
|
200
|
-
effect: "give_points",
|
|
201
|
-
pointsRule: { fixedValue: cfg.milestoneBonus },
|
|
202
|
-
},
|
|
203
|
-
],
|
|
204
|
-
conditions: [
|
|
205
|
-
{
|
|
206
|
-
operator: "is_equal",
|
|
207
|
-
attribute: "achievement.achievementId",
|
|
208
|
-
data: { value: achievementId },
|
|
209
|
-
},
|
|
210
|
-
],
|
|
211
|
-
},
|
|
212
|
-
],
|
|
213
|
-
active: true,
|
|
214
|
-
});
|
|
215
|
-
return { campaignId: response.campaignId };
|
|
216
|
-
}
|
|
217
|
-
catch (error) {
|
|
218
|
-
return {
|
|
219
|
-
error: `Failed to create bonus campaign: ${error instanceof Error ? error.message : String(error)}`,
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
async function verifySetup(result) {
|
|
224
|
-
const warnings = [];
|
|
225
|
-
try {
|
|
226
|
-
if (result.visitCampaignId) {
|
|
227
|
-
const campaigns = await campaignList({ active: true });
|
|
228
|
-
const found = campaigns.campaigns.some((c) => c.campaignId === result.visitCampaignId);
|
|
229
|
-
if (!found) {
|
|
230
|
-
warnings.push("Visit campaign created but not found in active campaigns list");
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
if (result.achievementId) {
|
|
234
|
-
const achievements = await achievementList({ active: true });
|
|
235
|
-
const found = achievements.achievements.some((a) => a.achievementId === result.achievementId);
|
|
236
|
-
if (!found) {
|
|
237
|
-
warnings.push("Achievement created but not found in active achievements list");
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
catch (error) {
|
|
242
|
-
warnings.push(`Verification error: ${error instanceof Error ? error.message : String(error)}`);
|
|
243
|
-
}
|
|
244
|
-
return { warnings };
|
|
245
|
-
}
|
|
246
|
-
function buildSummary(cfg, result) {
|
|
247
|
-
const lines = [];
|
|
248
|
-
if (result.visitCampaignId) {
|
|
249
|
-
lines.push(`Sports bar visit campaign created!`);
|
|
250
|
-
lines.push(`\nPer-visit reward: ${cfg.coinsPerVisit} coins`);
|
|
251
|
-
lines.push(`Daily limit: ${cfg.limitPerDay} visit${cfg.limitPerDay > 1 ? "s" : ""}`);
|
|
252
|
-
}
|
|
253
|
-
if (result.achievementId) {
|
|
254
|
-
lines.push(`\nAchievement: Visit ${cfg.visitMilestone} times`);
|
|
255
|
-
if (cfg.badgeName) {
|
|
256
|
-
lines.push(`Badge: ${cfg.badgeName}`);
|
|
257
|
-
}
|
|
258
|
-
if (result.bonusCampaignId) {
|
|
259
|
-
lines.push(`Completion bonus: ${cfg.milestoneBonus} coins`);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
lines.push(`\nSeason: ${cfg.seasonStart} to ${cfg.seasonEnd}`);
|
|
263
|
-
lines.push(`\nCustom event to trigger reward:`);
|
|
264
|
-
lines.push(` Event: sportsbar_visit`);
|
|
265
|
-
lines.push(`\nExample event payload:`);
|
|
266
|
-
lines.push(`{`);
|
|
267
|
-
lines.push(` "event": "sportsbar_visit"`);
|
|
268
|
-
lines.push(`}`);
|
|
269
|
-
if (result.errors.length > 0) {
|
|
270
|
-
lines.push(`\nWarnings/Errors:`);
|
|
271
|
-
for (const error of result.errors) {
|
|
272
|
-
lines.push(`- ${error}`);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
return lines.join("\n");
|
|
276
|
-
}
|
|
277
|
-
// ============================================================================
|
|
278
|
-
// Exports
|
|
279
|
-
// ============================================================================
|
|
280
|
-
export const sportsbarVisitWorkflow = {
|
|
281
|
-
id: "sportsbar-visit",
|
|
282
|
-
name: "Sports Bar Visit Campaign",
|
|
283
|
-
execute: executeSportsbarVisitWorkflow,
|
|
284
|
-
};
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* VOD Watching Workflow
|
|
3
|
-
*
|
|
4
|
-
* Creates a campaign to reward fans for watching video content,
|
|
5
|
-
* with achievements for total watch time using attribute aggregation.
|
|
6
|
-
*/
|
|
7
|
-
export interface VodWatchingConfig {
|
|
8
|
-
/** Track by "views" or "minutes" */
|
|
9
|
-
trackBy: "views" | "minutes";
|
|
10
|
-
/** Coins awarded per unit (view or unitSize minutes) */
|
|
11
|
-
coinsPerUnit: number;
|
|
12
|
-
/** For minutes tracking: how many minutes per reward unit */
|
|
13
|
-
unitSize: number;
|
|
14
|
-
/** Whether to create achievement for total watching */
|
|
15
|
-
createAchievement: boolean;
|
|
16
|
-
/** Target for achievement (minutes or views) */
|
|
17
|
-
achievementTarget: number;
|
|
18
|
-
/** Bonus coins for completing the achievement */
|
|
19
|
-
achievementBonus: number;
|
|
20
|
-
/** Badge name for achievement (optional) */
|
|
21
|
-
badgeName?: string;
|
|
22
|
-
/** Season start date (ISO format) */
|
|
23
|
-
seasonStart: string;
|
|
24
|
-
/** Season end date (ISO format) */
|
|
25
|
-
seasonEnd: string;
|
|
26
|
-
}
|
|
27
|
-
export interface VodWatchingResult {
|
|
28
|
-
success: boolean;
|
|
29
|
-
watchCampaignId?: string;
|
|
30
|
-
achievementId?: string;
|
|
31
|
-
bonusCampaignId?: string;
|
|
32
|
-
errors: string[];
|
|
33
|
-
summary: string;
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Execute the VOD watching workflow
|
|
37
|
-
*/
|
|
38
|
-
export declare function executeVodWatchingWorkflow(config?: Partial<VodWatchingConfig>): Promise<VodWatchingResult>;
|
|
39
|
-
export declare const vodWatchingWorkflow: {
|
|
40
|
-
id: string;
|
|
41
|
-
name: string;
|
|
42
|
-
execute: typeof executeVodWatchingWorkflow;
|
|
43
|
-
};
|