· 8 years ago · Jun 06, 2018, 07:10 PM
1import re
2import datetime
3import util
4import settings
5import random
6import secrets
7
8import tasks
9
10from peewee import *
11from playhouse.postgres_ext import PostgresqlExtDatabase
12
13# TODO - Redesign the schema
14# - TipBot grew into more than a tip bot quickly as features piled on
15# combined with a non-backwards compatible discord.py update and
16# the fact I never worked with python or peewee before this project.
17# So eventual schema redesign plans:
18# 1) Discord IDs are now integers. Every discord ID stored should become IntegerField
19# 2) Giveaway transactions should be further distinguished from normal TXs
20# after a giveaway is over, it's impossible to tell what transactions were part of the
21# giveaway. That's a mild design flaw
22# 3) Where applicable replace peewee default primary key with discord ID as primary key
23# many of our queries are written using discord IDs already.
24# e.g. user_id should be PrimaryKeyField on users table
25# 4) De-couple stats, accounts, etc. from users table.
26# (Make a one-to-many relationship between a users and stats/accounts table)
27# Why one-to-many instead of 1-1 for accounts? see point 8
28# 5) Foreign key relationships everywhere
29# 6) We only retrieve a DB connection 1 time, which isn't much of a problem for SQLite,
30# but perhaps a better practice would be to create and destroy connections as needed
31# which brings the next point:
32# 7) SQLite is fine for our purposes, but some operations would be much better if we
33# could do them atomically. (e.g. when multiple DB operations occur, if one fails
34# then everything rolls back). Not a problem with SQLite or peewee, per say, but
35# due to multithreading issues we use SQliteQueueDatabase which queues writes
36# synchronously and that breaks atomic transactions.
37# so....maybe eye a switch to MySQL or PostgreSQL
38# 8) Attach discord server ID to user accounts, stats, etc.
39# this will allow some important things for the future of NANO/BANANO bots
40# Mainly, allowing the bot to be used on multiple servers
41#
42# Of course, we need a way to migrate the old database into this re-design
43
44# (Seconds) how long a user must wait in between messaging the bot
45LAST_MSG_TIME = 1
46
47# How many messages consider a user rain eligible
48LAST_MSG_RAIN_COUNT = 5
49# (Seconds) How spaced out the messages must be
50LAST_MSG_RAIN_DELTA = 60
51# How many words messages must contain
52LAST_MSG_RAIN_WORDS = 3
53
54# (Seconds) How long user must wait between tiprandom
55TIP_RANDOM_WAIT = 10
56# (Seconds) How long user mus wait between tipfavorites
57TIP_FAVORITES_WAIT = 150
58
59db = PostgresqlExtDatabase(settings.database, user=settings.database_user, password=settings.database_password, host='localhost', port=5432)
60
61logger = util.get_logger("db")
62
63### User Stuff
64@db.connection_context()
65def get_accounts():
66 u = User.select()
67 accts = []
68 for a in u:
69 accts.append(a.wallet_address)
70 return accts
71
72@db.connection_context()
73def get_user_by_id(user_id, user_name=None):
74 try:
75 user = User.get(user_id=str(user_id))
76 if user_name is not None and user_name != user.user_name:
77 User.update(user_name=user_name).where(User.id == user.id).execute()
78 user.user_name = user_name
79 return user
80 except User.DoesNotExist:
81 # logger.debug('user %s does not exist !', user_id)
82 return None
83
84@db.connection_context()
85def get_user_by_wallet_address(address):
86 try:
87 user = User.get(wallet_address=address)
88 return user
89 except User.DoesNotExist:
90 # logger.debug('wallet %s does not exist !', address)
91 return None
92
93@db.connection_context()
94def user_exists(user_id):
95 return User.select().where(User.user_id == user_id).count() > 0
96
97@db.connection_context()
98def get_active_users(since_minutes):
99 since_ts = datetime.datetime.now() - datetime.timedelta(minutes=since_minutes)
100 users = User.select().where(User.last_msg > since_ts).order_by(User.user_id)
101 return_ids = []
102 for user in users:
103 if user.last_msg_count >= LAST_MSG_RAIN_COUNT:
104 if is_banned(user.user_id):
105 continue
106 return_ids.append(user.user_id)
107 return return_ids
108
109@db.connection_context()
110def get_address(user_id):
111 logger.info('getting wallet address for user %d ...', user_id)
112 user = get_user_by_id(user_id)
113 if user is None:
114 return None
115 else:
116 return user.wallet_address
117
118@db.connection_context()
119def get_top_users(count):
120 users = User.select().where((User.tipped_amount > 0) & (User.stats_ban == False)).order_by(User.tipped_amount.desc()).limit(count)
121 return_data = []
122 for idx, user in enumerate(users):
123 return_data.append({'index': idx + 1, 'name': user.user_name, 'amount': user.tipped_amount})
124 return return_data
125
126@db.connection_context()
127def get_giveaway_winners(count):
128 winners = Giveaway.select().where((Giveaway.active == False) & (Giveaway.winner_id.is_null(False))).order_by(Giveaway.end_time.desc()).limit(count)
129 return_data = []
130 for idx, winner in enumerate(winners):
131 user = get_user_by_id(winner.winner_id)
132 return_data.append({'index': idx + 1, 'name': user.user_name, 'amount': winner.amount + winner.tip_amount})
133 return return_data
134
135@db.connection_context()
136def get_tip_stats(user_id):
137 user_id = str(user_id)
138 user = get_user_by_id(user_id)
139 if user is None:
140 return None
141 rank = User.select().where((User.tipped_amount > user.tipped_amount) & (User.stats_ban == False)).count() + 1
142 if not user.stats_ban:
143 tipped_amount = user.tipped_amount
144 tip_count = user.tip_count
145 top_tip = user.top_tip
146 else:
147 tipped_amount = 0
148 tip_count = 0
149 top_tip = 0
150 rank = -1
151 if tip_count == 0:
152 average = 0
153 else:
154 average = tipped_amount / tip_count
155 return {'rank':rank, 'total':tipped_amount, 'average':average,'top':float(top_tip) / 1000000}
156
157# Update tip stats
158@db.connection_context()
159def update_tip_stats(user, tip, rain=False, giveaway=False):
160 (User.update(
161 tipped_amount=(User.tipped_amount + (tip / 1000000)),
162 tip_count = User.tip_count + 1
163 ).where(User.id == user.id)
164 ).execute()
165 # Update all time tip if necessary
166 if tip > int(float(user.top_tip)):
167 (User.update(
168 top_tip = tip,
169 top_tip_ts = datetime.datetime.now()
170 ).where(User.id == user.id)
171 ).execute()
172 # Update monthly tip if necessary
173 if user.top_tip_month_ts.month != datetime.datetime.now().month or tip > int(float(user.top_tip_month)):
174 (User.update(
175 top_tip_month = tip,
176 top_tip_month_ts = datetime.datetime.now()
177 ).where(User.id == user.id)
178 ).execute()
179 # Update 24H tip if necessary
180 delta = datetime.datetime.now() - user.top_tip_day_ts
181 if delta.total_seconds() > 86400 or tip > int(float(user.top_tip_day)):
182 (User.update(
183 top_tip_day = tip,
184 top_tip_day_ts = datetime.datetime.now()
185 ).where(User.id == user.id)
186 ).execute()
187 # Update rain or giveaway stats
188 if rain:
189 (User.update(
190 rain_amount = User.rain_amount + (tip / 1000000)
191 )
192 .where(User.id == user.id)
193 ).execute()
194 elif giveaway:
195 (User.update(
196 giveaway_amount = User.giveaway_amount + (tip / 1000000)
197 )
198 .where(User.id == user.id)
199 ).execute()
200
201@db.connection_context()
202def update_tip_total(user_id, new_total):
203 user_id = str(user_id)
204 User.update(tipped_amount = new_total).where(User.user_id == user_id).execute()
205 return
206
207@db.connection_context()
208def update_tip_count(user_id, new_count):
209 user_id = str(user_id)
210 User.update(tip_count = new_count).where(User.user_id == user_id).execute()
211 return
212
213@db.connection_context()
214def update_pending(user_id, send=0, receive=0):
215 user_id=str(user_id)
216 return (User.update(
217 pending_send = (User.pending_send + send),
218 pending_receive = (User.pending_receive + receive)
219 ).where(User.user_id == user_id)
220 ).execute()
221
222@db.connection_context()
223def create_user(user_id, user_name, wallet_address):
224 user_id=str(user_id)
225 user = User(user_id=user_id,
226 user_name=user_name,
227 wallet_address=wallet_address,
228 )
229 user.save()
230 return user
231
232### Transaction Stuff
233@db.connection_context()
234def create_transaction(src_usr, uuid, to_addr, amt, target_id=None, giveaway_id=0):
235 # Increment amount of giveaway TX if user has already donated to giveaway
236 if giveaway_id != 0:
237 try:
238 giveawayTx = (Transaction.select()
239 .where(
240 (Transaction.source_address == src_usr.wallet_address) &
241 (Transaction.giveawayid == giveaway_id)
242 )
243 ).get()
244 update = (Transaction.update(amount = Transaction.amount.cast('integer') + amt)
245 .where(Transaction.id == giveawayTx.id)
246 ).execute()
247 if update > 0:
248 update_pending(src_usr.user_id, send=amt)
249 return
250 except Transaction.DoesNotExist:
251 pass
252
253 tx = Transaction(uid=uuid,
254 source_address=src_usr.wallet_address,
255 to_address=to_addr,
256 amount=amt,
257 giveawayid=giveaway_id
258 )
259 tx.save()
260 update_pending(src_usr.user_id, send=amt)
261 if target_id is not None:
262 update_pending(target_id, receive=amt)
263 if tx.giveawayid == 0:
264 process_transaction(tx)
265 return tx
266
267def process_transaction(tx):
268 tasks.send_transaction.delay(tx)
269
270@db.connection_context()
271def update_last_withdraw(user_id):
272 user_id = str(user_id)
273 User.update(last_withdraw=datetime.datetime.now()).where(User.user_id == user_id).execute()
274
275@db.connection_context()
276def get_last_withdraw_delta(user_id):
277 user_id = str(user_id)
278 try:
279 user = User.select(User.last_withdraw).where(User.user_id == user_id).get()
280 delta = (datetime.datetime.now() - user.last_withdraw).total_seconds()
281 return delta
282 except User.DoesNotExist:
283 return None
284
285@db.connection_context()
286def get_unprocessed_transactions():
287 # We don't simply return the txs list cuz that causes issues with database locks in the thread
288 txs = Transaction.select().where((Transaction.processed == False) & (Transaction.giveawayid == 0)).order_by(Transaction.created)
289 return_data = []
290 for tx in txs:
291 return_data.append({'uid':tx.uid,'source_address':tx.source_address,'to_address':tx.to_address,'amount':tx.amount,'attempts':tx.attempts})
292 return return_data
293
294@db.connection_context()
295def process_giveaway_transactions(giveaway_id, winner_user_id):
296 txs = Transaction.select().where(Transaction.giveawayid == giveaway_id)
297 winner = get_user_by_id(winner_user_id)
298 pending_receive = 0
299 for tx in txs:
300 pending_receive += int(tx.amount)
301 process_transaction(tx)
302 update_pending(winner_user_id, receive=pending_receive)
303 (Transaction.update(
304 to_address = winner.wallet_address,
305 giveawayid = 0
306 ).where(
307 (Transaction.giveawayid == giveaway_id)
308 )).execute()
309
310# Start Giveaway
311@db.connection_context()
312def start_giveaway(user_id, user_name, amount, end_time, channel, entry_fee = 0):
313 user_id=str(user_id)
314 channel=str(channel)
315 giveaway = Giveaway(started_by=user_id,
316 started_by_name=user_name,
317 active=True,
318 amount = amount,
319 tip_amount = 0,
320 end_time=end_time,
321 channel_id = channel,
322 winner_id = None,
323 entry_fee = entry_fee
324 )
325 giveaway.save()
326 # Delete contestants not meeting fee criteria
327 deleted = []
328 if entry_fee > 0:
329 entries = Contestant.select()
330 for c in entries:
331 donated = get_tipgiveaway_contributions(c.user_id)
332 if entry_fee > donated:
333 c.delete_instance()
334 deleted.append(c.user_id)
335 tip_amt = update_giveaway_transactions(giveaway.id)
336 giveaway.tip_amount = tip_amt
337 giveaway.save()
338 return (giveaway, deleted)
339
340@db.connection_context()
341def get_giveaway():
342 try:
343 giveaway = Giveaway.get(active=True)
344 return giveaway
345 except:
346 return None
347
348@db.connection_context()
349def update_giveaway_transactions(giveawayid):
350 tip_sum = 0
351 txs = Transaction.select().where(Transaction.giveawayid == -1)
352 for tx in txs:
353 tip_sum += int(tx.amount)
354 (Transaction.update(
355 giveawayid = giveawayid
356 ).where(
357 (Transaction.giveawayid == -1)
358 )).execute()
359
360 return float(tip_sum)/ 1000000
361
362@db.connection_context()
363def add_tip_to_giveaway(amount):
364 giveawayupdt = (Giveaway
365 .update(
366 tip_amount = (Giveaway.tip_amount + amount)
367 ).where(Giveaway.active == True)
368 ).execute()
369
370@db.connection_context()
371def get_tipgiveaway_sum():
372 tip_sum = 0
373 txs = Transaction.select().where(Transaction.giveawayid == -1)
374 for tx in txs:
375 tip_sum += int(tx.amount)
376 return tip_sum
377
378# Get tipgiveaway contributions
379@db.connection_context()
380def get_tipgiveaway_contributions(user_id, giveawayid=-1):
381 tip_sum = 0
382 user = get_user_by_id(user_id)
383 txs = Transaction.select().where((Transaction.giveawayid == giveawayid) & (Transaction.source_address == user.wallet_address))
384 for tx in txs:
385 tip_sum += int(tx.amount)
386 return tip_sum
387
388@db.connection_context()
389def is_banned(user_id):
390 user_id=str(user_id)
391 banned = BannedUser.select().where(BannedUser.user_id == user_id).count()
392 return banned > 0
393
394@db.connection_context()
395def ban_user(user_id):
396 user_id = str(user_id)
397 already_banned = is_banned(user_id)
398 if already_banned > 0:
399 return False
400 ban = BannedUser(user_id=user_id)
401 ban.save()
402 return True
403
404@db.connection_context()
405def statsban_user(user_id):
406 user_id = str(user_id)
407 banned = User.update(stats_ban = True).where(User.user_id == user_id).execute()
408 return banned > 0
409
410@db.connection_context()
411def unban_user(user_id):
412 user_id = str(user_id)
413 deleted = BannedUser.delete().where(BannedUser.user_id == user_id).execute()
414 return deleted > 0
415
416@db.connection_context()
417def statsunban_user(user_id):
418 user_id = str(user_id)
419 unbanned = User.update(stats_ban = False).where(User.user_id == user_id).execute()
420 return unbanned > 0
421
422@db.connection_context()
423def get_banned():
424 banned = BannedUser.select(BannedUser.user_id)
425 users = User.select(User.user_name).where(User.user_id.in_(banned))
426 if users.count() == 0:
427 return "```Nobody Banned```"
428 ret = "```"
429 for idx,user in enumerate(users):
430 ret += "{0}: {1}\n".format(idx+1,user.user_name)
431 ret += "```"
432 return ret
433
434@db.connection_context()
435def get_statsbanned():
436 statsbanned = User.select().where(User.stats_ban == True)
437 if statsbanned.count() == 0:
438 return "```No stats bans```"
439 ret = "```"
440 for idx,user in enumerate(statsbanned):
441 ret += "{0}: {1}\n".format(idx+1,user.user_name)
442 ret += "```"
443 return ret
444
445@db.connection_context()
446def is_frozen(user_id):
447 return FrozenUser.select().where(FrozenUser.user_id == user_id).count() > 0
448
449@db.connection_context()
450def freeze(user):
451 if not is_frozen(user.id):
452 fu = FrozenUser(user_id=user.id, user_name=user.name)
453 saved = fu.save()
454 return saved > 0
455 return False
456
457@db.connection_context()
458def unfreeze(user_id):
459 if not is_frozen(user_id):
460 return False
461 return FrozenUser.delete().where(FrozenUser.user_id==user_id).execute() > 0
462
463@db.connection_context()
464def frozen():
465 frozen = FrozenUser.select()
466 if frozen.count() == 0:
467 return "```Nobody Frozen```"
468 ret = "```"
469 for idx, fu in enumerate(frozen):
470 ret += "{0}: {1}\n".format(idx+1, fu.user_name)
471 ret += "```"
472 return ret
473
474# Returns winning user
475@db.connection_context()
476def finish_giveaway():
477 contestants = Contestant.select(Contestant.user_id).order_by(Contestant.user_id)
478 contestant_ids = []
479 for c in contestants:
480 contestant_ids.append(c.user_id)
481 sysrand = random.SystemRandom()
482 sysrand.shuffle(contestant_ids)
483 offset = secrets.randbelow(len(contestant_ids))
484 winner = get_user_by_id(contestant_ids[offset])
485 Contestant.delete().execute()
486 giveaway = Giveaway.get(active=True)
487 giveaway.active=False
488 giveaway.winner_id = winner.user_id
489 giveaway.save()
490 process_giveaway_transactions(giveaway.id, winner.user_id)
491 return giveaway
492
493# Returns True is contestant added, False if contestant already exists
494@db.connection_context()
495def add_contestant(user_id):
496 user_id=str(user_id)
497 exists = Contestant.select().where(Contestant.user_id == user_id).count() > 0
498 if exists:
499 return False
500 contestant = Contestant(user_id=user_id,banned=False)
501 contestant.save()
502 return True
503
504@db.connection_context()
505def get_ticket_status(user_id):
506 user_id = str(user_id)
507 try:
508 giveaway = Giveaway.select().where(Giveaway.active==True).get()
509 if contestant_exists(user_id):
510 return "You are already entered into the giveaway!"
511 fee = giveaway.entry_fee
512 contributions = get_tipgiveaway_contributions(user_id, giveawayid=giveaway.id)
513 cost = fee - contributions
514 return_str = ("You do not have a ticket to the current giveaway!\n" +
515 "Giveaway fee: {0}\n" +
516 "Your donations: {1}\n" +
517 "Your ticket cost: {2}\n\n" +
518 "You may enter using `{3}ticket {2}`").format(fee, contributions, cost, settings.command_prefix)
519 return return_str
520 except Giveaway.DoesNotExist:
521 contributions = get_tipgiveaway_contributions(user_id)
522 return ("There is no active giveaway.\n" +
523 "So far you've contributed {0} naneroo towards the next one.\n" +
524 "I'll automatically enter you into the next giveaway if the fee is <= {0} naneroo").format(contributions)
525
526@db.connection_context()
527def contestant_exists(user_id):
528 user_id = str(user_id)
529 c = Contestant.select().where(Contestant.user_id == user_id).count()
530 return c > 0
531
532@db.connection_context()
533def is_active_giveaway():
534 giveaway = Giveaway.select().where(Giveaway.active==True).count()
535 if giveaway > 0:
536 return True
537 return False
538
539# Gets giveaway stats
540@db.connection_context()
541def get_giveaway_stats():
542 try:
543 giveaway = Giveaway.get(active=True)
544 entries = Contestant.select().count()
545 return {"amount":giveaway.amount + giveaway.tip_amount, "started_by":giveaway.started_by_name, "entries":entries, "end":giveaway.end_time,"fee":giveaway.entry_fee}
546 except Giveaway.DoesNotExist:
547 return None
548
549@db.connection_context()
550def inc_tx_attempts(uid):
551 tx = Transaction.get(uid = uid)
552 if tx is not None:
553 tx.attempts += 1
554 tx.save()
555 return
556
557@db.connection_context()
558def update_top_tips(user_id, month=0,day=0,alltime=0):
559 return (User.update(top_tip = User.top_tip + alltime,
560 top_tip_month = User.top_tip_month + month,
561 top_tip_day = User.top_tip_day + day
562 ).where(User.user_id == user_id)).execute()
563
564@db.connection_context()
565def get_top_tips():
566 dt = datetime.datetime.now()
567 past_dt = dt - datetime.timedelta(days=1) # Date 24H ago
568 month_str = dt.strftime("%B")
569 month_num = "{0:02d}".format(dt.month) # Sqlite uses 2 digit month (with leading 0)
570 amount = fn.MAX(User.top_tip).alias('amount')
571 amount_day = fn.MAX(User.top_tip_day).alias('amount')
572 amount_month = fn.MAX(User.top_tip_month).alias('amount')
573 top_24h = User.select(amount_day, User.user_name).where((User.top_tip_day_ts > past_dt) & (User.stats_ban == False)).order_by(User.top_tip_day_ts).limit(1)
574 top_month = User.select(amount_month, User.user_name).where((fn.strftime("%m", User.top_tip_month_ts) == month_num) & (User.stats_ban == False)).order_by(User.top_tip_month_ts).limit(1)
575 top_at = User.select(amount, User.user_name).where(User.stats_ban == False).order_by(User.top_tip_ts).limit(1)
576 # Formatted output
577 user24h = None
578 monthuser = None
579 atuser = None
580
581 for top in top_24h:
582 user24h = top.user_name
583 amount24h = float(top.amount) / 1000000
584 for top in top_month:
585 monthuser = top.user_name
586 monthamount = float(top.amount) / 1000000
587 for top in top_at:
588 atuser = top.user_name
589 atamount = float(top.amount) / 1000000
590
591 if user24h is None and monthuser is None and atuser is None:
592 return "```No Tips Found```"
593
594 result = ""
595 if user24h is not None:
596 result += "Biggest tip in the last 24 hours:```{0:.6f} NANO by {1}```".format(amount24h, user24h)
597 if monthuser is not None:
598 result += "Biggest tip in {0}:```{1:.6f} NANO by {2}```".format(month_str, monthamount, monthuser)
599 if atuser is not None:
600 result += "Biggest tip of all time:```{0:.6f} NANO by {1}```".format(atamount, atuser)
601
602 return result
603
604# Marks TX as processed and adds the block hash
605@db.connection_context()
606def mark_transaction_processed(uuid, amt, source_id, tranid, target_id=None):
607 tu = (Transaction.update(
608 sent = True,
609 processed = True,
610 tran_id = tranid
611 ).where(
612 (Transaction.uid == uuid) &
613 (Transaction.processed == False)
614 )).execute()
615 if tu > 0:
616 update_pending(source_id,send=amt)
617 if target_id is not None:
618 update_pending(target_id, receive=amt)
619
620# Return false if last message was < LAST_MSG_TIME
621# If > LAST_MSG_TIME, return True and update the user
622# Also return true, if user does not have a tip bot acct yet
623@db.connection_context()
624def last_msg_check(user_id, content, is_private):
625 user = get_user_by_id(user_id)
626 if user is None:
627 return True
628 # Get difference in seconds between now and last msg
629 since_last_msg_s = (datetime.datetime.now() - user.last_msg).total_seconds()
630 if since_last_msg_s < LAST_MSG_TIME:
631 return False
632 else:
633 update_last_msg(user, since_last_msg_s, content, is_private)
634 return True
635
636@db.connection_context()
637def update_last_msg(user, delta, content, is_private):
638 content_adjusted = unicode_strip(content)
639 words = content_adjusted.split(' ')
640 adjusted_count = 0
641 prev_len = 0
642 for word in words:
643 word = word.strip()
644 cur_len = len(word)
645 if cur_len > 0:
646 if word.startswith(":") and word.endswith(":"):
647 continue
648 if prev_len == 0:
649 prev_len = cur_len
650 adjusted_count += 1
651 else:
652 res = prev_len % cur_len
653 prev_len = cur_len
654 if res != 0:
655 adjusted_count += 1
656 if adjusted_count >= LAST_MSG_RAIN_WORDS:
657 break
658 if delta >= 1800:
659 user.last_msg_count = 0
660 if adjusted_count >= LAST_MSG_RAIN_WORDS and not is_private and (datetime.datetime.now() - user.last_msg_rain).total_seconds() > LAST_MSG_RAIN_DELTA:
661 user.last_msg_count += 1
662 user.last_msg_rain = datetime.datetime.now()
663 user.last_msg=datetime.datetime.now()
664 (User.update(
665 last_msg_count = user.last_msg_count,
666 last_msg_rain = user.last_msg_rain,
667 last_msg = user.last_msg
668 ).where(User.user_id == user.user_id)
669 ).execute()
670 return
671
672@db.connection_context()
673def unicode_strip(content):
674 pattern = re.compile("["
675 u"\U0001F600-\U0001F64F"
676 u"\U0001F300-\U0001F5FF"
677 u"\U0001F1E0-\U0001F1FF"
678 u"\U00002702-\U000027B0"
679 u"\U000024C2-\U0001F251"
680 "]+", flags=re.UNICODE)
681 return pattern.sub(r'',content)
682
683@db.connection_context()
684def mark_user_active(user):
685 if user is None:
686 return
687 if LAST_MSG_RAIN_COUNT > user.last_msg_count:
688 (User.update(
689 last_msg_count = LAST_MSG_RAIN_COUNT
690 ).where(User.user_id == user.user_id)
691 ).execute()
692
693## Favorites
694
695# Return true if favorite added
696@db.connection_context()
697def add_favorite(user_id, favorite_id):
698 user_id=str(user_id)
699 favorite_id=str(favorite_id)
700 if not user_exists(favorite_id):
701 return False
702 count = UserFavorite.select().where(UserFavorite.user_id == user_id).count()
703 # Identifier makes it easy for user to remove their favorite via DM
704 if count == 0:
705 identifier = 1
706 else:
707 identifier = count + 1
708 exists = UserFavorite.select().where((UserFavorite.user_id == user_id) & (UserFavorite.favorite_id == favorite_id)).count()
709 if exists == 0:
710 fav = UserFavorite(user_id=user_id,favorite_id=favorite_id,identifier=identifier)
711 fav.save()
712 return True
713 return False
714
715# Returns true if favorite deleted
716@db.connection_context()
717def remove_favorite(user_id, favorite_id=None,identifier=None):
718
719 if favorite_id is None and identifier is None:
720 return False
721 user_id=str(user_id)
722 if favorite_id is not None:
723 favorite_id = str(favorite_id)
724 return UserFavorite.delete().where((UserFavorite.user_id == user_id) & (UserFavorite.favorite_id == favorite_id)).execute() > 0
725 elif identifier is not None:
726 return UserFavorite.delete().where((UserFavorite.user_id == user_id) & (UserFavorite.identifier == identifier)).execute() > 0
727
728# Returns list of favorites for user ID
729@db.connection_context()
730def get_favorites_list(user_id):
731 user_id = str(user_id)
732 favorites = UserFavorite.select().where(UserFavorite.user_id==user_id).order_by(UserFavorite.identifier)
733 idx = 1
734 # Normalize identifiers
735 for fav in favorites:
736 fav.identifier = idx
737 UserFavorite.update(identifier=idx).where((UserFavorite.user_id==user_id) & (UserFavorite.favorite_id == fav.favorite_id)).execute()
738 idx += 1
739 return_data = []
740 for fav in favorites:
741 return_data.append({'user_id':fav.favorite_id,'id': fav.identifier})
742 return return_data
743
744# Returns list of muted for user id
745@db.connection_context()
746def get_muted(user_id):
747 user_id = str(user_id)
748 muted = MutedList.select().where(MutedList.user_id==user_id)
749 return_data = []
750 for m in muted:
751 return_data.append({'name':m.muted_name, 'id': m.muted_id})
752 return return_data
753
754# Return True if muted
755@db.connection_context()
756def muted(source_user, target_user):
757 source_user = str(source_user)
758 target_user = str(target_user)
759 return MutedList.select().where((MutedList.user_id==source_user) & (MutedList.muted_id==target_user)).count() > 0
760
761# Return false if already muted, True if muted
762@db.connection_context()
763def mute(source_user, target_user, target_name):
764 if muted(source_user, target_user):
765 return False
766 source_user = str(source_user)
767 target_user = str(target_user)
768 mute = MutedList(user_id=source_user,muted_id=target_user,muted_name=target_name)
769 mute.save()
770 return True
771
772# Return a number > 0 if user was unmuted
773@db.connection_context()
774def unmute(source_user, target_user):
775 source_user = str(source_user)
776 target_user = str(target_user)
777 return MutedList.delete().where((MutedList.user_id==source_user) & (MutedList.muted_id==target_user)).execute()
778
779
780# Returns seconds user must wait to tiprandom again
781@db.connection_context()
782def tiprandom_check(user):
783 delta = (datetime.datetime.now() - user.last_random).total_seconds()
784 if TIP_RANDOM_WAIT > delta:
785 return (TIP_RANDOM_WAIT - delta)
786 else:
787 User.update(last_random=datetime.datetime.now()).where(User.user_id == user.user_id).execute()
788 return 0
789
790# Returns seconds user must wait to tipfavorites again
791@db.connection_context()
792def tipfavorites_check(user):
793 delta = (datetime.datetime.now() -user.last_favorites).total_seconds()
794 if TIP_FAVORITES_WAIT > delta:
795 return (TIP_FAVORITES_WAIT - delta)
796 else:
797 User.update(last_favorites=datetime.datetime.now()).where(User.user_id == user.user_id).execute()
798 return 0
799
800# Base Model
801class BaseModel(Model):
802 class Meta:
803 database = db
804# User table
805class User(BaseModel):
806 user_id = CharField(unique=True)
807 user_name = CharField()
808 wallet_address = CharField(unique=True)
809 tipped_amount = FloatField(default=0.0)
810 pending_receive = IntegerField(default=0)
811 pending_send = IntegerField(default=0)
812 tip_count = IntegerField(default=0)
813 created = DateTimeField(default=datetime.datetime.now())
814 last_msg = DateTimeField(default=datetime.datetime.now())
815 last_msg_rain = DateTimeField(default=datetime.datetime.now())
816 last_msg_count = IntegerField(default=0)
817 top_tip = IntegerField(default=0)
818 top_tip_ts = DateTimeField(default=datetime.datetime.now())
819 top_tip_month = IntegerField(default=0)
820 top_tip_month_ts = DateTimeField(default=datetime.datetime.now())
821 top_tip_day = IntegerField(default=0)
822 top_tip_day_ts = DateTimeField(default=datetime.datetime.now())
823 last_withdraw = DateTimeField(default=datetime.datetime.now())
824 stats_ban = BooleanField(default=False)
825 rain_amount = FloatField(default=0.0,)
826 giveaway_amount = FloatField(default=0.0)
827 last_random = DateTimeField(default=datetime.datetime.now())
828 last_favorites = DateTimeField(default=datetime.datetime.now())
829
830 class Meta:
831 db_table='users'
832
833# Transaction table, keep trac of sends to process
834class Transaction(BaseModel):
835 uid = CharField(unique=True)
836 source_address = CharField()
837 to_address = CharField(null = True)
838 amount = CharField()
839 sent = BooleanField(default=False)
840 processed = BooleanField(default=False)
841 created = DateTimeField(default=datetime.datetime.now())
842 tran_id = CharField(default='', null=True)
843 attempts = IntegerField(default=0)
844 giveawayid = IntegerField(null = True)
845
846# Giveaway table, keep track of current giveaway
847class Giveaway(BaseModel):
848 started_by = CharField() # User ID
849 started_by_name = CharField() # User Name
850 active = BooleanField()
851 amount = FloatField()
852 tip_amount = FloatField()
853 end_time = DateTimeField()
854 channel_id = CharField() # The channel to post the results
855 winner_id = CharField(null = True)
856 entry_fee = IntegerField()
857
858# Giveaway Entrants
859class Contestant(BaseModel):
860 user_id = CharField(unique=True)
861 banned = BooleanField()
862
863# Banned List
864class BannedUser(BaseModel):
865 user_id = CharField()
866
867# Favorites List
868class UserFavorite(BaseModel):
869 user_id = CharField()
870 favorite_id = CharField()
871 created = DateTimeField(default=datetime.datetime.now())
872 identifier = IntegerField() # Identifier makes it easy for user to remove this favorite
873
874# Muted management
875class MutedList(BaseModel):
876 user_id = CharField()
877 muted_id = CharField()
878 muted_name = CharField()
879 created = DateTimeField(default=datetime.datetime.now())
880
881# Separate table for frozen so we can freeze even users not registered with bot
882class FrozenUser(BaseModel):
883 user_id = IntegerField(unique=True)
884 user_name = CharField()
885 created = DateTimeField(default=datetime.datetime.now())
886
887
888def create_db():
889 with db.connection_context():
890 db.create_tables([User, Transaction, Giveaway, Contestant, BannedUser, UserFavorite, MutedList, FrozenUser], safe=True)
891
892create_db()