Permalink
Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign up# TODO lets organize this and group jobs together, utilities together, commands together | |
import discord | |
from discord.ext import commands | |
from discord.ext.commands import Bot | |
import time | |
import secrets | |
import random | |
import collections | |
import random | |
import re | |
import errno | |
import asyncio | |
import uuid | |
import datetime | |
import json | |
import redis | |
import celery.result | |
import wallet | |
import util | |
import settings | |
import db | |
import paginator | |
from tasks import app, pocket_task | |
logger = util.get_logger("main") | |
BOT_VERSION = "3.3" | |
# How many users to display in the top users count | |
TOP_TIPPERS_COUNT=15 | |
# How many previous giveaway winners to display | |
WINNERS_COUNT=10 | |
# Minimum Amount for !rain | |
RAIN_MINIMUM = settings.rain_minimum | |
# Minimum amount for !startgiveaway | |
GIVEAWAY_MINIMUM = settings.giveaway_minimum | |
# Giveaway duration | |
GIVEAWAY_MIN_DURATION = settings.giveaway_min_duration | |
GIVEAWAY_MAX_DURATION = settings.giveaway_max_duration | |
GIVEAWAY_AUTO_DURATION = settings.giveaway_auto_duration | |
# Rain Delta (Minutes) - How long to look back for active users for !rain | |
RAIN_DELTA=30 | |
# Spam Threshold (Seconds) - how long to output certain commands (e.g. bigtippers) | |
SPAM_THRESHOLD=60 | |
# Withdraw Cooldown (Seconds) - how long a user must wait between withdraws | |
WITHDRAW_COOLDOWN=200 | |
# Rain Cooldown (Seconds) - how long a user must wait between rains | |
RAIN_COOLDOWN=300 | |
# Change command prefix to whatever you want to begin commands with | |
COMMAND_PREFIX=settings.command_prefix | |
# Pool giveaway auto amount (1%) | |
TIPGIVEAWAY_AUTO_ENTRY=int(.01 * GIVEAWAY_MINIMUM) | |
GIVEAWAY_CHANNELS=None | |
try: | |
GIVEAWAY_CHANNELS=settings.giveaway_channels | |
except: | |
pass | |
GIVEAWAY_ANNOUNCE_CHANNELS=None | |
try: | |
GIVEAWAY_ANNOUNCE_CHANNELS=settings.giveaway_announce_channels | |
except: | |
pass | |
# HELP menu header | |
AUTHOR_HEADER="Graham v{0} ({1} Edition)".format(BOT_VERSION, "BANANO" if settings.banano else "NANO") | |
DONATION_ADDRESS='ban_3jb1fp4diu79wggp7e171jdpxp95auji4moste6gmc55pptwerfjqu48okse' if settings.banano else 'xrb_1hmefcfq35td5f6rkh15hbpr4bkkhyyhmfhm7511jaka811bfp17xhkboyxo' | |
# Command DOC (TRIGGER, CMD, Overview, Info) | |
''' | |
TRIGGER: Users can get information about this command specifically via help $TRIGGER | |
CMD: Command overview for help doc | |
OVERVIEW: General overview of command for overview of command listings | |
INFO: Detailed command usage/examples/information/etc. | |
''' | |
def get_aliases(dict, exclude=''): | |
'''Returns list of command triggers excluding `exclude`''' | |
cmds = dict["TRIGGER"] | |
ret_cmds = [] | |
for cmd in cmds: | |
if cmd != exclude: | |
ret_cmds.append(cmd) | |
return ret_cmds | |
### All commands | |
TIP_UNIT = "BANANO" if settings.banano else "naneroo" | |
TIP_PREFIX = "ban" if settings.banano else "tip" | |
BALANCE = { | |
"TRIGGER" : ["balance", "bal", "$"], | |
"CMD" : "{0}balance".format(COMMAND_PREFIX), | |
"OVERVIEW" : "Display balance of your account", | |
"INFO" : ("Displays the balance of your tip account (in {0}) as described:" + | |
"\nActual Balance: The actual balance in your tip account" + | |
"\nAvailable Balance: The balance you are able to tip with (Actual - Pending Send)" + | |
"\nPending Send: Tips you have sent, but have not yet been broadcasted to network" + | |
"\nPending Receipt: Tips that have been sent to you, but have not yet been pocketed by the node. " + | |
"\nPending funds will be available for tip/withdraw after they have been pocketed by the node").format(TIP_UNIT) | |
} | |
DEPOSIT ={ | |
"TRIGGER" : ["deposit", "register", "wallet", "address"], | |
"CMD" : "{0}deposit or {0}register or {0}wallet or {0}address, takes: optional amount".format(COMMAND_PREFIX), | |
"OVERVIEW" : "Shows your account address", | |
"INFO" : ("Displays your tip bot account address along with a QR code. QR code is encoded with an amount if provided" + | |
"\n- Send NANO to this address to increase your tip bot balance" + | |
"\n- If you do not have a tip bot account yet, this command will create one for you (receiving a tip automatically creates an account too)") | |
} | |
SEND = { | |
"TRIGGER" : ["send"], | |
"CMD" : "{0}send, takes: amount, address".format(COMMAND_PREFIX), | |
"OVERVIEW" : "Allows you to send coins from your tip account to any address", | |
"INFO" : ("Send specified amount to specified address. Fractional digits be truncated." + | |
"\nExample: `{0}send 1000 xrb_3o7uzba8b9e1wqu5ziwpruteyrs3scyqr761x7ke6w1xctohxfh5du75qgaj` - Sends 1000 {1}").format(COMMAND_PREFIX, TIP_UNIT) | |
} | |
SENDMAX = { | |
"TRIGGER" : ["sendmax"], | |
"CMD" : "{0}sendmax, takes: address".format(COMMAND_PREFIX), | |
"OVERVIEW" : "Allows you to withdraw everything from your tip account", | |
"INFO" : ("Withdraws complete balance to specified address" + | |
"\nExample: `{0}sendmax xrb_3o7uzba8b9e1wqu5ziwpruteyrs3scyqr761x7ke6w1xctohxfh5du75qgaj` - Withdraws everything") | |
} | |
TIP = { | |
"TRIGGER" : ["ban", "b"] if settings.banano else ["tip", "t"], | |
"CMD" : "{0}{1}, takes: amount <*users>".format(COMMAND_PREFIX, TIP_PREFIX), | |
"OVERVIEW" : "Send a tip to mentioned users", | |
"INFO" : ("Tip specified amount to mentioned user(s) (minimum tip is 1 {2})" + | |
"\nThe recipient(s) will be notified of your tip via private message" + | |
"\nSuccessful tips will be deducted from your available balance immediately" + | |
"\nExample: `{0}{1} 2 @user1 @user2` would send 2 to user1 and 2 to user2").format(COMMAND_PREFIX, TIP_PREFIX, TIP_UNIT) | |
} | |
TIPSPLIT = { | |
"TRIGGER" : ["bansplit", "bsplit", "bs"] if settings.banano else ["tipsplit", "tsplit"], | |
"CMD" : "{0}{1}split, takes: amount, <*users>".format(COMMAND_PREFIX, TIP_PREFIX), | |
"OVERVIEW" : "Split a tip among mentioned uses", | |
"INFO" : "Distributes a tip evenly to all mentioned users.\nExample: `{0}{1}split 2 @user1 @user2` would send 1 to user1 and 1 to user2".format(COMMAND_PREFIX, TIP_PREFIX) | |
} | |
TIPRANDOM = { | |
"TRIGGER" : ["banrandom", "br"] if settings.banano else ["tiprandom", "tr"], | |
"CMD" : "{0}{1}random, takes: amount".format(COMMAND_PREFIX, TIP_PREFIX), | |
"OVERVIEW" : "Tips a random active user", | |
"INFO" : ("Tips amount to a random active user. Active user list picked using same logic as rain" + | |
"\n**Minimum {2}random amount: {0} {1}**").format(settings.tiprandom_minimum, TIP_UNIT, TIP_PREFIX) | |
} | |
RAIN = { | |
"TRIGGER" : ["brain"] if settings.banano else ["rain"], | |
"CMD" : "{0}{1}, takes: amount".format(COMMAND_PREFIX, "brain" if settings.banano else "rain"), | |
"OVERVIEW" : "Split tip among all active* users", | |
"INFO" : ("Distribute <amount> evenly to users who are eligible.\n" + | |
"Eligibility is determined based on your *recent* activity **and** contributions to public channels. " + | |
"Several factors are considered in picking who receives rain. If you aren't receiving it, you aren't contributing enough or your contributions are low-quality/spammy.\n" + | |
"Note: Users who have a status of 'offline' or 'do not disturb' do not receive rain.\n" + | |
"Example: `{0}rain 1000` - distributes 1000 evenly to eligible users (similar to `tipsplit`)" + | |
"\n**Minimum rain amount: {1} {2}**").format(COMMAND_PREFIX, RAIN_MINIMUM, TIP_UNIT) | |
} | |
START_GIVEAWAY = { | |
"TRIGGER" : ["givearai", "giveaway", "sponsorgiveaway"], | |
"CMD" : "{0}givearai, takes: amount, fee=(amount), duration=(minutes)".format(COMMAND_PREFIX), | |
"OVERVIEW" : "Sponsor a giveaway", | |
"INFO" : ("Start a giveaway with given amount, entry fee, and duration." + | |
"\nEntry fees are added to the total prize pool" + | |
"\nGiveaway will end and choose random winner after (duration)" + | |
"\nExample: `{0}giveaway 1000 fee=5 duration=30` - Starts a giveaway of 1000, with fee of 5, duration of 30 minutes" + | |
"\n**Minimum required to sponsor a giveaway: {1} {4}**" + | |
"\n**Minimum giveaway duration: {2} minutes**" + | |
"\n**Maximum giveaway duration: {3} minutes**").format(COMMAND_PREFIX, GIVEAWAY_MINIMUM, GIVEAWAY_MIN_DURATION, GIVEAWAY_MAX_DURATION, TIP_UNIT) | |
} | |
ENTER = { | |
"TRIGGER" : ["ticket", "enter", "e"], | |
"CMD" : "{0}ticket, takes: fee (conditional)".format(COMMAND_PREFIX), | |
"OVERVIEW" : "Enter the current giveaway", | |
"INFO" : ("Enter the current giveaway, if there is one. Takes (fee) as argument only if there's an entry fee." + | |
"\n Fee will go towards the prize pool and be deducted from your available balance immediately" + | |
"\nExample: `{0}ticket` (to enter a giveaway without a fee), `{0}ticket 10` (to enter a giveaway with a fee of 10)").format(COMMAND_PREFIX) | |
} | |
TIPGIVEAWAY = { | |
"TRIGGER" : ["donate", "do"] if settings.banano else ["tipgiveaway", "tg"], | |
"CMD" : "{0}tipgiveaway, takes: amount".format(COMMAND_PREFIX), | |
"OVERVIEW" : "Add to present or future giveaway prize pool", | |
"INFO" : ("Add <amount> to the current giveaway pool\n"+ | |
"If there is no giveaway, one will be started when minimum is reached." + | |
"\nTips >= {0} {2} automatically enter you for giveaways sponsored by the community." + | |
"\nDonations count towards the next giveaways entry fee" + | |
"\nExample: `{1}tipgiveaway 1000` - Adds 1000 to giveaway pool").format(TIPGIVEAWAY_AUTO_ENTRY, COMMAND_PREFIX, TIP_UNIT) | |
} | |
TICKETSTATUS = { | |
"TRIGGER" : ["ticketstatus", "ts"], | |
"CMD" : "{0}ticketstatus".format(COMMAND_PREFIX), | |
"OVERVIEW" : "Check if you are entered into the current giveaway", | |
"INFO" : "Check if you are entered into the current giveaway" | |
} | |
GIVEAWAY_STATS= { | |
"TRIGGER" : ["gstats", "giveawaystats", "gs", "giveawaystatus", "gstatus"], | |
"CMD" : "{0}giveawaystats or {0}goldenticket".format(COMMAND_PREFIX), | |
"OVERVIEW" : "Display statistics relevant to the current giveaway", | |
"INFO" : "Display statistics relevant to the current giveaway" | |
} | |
WINNERS = { | |
"TRIGGER" : ["winners"], | |
"CMD" : "{0}winners".format(COMMAND_PREFIX), | |
"INFO" : "Display previous giveaway winners", | |
"OVERVIEW" : "Display previous giveaway winners" | |
} | |
LEADERBOARD = { | |
"TRIGGER" : ["leaderboard", "ballers", "bigtippers"], | |
"CMD" : "{0}leaderboard or {0}ballers".format(COMMAND_PREFIX), | |
"INFO" : "Display the all-time tip leaderboard", | |
"OVERVIEW" : "Display the all-time tip leaderboard" | |
} | |
TOPTIPS = { | |
"TRIGGER" : ["toptips"], | |
"CMD" : "{0}toptips".format(COMMAND_PREFIX), | |
"OVERVIEW" : "Display largest individual tips", | |
"INFO" : "Display the single largest tips for the past 24 hours, current month, and all time" | |
} | |
STATS = { | |
"TRIGGER" : ["tipstats"], | |
"CMD" : "{0}tipstats".format(COMMAND_PREFIX), | |
"OVERVIEW" : "Display your personal tipping stats", | |
"INFO" : "Display your personal tipping stats (rank, total tipped, and average tip)" | |
} | |
ADD_FAVORITE = { | |
"TRIGGER" : ["addfav", "addfavorite", "addfavourite"], | |
"CMD" : "{0}addfavorite, takes: *users".format(COMMAND_PREFIX), | |
"OVERVIEW" : "Add users to your favorites list", | |
"INFO" : "Adds mentioned users to your favorites list.\nExample: `{0}addfavorite @user1 @user2 @user3` - Adds user1,user2,user3 to your favorites".format(COMMAND_PREFIX) | |
} | |
DEL_FAVORITE = { | |
"TRIGGER" : ["removefavorite", "removefavourite", "removefav"], | |
"CMD" : "{0}removefavorite, takes: *users or favorite ID".format(COMMAND_PREFIX), | |
"OVERVIEW" : "Removes users from your favorites list", | |
"INFO" : ("Removes users from your favorites list. " + | |
"You can either @mention the user in a public channel or use the ID in your `favorites` list" + | |
"\nExample 1: `{0}removefavorite @user1 @user2` - Removes user1 and user2 from your favorites" + | |
"\nExample 2: `{0}removefavorite 1 6 3` - Removes favorites with ID : 1, 6, and 3").format(COMMAND_PREFIX) | |
} | |
FAVORITES = { | |
"TRIGGER" : ["favorites", "favs", "favourites"], | |
"CMD" : "{0}favorites".format(COMMAND_PREFIX), | |
"OVERVIEW" : "View your favorites list", | |
"INFO" : "View your favorites list. Use `{0}addfavorite` to add favorites to your list and `{0}removefavorite` to remove favories".format(COMMAND_PREFIX) | |
} | |
TIP_FAVORITES = { | |
"TRIGGER" : ["banfavs", "banfavorites", "banfavourties", "bf"] if settings.banano else ["tipfavs", "tipfavorites", "tipfavourites", "tf"], | |
"CMD" : "{0}{1}favorites, takes: amount".format(COMMAND_PREFIX, TIP_PREFIX), | |
"OVERVIEW" : "Tip your entire favorites list", | |
"INFO" : ("Tip everybody in your favorites list specified amount" + | |
"\nExample: `{0}{1}favorites 1000` Distributes 1000 to your entire favorites list (similar to `{0}{1}split`)").format(COMMAND_PREFIX, TIP_PREFIX) | |
} | |
TIP_AUTHOR = { | |
"TRIGGER" : ["tipauthor"], | |
"CMD" : "{0}tipauthor, takes: amount".format(COMMAND_PREFIX), | |
"OVERVIEW" : "Donate to the author of this bot :heart:", | |
"INFO" : "The author is BBedward but there was no INFO property here so I added this as an easter egg. Cheers, Newguyneal" | |
} | |
MUTE = { | |
"TRIGGER" : ["mute"], | |
"CMD" : "{0}mute, takes: user id".format(COMMAND_PREFIX), | |
"OVERVIEW" : "Block tip notifications when sent by this user", | |
"INFO" : "When someone is spamming you with tips and you can't take it anymore" | |
} | |
UNMUTE = { | |
"TRIGGER" : ["unmute"], | |
"CMD" : "{0}unmute, takes: user id".format(COMMAND_PREFIX), | |
"OVERVIEW" : "Unblock tip notificaitons sent by this user", | |
"INFO" : "When the spam is over and you want to know they still love you" | |
} | |
MUTED = { | |
"TRIGGER" : ["muted"], | |
"CMD" : "{0}muted".format(COMMAND_PREFIX), | |
"OVERVIEW" : "View list of users you have muted", | |
"INFO" : "Are you really gonna drunk dial?" | |
} | |
### ADMIN-only commands | |
FREEZE = { | |
"CMD" : "{0}freeze, takes: users".format(COMMAND_PREFIX), | |
"INFO" : "Suspends every action from user, including withdraw" | |
} | |
UNFREEZE = { | |
"CMD" : "{0}unfreeze, takes: users".format(COMMAND_PREFIX), | |
"INFO" : "Unfreezes mentioned user" | |
} | |
FROZEN = { | |
"CMD" : "{0}frozen".format(COMMAND_PREFIX), | |
"INFO" : "List frozen users" | |
} | |
WALLET_FOR = { | |
"CMD" : "{0}walletfor, takes: user".format(COMMAND_PREFIX), | |
"INFO" : "Returns wallet address for mentioned user" | |
} | |
USER_FOR_WALLET = { | |
"CMD" : "{0}userforwallet, takes: address".format(COMMAND_PREFIX), | |
"INFO" : "Returns user owning wallet address" | |
} | |
PAUSE = { | |
"CMD" : "{0}pause".format(COMMAND_PREFIX), | |
"INFO" : "Pause all transaction-related activity" | |
} | |
UNPAUSE = { | |
"CMD" : "{0}unpause".format(COMMAND_PREFIX), | |
"INFO" : "Resume all transaction-related activity" | |
} | |
TIPBAN = { | |
"CMD" : "{0}tipban, takes: users".format(COMMAND_PREFIX), | |
"INFO" : "Makes it so mentioned users can no longer receive tips" | |
} | |
TIPUNBAN = { | |
"CMD" : "{0}tipunban, takes: users".format(COMMAND_PREFIX), | |
"INFO" : "Makes it so mentioned users can receive tips again" | |
} | |
BANNED = { | |
"CMD" : "{0}banned".format(COMMAND_PREFIX), | |
"INFO" : "View list of users currently tip banned" | |
} | |
STATSBAN = { | |
"CMD" : "{0}statsban, takes: users".format(COMMAND_PREFIX), | |
"INFO" : "Bans mentioned users from all stats consideration" | |
} | |
STATSUNBAN = { | |
"CMD" : "{0}statsunban, takes: users".format(COMMAND_PREFIX), | |
"INFO" : "Unbans mentioned users from stats considerations" | |
} | |
STATSBANNED = { | |
"CMD" : "{0}statsbanned".format(COMMAND_PREFIX), | |
"INFO" : "View list of stats banned users" | |
} | |
INCREASETIPTOTAL = { | |
"CMD" : "{0}increasetips (amount) (user)".format(COMMAND_PREFIX), | |
"INFO" : "Increases users tip total by (amount), for stats purposes. Amount is in NANO or BANANO (not naneroo)" | |
} | |
DECREASETIPTOTAL = { | |
"CMD" : "{0}decreasetips (amount) (user)".format(COMMAND_PREFIX), | |
"INFO" : "Decreases users tip total by (amount), for stats purposes. Amount is in NANO or BANANO (not naneroo)" | |
} | |
SETTOPTIP = { | |
"CMD" : "{0}settoptip".format(COMMAND_PREFIX), | |
"INFO" : ("Allows you to set a users top tips. You can set 1 or all of monthly, 24h, and all-time " + | |
"toptips.\n Example: \n `settoptip @user alltime=2.38 month=1.23 day=0.5` " + | |
"sets @user's biggest alltime tip to 2.38 NANO, month to 1.23 NANO, and day to 0.5 NANO") | |
} | |
INCREASETIPCOUNT = { | |
"CMD" : "{0}increasetipcount (amount) (user)".format(COMMAND_PREFIX), | |
"INFO" : "Increases the number of tips a user has made (used for average TIP)" | |
} | |
DECREASETIPCOUNT = { | |
"CMD" : "{0}decreasetipcount (amount) (user)".format(COMMAND_PREFIX), | |
"INFO" : "Decreases the number of tips a user has made (used for average TIP)" | |
} | |
COMMANDS = { | |
"ACCOUNT_COMMANDS" : [BALANCE, DEPOSIT, SEND, SENDMAX], | |
"TIPPING_COMMANDS" : [TIP, TIPSPLIT, TIPRANDOM, RAIN], | |
"GIVEAWAY_COMMANDS" : [START_GIVEAWAY, ENTER, TIPGIVEAWAY, TICKETSTATUS], | |
"STATISTICS_COMMANDS" : [GIVEAWAY_STATS, WINNERS, LEADERBOARD, TOPTIPS,STATS], | |
"FAVORITES_COMMANDS" : [ADD_FAVORITE, DEL_FAVORITE, FAVORITES, TIP_FAVORITES], | |
"NOTIFICATION_COMMANDS" : [MUTE, UNMUTE, MUTED], | |
"AUTHOR_COMMANDS" : [TIP_AUTHOR], | |
"ADMIN_COMMANDS" : [FREEZE, UNFREEZE, FROZEN, USER_FOR_WALLET, WALLET_FOR, PAUSE, UNPAUSE, TIPBAN, TIPUNBAN, BANNED, STATSBAN, STATSUNBAN, STATSBANNED, INCREASETIPTOTAL, DECREASETIPTOTAL, SETTOPTIP, INCREASETIPCOUNT, DECREASETIPCOUNT] | |
} | |
### Response Templates### | |
# balance | |
if settings.banano: | |
BALANCE_TEXT=( "```Actual Balance : {0:,.2f} BANANO\n" + | |
"Available : {1:,.2f} BANANO\n" + | |
"Pending Send : {2:,.2f} BANANO\n" + | |
"Pending Receipt: {3:,.2f} BANANO```") | |
else: | |
BALANCE_TEXT=( "```Actual Balance : {0} naneroo ({1:.6f} NANO)\n" + | |
"Available : {2} naneroo ({3:.6f} NANO)\n" + | |
"Pending Send : {4} naneroo ({5:.6f} NANO)\n" + | |
"Pending Receipt: {6} naneroo ({7:.6f} NANO)```") | |
# generic tip replies (apply to numerous tip commands) | |
INSUFFICIENT_FUNDS_TEXT="You don't have enough {0} in your available balance!".format(TIP_UNIT) | |
TIP_RECEIVED_TEXT="You were tipped {0} " + TIP_UNIT + " by {1}. You can mute tip notifications from this person using `" + COMMAND_PREFIX + "mute {2}`" | |
TIP_SELF="No valid recipients found in your tip.\n(You cannot tip yourself and certain other users are exempt from receiving tips)" | |
# send | |
SEND_AMBIGUOUS_AMOUNT="More than one amount found. You probably use a wrong number format." | |
SEND_AMOUNT_NOT_FOUND="Specify an amount to send." | |
# sendmax and send | |
SEND_INVALID_ADDRESS_TEXT="Address rejected by the node. You probably made a typo or copied an address from a different network." | |
SEND_PROCESSED_TEXT="Withdraw processed:\nTransaction: {0}block/{1}\nIf you have an issue with a withdraw please wait **24 hours** before contacting my master." | |
SEND_COOLDOWN_TEXT="You need to wait {0:.2f} seconds before making another withdraw" | |
SEND_TOO_MANY_ADDRESSES_TEXT="More than one address found. You can only make one transaction at a time." | |
SEND_NO_BALANCE_TEXT="You have no {0} remaining on this account".format(TIP_UNIT) | |
SEND_SUCCESS_TEXT="Transaction has been queued for processing, I'll send you a link to the block explorer when I've broadcasted it to the network!" | |
SEND_ADDRESS_NOT_FOUND_TEXT="No valid address recognized. If you entered an address, make sure you didn't miss any characters." | |
# legacy/deprecated | |
WITHDRAW_COMMAND_CHANGED="The ~~{0}withdraw~~ command is now divided into `{0}send` and `{0}sendmax`. Use those instead.".format(COMMAND_PREFIX) | |
# leaderboard | |
TOP_HEADER_TEXT="Here are the top {0} tippers :clap:" | |
TOP_HEADER_EMPTY_TEXT="The leaderboard is empty!" | |
TOP_SPAM="No more big tippers for {0} seconds" | |
# tipstats (individual) | |
STATS_ACCT_NOT_FOUND_TEXT="I could not find an account for you, try private messaging me `{0}register`".format(COMMAND_PREFIX) | |
if settings.banano: | |
STATS_TEXT="You are rank #{0}, you've tipped a total of {1:.2f} BANANO, your average tip is {2:.2f} BANANO, and your biggest tip of all time is {3:.2f} BANANO" | |
else: | |
STATS_TEXT="You are rank #{0}, you've tipped a total of {1:.6f} NANO, your average tip is {2:.6f} NANO, and your biggest tip of all time is {3:.6f} NANO" | |
# tipsplit | |
TIPSPLIT_SMALL="Tip amount is too small to be distributed to that many users" | |
# rain | |
RAIN_NOBODY="I couldn't find anybody eligible to receive rain" | |
# giveaway (all giveaway related commands) | |
if settings.banano: | |
GIVEAWAY_EXISTS="There's already an active giveaway" | |
GIVEAWAY_STARTED="{0} has sponsored a giveaway of {1:.2f} BANANO! Use:\n - `" + COMMAND_PREFIX + "ticket` to enter\n - `" + COMMAND_PREFIX + "donate` to increase the pot\n - `" + COMMAND_PREFIX + "ticketstatus` to check the status of your entry" | |
GIVEAWAY_STARTED_FEE="{0} has sponsored a giveaway of {1:.2f} BANANO, including community contributions the total pot is {2:.2f} BANANO! The entry fee is {3} BANANO.\nUse:\n - `" + COMMAND_PREFIX + "ticket {3}` to buy your ticket\n - `" + COMMAND_PREFIX + "donate` to increase the pot\n - `" + COMMAND_PREFIX + "ticketstatus` to check the status of your entry" | |
GIVEAWAY_FEE_TOO_HIGH="A giveaway has started where the entry fee is higher than your donations! Use `{0}ticketstatus` to see how much you need to enter!".format(COMMAND_PREFIX) | |
GIVEAWAY_MAX_FEE="Giveaway entry fee cannot be more than 5% of the prize pool" | |
GIVEAWAY_ENDED="Congratulations! <@{0}> was the winner of the giveaway! They have been sent {1:.2f} BANANO!" | |
GIVEAWAY_STATS_NF="There are {0} entries to win {1:.2f} BANANO ending in {2} - sponsored by {3}.\nUse:\n - `" + COMMAND_PREFIX + "ticket` to enter\n - `" + COMMAND_PREFIX + "donate` to add to the pot\n - `" + COMMAND_PREFIX + "ticketstatus` to check status of your entry" | |
GIVEAWAY_STATS_FEE="There are {0} entries to win {1:.2f} BANANO ending in {2} - sponsored by {3}.\nEntry fee: {4} BANANO. Use:\n - `" + COMMAND_PREFIX + "ticket {4}` to enter\n - `" + COMMAND_PREFIX + "donate` to add to the pot\n - `" + COMMAND_PREFIX + "ticketstatus` to check the status of your entry" | |
GIVEAWAY_STATS_INACTIVE="There are no active giveaways\n{0} BANANO required to to automatically start one! Use\n - `" + COMMAND_PREFIX + "donate` to donate to the next giveaway.\n - `" + COMMAND_PREFIX + "giveaway` to sponsor your own giveaway\n - `" + COMMAND_PREFIX + "ticketstatus` to see how much you've already donated to the next giveaway" | |
ENTER_ADDED="You've been successfully entered into the giveaway" | |
ENTER_DUP="You've already entered the giveaway" | |
TIPGIVEAWAY_NO_ACTIVE="There are no active giveaways. Check giveaway status using `{0}giveawaystats`, or donate to the next one using `{0}tipgiveaway`".format(COMMAND_PREFIX) | |
TIPGIVEAWAY_ENTERED_FUTURE="With your bantastic donation I have reserved your ticket for the next community sponsored giveaway!" | |
else: | |
GIVEAWAY_EXISTS="There's already an active giveaway" | |
GIVEAWAY_STARTED="{0} has sponsored a giveaway of {1:.6f} NANO, including community contributions the total pot is {2:.6f} NANO!\nUse:\n - `" + COMMAND_PREFIX + "ticket` to enter\n - `" + COMMAND_PREFIX + "tipgiveaway` to increase the pot\n - `" + COMMAND_PREFIX + "ticketstatus` to check the status of your entry" | |
GIVEAWAY_STARTED_FEE="{0} has sponsored a giveaway of {1:.6f} NANO, including community contributions the total pot is {2:.6f} NANO! The entry fee is {3} naneroo.\nUse:\n - `" + COMMAND_PREFIX + "ticket {3}` to buy your ticket\n - `" + COMMAND_PREFIX + "tipgiveaway` to increase the pot\n - `" + COMMAND_PREFIX + "ticketstatus` to check the status of your entry" | |
GIVEAWAY_FEE_TOO_HIGH="A giveaway has started where the entry fee is higher than your donations! Use `{0}ticketstatus` to see how much you need to enter!".format(COMMAND_PREFIX) | |
GIVEAWAY_MAX_FEE="Giveaway entry fee cannot be more than 5% of the prize pool" | |
GIVEAWAY_ENDED="Congratulations! <@{0}> was the winner of the giveaway! They have been sent {1:.6f} NANO!" | |
GIVEAWAY_STATS_NF="There are {0} entries to win {1:.6f} NANO ending in {2} - sponsored by {3}.\nUse:\n - `" + COMMAND_PREFIX + "ticket` to enter\n - `" + COMMAND_PREFIX + "tipgiveaway` to add to the pot\n - `" + COMMAND_PREFIX + "ticketstatus` to check status of your entry" | |
GIVEAWAY_STATS_FEE="There are {0} entries to win {1:.6f} NANO ending in {2} - sponsored by {3}.\nEntry fee: {4} naneroo. Use:\n - `" + COMMAND_PREFIX + "ticket {4}` to enter\n - `" + COMMAND_PREFIX + "tipgiveaway` to add to the pot\n - `" + COMMAND_PREFIX + "ticketstatus` to check the status of your entry" | |
GIVEAWAY_STATS_INACTIVE="There are no active giveaways\n{0} naneroo required to to automatically start one! Use\n - `" + COMMAND_PREFIX + "tipgiveaway` to donate to the next giveaway.\n - `" + COMMAND_PREFIX + "givearai` to sponsor your own giveaway\n - `" + COMMAND_PREFIX + "ticketstatus` to see how much you've already donated to the next giveaway" | |
ENTER_ADDED="You've been successfully entered into the giveaway" | |
ENTER_DUP="You've already entered the giveaway" | |
TIPGIVEAWAY_NO_ACTIVE="There are no active giveaways. Check giveaway status using `{0}giveawaystats`, or donate to the next one using `{0}tipgiveaway`".format(COMMAND_PREFIX) | |
TIPGIVEAWAY_ENTERED_FUTURE="With your gorgeous donation I have reserved your ticket for the next community sponsored giveaway!" | |
# toptips | |
TOPTIP_SPAM="No more top tips for {0} seconds" | |
# admin command responses | |
PAUSE_MSG="All transaction activity is currently suspended. Check back later." | |
BAN_SUCCESS="User {0} can no longer receive tips" | |
BAN_DUP="User {0} is already banned" | |
UNBAN_SUCCESS="User {0} has been unbanned" | |
UNBAN_DUP="User {0} is not banned" | |
STATSBAN_SUCCESS="User {0} is no longer considered in tip statistics" | |
STATSBAN_DUP="User {0} is already stats banned" | |
STATSUNBAN_SUCCESS="User {0} is now considered in tip statistics" | |
STATSUNBAN_DUP="User {0} is not stats banned" | |
FROZEN_MSG="Your account is frozen. Contact an admin for help" | |
# past giveaway winners | |
WINNERS_HEADER="Here are the previous {0} giveaway winners! :trophy:".format(WINNERS_COUNT) | |
WINNERS_EMPTY="There are no previous giveaway winners" | |
WINNERS_SPAM="No more winners for {0} seconds" | |
### END Response Templates ### | |
# Paused flag, indicates whether or not bot is paused | |
paused = False | |
# Create discord client | |
client = Bot(command_prefix=COMMAND_PREFIX) | |
client.remove_command('help') | |
# Don't make them wait when bot first launches | |
initial_ts=datetime.datetime.utcnow() - datetime.timedelta(seconds=SPAM_THRESHOLD) | |
last_big_tippers = {} | |
last_top_tips = {} | |
last_winners = {} | |
last_gs = {} | |
last_blocks = {} | |
last_rains = {} | |
def create_spam_dicts(): | |
"""map every channel the client can see to datetime objects | |
this way we can have channel-specific spam prevention""" | |
global last_big_tippers | |
global last_top_tips | |
global last_winners | |
global last_gs | |
global last_blocks | |
for c in client.get_all_channels(): | |
if not is_private(c): | |
last_big_tippers[c.id] = initial_ts | |
last_top_tips[c.id] = initial_ts | |
last_winners[c.id] = initial_ts | |
last_gs[c.id] = initial_ts | |
last_blocks[c.id] = initial_ts | |
### Redis stuff | |
async def pocket_pending_tx(): | |
accts = db.get_accounts() | |
logger.debug("Firing pocket_task") | |
pocket_task.delay(accts) | |
await asyncio.sleep(120) | |
asyncio.get_event_loop().create_task(pocket_pending_tx()) | |
r = redis.StrictRedis() | |
MAX_TX_RETRIES=3 | |
async def process_finished_tx(): | |
"""Use blocking get on redis queue to process results""" | |
# This will block until it receives a result | |
logger.info("waiting for werk") | |
q, result = await asyncio.get_event_loop().run_in_executor(None, r.blpop, '/tx_completed') | |
try: | |
result = json.loads(result.decode('utf-8').replace("'", "\"")) | |
if 'success' in result: | |
logger.info("TX Processed: %s", result['success']['txid']) | |
await mark_tx_processed(result['success']['source'], | |
result['success']['txid'], | |
result['success']['uid'], | |
result['success']['destination'], | |
result['success']['amount']) | |
except Exception as e: | |
logger.exception(e) | |
r.rpush('/tx_completed', result) | |
# Wait for next one | |
asyncio.get_event_loop().create_task(process_finished_tx()) | |
async def mark_tx_processed(source_address, block, uid, to_address, amount): | |
src_usr = db.get_user_by_wallet_address(source_address) | |
trg_usr = db.get_user_by_wallet_address(to_address) | |
source_id=None | |
target_id=None | |
pending_delta = int(amount) * -1 | |
if src_usr is not None: | |
source_id=src_usr.user_id | |
if trg_usr is not None: | |
target_id=trg_usr.user_id | |
db.mark_transaction_processed(uid, pending_delta, source_id, block, target_id) | |
logger.info('TX processed. UID: %s, HASH: %s', uid, block) | |
if target_id is None and to_address != DONATION_ADDRESS and block != 'invalid': | |
await notify_of_withdraw(source_id, block) | |
@client.event | |
async def on_ready(): | |
logger.info("Graham v%s started.", BOT_VERSION) | |
logger.info("Discord.py API version %s", discord.__version__) | |
logger.info("Name: %s", client.user.name) | |
logger.info("ID: %s", client.user.id) | |
create_spam_dicts() | |
await client.change_presence(activity=discord.Game(settings.playing_status)) | |
logger.info("Continuing outstanding giveaway") | |
asyncio.get_event_loop().create_task(start_giveaway_timer()) | |
logger.info("Starting TX processor") | |
asyncio.get_event_loop().create_task(process_finished_tx()) | |
logger.info("Starting receive trigger job") | |
asyncio.get_event_loop().create_task(pocket_pending_tx()) | |
async def notify_of_withdraw(user_id, txid): | |
"""Notify user of withdraw with a block explorer link""" | |
if user_id is not None: | |
user = await client.get_user_info(int(user_id)) | |
await post_dm(user, SEND_PROCESSED_TEXT, settings.block_explorer, txid) | |
def is_private(channel): | |
"""Check if a discord channel is private""" | |
return isinstance(channel, discord.abc.PrivateChannel) | |
@client.event | |
async def on_message(message): | |
# disregard messages sent by our own bot | |
if message.author.id == client.user.id: | |
return | |
if db.last_msg_check(message.author.id, message.content, is_private(message.channel)) == False: | |
return | |
await client.process_commands(message) | |
def has_admin_role(roles): | |
"""Check if user has an admin role defined in our settings""" | |
for r in roles: | |
if r.name in settings.admin_roles: | |
return True | |
return False | |
async def pause_msg(message): | |
if paused: | |
await post_dm(message.author, PAUSE_MSG) | |
def is_admin(user): | |
"""Returns true if user is an admin""" | |
if str(user.id) in settings.admin_ids: | |
return True | |
for m in client.get_all_members(): | |
if m.id == user.id: | |
if has_admin_role(m.roles): | |
return True | |
return False | |
def has_giveaway_role(user): | |
"""Returns true if user has giveaway role""" | |
if settings.giveaway_role is None: | |
return True | |
for m in client.get_all_members(): | |
if m.id == user.id: | |
for r in m.roles: | |
if r.name == settings.giveaway_role: | |
return True | |
return False | |
### Commands | |
def build_page(group_name,commands_dictionary): | |
"""Return array of paginator.Entry objects based on passed in dictionary""" | |
entries = [] | |
for cmd in commands_dictionary[group_name]: | |
entries.append(paginator.Entry(cmd["CMD"],cmd["INFO"])) | |
return entries | |
def build_help(): | |
"""Returns an array of paginator.Page objects for help menu""" | |
pages = [] | |
# Overview | |
author=AUTHOR_HEADER | |
title="Command Overview" | |
description=("Use `{0}help command` for more information about a specific command " + | |
" or go to the next page").format(COMMAND_PREFIX) | |
entries = [] | |
tmp_command_list = [ | |
"ACCOUNT_COMMANDS", | |
"TIPPING_COMMANDS", | |
"GIVEAWAY_COMMANDS", | |
"STATISTICS_COMMANDS", | |
"FAVORITES_COMMANDS", | |
"NOTIFICATION_COMMANDS" | |
] | |
for command_group in tmp_command_list: | |
for cmd in COMMANDS[command_group]: | |
entries.append(paginator.Entry(cmd["CMD"],cmd["OVERVIEW"])) | |
pages.append(paginator.Page(entries=entries, title=title,author=author, description=description)) | |
# Account | |
author="Account Commands" | |
description="Check account balance, withdraw, or deposit" | |
entries = build_page("ACCOUNT_COMMANDS",COMMANDS) | |
pages.append(paginator.Page(entries=entries, author=author,description=description)) | |
# Tipping | |
author="Tipping Commands" | |
description="The different ways you are able to tip with this bot" | |
entries = build_page("TIPPING_COMMANDS",COMMANDS) | |
pages.append(paginator.Page(entries=entries, author=author,description=description)) | |
# Giveaway | |
author="Giveaway Commands" | |
description="The different ways to interact with the bot's giveaway functionality" | |
entries = build_page("GIVEAWAY_COMMANDS",COMMANDS) | |
pages.append(paginator.Page(entries=entries, author=author, description=description)) | |
# Stats | |
author="Statistics Commands" | |
description="Individual, bot-wide, and giveaway stats" | |
entries = build_page("STATISTICS_COMMANDS",COMMANDS) | |
pages.append(paginator.Page(entries=entries, author=author,description=description)) | |
# Favorites | |
author="Favorites Commands" | |
description="How to interact with your favorites list" | |
entries = build_page("FAVORITES_COMMANDS",COMMANDS) | |
pages.append(paginator.Page(entries=entries, author=author,description=description)) | |
# notifications | |
author="Notification Settings" | |
description="Handle how tip bot gives you notifications" | |
entries = build_page("NOTIFICATION_COMMANDS",COMMANDS) | |
pages.append(paginator.Page(entries=entries, author=author, description=description)) | |
# Info | |
entries = [paginator.Entry(TIP_AUTHOR['CMD'], TIP_AUTHOR['OVERVIEW'])] | |
author=AUTHOR_HEADER + " - by bbedward" | |
description=("**Reviews**:\n" + "'10/10 True Masterpiece' - That one guy" + | |
"\n'0/10 Didn't get rain' - Almost everybody else\n\n" + | |
"This bot is completely free to use and open source." + | |
" Developed by bbedward (reddit: /u/bbedward, discord: bbedward#9246)" + | |
"\nFeel free to send tips, suggestions, and feedback.\n\n" + | |
"Consider using this node as a representative to help decentralize the network!\n" + | |
"Representative Address: {0}\n\n" | |
"github: https://github.com/bbedward/Graham_Nano_Tip_Bot").format(settings.representative) | |
pages.append(paginator.Page(entries=entries, author=author,description=description)) | |
return pages | |
@client.command() | |
async def help(ctx): | |
message = ctx.message | |
# If they spplied an argument post usage for a specific command if applicable | |
content = message.content.split(' ') | |
if len(content) > 1: | |
arg = content[1].strip().lower() | |
for key, value in COMMANDS.items(): | |
if key == 'ADMIN_COMMANDS': | |
continue | |
for v in value: | |
if arg in v["TRIGGER"]: | |
await post_usage(message, v) | |
return | |
try: | |
pages = paginator.Paginator(client, message=message, page_list=build_help(),as_dm=True) | |
await pages.paginate(start_page=1) | |
except paginator.CannotPaginate as e: | |
logger.exception(str(e)) | |
@client.command() | |
async def adminhelp(ctx): | |
message = ctx.message | |
if not is_admin(ctx.message.author): | |
return | |
embed = discord.Embed(colour=discord.Colour.magenta()) | |
embed.title = "Admin Commands" | |
for cmd in COMMANDS["ADMIN_COMMANDS"]: | |
embed.add_field(name=cmd['CMD'], value=cmd['INFO'], inline=False) | |
await message.author.send(embed=embed) | |
@client.command(aliases=get_aliases(BALANCE, exclude='balance')) | |
async def balance(ctx): | |
message = ctx.message | |
if is_private(message.channel): | |
user = db.get_user_by_id(message.author.id, user_name=message.author.name) | |
if user is None: | |
return | |
balances = await wallet.get_balance(user) | |
actual = balances['actual'] | |
available = balances['available'] | |
send = balances['pending_send'] | |
receive = balances['pending'] | |
if settings.banano: | |
balance_desc = BALANCE_TEXT.format(actual, available, send, receive) | |
embed = discord.Embed(colour=0xFBDD11 if settings.banano else discord.Colour.dark_blue()) | |
embed.set_author(name="Balance", icon_url="https://github.com/bbedward/Graham_Nano_Tip_Bot/raw/master/assets/banano_logo.png" if settings.banano else "https://github.com/bbedward/Graham_Nano_Tip_Bot/raw/master/assets/nano_logo.png") | |
embed.description = balance_desc | |
embed.set_footer(text="1 BAN = 1 ^ 29 RAW | Pending balances are in queue and will become available after processing.") | |
await message.author.send(embed=embed) | |
else: | |
# NANO-version uses micro units so we show the NANO-equivalent for convenience | |
receivenano = receive / 1000000 | |
actualnano = actual / 1000000 | |
availablenano = available / 1000000 | |
sendnano = send / 1000000 | |
receivenano = receive / 1000000 | |
balance_desc = BALANCE_TEXT.format("{:,}".format(actual), | |
actualnano, | |
"{:,}".format(available), | |
availablenano, | |
"{:,}".format(send), | |
sendnano, | |
"{:,}".format(receive), | |
receivenano) | |
embed = discord.Embed(colour=0xFBDD11 if settings.banano else discord.Colour.dark_blue()) | |
embed.set_author(name="Balance", icon_url="https://github.com/bbedward/Graham_Nano_Tip_Bot/raw/master/assets/banano_logo.png" if settings.banano else "https://github.com/bbedward/Graham_Nano_Tip_Bot/raw/master/assets/nano_logo.png") | |
embed.description = balance_desc | |
embed.set_footer(text="1 naneroo = 0.000001 NANO = 10 ^ 24 RAW | Pending balances are in queue and will become available after processing.") | |
await message.author.send(embed=embed) | |
@client.command(aliases=get_aliases(DEPOSIT, exclude='deposit')) | |
async def deposit(ctx): | |
message = ctx.message | |
if is_private(message.channel): | |
try: | |
amount = find_amount(message.content) | |
except util.TipBotException: | |
amount = 0 | |
user = await wallet.create_or_fetch_user(message.author.id, message.author.name) | |
user_deposit_address = user.wallet_address | |
uri_scheme = "ban:" if settings.banano else "nano:" | |
if amount == 0: | |
uri = user_deposit_address | |
else: | |
uri = "{0}{1}?amount={2}".format(uri_scheme, user_deposit_address, util.BananoConversions.banano_to_raw(int(amount)) if settings.banano else util.NanoConversions.rai_to_raw(int(amount))) | |
embed = discord.Embed(colour=0xFBDD11 if settings.banano else discord.Colour.dark_blue()) | |
embed.set_author(name=user_deposit_address, icon_url="https://github.com/bbedward/Graham_Nano_Tip_Bot/raw/master/assets/banano_logo.png" if settings.banano else "https://github.com/bbedward/Graham_Nano_Tip_Bot/raw/master/assets/nano_logo.png") | |
embed.set_image(url=get_qr_url(uri)) | |
await message.author.send(embed=embed) | |
await post_response(message, user_deposit_address) | |
@client.command() | |
async def send(ctx): | |
message = ctx.message | |
if paused: | |
await pause_msg(message) | |
return | |
elif db.is_frozen(message.author.id): | |
await post_dm(message.author, FROZEN_MSG) | |
elif is_private(message.channel): | |
try: | |
withdraw_amount = find_send_amounts(remove_address(message.content)) | |
except util.TipBotException as e: | |
if e.error_type == "amount_not_found": | |
await post_response(message, SEND_AMOUNT_NOT_FOUND) | |
return | |
elif e.error_type == "amount_ambiguous": | |
await post_response(message, SEND_AMBIGUOUS_AMOUNT) | |
return | |
try: | |
withdraw_address = return_address_match(message.content) | |
user = db.get_user_by_id(message.author.id, user_name=message.author.name) | |
if user is None: | |
return | |
last_withdraw_delta = db.get_last_withdraw_delta(user.user_id) | |
if WITHDRAW_COOLDOWN > last_withdraw_delta: | |
raise util.TipBotException("cooldown_error") | |
source_id = user.user_id | |
source_address = user.wallet_address | |
balance = await wallet.get_balance(user) | |
available_balance = balance['available'] | |
if 1 > withdraw_amount: | |
await post_response(message, "Minimum withdraw is 1 {0}", TIP_UNIT) | |
return | |
else: | |
withdraw_amount = abs(withdraw_amount) | |
if available_balance == 0: | |
await post_response(message, SEND_NO_BALANCE_TEXT) | |
elif withdraw_amount > available_balance: | |
await post_response(message, INSUFFICIENT_FUNDS_TEXT) | |
else: | |
uid = str(uuid.uuid4()) | |
await wallet.make_transaction_to_address(user, withdraw_amount, withdraw_address, uid,verify_address = True) | |
await post_response(message, SEND_SUCCESS_TEXT) | |
db.update_last_withdraw(user.user_id) | |
except util.TipBotException as e: | |
if e.error_type == "address_not_found": | |
await post_response(message, SEND_ADDRESS_NOT_FOUND_TEXT) | |
await post_response(message, SEND) | |
elif e.error_type == "too_many_addresses": | |
await post_response(message, SEND_TOO_MANY_ADDRESSES_TEXT) | |
elif e.error_type == "invalid_address": | |
await post_response(message, SEND_INVALID_ADDRESS_TEXT) | |
elif e.error_type == "balance_error": | |
await post_response(message, INSUFFICIENT_FUNDS_TEXT) | |
elif e.error_type == "cooldown_error": | |
await post_response(message, SEND_COOLDOWN_TEXT, (WITHDRAW_COOLDOWN - last_withdraw_delta)) | |
@client.command() | |
async def sendmax(ctx): | |
message = ctx.message | |
if paused: | |
await pause_msg(message) | |
return | |
elif db.is_frozen(message.author.id): | |
await post_dm(message.author, FROZEN_MSG) | |
elif is_private(message.channel): | |
try: | |
withdraw_address = return_address_match(message.content) | |
user = db.get_user_by_id(message.author.id, user_name=message.author.name) | |
if user is None: | |
return | |
last_withdraw_delta = db.get_last_withdraw_delta(user.user_id) | |
if WITHDRAW_COOLDOWN > last_withdraw_delta: | |
raise util.TipBotException("cooldown_error") | |
source_id = user.user_id | |
source_address = user.wallet_address | |
balance = await wallet.get_balance(user) | |
withdraw_amount = balance['available'] | |
if withdraw_amount == 0: | |
await post_response(message, SEND_NO_BALANCE_TEXT) | |
elif 1 > withdraw_amount: | |
await post_response(message, "Minimum withdraw is 1 {0}", TIP_UNIT) | |
return | |
else: | |
withdraw_amount = int(withdraw_amount) | |
uid = str(uuid.uuid4()) | |
await wallet.make_transaction_to_address(user, withdraw_amount, withdraw_address, uid,verify_address = True) | |
await post_response(message, SEND_SUCCESS_TEXT) | |
db.update_last_withdraw(user.user_id) | |
except util.TipBotException as e: | |
if e.error_type == "address_not_found": | |
await post_usage(message, SEND_ADDRESS_NOT_FOUND_TEXT) | |
await post_usage(message, SENDMAX) | |
elif e.error_type == "too_many_addresses": | |
await post_response(message, SEND_TOO_MANY_ADDRESSES_TEXT) | |
elif e.error_type == "invalid_address": | |
await post_response(message, SEND_INVALID_ADDRESS_TEXT) | |
elif e.error_type == "balance_error": | |
await post_response(message, INSUFFICIENT_FUNDS_TEXT) | |
elif e.error_type == "cooldown_error": | |
await post_response(message, SEND_COOLDOWN_TEXT, (WITHDRAW_COOLDOWN - last_withdraw_delta)) | |
@client.command() | |
async def withdraw(ctx): | |
message = ctx.message | |
await post_response(message, WITHDRAW_COMMAND_CHANGED) | |
await post_response(message, SEND) | |
await post_response(message, SENDMAX) | |
@client.command(aliases=get_aliases(TIP,exclude='tip')) | |
async def tip(ctx): | |
await do_tip(ctx.message) | |
@client.command(aliases=get_aliases(TIPRANDOM, exclude='tiprandom')) | |
async def tiprandom(ctx): | |
await do_tip(ctx.message, rand=True) | |
async def do_tip(message, rand=False): | |
if is_private(message.channel): | |
return | |
elif paused: | |
await pause_msg(message) | |
return | |
elif db.is_frozen(message.author.id): | |
await post_dm(message.author, FROZEN_MSG) | |
return | |
try: | |
user = db.get_user_by_id(message.author.id, user_name=message.author.name) | |
if user is None: | |
return | |
amount = find_amount(message.content) | |
if rand and amount < settings.tiprandom_minimum: | |
raise util.TipBotException("usage_error") | |
# Make sure amount is valid and at least 1 user is mentioned | |
if amount < 1 or (len(message.mentions) < 1 and not rand): | |
raise util.TipBotException("usage_error") | |
# Create tip list | |
users_to_tip = [] | |
if not rand: | |
for member in message.mentions: | |
# Disregard mentions of exempt users and self | |
if member.id not in settings.exempt_users and member.id != message.author.id and not db.is_banned(member.id) and not member.bot: | |
users_to_tip.append(member) | |
if len(users_to_tip) < 1: | |
raise util.TipBotException("no_valid_recipient") | |
else: | |
# Spam Check | |
spam = db.tiprandom_check(user) | |
if spam > 0: | |
await post_dm(message.author, "You need to wait {0} seconds before you can tiprandom again", spam) | |
await add_x_reaction(message) | |
return | |
# Pick a random active user | |
active = db.get_active_users(RAIN_DELTA) | |
if len(active) == 0: | |
await post_dm(message.author, "I couldn't find any active user to tip") | |
return | |
if str(message.author.id) in active: | |
active.remove(str(message.author.id)) | |
# Remove bots from consideration | |
for a in active: | |
dmember = message.guild.get_member(int(a)) | |
if dmember is None or dmember.bot: | |
active.remove(a) | |
sysrand = random.SystemRandom() | |
sysrand.shuffle(active) | |
offset = secrets.randbelow(len(active)) | |
users_to_tip.append(message.guild.get_member(int(active[offset]))) | |
# Cut out duplicate mentions | |
users_to_tip = list(set(users_to_tip)) | |
# Make sure this user has enough in their balance to complete this tip | |
required_amt = amount * len(users_to_tip) | |
balance = await wallet.get_balance(user) | |
user_balance = balance['available'] | |
if user_balance < required_amt: | |
await add_x_reaction(message) | |
await post_dm(message.author, INSUFFICIENT_FUNDS_TEXT) | |
return | |
# Distribute tips | |
for member in users_to_tip: | |
uid = str(uuid.uuid4()) | |
actual_amt = await wallet.make_transaction_to_user(user, amount, member.id, member.name, uid) | |
# Something went wrong, tip didn't go through | |
if actual_amt == 0: | |
required_amt -= amount | |
else: | |
msg = TIP_RECEIVED_TEXT | |
if rand: | |
msg += ". You were randomly chosen by {0}'s `tiprandom`".format(message.author.name) | |
await post_dm(message.author, "{0} was the recipient of your random {1} {2} tip", member.name, actual_amt, TIP_UNIT, skip_dnd=True) | |
if not db.muted(member.id, message.author.id): | |
await post_dm(member, msg, actual_amt, message.author.name, message.author.id, skip_dnd=True) | |
# Post message reactions | |
await react_to_message(message, required_amt) | |
# Update tip stats | |
if message.channel.id not in (416306340848336896, 443985110371401748) and not user.stats_ban: | |
db.update_tip_stats(user, required_amt) | |
except util.TipBotException as e: | |
if e.error_type == "amount_not_found" or e.error_type == "usage_error": | |
if rand: | |
await post_usage(message, TIPRANDOM) | |
else: | |
await post_usage(message, TIP) | |
elif e.error_type == "no_valid_recipient": | |
await post_dm(message.author, TIP_SELF) | |
@client.command() | |
async def tipauthor(ctx): | |
message = ctx.message | |
try: | |
amount = find_amount(message.content) | |
if amount < 1: | |
return | |
user = db.get_user_by_id(message.author.id, user_name=message.author.name) | |
if user is None: | |
return | |
source_id = user.user_id | |
source_address = user.wallet_address | |
balance = await wallet.get_balance(user) | |
available_balance = balance['available'] | |
if amount > available_balance: | |
return | |
uid = str(uuid.uuid4()) | |
await wallet.make_transaction_to_address(user, amount, DONATION_ADDRESS, uid,verify_address = True) | |
await message.add_reaction('\U00002611') | |
await message.add_reaction('\U0001F618') | |
await message.add_reaction('\u2764') | |
await message.add_reaction('\U0001F499') | |
await message.add_reaction('\U0001F49B') | |
db.update_tip_stats(user, amount) | |
except util.TipBotException as e: | |
pass | |
@client.command(aliases=get_aliases(TIPSPLIT, exclude='tipsplit')) | |
async def tipsplit(ctx): | |
await do_tipsplit(ctx.message) | |
async def do_tipsplit(message, user_list=None): | |
if is_private(message.channel): | |
return | |
elif paused: | |
await pause_msg(message) | |
return | |
elif db.is_frozen(message.author.id): | |
await post_dm(message.author, FROZEN_MSG) | |
return | |
try: | |
amount = find_amount(message.content) | |
# Make sure amount is valid and at least 1 user is mentioned | |
if amount < 1 or (len(message.mentions) < 1 and user_list is None): | |
raise util.TipBotException("usage_error") | |
# Create tip list | |
users_to_tip = [] | |
if user_list is not None: | |
for m in message.mentions: | |
user_list.append(m) | |
else: | |
user_list = message.mentions | |
if int(amount / len(user_list)) < 1: | |
raise util.TipBotException("invalid_tipsplit") | |
for member in user_list: | |
# Disregard mentions of self and exempt users | |
if member.id not in settings.exempt_users and member.id != message.author.id and not db.is_banned(member.id) and not member.bot: | |
users_to_tip.append(member) | |
if len(users_to_tip) < 1: | |
raise util.TipBotException("no_valid_recipient") | |
# Remove duplicates | |
users_to_tip = list(set(users_to_tip)) | |
# Make sure user has enough in their balance | |
user = db.get_user_by_id(message.author.id, user_name=message.author.name) | |
if user is None: | |
return | |
balance = await wallet.get_balance(user) | |
user_balance = balance['available'] | |
if user_balance < amount: | |
await add_x_reaction(message) | |
await post_dm(message.author, INSUFFICIENT_FUNDS_TEXT) | |
return | |
# Distribute tips | |
tip_amount = int(amount / len(users_to_tip)) | |
# Recalculate amount as it may be different since truncating decimal | |
real_amount = tip_amount * len(users_to_tip) | |
for member in users_to_tip: | |
uid = str(uuid.uuid4()) | |
actual_amt = await wallet.make_transaction_to_user(user, tip_amount, member.id, member.name, uid) | |
# Tip didn't go through | |
if actual_amt == 0: | |
amount -= tip_amount | |
else: | |
if not db.muted(member.id, message.author.id): | |
await post_dm(member, TIP_RECEIVED_TEXT, tip_amount, message.author.name, message.author.id, skip_dnd=True) | |
await react_to_message(message, amount) | |
if message.channel.id not in (416306340848336896, 443985110371401748) and not user.stats_ban: | |
db.update_tip_stats(user, real_amount) | |
except util.TipBotException as e: | |
if e.error_type == "amount_not_found" or e.error_type == "usage_error": | |
if user_list is None: | |
await post_usage(message, TIPSPLIT) | |
else: | |
await post_usage(message, TIP_FAVORITES) | |
elif e.error_type == "invalid_tipsplit": | |
await post_dm(message.author, TIPSPLIT_SMALL) | |
elif e.error_type == "no_valid_recipient": | |
await post_dm(message.author, TIP_SELF) | |
@client.command(aliases=get_aliases(TIP_FAVORITES, exclude='tipfavorites')) | |
async def tipfavorites(ctx): | |
message = ctx.message | |
user = db.get_user_by_id(message.author.id, user_name=message.author.name) | |
if user is None: | |
return | |
# Spam Check | |
spam = db.tipfavorites_check(user) | |
if spam > 0: | |
await post_dm(message.author, "You need to wait {0} seconds before you can tipfavorites again", spam) | |
await add_x_reaction(message) | |
return | |
favorites = db.get_favorites_list(message.author.id) | |
if len(favorites) == 0: | |
await post_dm(message.author, "There's nobody in your favorites list. Add some people by using `{0}addfavorite`", COMMAND_PREFIX) | |
return | |
user_list = [] | |
for fav in favorites: | |
discord_user = message.guild.get_member(int(fav['user_id'])) | |
if discord_user is not None: | |
user_list.append(discord_user) | |
await do_tipsplit(message, user_list=user_list) | |
@client.command(aliases=get_aliases(RAIN, exclude='rain')) | |
async def rain(ctx): | |
message = ctx.message | |
if is_private(message.channel): | |
return | |
elif paused: | |
await pause_msg(message) | |
return | |
try: | |
amount = find_amount(message.content) | |
if amount < RAIN_MINIMUM: | |
raise util.TipBotException("usage_error") | |
# Create tip list | |
users_to_tip = [] | |
active_user_ids = db.get_active_users(RAIN_DELTA) | |
if len(active_user_ids) < 1: | |
raise util.TipBotException("no_valid_recipient") | |
for auid in active_user_ids: | |
dmember = message.guild.get_member(int(auid)) | |
if dmember is not None and (dmember.status == discord.Status.online or dmember.status == discord.Status.idle): | |
if str(dmember.id) not in settings.exempt_users and dmember.id != message.author.id and not db.is_banned(dmember.id) and not dmember.bot: | |
users_to_tip.append(dmember) | |
users_to_tip = list(set(users_to_tip)) | |
if len(users_to_tip) < 1: | |
raise util.TipBotException("no_valid_recipient") | |
if int(amount / len(users_to_tip)) < 1: | |
raise util.TipBotException("invalid_tipsplit") | |
user = db.get_user_by_id(message.author.id, user_name=message.author.name) | |
if user is None: | |
return | |
balance = await wallet.get_balance(user) | |
user_balance = balance['available'] | |
if user_balance < amount: | |
await add_x_reaction(message) | |
await post_dm(message.author, INSUFFICIENT_FUNDS_TEXT) | |
return | |
# At this point stash this as the last rain for this user | |
if message.author.id not in last_rains: | |
last_rains[message.author.id] = datetime.datetime.utcnow() | |
else: | |
rain_delta = (datetime.datetime.utcnow() - last_rains[message.author.id]).total_seconds() | |
if RAIN_COOLDOWN > rain_delta: | |
await post_dm(message.author, "You can rain again in {0:.2f} seconds", RAIN_COOLDOWN - rain_delta) | |
return | |
last_rains[message.author.id] = datetime.datetime.utcnow() | |
# Distribute Tips | |
tip_amount = int(amount / len(users_to_tip)) | |
# Recalculate actual tip amount as it may be smaller now | |
real_amount = tip_amount * len(users_to_tip) | |
# 1) Make all transactions first | |
for member in users_to_tip: | |
uid = str(uuid.uuid4()) | |
actual_amt = await wallet.make_transaction_to_user(user, tip_amount, member.id, member.name, uid) | |
# 2) Add reaction | |
await react_to_message(message, amount) | |
await message.add_reaction('\U0001F4A6') # Sweat Drops | |
# 3) Update tip stats | |
db.update_tip_stats(user, real_amount,rain=True) | |
db.mark_user_active(user) | |
# 4) Send DMs (do this last because this takes the longest) | |
for member in users_to_tip: | |
if not db.muted(member.id, message.author.id): | |
await post_dm(member, TIP_RECEIVED_TEXT, actual_amt, message.author.name, message.author.id) | |
except util.TipBotException as e: | |
if e.error_type == "amount_not_found" or e.error_type == "usage_error": | |
await post_usage(message, RAIN) | |
elif e.error_type == "no_valid_recipient": | |
await post_dm(message.author, RAIN_NOBODY) | |
elif e.error_type == "invalid_tipsplit": | |
await post_dm(message.author, TIPSPLIT_SMALL) | |
@client.command(aliases=get_aliases(ENTER, exclude='ticket')) | |
async def ticket(ctx): | |
message = ctx.message | |
if not db.is_active_giveaway(): | |
await post_dm(message.author, TIPGIVEAWAY_NO_ACTIVE) | |
await remove_message(message) | |
return | |
elif not has_giveaway_role(message.author): | |
await post_dm(message.author, "Only users with the \"{0}\" role can enter giveaways.", settings.giveaway_role) | |
await remove_message(message) | |
return | |
giveaway = db.get_giveaway() | |
if db.is_banned(message.author.id) or db.is_frozen(message.author.id): | |
await post_dm(message.author, "You may not enter giveaways at this time") | |
elif giveaway.entry_fee == 0: | |
entered = db.add_contestant(message.author.id) | |
if entered: | |
await wallet.create_or_fetch_user(message.author.id, message.author.name) | |
await post_dm(message.author, ENTER_ADDED) | |
else: | |
await post_dm(message.author, ENTER_DUP) | |
else: | |
if db.contestant_exists(message.author.id): | |
await post_dm(message.author, ENTER_DUP) | |
else: | |
await tip_giveaway(message,ticket=True) | |
await remove_message(message) | |
@client.command(aliases=get_aliases(START_GIVEAWAY,exclude='givearai')) | |
async def givearai(ctx): | |
message = ctx.message | |
if is_private(message.channel): | |
return | |
elif paused: | |
await pause_msg(message) | |
return | |
try: | |
# One giveaway at a time | |
if db.is_active_giveaway(): | |
await post_dm(message.author, GIVEAWAY_EXISTS) | |
return | |
amount = find_amount(message.content) | |
# Find fee and duration in message | |
fee = -1 | |
duration = -1 | |
split_content = message.content.split(' ') | |
for split in split_content: | |
if split.startswith('fee='): | |
split = split.replace('fee=','').strip() | |
if not split: | |
continue | |
try: | |
fee = int(split) | |
except ValueError as e: | |
fee = -1 | |
elif split.startswith('duration='): | |
split=split.replace('duration=','').strip() | |
if not split: | |
continue | |
try: | |
duration = int(split) | |
except ValueError as e: | |
duration = -1 | |
# Sanity checks | |
max_fee = int(0.05 * amount) | |
user = db.get_user_by_id(message.author.id, user_name=message.author.name) | |
if fee == -1 or duration == -1: | |
raise util.TipBotException("usage_error") | |
elif amount < GIVEAWAY_MINIMUM: | |
raise util.TipBotException("usage_error") | |
elif fee > max_fee: | |
await post_dm(message.author, GIVEAWAY_MAX_FEE) | |
return | |
elif duration > GIVEAWAY_MAX_DURATION or GIVEAWAY_MIN_DURATION > duration: | |
raise util.TipBotException("usage_error") | |
elif 0 > fee: | |
raise util.TipBotException("usage_error") | |
elif user is None: | |
return | |
# If balance is sufficient fire up the giveaway | |
balance = await wallet.get_balance(user) | |
user_balance = balance['available'] | |
if user_balance < amount: | |
await add_x_reaction(message) | |
await post_dm(message.author, INSUFFICIENT_FUNDS_TEXT) | |
return | |
end_time = datetime.datetime.utcnow() + datetime.timedelta(minutes=duration) | |
nano_amt = amount if settings.banano else amount / 1000000 | |
tipped_amount = db.get_tipgiveaway_sum() if settings.banano else db.get_tipgiveaway_sum() / 1000000 | |
giveaway,deleted = db.start_giveaway(message.author.id, message.author.name, nano_amt, end_time, message.channel.id, entry_fee=fee) | |
uid = str(uuid.uuid4()) | |
await wallet.make_transaction_to_address(user, amount, None, uid, giveaway_id=giveaway.id) | |
if fee > 0: | |
# Announce to all specified channels if configured that way | |
if GIVEAWAY_CHANNELS is not None or GIVEAWAY_ANNOUNCE_CHANNELS is not None: | |
announce_channels = [] | |
if GIVEAWAY_CHANNELS is not None: | |
announce_channels.extend(GIVEAWAY_CHANNELS) | |
if GIVEAWAY_ANNOUNCE_CHANNELS is not None: | |
announce_channels.extend(GIVEAWAY_ANNOUNCE_CHANNELS) | |
announce_channels = list(set(announce_channels)) | |
for c in announce_channels: | |
channel = message.guild.get_channel(c) | |
if channel is not None: | |
await channel.send(GIVEAWAY_STARTED_FEE.format(message.author.name, nano_amt, nano_amt + tipped_amount, fee)) | |
else: | |
await post_response(message, GIVEAWAY_STARTED_FEE, message.author.name, nano_amt, nano_amt + tipped_amount, fee) | |
else: | |
# Announce to all specified channels if configured that way | |
if GIVEAWAY_CHANNELS is not None or GIVEAWAY_ANNOUNCE_CHANNELS is not None: | |
announce_channels = [] | |
if GIVEAWAY_CHANNELS is not None: | |
announce_channels.extend(GIVEAWAY_CHANNELS) | |
if GIVEAWAY_ANNOUNCE_CHANNELS is not None: | |
announce_channels.extend(GIVEAWAY_ANNOUNCE_CHANNELS) | |
announce_channels = list(set(announce_channels)) | |
for c in announce_channels: | |
channel = message.guild.get_channel(c) | |
if channel is not None: | |
await channel.send(GIVEAWAY_STARTED.format(message.author.name, nano_amt, nano_amt + tipped_amount)) | |
else: | |
await post_response(message, GIVEAWAY_STARTED, message.author.name, nano_amt, nano_amt + tipped_amount) | |
asyncio.get_event_loop().create_task(start_giveaway_timer()) | |
db.update_tip_stats(user, amount, giveaway=True) | |
db.add_contestant(message.author.id) | |
for d in deleted: | |
await post_dm(await client.get_user_info(int(d)), GIVEAWAY_FEE_TOO_HIGH) | |
db.mark_user_active(user) | |
except util.TipBotException as e: | |
if e.error_type == "amount_not_found" or e.error_type == "usage_error": | |
await post_usage(message, START_GIVEAWAY) | |
@client.command(aliases=get_aliases(TIPGIVEAWAY, exclude='tipgiveaway')) | |
async def tipgiveaway(ctx): | |
await tip_giveaway(ctx.message) | |
async def tip_giveaway(message, ticket=False): | |
if is_private(message.channel) and not ticket: | |
return | |
elif paused: | |
await pause_msg(message) | |
return | |
elif db.is_frozen(message.author.id): | |
return | |
elif not has_giveaway_role(message.author): | |
await post_dm(message.author, "Only users with the \"{0}\" role can participate in giveaways.", settings.giveaway_role) | |
await remove_message(message) | |
return | |
try: | |
private_mode = False | |
if GIVEAWAY_CHANNELS is not None and not is_private(message.channel) and message.channel.id not in GIVEAWAY_CHANNELS: | |
private_mode = True | |
giveaway = db.get_giveaway() | |
amount = find_amount(message.content) | |
user = db.get_user_by_id(message.author.id, user_name=message.author.name) | |
if user is None: | |
return | |
balance = await wallet.get_balance(user) | |
user_balance = balance['available'] | |
if user_balance < amount: | |
if (private_mode): | |
await remove_message(message) | |
else: | |
await add_x_reaction(message) | |
await post_dm(message.author, INSUFFICIENT_FUNDS_TEXT) | |
return | |
nano_amt = amount if settings.banano else amount / 1000000 | |
if giveaway is not None: | |
giveawayid = giveaway.id | |
fee = giveaway.entry_fee | |
else: | |
giveawayid = -1 | |
fee = TIPGIVEAWAY_AUTO_ENTRY | |
contributions = db.get_tipgiveaway_contributions(message.author.id, giveawayid) | |
if ticket: | |
if fee > (amount + contributions): | |
owed = fee - contributions | |
await post_dm(message.author, | |
"You were NOT entered into the giveaway!\n" + | |
"This giveaway has a fee of **{0} {5}**\n" + | |
"You've donated **{1} {5}** so far\n" + | |
"You need **{2} {5}** to enter\n" + | |
"You may enter using `{3}sticket {4}`" | |
, fee, contributions, owed, COMMAND_PREFIX, owed, TIP_UNIT) | |
return | |
uid = str(uuid.uuid4()) | |
db.add_tip_to_giveaway(nano_amt) | |
await wallet.make_transaction_to_address(user, amount, None, uid, giveaway_id=giveawayid) | |
if not ticket and not private_mode: | |
await react_to_message(message, amount) | |
# If eligible, add them to giveaway | |
if (amount + contributions) >= fee and not db.is_banned(message.author.id): | |
if (amount + contributions) >= (TIPGIVEAWAY_AUTO_ENTRY * 4): | |
db.mark_user_active(user) | |
entered = db.add_contestant(message.author.id) | |
if entered: | |
if giveaway is None: | |
if message.channel.id not in settings.no_spam_channels and not private_mode: | |
await post_response(message, TIPGIVEAWAY_ENTERED_FUTURE) | |
else: | |
await post_dm(message.author, TIPGIVEAWAY_ENTERED_FUTURE) | |
else: | |
await post_dm(message.author, ENTER_ADDED) | |
elif ticket: | |
await post_dm(message.author, ENTER_DUP) | |
# If tip sum is >= GIVEAWAY MINIMUM then start giveaway | |
if giveaway is None: | |
tipgiveaway_sum = db.get_tipgiveaway_sum() | |
nano_amt = tipgiveaway_sum if settings.banano else float(tipgiveaway_sum)/ 1000000 | |
if tipgiveaway_sum >= GIVEAWAY_MINIMUM: | |
end_time = datetime.datetime.utcnow() + datetime.timedelta(minutes=GIVEAWAY_AUTO_DURATION) | |
db.start_giveaway(client.user.id, client.user.name, 0, end_time, message.channel.id,entry_fee=fee) | |
# Announce to all specified channels if configured that way | |
if GIVEAWAY_CHANNELS is not None or GIVEAWAY_ANNOUNCE_CHANNELS is not None: | |
announce_channels = [] | |
if GIVEAWAY_CHANNELS is not None: | |
announce_channels.extend(GIVEAWAY_CHANNELS) | |
if GIVEAWAY_ANNOUNCE_CHANNELS is not None: | |
announce_channels.extend(GIVEAWAY_ANNOUNCE_CHANNELS) | |
announce_channels = list(set(announce_channels)) | |
for c in announce_channels: | |
channel = message.guild.get_channel(c) | |
if channel is not None: | |
await channel.send(GIVEAWAY_STARTED_FEE.format(client.user.name, nano_amt, nano_amt, fee)) | |
else: | |
await post_response(message, GIVEAWAY_STARTED_FEE, client.user.name, nano_amt, nano_amt, fee) | |
asyncio.get_event_loop().create_task(start_giveaway_timer()) | |
# Update top tipY | |
if not user.stats_ban: | |
db.update_tip_stats(user, amount, giveaway=True) | |
if private_mode: | |
await remove_message(message) | |
except util.TipBotException as e: | |
if e.error_type == "amount_not_found" or e.error_type == "usage_error": | |
if ticket: | |
await post_usage(message, ENTER) | |
else: | |
await post_usage(message, TIPGIVEAWAY) | |
@client.command(aliases=get_aliases(TICKETSTATUS,exclude='ticketstatus')) | |
async def ticketstatus(ctx): | |
message = ctx.message | |
user = db.get_user_by_id(message.author.id) | |
if user is not None: | |
await post_dm(message.author, db.get_ticket_status(message.author.id)) | |
await remove_message(message) | |
@client.command(aliases=get_aliases(GIVEAWAY_STATS,exclude='giveawaystats')) | |
async def giveawaystats(ctx): | |
message = ctx.message | |
global last_gs | |
do_dm = False | |
delete_message = False | |
if message.channel.id in settings.no_spam_channels: | |
return | |
if not is_private(message.channel): | |
if GIVEAWAY_CHANNELS is not None and message.channel.id in GIVEAWAY_CHANNELS: | |
delete_message = True | |
if message.channel.id not in last_gs: | |
last_gs[message.channel.id] = datetime.datetime.utcnow() | |
if SPAM_THRESHOLD > (datetime.datetime.utcnow() - last_gs[message.channel.id]).total_seconds(): | |
do_dm = True | |
else: | |
last_gs[message.channel.id] = datetime.datetime.utcnow() | |
else: | |
delete_message = True | |
do_dm = True | |
stats = db.get_giveaway_stats() | |
if stats is None: | |
for_next = GIVEAWAY_MINIMUM - db.get_tipgiveaway_sum() | |
if do_dm: | |
await post_dm(message.author, GIVEAWAY_STATS_INACTIVE, for_next) | |
else: | |
await post_response(message, GIVEAWAY_STATS_INACTIVE, for_next) | |
else: | |
end = stats['end'] - datetime.datetime.utcnow() | |
end_s = int(end.total_seconds()) | |
str_delta = time.strftime("%M Minutes and %S Seconds", time.gmtime(end_s)) | |
fee = stats['fee'] | |
if fee == 0: | |
if do_dm: | |
await post_dm(message.author, GIVEAWAY_STATS_NF, stats['entries'], stats['amount'], str_delta, stats['started_by']) | |
else: | |
await post_response(message, GIVEAWAY_STATS_NF, stats['entries'], stats['amount'], str_delta, stats['started_by']) | |
else: | |
if do_dm: | |
await post_dm(message.author, GIVEAWAY_STATS_FEE, stats['entries'], stats['amount'], str_delta, stats['started_by'], fee) | |
else: | |
await post_response(message, GIVEAWAY_STATS_FEE, stats['entries'], stats['amount'], str_delta, stats['started_by'], fee) | |
if delete_message: | |
await message.delete() | |
async def start_giveaway_timer(): | |
giveaway = db.get_giveaway() | |
if giveaway is None: | |
return | |
delta = (giveaway.end_time - datetime.datetime.utcnow()).total_seconds() | |
if delta <= 0: | |
await finish_giveaway(0) | |
return | |
await finish_giveaway(delta) | |
async def finish_giveaway(delay): | |
await asyncio.sleep(delay) | |
giveaway = db.finish_giveaway() | |
if giveaway is not None: | |
announce_channels = [int(giveaway.channel_id)] | |
if GIVEAWAY_CHANNELS is not None: | |
announce_channels.extend(GIVEAWAY_CHANNELS) | |
response = GIVEAWAY_ENDED.format(giveaway.winner_id, giveaway.amount + giveaway.tip_amount) | |
for c in list(set(announce_channels)): | |
channel = client.get_channel(c) | |
await channel.send(response) | |
await post_dm(await client.get_user_info(int(giveaway.winner_id)), response) | |
@client.command() | |
async def winners(ctx): | |
message = ctx.message | |
if message.channel.id in settings.no_spam_channels: | |
return | |
elif GIVEAWAY_CHANNELS and message.channel.id not in GIVEAWAY_CHANNELS and not is_private(message.channel): | |
return | |
# Check spam | |
global last_winners | |
if not is_private(message.channel): | |
if message.channel.id not in last_winners: | |
last_winners[message.channel.id] = datetime.datetime.utcnow() | |
tdelta = datetime.datetime.utcnow() - last_winners[message.channel.id] | |
if SPAM_THRESHOLD > tdelta.seconds: | |
await post_response(message, WINNERS_SPAM, (SPAM_THRESHOLD - tdelta.seconds)) | |
return | |
last_winners[message.channel.id] = datetime.datetime.utcnow() | |
winners = db.get_giveaway_winners(WINNERS_COUNT) | |
if len(winners) == 0: | |
await post_response(message, WINNERS_EMPTY) | |
else: | |
response = WINNERS_HEADER | |
response += "```" | |
max_l = 0 | |
winner_nms = [] | |
for winner in winners: | |
if winner['index'] >= 10: | |
winner_nm = '{0}: {1} '.format(winner['index'], winner['name']) | |
else: | |
winner_nm = '{0}: {1} '.format(winner['index'], winner['name']) | |
if len(winner_nm) > max_l: | |
max_l = len(winner_nm) | |
winner_nms.append(winner_nm) | |
for winner in winners: | |
winner_nm = winner_nms[winner['index'] - 1] | |
padding = " " * ((max_l - len(winner_nm)) + 1) | |
response += winner_nm | |
response += padding | |
if settings.banano: | |
response += 'won {0:.2f} BANANO'.format(winner['amount']) | |
else: | |
response += 'won {0:.6f} NANO'.format(winner['amount']) | |
response += '\n' | |
response += "```" | |
await post_response(message, response) | |
@client.command(aliases=get_aliases(LEADERBOARD, exclude='leaderboard')) | |
async def leaderboard(ctx): | |
message = ctx.message | |
if message.channel.id in settings.no_spam_channels: | |
return | |
# Check spam | |
global last_big_tippers | |
if not is_private(message.channel): | |
if message.channel.id not in last_big_tippers: | |
last_big_tippers[message.channel.id] = datetime.datetime.utcnow() | |
tdelta = datetime.datetime.utcnow() - last_big_tippers[message.channel.id] | |
if SPAM_THRESHOLD > tdelta.seconds: | |
await post_response(message, TOP_SPAM, (SPAM_THRESHOLD - tdelta.seconds)) | |
return | |
last_big_tippers[message.channel.id] = datetime.datetime.utcnow() | |
top_users = db.get_top_users(TOP_TIPPERS_COUNT) | |
if len(top_users) == 0: | |
await post_response(message, TOP_HEADER_EMPTY_TEXT) | |
else: | |
# Probably a very clunky and sloppy way to format this output, I'm sure there's something better | |
response = TOP_HEADER_TEXT.format(TOP_TIPPERS_COUNT) | |
response += "```" | |
max_l = 0 | |
top_user_nms = [] | |
for top_user in top_users: | |
if top_user['index'] >= 10: | |
top_user_nm = '{0}: {1} '.format(top_user['index'], top_user['name']) | |
else: | |
top_user_nm = '{0}: {1} '.format(top_user['index'], top_user['name']) | |
if len(top_user_nm) > max_l: | |
max_l = len(top_user_nm) | |
top_user_nms.append(top_user_nm) | |
for top_user in top_users: | |
top_user_nm = top_user_nms[top_user['index'] - 1] | |
padding = " " * ((max_l - len(top_user_nm)) + 1) | |
response += top_user_nm | |
response += padding | |
response += '- {0:.2f} BANANO'.format(top_user['amount']) if settings.banano else '- {0:.6f} NANO'.format(top_user['amount']) | |
response += '\n' | |
response += "```" | |
await post_response(message, response) | |
@client.command() | |
async def toptips(ctx): | |
message = ctx.message | |
if message.channel.id in settings.no_spam_channels: | |
return | |
# Check spam | |
global last_top_tips | |
if not is_private(message.channel): | |
if not message.channel.id in last_top_tips: | |
last_top_tips[message.channel.id] = datetime.datetime.utcnow() | |
tdelta = datetime.datetime.utcnow() - last_top_tips[message.channel.id] | |
if SPAM_THRESHOLD > tdelta.seconds: | |
await post_response(message, TOPTIP_SPAM, (SPAM_THRESHOLD - tdelta.seconds)) | |
return | |
last_top_tips[message.channel.id] = datetime.datetime.utcnow() | |
top_tips_msg = db.get_top_tips() | |
await post_response(message, top_tips_msg) | |
@client.command() | |
async def tipstats(ctx): | |
message = ctx.message | |
if message.channel.id in settings.no_spam_channels: | |
return | |
tip_stats = db.get_tip_stats(message.author.id) | |
if tip_stats is None or len(tip_stats) == 0: | |
await post_response(message, STATS_ACCT_NOT_FOUND_TEXT) | |
return | |
if tip_stats['rank'] == -1: | |
tip_stats['rank'] = 'N/A' | |
await post_response(message, STATS_TEXT, str(tip_stats['rank']), tip_stats['total'], tip_stats['average'],tip_stats['top']) | |
@client.command(aliases=get_aliases(ADD_FAVORITE, exclude='addfavorite')) | |
async def addfavorite(ctx): | |
message = ctx.message | |
user = db.get_user_by_id(message.author.id) | |
if user is None: | |
return | |
added_count = 0 | |
for mention in message.mentions: | |
if mention.id != message.author.id and not mention.bot: | |
if db.add_favorite(user.user_id,mention.id): | |
added_count += 1 | |
if added_count > 0: | |
await post_dm(message.author, "{0} users added to your favorites!", added_count) | |
else: | |
await post_dm(message.author, "I couldn't find any users to add as favorites in your message! They may already be in your favorites or they may not have accounts with me") | |
@client.command(aliases=get_aliases(DEL_FAVORITE, exclude='removefavorite')) | |
async def removefavorite(ctx): | |
message = ctx.message | |
user = db.get_user_by_id(message.author.id) | |
if user is None: | |
return | |
remove_count = 0 | |
# Remove method #1: Mentions | |
if len(message.mentions) > 0: | |
for mention in message.mentions: | |
if db.remove_favorite(user.user_id,favorite_id=mention.id): | |
remove_count += 1 | |
# Remove method #2: identifiers | |
remove_ids = [] | |
for c in message.content.split(' '): | |
try: | |
id=int(c.strip()) | |
remove_ids.append(id) | |
except ValueError as e: | |
pass | |
for id in remove_ids: | |
if db.remove_favorite(user.user_id,identifier=id): | |
remove_count += 1 | |
if remove_count > 0: | |
await post_dm(message.author, "{0} users removed from your favorites!", remove_count) | |
else: | |
await post_dm(message.author, "I couldn't find anybody in your message to remove from your favorites!") | |
@client.command(aliases=get_aliases(FAVORITES, exclude='favorites')) | |
async def favorites(ctx): | |
message = ctx.message | |
user = db.get_user_by_id(message.author.id) | |
if user is None: | |
return | |
favorites = db.get_favorites_list(message.author.id) | |
if len(favorites) == 0: | |
embed = discord.Embed(colour=discord.Colour.red()) | |
embed.title="No Favorites" | |
embed.description="Your favorites list is empty. Add to it with `{0}addfavorite`".format(COMMAND_PREFIX) | |
await message.author.send(embed=embed) | |
return | |
title="Favorites List" | |
description=("Here are your favorites! " + | |
"You can tip everyone in this list at the same time using `{0}tipfavorites amount`").format(COMMAND_PREFIX) | |
entries = [] | |
for fav in favorites: | |
fav_user = db.get_user_by_id(fav['user_id']) | |
name = str(fav['id']) + ": " + fav_user.user_name | |
value = "You can remove this favorite with `{0}removefavorite {1}`".format(COMMAND_PREFIX, fav['id']) | |
entries.append(paginator.Entry(name,value)) | |
# Do paginator for favorites > 10 | |
if len(entries) > 10: | |
pages = paginator.Paginator.format_pages(entries=entries,title=title,description=description) | |
p = paginator.Paginator(client,message=message,page_list=pages,as_dm=True) | |
await p.paginate(start_page=1) | |
else: | |
embed = discord.Embed(colour=discord.Colour.teal()) | |
embed.title = title | |
embed.description = description | |
for e in entries: | |
embed.add_field(name=e.name,value=e.value,inline=False) | |
await message.author.send(embed=embed) | |
@client.command() | |
async def muted(ctx): | |
message = ctx.message | |
user = db.get_user_by_id(message.author.id) | |
if user is None: | |
return | |
muted = db.get_muted(message.author.id) | |
if len(muted) == 0: | |
embed = discord.Embed(colour=discord.Colour.red()) | |
embed.title="Nobody Muted" | |
embed.description="Nobody is muted. You can mute people with `{0}mute discord_id`".format(COMMAND_PREFIX) | |
await message.author.send(embed=embed) | |
return | |
title="Muted List" | |
description=("This is your muted list. You can still receive tips from these people, but the bot will not PM you " + | |
"when you receive a tip from them.") | |
entries = [] | |
idx = 1 | |
for m in muted: | |
name = str(idx) + ": " + m['name'] | |
value = "You can unmute with person with `{0}unmute {1}`".format(COMMAND_PREFIX, m['id']) | |
entries.append(paginator.Entry(name,value)) | |
idx += 1 | |
# Do paginator for favorites > 10 | |
if len(entries) > 10: | |
pages = paginator.Paginator.format_pages(entries=entries,title=title,description=description) | |
p = paginator.Paginator(client,message=message,page_list=pages,as_dm=True) | |
await p.paginate(start_page=1) | |
else: | |
embed = discord.Embed(colour=discord.Colour.teal()) | |
embed.title = title | |
embed.description = description | |
for e in entries: | |
embed.add_field(name=e.name,value=e.value,inline=False) | |
await message.author.send(embed=embed) | |
@client.command() | |
async def mute(ctx): | |
message = ctx.message | |
if not is_private(message.channel): | |
return | |
user = db.get_user_by_id(message.author.id) | |
if user is None: | |
return | |
muted_count = 0 | |
mute_ids = [] | |
for c in message.content.split(' '): | |
try: | |
id=int(c.strip()) | |
mute_ids.append(id) | |
except ValueError as e: | |
pass | |
for id in mute_ids: | |
target = db.get_user_by_id(id) | |
if target is not None and db.mute(user.user_id, target.user_id, target.user_name): | |
muted_count += 1 | |
if muted_count > 0: | |
await post_dm(message.author, "{0} users have been muted!", muted_count) | |
else: | |
await post_dm(message.author, "I couldn't find any users to mute in your message. They probably are already muted or they aren't registered with me") | |
@client.command() | |
async def unmute(ctx): | |
message = ctx.message | |
if not is_private(message.channel): | |
return | |
user = db.get_user_by_id(message.author.id) | |
if user is None: | |
return | |
unmute_count = 0 | |
unmute_ids = [] | |
for c in message.content.split(' '): | |
try: | |
id=int(c.strip()) | |
unmute_ids.append(id) | |
except ValueError as e: | |
pass | |
for id in unmute_ids: | |
if db.unmute(user.user_id,id) > 0: | |
unmute_count += 1 | |
if unmute_count > 0: | |
await post_dm(message.author, "{0} users have been unmuted!", unmute_count) | |
else: | |
await post_dm(message.author, "I couldn't find anybody in your message to unmute!") | |
@client.command() | |
async def blocks(ctx): | |
message = ctx.message | |
global last_blocks | |
if not is_private(message.channel): | |
if message.channel.id not in last_blocks: | |
last_blocks[message.channel.id] = datetime.datetime.utcnow() | |
tdelta = datetime.datetime.utcnow() - last_blocks[message.channel.id] | |
if SPAM_THRESHOLD > tdelta.seconds: | |
await post_response(message, "No more blocks for {0} seconds", (SPAM_THRESHOLD - tdelta.seconds)) | |
return | |
last_blocks[message.channel.id] = datetime.datetime.utcnow() | |
count, unchecked = await wallet.get_blocks() | |
await post_response(message, "```Count: {0:,}\nUnchecked: {1:,}```", int(count), int(unchecked)) | |
@client.command() | |
async def banned(ctx): | |
message = ctx.message | |
if is_admin(message.author): | |
await post_dm(message.author, db.get_banned()) | |
@client.command() | |
async def statsbanned(ctx): | |
message = ctx.message | |
if is_admin(message.author): | |
await post_dm(message.author, db.get_statsbanned()) | |
@client.command() | |
async def frozen(ctx): | |
message = ctx.message | |
if is_admin(message.author): | |
await post_dm(message.author, db.frozen()) | |
@client.command() | |
async def pause(ctx): | |
message = ctx.message | |
if is_admin(message.author): | |
global paused | |
paused = True | |
await post_response(message, "Transaction-related activity is now suspended") | |
@client.command() | |
async def unpause(ctx): | |
message = ctx.message | |
if is_admin(message.author): | |
global paused | |
paused = False | |
await post_response(message, "Transaction-related activity is no longer suspended") | |
@client.command() | |
async def freeze(ctx): | |
message = ctx.message | |
if is_admin(message.author): | |
for member in message.mentions: | |
if db.freeze(member): | |
await post_dm(message.author, "User {0} is frozen", member.name) | |
else: | |
await post_dm(message.author, "Couldn't freeze user they may already be frozen") | |
@client.command() | |
async def tipban(ctx): | |
message = ctx.message | |
if is_admin(message.author): | |
for member in message.mentions: | |
if member.id not in settings.admin_ids and not has_admin_role(member.roles): | |
if db.ban_user(member.id): | |
await post_dm(message.author, BAN_SUCCESS, member.name) | |
else: | |
await post_dm(message.author, BAN_DUP, member.name) | |
@client.command() | |
async def statsban(ctx): | |
message = ctx.message | |
if is_admin(message.author): | |
for member in message.mentions: | |
if db.statsban_user(member.id): | |
await post_dm(message.author, STATSBAN_SUCCESS, member.name) | |
else: | |
await post_dm(message.author, STATSBAN_DUP, member.name) | |
@client.command() | |
async def tipunban(ctx): | |
message = ctx.message | |
if is_admin(message.author): | |
for member in message.mentions: | |
if db.unban_user(member.id): | |
await post_dm(message.author, UNBAN_SUCCESS, member.name) | |
else: | |
await post_dm(message.author, UNBAN_DUP, member.name) | |
@client.command() | |
async def unfreeze(ctx): | |
message = ctx.message | |
if is_admin(message.author): | |
for member in message.mentions: | |
if db.unfreeze(member.id): | |
await post_dm(message.author, "{0} has been unfrozen", member.name) | |
else: | |
await post_dm(message.author, "Couldn't unfreeze, {0} may not be frozen", member.name) | |
@client.command() | |
async def statsunban(ctx): | |
message = ctx.message | |
if is_admin(message.author): | |
for member in message.mentions: | |
if db.statsunban_user(member.id): | |
await post_dm(message.author, STATSUNBAN_SUCCESS, member.name) | |
else: | |
await post_dm(message.author, STATSUNBAN_DUP, member.name) | |
@client.command(aliases=['wfu']) | |
async def walletfor(ctx, user = None): | |
if user is None: | |
await post_usage(ctx.message, WALLET_FOR) | |
return | |
user_id = user.id if isinstance(user, discord.Member) else user | |
wa = db.get_address(user_id) | |
user = db.get_user_by_id(user_id) | |
user_name = user.name if isinstance(user, discord.Member) else user.user_name | |
if wa is not None: | |
await post_dm(ctx.message.author, | |
"Address for user: '{0}' with Discord ID {1}: {2} {3}account/{2}", | |
user_name, | |
user_id, | |
wa, settings.block_explorer) | |
else: | |
await post_dm(ctx.message.author, "Could not find address for user") | |
@client.command(aliases=['ufw']) | |
async def userforwallet(ctx, address: str): | |
u = db.get_user_by_wallet_address(address) | |
if u is None: | |
await post_dm(ctx.message.author, "No user with that wallet address") | |
else: | |
await post_dm(ctx.message.author, "username: {0}, discordid: {1}", u.user_name, u.user_id) | |
@client.command(aliases=['addtips', 'incrementtips']) | |
async def increasetips(ctx, amount: float = -1.0, user: discord.Member = None): | |
if not is_admin(ctx.message.author): | |
return | |
u = db.get_user_by_id(user.id) | |
if u is None or 0 > amount: | |
await post_usage(ctx.message, INCREASETIPTOTAL) | |
return | |
new_total = u.tipped_amount + amount | |
db.update_tip_total(user.id, new_total) | |
await post_dm(ctx.message.author, "New tip total for {0} is {1:.6f}", user.name, new_total) | |
@client.command(aliases=['decrementtips', 'decreasetips', 'removetips']) | |
async def reducetips(ctx, amount: float = -1.0, user: discord.Member = None): | |
if not is_admin(ctx.message.author): | |
return | |
u = db.get_user_by_id(user.id) | |
if u is None or amount < 0: | |
await post_usage(ctx.message, DECREASETIPTOTAL) | |
return | |
new_total = u.tipped_amount - amount | |
db.update_tip_total(user.id, new_total) | |
await post_dm(ctx.message.author, "New tip total for {0} is {1:.6f}", user.name, new_total) | |
@client.command(aliases=['incrementtipcount']) | |
async def increasetipcount(ctx, cnt: int = -1, user: discord.Member = None): | |
if is_admin(ctx.message.author): | |
u = db.get_user_by_id(user.id) | |
if u is None or cnt < 0: | |
await post_usage(ctx.message, INCREASETIPCOUNT) | |
return | |
new_cnt = u.tip_count + cnt | |
db.update_tip_count(user.id, new_cnt) | |
await post_dm(ctx.message.author, "New tip count for {0} is {1}", user.name, new_cnt) | |
@client.command(aliases=['decrementtipcount', 'reducetipcount']) | |
async def decreasetipcount(ctx, cnt: int = -1, user: discord.Member = None): | |
if is_admin(ctx.message.author): | |
u = db.get_user_by_id(user.id) | |
if u is None or 0 > cnt: | |
await post_usage(ctx.message, DECREASETIPCOUNT) | |
return | |
new_cnt = u.tip_count - cnt | |
db.update_tip_count(user.id, new_cnt) | |
await post_dm(ctx.message.author, "New tip count for {0} is {1}", user.name, new_cnt) | |
@client.command(aliases=['settoptips']) | |
async def settoptip(ctx): | |
if not is_admin(ctx.message.author): | |
return | |
month = -1.0 | |
alltime = -1.0 | |
day = -1.0 | |
for split in ctx.message.content.split(' '): | |
if split.startswith('month='): | |
split = split.replace('month=', '').strip() | |
if not split: | |
continue | |
try: | |
month = float(split) | |
except ValueError: | |
pass | |
elif split.startswith('alltime='): | |
split = split.replace('alltime=', '').strip() | |
if not split: | |
continue | |
try: | |
alltime = float(split) | |
except ValueError: | |
pass | |
elif split.startswith('day='): | |
split = split.replace('day=', '').strip() | |
if not split: | |
continue | |
try: | |
day = float(split) | |
except ValueError: | |
pass | |
if month == -1 and alltime == -1 and day == -1: | |
await post_usage(ctx.message, SETTOPTIP) | |
return | |
else: | |
if month != -1: | |
month = int(month * 1000000) | |
if alltime != -1: | |
alltime = int(alltime * 1000000) | |
if day != -1: | |
day = int(day * 1000000) | |
for m in ctx.message.mentions: | |
u = db.get_user_by_id(m.id) | |
if u is None: | |
continue | |
# We use increments in db.update_top_tips so compute those | |
if month == -1: | |
mdelta = 0 | |
elif month > u.top_tip_month: | |
mdelta = month - u.top_tip_month | |
elif u.top_tip_month > month: | |
mdelta = -1 * (u.top_tip_month - month) | |
if alltime == -1: | |
adelta = 0 | |
elif alltime > u.top_tip: | |
adelta = alltime - u.top_tip | |
elif u.top_tip > alltime: | |
adelta = -1 * (u.top_tip - alltime) | |
if day == -1: | |
ddelta = 0 | |
elif day > u.top_tip_day: | |
ddelta = day - u.top_tip_day | |
elif u.top_tip_day > day: | |
ddelta = -1 * (u.top_tip_day - day) | |
upd = db.update_top_tips(u.user_id, month=mdelta, alltime=adelta, day=ddelta) | |
if upd > 0: | |
await post_dm(ctx.message.author, "top tips for {0} adjusted successfully", u.user_name) | |
else: | |
await post_dm(ctx.message.author, "Could not adjust top tip for {0}", u.user_name) | |
### Utility Functions | |
def get_qr_url(text): | |
return 'https://chart.googleapis.com/chart?cht=qr&chl={0}&chs=180x180&choe=UTF-8&chld=L|2'.format(text) | |
def return_address_match(input_text): | |
address_regex = '(?:xrb|nano|ban)(?:_)(?:1|3)(?:[13456789abcdefghijkmnopqrstuwxyz]{59})' | |
matches = re.findall(address_regex, input_text) | |
if len(matches) == 1: | |
return matches[0] | |
elif len(matches) > 1: | |
raise util.TipBotException("too_many_addresses") | |
else: | |
raise util.TipBotException("address_not_found") | |
def remove_address(input_text): | |
address_regex = '(?:xrb|nano|ban)(?:_)(?:1|3)(?:[13456789abcdefghijkmnopqrstuwxyz]{59})' | |
return re.sub(address_regex, '', input_text) | |
# find amount in outbound sends | |
def find_send_amounts(input_text): | |
regex = r'(?:^|\s)(\d*\.?\d+)(?=$|\s)' | |
matches = re.findall(regex, input_text, re.IGNORECASE) | |
if len(matches) > 1: | |
raise util.TipBotException("amount_ambiguous") | |
elif len(matches) == 1: | |
return float(matches[0].strip()) | |
else: | |
raise util.TipBotException("amount_not_found") | |
# find amount in regular tips | |
def find_amount(input_text): | |
str_split = input_text.split('<@') | |
if (len(str_split) == 0): | |
raise util.TipBotException("amount_not_found") | |
input_text = str_split[0] | |
regex = r'(?:^|\s)(\d*\.?\d+)(?=$|\s)' | |
matches = re.findall(regex, input_text, re.IGNORECASE) | |
if len(matches) >= 1: | |
return float(matches[0].strip()) | |
else: | |
raise util.TipBotException("amount_not_found") | |
### Re-Used Discord Functions | |
async def post_response(message, template, *args): | |
response = template.format(*args) | |
if not is_private(message.channel): | |
response = "<@" + str(message.author.id) + "> \n" + response | |
logger.info("sending response: '%s' for message: '%s' to userid: '%s' name: '%s'", response, message.content, message.author.id, message.author.name) | |
return await message.channel.send(response) | |
async def post_usage(message, command): | |
embed = discord.Embed(colour=discord.Colour.purple()) | |
embed.title = "Usage:" | |
embed.add_field(name=command['CMD'], value=command['INFO'],inline=False) | |
await message.author.send(embed=embed) | |
async def post_dm(member, template, *args, skip_dnd=False): | |
response = template.format(*args) | |
logger.info("sending dm: '%s' to user: %s", response, member.id) | |
try: | |
if skip_dnd and member.status == discord.Status.dnd: | |
return None | |
return await member.send(response) | |
except: | |
return None | |
async def post_edit(message, template, *args): | |
response = template.format(*args) | |
return await message.edit(content=response) | |
async def remove_message(message): | |
if is_private(message.channel): | |
return | |
client_member = message.guild.get_member(client.user.id) | |
if client_member.permissions_in(message.channel).manage_messages: | |
await message.delete() | |
async def add_x_reaction(message): | |
await message.add_reaction('\U0000274C') # X | |
return | |
async def react_to_message(message, amount): | |
if settings.banano: | |
if amount > 0: | |
await message.add_reaction('\:tip:425878628119871488') # TIP mark | |
await message.add_reaction('\:tick:425880814266351626') # check mark | |
if amount > 0 and amount < 50: | |
await message.add_reaction('\U0001F987') # S | |
elif amount >= 50 and amount < 250: | |
await message.add_reaction('\U0001F412') # C | |
elif amount >= 250: | |
await message.add_reaction('\U0001F98D') # W | |
else: | |
if amount > 0: | |
await message.add_reaction('\U00002611') # check mark | |
if amount > 0 and amount < 1000: | |
await message.add_reaction('\U0001F1F8') # S | |
await message.add_reaction('\U0001F1ED') # H | |
await message.add_reaction('\U0001F1F7') # R | |
await message.add_reaction('\U0001F1EE') # I | |
await message.add_reaction('\U0001F1F2') # M | |
await message.add_reaction('\U0001F1F5') # P | |
elif amount >= 1000 and amount < 10000: | |
await message.add_reaction('\U0001F1E8') # C | |
await message.add_reaction('\U0001F1F7') # R | |
await message.add_reaction('\U0001F1E6') # A | |
await message.add_reaction('\U0001F1E7') # B | |
elif amount >= 10000 and amount < 100000: | |
await message.add_reaction('\U0001F1FC') # W | |
await message.add_reaction('\U0001F1E6') # A | |
await message.add_reaction('\U0001F1F1') # L | |
await message.add_reaction('\U0001F1F7') # R | |
await message.add_reaction('\U0001F1FA') # U | |
await message.add_reaction('\U0001F1F8') # S | |
elif amount >= 100000 and amount < 1000000: | |
await message.add_reaction('\U0001F1F8') # S | |
await message.add_reaction('\U0001F1ED') # H | |
await message.add_reaction('\U0001F1E6') # A | |
await message.add_reaction('\U0001F1F7') # R | |
await message.add_reaction('\U0001F1F0') # K | |
elif amount >= 1000000: | |
await message.add_reaction('\U0001F1F2') # M | |
await message.add_reaction('\U0001F1EA') # E | |
await message.add_reaction('\U0001F1EC') # G | |
await message.add_reaction('\U0001F1E6') # A | |
await message.add_reaction('\U0001F1F1') # L | |
await message.add_reaction('\U0001F1E9') # D | |
await message.add_reaction('\U0001F1F4') # O | |
await message.add_reaction('\U0001F1F3') # N | |
# Start the bot | |
client.run(settings.discord_bot_token) | |