nodebb-plugin-niki-loyalty 1.5.10 → 1.6.0
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/.claude/settings.local.json +4 -1
- package/library.js +44 -16
- package/package.json +1 -1
- package/static/lib/client.js +36 -0
package/library.js
CHANGED
|
@@ -10,8 +10,14 @@ const SocketPlugins = require.main.require('./src/socket.io/plugins');
|
|
|
10
10
|
const groups = require.main.require('./src/groups');
|
|
11
11
|
const Plugin = {};
|
|
12
12
|
|
|
13
|
-
//
|
|
14
|
-
|
|
13
|
+
// Grup bazlı kazanım çarpanları (en yüksek üyelik kazanır).
|
|
14
|
+
// Bu gruplara üye olmayan kullanıcılar DEFAULT_EARN_RATE alır.
|
|
15
|
+
const EARN_RATES = {
|
|
16
|
+
VIP: 1.5,
|
|
17
|
+
Premium: 1.25,
|
|
18
|
+
Lite: 1.0,
|
|
19
|
+
};
|
|
20
|
+
const DEFAULT_EARN_RATE = 0.5;
|
|
15
21
|
|
|
16
22
|
// =========================
|
|
17
23
|
// ⚙️ AYARLAR & KURALLAR (GAME LOGIC)
|
|
@@ -136,6 +142,24 @@ async function addKasaLog(staffUid, customerName, customerUid, rewardName, amoun
|
|
|
136
142
|
await db.listTrim('niki:kasa:history', -100, -1);
|
|
137
143
|
}
|
|
138
144
|
|
|
145
|
+
// Kademeli çarpan: VIP 1.5x, Premium 1.25x, Lite 1.0x, üyelik yok 0.5x.
|
|
146
|
+
// Birden fazla gruba üyeyse en yüksek çarpan uygulanır.
|
|
147
|
+
async function getEarnMultiplier(uid) {
|
|
148
|
+
try {
|
|
149
|
+
const groupNames = Object.keys(EARN_RATES);
|
|
150
|
+
const memberChecks = await Promise.all(groupNames.map(g => groups.isMember(uid, g)));
|
|
151
|
+
let maxRate = DEFAULT_EARN_RATE;
|
|
152
|
+
groupNames.forEach((g, i) => {
|
|
153
|
+
if (memberChecks[i] && EARN_RATES[g] > maxRate) {
|
|
154
|
+
maxRate = EARN_RATES[g];
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
return maxRate;
|
|
158
|
+
} catch (err) {
|
|
159
|
+
return DEFAULT_EARN_RATE;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
139
163
|
// 🔥 MERKEZİ PUAN DAĞITIM FONKSİYONU 🔥
|
|
140
164
|
// Bütün puan işlemleri buradan geçer, limitleri kontrol eder.
|
|
141
165
|
async function awardDailyAction(uid, actionKey) {
|
|
@@ -165,8 +189,9 @@ async function awardDailyAction(uid, actionKey) {
|
|
|
165
189
|
return { success: false, reason: 'action_limit_reached' };
|
|
166
190
|
}
|
|
167
191
|
|
|
168
|
-
// 3. Puan Hesapla
|
|
169
|
-
|
|
192
|
+
// 3. Puan Hesapla (premium-dışı kullanıcı için yarım puan)
|
|
193
|
+
const multiplier = await getEarnMultiplier(uid);
|
|
194
|
+
let pointsToGive = rule.points * multiplier;
|
|
170
195
|
if (currentDailyScore + pointsToGive > SETTINGS.dailyCap) {
|
|
171
196
|
pointsToGive = SETTINGS.dailyCap - currentDailyScore;
|
|
172
197
|
}
|
|
@@ -393,18 +418,26 @@ Plugin.init = async function (params) {
|
|
|
393
418
|
const uid = req.uid;
|
|
394
419
|
const today = new Date().toISOString().slice(0, 10).replace(/-/g, '');
|
|
395
420
|
|
|
421
|
+
const earnGroupNames = Object.keys(EARN_RATES);
|
|
422
|
+
|
|
396
423
|
// Veritabanından verileri çek
|
|
397
424
|
const [userData, dailyData, actionCounts, historyRaw, memberChecks] = await Promise.all([
|
|
398
425
|
user.getUserFields(uid, ['niki_points']),
|
|
399
426
|
db.getObject(`niki:daily:${uid}:${today}`),
|
|
400
427
|
db.getObject(`niki:daily:${uid}:${today}:counts`),
|
|
401
428
|
db.getListRange(`niki:activity:${uid}`, 0, -1),
|
|
402
|
-
Promise.all(
|
|
429
|
+
Promise.all(earnGroupNames.map(g => groups.isMember(uid, g))),
|
|
403
430
|
]);
|
|
404
431
|
|
|
405
|
-
//
|
|
406
|
-
|
|
407
|
-
|
|
432
|
+
// En yüksek kazanım grubunu seç (üye olduğu birden fazla varsa en iyisi).
|
|
433
|
+
let earnRate = DEFAULT_EARN_RATE;
|
|
434
|
+
let userGroup = null;
|
|
435
|
+
earnGroupNames.forEach((g, i) => {
|
|
436
|
+
if (memberChecks[i] && EARN_RATES[g] > earnRate) {
|
|
437
|
+
earnRate = EARN_RATES[g];
|
|
438
|
+
userGroup = g;
|
|
439
|
+
}
|
|
440
|
+
});
|
|
408
441
|
|
|
409
442
|
const dailyScore = parseFloat(dailyData?.score || 0);
|
|
410
443
|
let dailyPercent = (dailyScore / SETTINGS.dailyCap) * 100;
|
|
@@ -421,9 +454,10 @@ Plugin.init = async function (params) {
|
|
|
421
454
|
actions: ACTIONS,
|
|
422
455
|
history,
|
|
423
456
|
rewards: REWARDS,
|
|
424
|
-
canRedeem,
|
|
457
|
+
canRedeem: true,
|
|
425
458
|
userGroup,
|
|
426
|
-
|
|
459
|
+
earnRate,
|
|
460
|
+
walletGroups: earnGroupNames,
|
|
427
461
|
});
|
|
428
462
|
} catch (err) {
|
|
429
463
|
return res.status(500).json({ points: 0, history: [] });
|
|
@@ -543,12 +577,6 @@ Plugin.init = async function (params) {
|
|
|
543
577
|
try {
|
|
544
578
|
const uid = req.uid;
|
|
545
579
|
|
|
546
|
-
// Grup kontrolü
|
|
547
|
-
const memberChecks = await Promise.all(WALLET_GROUPS.map(g => groups.isMember(uid, g)));
|
|
548
|
-
if (!memberChecks.some(Boolean)) {
|
|
549
|
-
return res.json({ success: false, message: 'Ödül kullanmak için Premium, Lite veya VIP grubuna katılmalısın.' });
|
|
550
|
-
}
|
|
551
|
-
|
|
552
580
|
const rewardIndex = parseInt(req.body.rewardIndex, 10);
|
|
553
581
|
const points = parseFloat((await user.getUserField(uid, 'niki_points')) || 0);
|
|
554
582
|
|
package/package.json
CHANGED
package/static/lib/client.js
CHANGED
|
@@ -330,6 +330,42 @@ $(document).ready(function () {
|
|
|
330
330
|
$('#daily-score').text(formatPoints(data.dailyScore));
|
|
331
331
|
$('#daily-cap').text(data.dailyCap);
|
|
332
332
|
|
|
333
|
+
// Kademeli upsell rozeti: bir üst kademeyi göster (VIP'te yok)
|
|
334
|
+
$('#niki-earn-rate-badge').remove();
|
|
335
|
+
const TIERS = [
|
|
336
|
+
{ rate: 0.5, label: null },
|
|
337
|
+
{ rate: 1.0, label: 'Lite' },
|
|
338
|
+
{ rate: 1.25, label: 'Premium' },
|
|
339
|
+
{ rate: 1.5, label: 'VIP' },
|
|
340
|
+
];
|
|
341
|
+
const currentRate = parseFloat(data.earnRate) || 0.5;
|
|
342
|
+
const currentIdx = TIERS.findIndex(t => Math.abs(t.rate - currentRate) < 0.01);
|
|
343
|
+
const nextTier = currentIdx >= 0 ? TIERS[currentIdx + 1] : null;
|
|
344
|
+
|
|
345
|
+
if (nextTier && $('#user-points').length) {
|
|
346
|
+
const factor = (nextTier.rate / currentRate).toFixed(2).replace(/\.?0+$/, '');
|
|
347
|
+
const badgeHtml = `
|
|
348
|
+
<div id="niki-earn-rate-badge" style="
|
|
349
|
+
display: inline-flex;
|
|
350
|
+
align-items: center;
|
|
351
|
+
gap: 6px;
|
|
352
|
+
margin-top: 10px;
|
|
353
|
+
padding: 6px 12px;
|
|
354
|
+
background: linear-gradient(135deg, #FFD700 0%, #FFA000 100%);
|
|
355
|
+
color: #3E2723;
|
|
356
|
+
border-radius: 20px;
|
|
357
|
+
font-size: 12px;
|
|
358
|
+
font-weight: 700;
|
|
359
|
+
box-shadow: 0 2px 6px rgba(0,0,0,0.15);
|
|
360
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
361
|
+
">
|
|
362
|
+
<span>💎</span>
|
|
363
|
+
<span>${nextTier.label} ile <strong style="font-size:13px;">${factor}x</strong> daha hızlı kazan</span>
|
|
364
|
+
</div>
|
|
365
|
+
`;
|
|
366
|
+
$('#user-points').after(badgeHtml);
|
|
367
|
+
}
|
|
368
|
+
|
|
333
369
|
// Progress Bar
|
|
334
370
|
const percent = data.dailyPercent > 100 ? 100 : data.dailyPercent;
|
|
335
371
|
$('#daily-progress').css('width', percent + '%').text(Math.round(percent) + '%');
|