nodebb-plugin-niki-loyalty 1.5.0 → 1.5.5

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/library.js CHANGED
@@ -6,8 +6,12 @@ const routeHelpers = require.main.require('./src/controllers/helpers');
6
6
  const nconf = require.main.require('nconf');
7
7
  const socketHelpers = require.main.require('./src/socket.io/index');
8
8
  const SocketPlugins = require.main.require('./src/socket.io/plugins');
9
+ const groups = require.main.require('./src/groups');
9
10
  const Plugin = {};
10
11
 
12
+ // Ödül kullanabilecek gruplar
13
+ const WALLET_GROUPS = ['Premium', 'Lite', 'VIP'];
14
+
11
15
  // =========================
12
16
  // ⚙️ AYARLAR & KURALLAR (GAME LOGIC)
13
17
  // =========================
@@ -80,7 +84,7 @@ async function awardDailyAction(uid, actionKey) {
80
84
  const rule = ACTIONS[actionKey];
81
85
 
82
86
  if (!rule) {
83
- console.log(`[Niki-Loyalty] Bilinmeyen aksiyon: ${actionKey}`);
87
+
84
88
  return { success: false, reason: 'unknown_action' };
85
89
  }
86
90
 
@@ -88,7 +92,7 @@ async function awardDailyAction(uid, actionKey) {
88
92
  const dailyScoreKey = `niki:daily:${uid}:${today}`;
89
93
  const currentDailyScore = parseFloat((await db.getObjectField(dailyScoreKey, 'score')) || 0);
90
94
  if (currentDailyScore >= SETTINGS.dailyCap) {
91
- console.log(`[Niki-Loyalty] Günlük limit doldu. UID: ${uid}, Score: ${currentDailyScore}`);
95
+
92
96
  return { success: false, reason: 'daily_cap_reached' };
93
97
  }
94
98
 
@@ -96,7 +100,7 @@ async function awardDailyAction(uid, actionKey) {
96
100
  const actionCountKey = `niki:daily:${uid}:${today}:counts`;
97
101
  const currentActionCount = parseInt((await db.getObjectField(actionCountKey, actionKey)) || 0, 10);
98
102
  if (currentActionCount >= rule.limit) {
99
- console.log(`[Niki-Loyalty] Aksiyon limiti doldu. UID: ${uid}, Action: ${actionKey}, Count: ${currentActionCount}/${rule.limit}`);
103
+
100
104
  return { success: false, reason: 'action_limit_reached' };
101
105
  }
102
106
 
@@ -121,9 +125,7 @@ async function awardDailyAction(uid, actionKey) {
121
125
  // Logla
122
126
  await addUserLog(uid, 'earn', pointsToGive, rule.name);
123
127
 
124
- console.log(`[Niki-Loyalty] PUAN VERİLDİ! UID: ${uid}, Action: ${actionKey}, Points: +${pointsToGive}`);
125
-
126
- // ✅ Kullanıcıya Bildirim Gönder (Socket Emit) - Güçlendirilmiş
128
+ // Kullanıcıya Bildirim Gönder (Socket Emit)
127
129
  try {
128
130
  if (socketHelpers && socketHelpers.server && socketHelpers.server.sockets) {
129
131
  const newTotal = parseFloat((await user.getUserField(uid, 'niki_points')) || 0);
@@ -132,18 +134,14 @@ async function awardDailyAction(uid, actionKey) {
132
134
  message: `${rule.name} işleminden <strong style="color:#ffd700">+${pointsToGive} Puan</strong> kazandın!`,
133
135
  newTotal: newTotal
134
136
  });
135
- console.log(`[Niki-Loyalty] 📢 Socket bildirim gönderildi. UID: ${uid}`);
136
- } else {
137
- console.log(`[Niki-Loyalty] ⚠️ Socket server hazır değil, bildirim gönderilemedi.`);
138
137
  }
139
138
  } catch (socketErr) {
140
- console.error(`[Niki-Loyalty] Socket emit hatası:`, socketErr.message);
141
139
  }
142
140
 
143
141
  return { success: true, points: pointsToGive };
144
142
 
145
143
  } catch (err) {
146
- console.error(`[Niki-Loyalty] Error awarding points for ${actionKey}:`, err);
144
+
147
145
  return { success: false, reason: 'error', error: err.message };
148
146
  }
149
147
  }
@@ -181,67 +179,41 @@ Plugin.onPostCreate = async function (data) {
181
179
  // 4. BEĞENİ (Like Atma ve Alma) - Spam Korumalı + Debug Loglı
182
180
  // NodeBB upvote hook'u { pid, uid, ... } formatında data gönderir (post nesnesi değil!)
183
181
  Plugin.onUpvote = async function (data) {
184
- console.log('[Niki-Loyalty] 👍 Upvote hook tetiklendi. Raw Data:', JSON.stringify(data));
185
-
186
- // NodeBB bazen farklı formatlar gönderebilir, hepsini kontrol et
187
182
  const pid = data.pid || (data.post && data.post.pid);
188
183
  const voterUid = data.uid || (data.current && data.current.uid);
189
184
 
190
- if (!pid) {
191
- console.log('[Niki-Loyalty] ⚠️ Post PID bulunamadı, işlem iptal.');
192
- return;
193
- }
194
-
195
- if (!voterUid) {
196
- console.log('[Niki-Loyalty] ⚠️ Voter UID bulunamadı, işlem iptal.');
197
- return;
198
- }
185
+ if (!pid || !voterUid) return;
199
186
 
200
- // Post sahibini bul (NodeBB upvote hook'u post sahibini göndermez!)
201
187
  let postOwnerUid;
202
188
  try {
203
189
  postOwnerUid = await posts.getPostField(pid, 'uid');
204
- console.log(`[Niki-Loyalty] Post sahibi bulundu: PID=${pid}, Owner UID=${postOwnerUid}`);
205
190
  } catch (err) {
206
- console.log('[Niki-Loyalty] ⚠️ Post sahibi bulunamadı:', err.message);
207
- return;
208
- }
209
-
210
- if (!postOwnerUid) {
211
- console.log('[Niki-Loyalty] ⚠️ Post sahibi UID boş, işlem iptal.');
212
191
  return;
213
192
  }
193
+ if (!postOwnerUid) return;
214
194
 
215
195
  const today = new Date().toISOString().slice(0, 10).replace(/-/g, '');
216
196
 
217
- // Like Atan Kazanır:
197
+ // Like Atan Kazanır
218
198
  const likeGivenKey = `niki:liked:${voterUid}:${today}`;
219
199
  const alreadyLiked = await db.isSetMember(likeGivenKey, pid.toString());
220
200
 
221
- console.log(`[Niki-Loyalty] Like Atan: UID=${voterUid}, PID=${pid}, Daha önce beğenmiş mi=${alreadyLiked}`);
222
-
223
201
  if (!alreadyLiked) {
224
- const result = await awardDailyAction(voterUid, 'like_given');
225
- console.log('[Niki-Loyalty] like_given sonuç:', result);
202
+ await awardDailyAction(voterUid, 'like_given');
226
203
  await db.setAdd(likeGivenKey, pid.toString());
227
204
  await db.expire(likeGivenKey, 86400);
228
205
  }
229
206
 
230
- // Like Alan Kazanır (Post sahibi - kendine beğeni atamaz):
207
+ // Like Alan Kazanır (kendine beğeni atamaz)
231
208
  if (postOwnerUid && String(postOwnerUid) !== String(voterUid)) {
232
209
  const likeTakenKey = `niki:liked_taken:${postOwnerUid}:${today}`;
233
210
  const alreadyTaken = await db.isSetMember(likeTakenKey, pid.toString());
234
211
 
235
- console.log(`[Niki-Loyalty] Like Alan: UID=${postOwnerUid}, PID=${pid}, Daha önce puan almış mı=${alreadyTaken}`);
236
-
237
212
  if (!alreadyTaken) {
238
- const result = await awardDailyAction(postOwnerUid, 'like_taken');
239
- console.log('[Niki-Loyalty] like_taken sonuç:', result);
213
+ await awardDailyAction(postOwnerUid, 'like_taken');
240
214
  await db.setAdd(likeTakenKey, pid.toString());
241
215
  await db.expire(likeTakenKey, 86400);
242
216
  }
243
- } else {
244
- console.log('[Niki-Loyalty] ⚠️ Kullanıcı kendi postunu beğenmiş veya post sahibi bulunamadı. Post owner:', postOwnerUid, 'Voter:', voterUid);
245
217
  }
246
218
  };
247
219
 
@@ -260,7 +232,6 @@ Plugin.onGroupJoin = async function (data) {
260
232
  const flagKey = `niki:group_bonus:${uid}:${groupName}`;
261
233
  const alreadyClaimed = await db.get(flagKey);
262
234
  if (alreadyClaimed) {
263
- console.log(`[Niki-Loyalty] Grup bonusu zaten alınmış. UID: ${uid}, Group: ${groupName}`);
264
235
  return;
265
236
  }
266
237
 
@@ -268,8 +239,6 @@ Plugin.onGroupJoin = async function (data) {
268
239
  await db.set(flagKey, '1');
269
240
  await addUserLog(uid, 'earn', bonus, `${groupName} Grubu Katılım Bonusu 🎉`);
270
241
 
271
- console.log(`[Niki-Loyalty] ✅ GRUP BONUSU! UID: ${uid}, Group: ${groupName}, Points: +${bonus}`);
272
-
273
242
  // Socket bildirimi
274
243
  try {
275
244
  if (socketHelpers && socketHelpers.server && socketHelpers.server.sockets) {
@@ -279,10 +248,8 @@ Plugin.onGroupJoin = async function (data) {
279
248
  });
280
249
  }
281
250
  } catch (socketErr) {
282
- console.error('[Niki-Loyalty] Socket emit hatası:', socketErr.message);
283
251
  }
284
252
  } catch (err) {
285
- console.error('[Niki-Loyalty] Grup bonus hatası:', err);
286
253
  }
287
254
  };
288
255
 
@@ -293,33 +260,6 @@ Plugin.init = async function (params) {
293
260
  const router = params.router;
294
261
  const middleware = params.middleware;
295
262
 
296
- // 🔄 1 SEFERLİK MİGRASYON: 180+ puanlı herkesten 50 puan düş
297
- try {
298
- const migrationKey = 'niki:migration:deduct50_from_180plus_v1';
299
- const alreadyDone = await db.get(migrationKey);
300
- if (!alreadyDone) {
301
- console.log('[Niki-Loyalty] Migration başlıyor: 180+ puanlı kullanıcılardan 50 puan düşülecek...');
302
- const allUids = await db.getSortedSetRange('users:joindate', 0, -1);
303
- if (allUids && allUids.length > 0) {
304
- const usersData = await user.getUsersFields(allUids, ['uid', 'niki_points', 'username']);
305
- let affected = 0;
306
- for (const u of usersData) {
307
- const pts = parseFloat(u.niki_points || 0);
308
- if (pts >= 180) {
309
- await user.decrementUserFieldBy(u.uid, 'niki_points', 50);
310
- await addUserLog(u.uid, 'admin_adjust', 50, 'Sistem düzenlemesi: -50 puan (dengeleme)');
311
- affected++;
312
- console.log(`[Niki-Loyalty] Migration: ${u.username} (UID:${u.uid}) ${pts} → ${pts - 50} puan`);
313
- }
314
- }
315
- console.log(`[Niki-Loyalty] Migration tamamlandı. Etkilenen kullanıcı: ${affected}`);
316
- }
317
- await db.set(migrationKey, Date.now().toString());
318
- }
319
- } catch (migErr) {
320
- console.error('[Niki-Loyalty] Migration hatası:', migErr);
321
- }
322
-
323
263
  // 1) HEARTBEAT (Artık "Okuma" Puanı veriyor)
324
264
  // Client-side script her 30-60 saniyede bir bu adrese istek atmalıdır.
325
265
  router.post('/api/niki-loyalty/heartbeat', middleware.ensureLoggedIn, async (req, res) => {
@@ -368,13 +308,18 @@ Plugin.init = async function (params) {
368
308
  const today = new Date().toISOString().slice(0, 10).replace(/-/g, '');
369
309
 
370
310
  // Veritabanından verileri çek
371
- const [userData, dailyData, actionCounts, historyRaw] = await Promise.all([
311
+ const [userData, dailyData, actionCounts, historyRaw, memberChecks] = await Promise.all([
372
312
  user.getUserFields(uid, ['niki_points']),
373
313
  db.getObject(`niki:daily:${uid}:${today}`),
374
- db.getObject(`niki:daily:${uid}:${today}:counts`), // <--- YENİ: Sayaçları çekiyoruz
314
+ db.getObject(`niki:daily:${uid}:${today}:counts`),
375
315
  db.getListRange(`niki:activity:${uid}`, 0, -1),
316
+ Promise.all(WALLET_GROUPS.map(g => groups.isMember(uid, g))),
376
317
  ]);
377
318
 
319
+ // Kullanıcı WALLET_GROUPS'dan herhangi birinde mi?
320
+ const canRedeem = memberChecks.some(Boolean);
321
+ const userGroup = canRedeem ? WALLET_GROUPS[memberChecks.indexOf(true)] : null;
322
+
378
323
  const dailyScore = parseFloat(dailyData?.score || 0);
379
324
  let dailyPercent = (dailyScore / SETTINGS.dailyCap) * 100;
380
325
  if (dailyPercent > 100) dailyPercent = 100;
@@ -386,9 +331,13 @@ Plugin.init = async function (params) {
386
331
  dailyScore,
387
332
  dailyCap: SETTINGS.dailyCap,
388
333
  dailyPercent,
389
- counts: actionCounts || {}, // <--- YENİ: Frontend'e gönderiyoruz
334
+ counts: actionCounts || {},
335
+ actions: ACTIONS,
390
336
  history,
391
337
  rewards: REWARDS,
338
+ canRedeem,
339
+ userGroup,
340
+ walletGroups: WALLET_GROUPS,
392
341
  });
393
342
  } catch (err) {
394
343
  return res.status(500).json({ points: 0, history: [] });
@@ -499,7 +448,6 @@ Plugin.init = async function (params) {
499
448
  hasMore: enriched.length > 100
500
449
  });
501
450
  } catch (e) {
502
- console.error('[Niki-Loyalty] Kasa history error:', e);
503
451
  return res.status(500).json({ error: 'Sunucu hatası' });
504
452
  }
505
453
  });
@@ -508,43 +456,63 @@ Plugin.init = async function (params) {
508
456
  router.post('/api/niki-loyalty/generate-qr', middleware.ensureLoggedIn, async (req, res) => {
509
457
  try {
510
458
  const uid = req.uid;
459
+
460
+ // Grup kontrolü
461
+ const memberChecks = await Promise.all(WALLET_GROUPS.map(g => groups.isMember(uid, g)));
462
+ if (!memberChecks.some(Boolean)) {
463
+ return res.json({ success: false, message: 'Ödül kullanmak için Premium, Lite veya VIP grubuna katılmalısın.' });
464
+ }
465
+
466
+ const rewardIndex = parseInt(req.body.rewardIndex, 10);
511
467
  const points = parseFloat((await user.getUserField(uid, 'niki_points')) || 0);
512
- const minCost = REWARDS[REWARDS.length - 1].cost; // En ucuz ödül
513
468
 
514
- if (!TEST_MODE_UNLIMITED && points < minCost) {
515
- return res.json({ success: false, message: `Yetersiz Puan. En az ${minCost} gerekli.` });
469
+ // Seçilen ödülü bul
470
+ if (isNaN(rewardIndex) || rewardIndex < 0 || rewardIndex >= REWARDS.length) {
471
+ return res.json({ success: false, message: 'Geçersiz ödül seçimi.' });
472
+ }
473
+ const selectedReward = REWARDS[rewardIndex];
474
+
475
+ if (!TEST_MODE_UNLIMITED && points < selectedReward.cost) {
476
+ return res.json({ success: false, message: `Yetersiz Puan. ${selectedReward.name} için ${selectedReward.cost} puan gerekli.` });
516
477
  }
517
478
  const token = crypto.randomBytes(16).toString('hex');
518
- await db.set(`niki:qr:${token}`, uid);
479
+ // Token'a kullanıcı ve seçilen ödül bilgisini kaydet
480
+ await db.setObject(`niki:qr:${token}`, { uid: String(uid), rewardIndex: String(rewardIndex) });
519
481
  await db.expire(`niki:qr:${token}`, 120); // 2 dakika geçerli
520
- return res.json({ success: true, token });
482
+ return res.json({ success: true, token, rewardName: selectedReward.name, rewardCost: selectedReward.cost });
521
483
  } catch (e) { return res.status(500).json({ success: false }); }
522
484
  });
523
485
 
524
486
  // 5) QR TARATMA (Kasa İşlemi)
525
487
  router.post('/api/niki-loyalty/scan-qr', middleware.ensureLoggedIn, async (req, res) => {
526
- // ... (Mevcut kodunun aynısı)
527
488
  try {
528
489
  const token = req.body.token;
529
490
  const isAdmin = await user.isAdministrator(req.uid);
530
491
  const isMod = await user.isGlobalModerator(req.uid);
531
492
  if (!isAdmin && !isMod) return res.status(403).json({ success: false, message: 'Yetkisiz' });
532
493
 
533
- const custUid = await db.get(`niki:qr:${token}`);
534
- if (!custUid) return res.json({ success: false, message: 'Geçersiz Kod' });
494
+ const qrData = await db.getObject(`niki:qr:${token}`);
495
+ if (!qrData || !qrData.uid) return res.json({ success: false, message: 'Geçersiz Kod' });
535
496
 
497
+ const custUid = qrData.uid;
498
+ const rewardIndex = parseInt(qrData.rewardIndex, 10);
536
499
  const pts = parseFloat(await user.getUserField(custUid, 'niki_points') || 0);
537
500
 
538
501
  let selectedReward = null;
539
502
  if (!TEST_MODE_UNLIMITED) {
540
- for (const r of REWARDS) {
541
- if (pts >= r.cost) { selectedReward = r; break; }
503
+ // Token'daki ödül index'ini kullan
504
+ if (!isNaN(rewardIndex) && rewardIndex >= 0 && rewardIndex < REWARDS.length) {
505
+ selectedReward = REWARDS[rewardIndex];
506
+ } else {
507
+ // Fallback: en yüksek ödülü seç
508
+ for (const r of REWARDS) {
509
+ if (pts >= r.cost) { selectedReward = r; break; }
510
+ }
542
511
  }
543
512
  if (!selectedReward) return res.json({ success: false, message: 'Puan Yetersiz' });
544
513
  } else { selectedReward = REWARDS[0]; }
545
514
 
546
515
  if (!TEST_MODE_UNLIMITED) {
547
- // Negatif bakiye kontrolü - önce kontrol, sonra düş
548
516
  if (pts < selectedReward.cost) {
549
517
  await db.delete(`niki:qr:${token}`);
550
518
  return res.json({ success: false, message: 'Puan yetersiz, işlem iptal edildi.' });
@@ -603,13 +571,11 @@ Plugin.adminGetUsers = async function (socket, data) {
603
571
 
604
572
  // TÜM kullanıcıları al (limit yok: -1)
605
573
  const uids = await db.getSortedSetRevRange('users:joindate', 0, -1);
606
- console.log('[Niki-Admin] Çekilen UID sayısı:', uids ? uids.length : 0);
607
574
 
608
575
  if (!uids || uids.length === 0) return [];
609
576
 
610
577
  // Kullanıcı bilgilerini al
611
578
  const usersData = await user.getUsersFields(uids, ['uid', 'username', 'userslug', 'picture', 'niki_points', 'icon:bgColor']);
612
- console.log('[Niki-Admin] Kullanıcı verisi alındı:', usersData ? usersData.length : 0);
613
579
 
614
580
  // Puanları işle ve sırala (puanı yüksek olan önce)
615
581
  const result = usersData
@@ -624,10 +590,8 @@ Plugin.adminGetUsers = async function (socket, data) {
624
590
  }))
625
591
  .sort((a, b) => b.points - a.points); // Yüksekten düşüğe sırala
626
592
 
627
- console.log('[Niki-Admin] Döndürülen kullanıcı sayısı:', result.length);
628
593
  return result;
629
594
  } catch (err) {
630
- console.error('[Niki-Admin] adminGetUsers HATA:', err.message);
631
595
  throw err;
632
596
  }
633
597
  };
@@ -650,15 +614,21 @@ Plugin.socketScanQR = async function (socket, data) {
650
614
  const token = data.token;
651
615
  if (!token) throw new Error('Geçersiz Token');
652
616
 
653
- const custUid = await db.get(`niki:qr:${token}`);
654
- if (!custUid) throw new Error('QR Kod Geçersiz veya Süresi Dolmuş');
617
+ const qrData = await db.getObject(`niki:qr:${token}`);
618
+ if (!qrData || !qrData.uid) throw new Error('QR Kod Geçersiz veya Süresi Dolmuş');
655
619
 
620
+ const custUid = qrData.uid;
621
+ const rewardIndex = parseInt(qrData.rewardIndex, 10);
656
622
  const pts = parseFloat((await user.getUserField(custUid, 'niki_points')) || 0);
657
623
 
658
624
  let selectedReward = null;
659
625
  if (!TEST_MODE_UNLIMITED) {
660
- for (const r of REWARDS) {
661
- if (pts >= r.cost) { selectedReward = r; break; }
626
+ if (!isNaN(rewardIndex) && rewardIndex >= 0 && rewardIndex < REWARDS.length) {
627
+ selectedReward = REWARDS[rewardIndex];
628
+ } else {
629
+ for (const r of REWARDS) {
630
+ if (pts >= r.cost) { selectedReward = r; break; }
631
+ }
662
632
  }
663
633
  if (!selectedReward) throw new Error('Puan Yetersiz');
664
634
  } else { selectedReward = REWARDS[0]; }
@@ -758,6 +728,7 @@ Plugin.adminManagePoints = async function (socket, data) {
758
728
  // Denetim Logu
759
729
  const auditLog = { ts: Date.now(), adminUid: uid, adminName: adminUserData.username, targetUid: targetUid, action: action, amount: amount, reason: reason };
760
730
  await db.listAppend('niki:audit:admin_points', JSON.stringify(auditLog));
731
+ await db.listTrim('niki:audit:admin_points', -500, -1);
761
732
 
762
733
  const newPoints = await user.getUserField(targetUid, 'niki_points');
763
734
  return { success: true, newPoints: parseFloat(newPoints) };