def run_tests(): print("Login Test:") print(functions.login()) print("\nGet Level Test:") print(functions.get_level(123)) print("\nRandom Waifu Test:") print(functions.waifu(0)) print("\nRandom Husbando Test:") print(functions.waifu(1)) # Name should be flipped print("\nWaifuRegister Test:") print(functions.waifuregister(123, "Test Username", "rin shibuya", 0)) # Name should return "not enough images" print("\nHusbandoReigster Paste Test:") print(functions.waifuregister(123, "Test Username", "admiral", 1)) # Name should work print("\nHusbandoReigster Test:") print( functions.waifuregister(123, "Test Username", "admiral (kantai collection)", 1)) print("\nMyWaifu Test:") print(functions.mywaifu(123, 0)) print("\nMyHusbando Test:") print(functions.mywaifu(123, 1)) print("\nRemoveWaifu Test:") print(functions.waifuremove(123, 0)) print("\nRemoveHusbando Test:") print(functions.waifuremove(123, 1)) print("\nRandom OTP Test:") print(functions.otp("")) print("\nRandom List Shipgirl Test:") print(functions.random_list("Shipgirl", "")) print("\nRandom List Imouto Test:") print(functions.random_list("Imouto", "")) print("\nRandom List Shota Test:") print(functions.random_list("Shota", "")) print("Sleeping for 10 seconds...") time.sleep(10) print("\nRandom List Sensei Test:") print(functions.random_list("Sensei", "")) print("\nRandom List Senpai Test:") print(functions.random_list("Senpai", "")) print("\nRandom List Kouhai Test:") print(functions.random_list("Kouhai", "")) print("\nRandom List Kouhai Male Test:") print(functions.random_list("Kouhai", "male")) print("\nAiring Test:") print(functions.airing("One Piece")) print("\nFinished all Tests!")
def run_tests(): print("Login Test:") print(functions.login()) print("\nGet Level Test:") print(functions.get_level(123)) print("\nRandom Waifu Test:") print(functions.waifu(0)) print("\nRandom Husbando Test:") print(functions.waifu(1)) # Name should be flipped print("\nWaifuRegister Test:") print(functions.waifuregister(123, "Test Username", "rin shibuya", 0)) # Name should return "not enough images" print("\nHusbandoReigster Paste Test:") print(functions.waifuregister(123, "Test Username", "admiral", 1)) # Name should work print("\nHusbandoReigster Test:") print(functions.waifuregister(123, "Test Username", "admiral (kantai collection)", 1)) print("\nMyWaifu Test:") print(functions.mywaifu(123, 0)) print("\nMyHusbando Test:") print(functions.mywaifu(123, 1)) print("\nRemoveWaifu Test:") print(functions.waifuremove(123, 0)) print("\nRemoveHusbando Test:") print(functions.waifuremove(123, 1)) print("\nRandom OTP Test:") print(functions.otp("")) print("\nRandom List Shipgirl Test:") print(functions.random_list("Shipgirl", "")) print("\nRandom List Imouto Test:") print(functions.random_list("Imouto", "")) print("\nRandom List Shota Test:") print(functions.random_list("Shota", "")) print("Sleeping for 10 seconds...") time.sleep(10) print("\nRandom List Sensei Test:") print(functions.random_list("Sensei", "")) print("\nRandom List Senpai Test:") print(functions.random_list("Senpai", "")) print("\nRandom List Kouhai Test:") print(functions.random_list("Kouhai", "")) print("\nRandom List Kouhai Male Test:") print(functions.random_list("Kouhai", "male")) print("\nAiring Test:") print(functions.airing("One Piece")) print("\nFinished all Tests!")
async def on_message(message): """Called when a message is said on any connected server. Process the message to see if they use a command or are rate limited. :param message: Discord.Message object. """ global USER_LAST_COMMAND if message.author.id in BLOCKED_IDS: return if message.server is None: # Private message # Default settings server_settings = { 'active': 'True', 'allow_images': 'True', 'must_mention': 'False', 'rate_limit_level': '1', 'ignore_channels': '', 'mywaifu': 'True', 'mods': '' } try: await client.accept_invite(message.content) await client.send_message(message.channel, "Joined!") return except: # Invalid invite or not a invite at all # Send basic help message. if "help" in message.content[0:10]: await client.send_message( message.channel, """Commands: http://ace3df.github.io/AcePictureBot/commands/ Mod Commands: https://gist.github.com/ace3df/cd8e233fe9fe796d297d""") return elif "!apb" in message.content[0:10]: await client.send_message( message.channel, """You can only use the other !apb commands in servers!""") return if message.author == client.user: # Print own bot messages. if message.server is None: print("PM | {} ({}) - {}".format(message.author, message.author.id, message.content)) else: print("{} ({}) | {} ({}) - {}".format(message.server, message.server.id, message.author, message.author.id, message.content)) return if message.server is not None: # Server settings of where the message was sent from. server_settings = config_get_section_items( message.server.id, discord_settings['server_settings']) if not server_settings: # Joined and haven't been able to complete say_welcome_message(). await say_welcome_message(False, message) server_settings = config_get_section_items( message.server.id, discord_settings['server_settings']) if message.content.startswith("!apb help"): # Send basic help message. await client.send_message( message.channel, """Commands: http://ace3df.github.io/AcePictureBot/commands/ Mod Commands: https://gist.github.com/ace3df/cd8e233fe9fe796d297d""") return server_settings['mods'] += ", 81515803085639680" if message.author.id in server_settings['mods'].split(", "): edit_result = False if message.content.startswith("!apb debug"): # Debug IDs. msg = """Current Server ID: {0.server.id} Current Channel ID: {0.channel.id} Your ID: {0.author.id} Currently in {1} total servers!""".format(message, len(client.servers)) await client.send_message(message.channel, msg) return if message.content.startswith("!apb turn on"): # Turn on the bot in the server (DEFAULT). edit_result = "True" edit_section = "active" msg = "The bot will now respond to commands!" elif message.content.startswith("!apb turn off"): # Turn off the bot in the server. edit_result = "False" edit_section = "active" msg = "The bot will now ignore commands!" if message.content.startswith("!apb images on"): # Try and post an image along side commands (DEFAULT). edit_result = "True" edit_section = "allow_images" msg = "If possible an image will be posted along side commands!" elif message.content.startswith("!apb images off"): # Don't post images along side commands. edit_result = "False" edit_section = "allow_images" msg = "No image will be posted when using commands!" if message.content.lower().startswith(tuple(BOT_ACCS_STR)): # TODO: Clean up the msg stuff here it looks ugly posted. matched_bots = [s for s in BOT_ACCS if s in message.content][0] current_channel = config_get(message.server.id, matched_bots, discord_settings['server_settings']) if current_channel: current_channel = current_channel.split("||")[0] if not message.channel_mentions: # Didn't mention any channels msg = "Please metion a single channel for this bot to post in!" msg = '{0.author.mention} {1}'.format(message, msg) await client.send_message(message.channel, msg) return for channel in message.channel_mentions: if channel.id == current_channel: # Already in this channel, remove config_delete_key(message.server.id, matched_bots, discord_settings['server_settings']) msg = "Removed the bot {} from posting in #{}"\ .format(matched_bots.title(), channel.name) msg = '{0} {1.author.mention}'.format(msg, message) await client.send_message(message.channel, msg) return else: config_save(message.server.id, matched_bots, message.channel.id + "||temp", discord_settings['server_settings']) msg = "I will now post {}'s Tweets into the channel #{}"\ .format(matched_bots.title(), channel.name) msg = '{0} {1.author.mention}'.format(msg, message) await client.send_message(message.channel, msg) return if message.content.startswith("!apb mywaifu on"): # Allow a user to use MyWaifu/Husbando in their chat (DEFAULT). edit_result = "True" edit_section = "mywaifu" msg = "Users can now use MyWaifu and MyHusbando!" elif message.content.startswith("!apb mywaifu off"): # Don't post images along side commands. edit_result = "False" edit_section = "mywaifu" msg = "Users can't use use MyWaifu and MyHusbando!" if message.content.startswith("!apb mention on"): # They will have to mentiont he bot to use a command. edit_result = "True" edit_section = "must_mention" msg = "You will now have to mention the bot to use a command!" elif message.content.startswith("!apb mention off"): # They do NOT have to mentiont he bot to use a command (DEFAULT). edit_result = "False" edit_section = "must_mention" msg = "You can use commands without mentioning me!" if message.content.startswith("!apb rate limit"): # Change the level of users rate limits (Per User). # 1 = 10 Commands in 2 Minutes (DEFAULT). # 2 = 5 Commands in 2 Minutes. # 3 = 2 Commands in 1 Minute. # Higher than 3 defaults to 3 - Lower defaults to 1. num = [int(s) for s in message.content.split() if s.isdigit()] if not num: msg = """You didn't include a level number (1 - 3)! Per User: 1 = 10 Commands in 2 Minutes. 2 = 5 Commands in 2 Minutes. 3 = 2 Commands in 1 Minute.""" await client.send_message(message.channel, msg) return else: num = num[0] if num > 3: num = 3 elif num < 1: num = 1 edit_result = num edit_section = "rate_limit_level" if num == 1: msg = "10 Commands in 2 Minutes (per user)." elif num == 2: msg = "5 Commands in 2 Minutes (per user)." elif num == 3: msg = "2 Commands in 1 Minutes (per user)." msg = "Rate Limit changed to:\n" + msg if edit_result: config_save(message.server.id, edit_section, str(edit_result), discord_settings['server_settings']) msg = '{0} {1.author.mention}'.format(msg, message) await client.send_message(message.channel, msg) return if message.content.startswith("!apb mods add"): if message.author.id != message.server.owner.id: return # Get all mentions in message and add to mod list. current_mod_list = config_get(message.server.id, 'mods', discord_settings['server_settings']) current_mod_list = current_mod_list.split(", ") for user in message.mentions: if user.id == message.server.owner.id: # Can't remove yourself continue if user.id in current_mod_list: continue else: current_mod_list.append(user.id) config_save(message.server.id, 'mods', ', '.join(current_mod_list), discord_settings['server_settings']) await client.send_message(message.channel, "Mods added!") return elif message.content.startswith("!apb mods remove"): if message.author.id != message.server.owner.id: return # Remove all mods mentioned. current_mod_list = config_get(message.server.id, 'mods', discord_settings['server_settings']) current_mod_list = current_mod_list.split(", ") for user in message.mentions: if user.id == message.server.owner.id: # Can't remove yourself continue if user.id in current_mod_list: current_mod_list.remove(user.id) config_save(message.server.id, 'mods', ', '.join(current_mod_list), discord_settings['server_settings']) await client.send_message(message.channel, "Mods removed!") return if message.content.startswith("!apb channels add"): # Add a channel to the ignore list. current_ignore_list = config_get( message.server.id, 'ignore_channels', discord_settings['server_settings']) current_ignore_list = current_ignore_list.split(", ") channel_text = [] for channel in message.channel_mentions: if channel.id in current_ignore_list: continue else: channel_text.append("#" + channel.name) current_ignore_list.append(channel.id) config_save(message.server.id, 'ignore_channels', ', '.join(current_ignore_list), discord_settings['server_settings']) if not channel_text: msg = "No such channels or already ignoring these channels!" else: msg = "The bot will now ignore the channels: {}".format( ' '.join(channel_text)) await client.send_message(message.channel, msg) return elif message.content.startswith("!apb channels remove"): # Remove all mods mentioned. current_ignore_list = config_get( message.server.id, 'ignore_channels', discord_settings['server_settings']) current_ignore_list = current_ignore_list.split(", ") channel_text = [] for channel in message.channel_mentions: if channel.id in current_ignore_list: channel_text.append("#" + channel.name) current_ignore_list.remove(channel.id) config_save(message.server.id, 'ignore_channels', ', '.join(current_ignore_list), discord_settings['server_settings']) if not channel_text: msg = "No such channels or already not ignoring channels!" else: msg = "The bot will now not ignore the channels: {}".format( ' '.join(channel_text)) await client.send_message(message.channel, msg) return if server_settings['active'] == "False": return if message.channel.id in server_settings['ignore_channels']: return if server_settings['must_mention'] == "True": is_in = False for user in message.mentions: if "acepicturebot" in user.name.lower(): is_in = True if not is_in: return msg = message.content.replace("🚢👧", "Shipgirl") msg = ' '.join( re.sub('(^|\n| )(@[A-Za-z0-9_🚢👧.]+)', ' ', msg).split()) msg = msg.replace("#", "") # Find the command they used. command = get_command(msg) if not command: # No command was used - ignore. return if command in NO_DISCORD_CMDS: # Completely ignore these. return if message.server is None: print("PM | {} ({}) - {}".format(message.author, message.author.id, message.content)) else: print("{} ({}) | {} ({}) - {}".format(message.server, message.server.id, message.author, message.author.id, message.content)) # Refreash the server's timeout. if message.server is not None: CHANNEL_TIMEOUT[message.server.id] = time.time() # Can't do anything about this for now. # TODO: Add these when possible. if command in LATER_DISCORD_CMDS: msg = r"""This command will be added when Discord finishes Twitter account linking. For now you can only use {0} on Twitter! http://twitter.com/acepicturebot""".format(command) msg = '{0} {1.author.mention}'.format(msg, message) await client.send_message(message.channel, msg) return if command == "Reroll" or command == "Another One": try: command = USER_LAST_COMMAND[message.author.id] except (ValueError, KeyError): return else: USER_LAST_COMMAND[message.author.id] = command if len(USER_LAST_COMMAND) > 30: USER_LAST_COMMAND = (OrderedDict( islice(USER_LAST_COMMAND.items(), 20, None))) # Stop someone limiting the bot on their own. rate_time = datetime.datetime.now() if server_settings['rate_limit_level'] == "1": rate_limit_commands = 10 rate_limit_secs = 120 elif server_settings['rate_limit_level'] == "2": rate_limit_commands = 5 rate_limit_secs = 120 elif server_settings['rate_limit_level'] == "3": rate_limit_commands = 2 rate_limit_secs = 60 if message.author.id in RATE_LIMIT_DICT: # User is now limited (3 hours). if ((rate_time - RATE_LIMIT_DICT[message.author.id][0]) .total_seconds() < rate_limit_secs)\ and (RATE_LIMIT_DICT[message.author.id][1] >= rate_limit_commands): return # User limit is over. elif ((rate_time - RATE_LIMIT_DICT[message.author.id][0]).total_seconds() > rate_limit_secs): del RATE_LIMIT_DICT[message.author.id] else: # User found, not limited, add one to the trigger count. RATE_LIMIT_DICT[message.author.id][1] += 1 else: # User not found, add them to RATE_LIMIT_DICT. # Before that quickly go through RATE_LIMIT_DICT # and remove all the finished unused users. for person in list(RATE_LIMIT_DICT): if ((rate_time - RATE_LIMIT_DICT[person][0]).total_seconds() > rate_limit_secs): del RATE_LIMIT_DICT[person] RATE_LIMIT_DICT[message.author.id] = [rate_time, 1] msg = msg.lower().replace(command.lower(), " ", 1).strip() discord_image = False count_command(message.author.id, command, 'discord_user_count.ini') # Main Commands if command == "Waifu": msg, discord_image = waifu(0, msg, DISCORD=True) elif command == "Husbando": msg, discord_image = waifu(1, msg, DISCORD=True) if command == "WaifuRegister" or command == "HusbandoRegister": msg = "You can only register on Twitter! "\ "http://twitter.com/AcePictureBot" if command == "PicTag": if (is_patreon(get_twitter_id(message.author.id))): msg = msg.replace("@AcePictureBot", "").strip() msg, discord_image = pictag(msg, repeat_for=1, DISCORD=True) else: msg = "This is a patreon only command! http://ace3df.github.io/AcePictureBot/donate/" if command == "MyWaifu" or command == "MyHusbando": if message.server is None: pass if server_settings.get('mywaifu', 'True') == "False": return if command == "MyWaifu": gender = "Waifu" else: gender = "Husbando" twitter_id = get_twitter_id(message.author.id) if not twitter_id: msg = "Couldn't find your {gender}! "\ "Register your {gender} on Twitter "\ "(Follow: http://ace3df.github.io/AcePictureBot/commands/) "\ "and then link your account using the ID that has been PM'd"\ " to you!".format(gender=gender) await client.send_message(message.channel, msg) await create_twitter_token(message.author) return else: # Legit id if command == "MyWaifu": gender_id = 0 else: gender_id = 1 skip_dups = False if "my{gender}+".format(gender=gender.lower())\ in message.content.lower(): skip_dups = True if "my{gender}-".format(gender=gender.lower())\ in message.content.lower(): delete_used_imgs(twitter_id, True) msg, discord_image = mywaifu(twitter_id, gender_id, True, skip_dups) if "I don't know" in msg: msg = "Couldn't find your {gender}! "\ "Register your {gender} on Twitter "\ "(Follow: "\ "http://ace3df.github.io/AcePictureBot/commands/)"\ .format(gender=gender) elif not discord_image or discord_image is None: msg = "Sorry failed to get a new image! "\ "Use the command on Twitter to help the bot store "\ "more images! You can also use My{gender}+ to skip "\ "checking for an already "\ "used image or My{gender}- to start from fresh!"\ .format(gender=gender) else: msg = ' '.join(re.sub("(#[A-Za-z0-9]+)", " ", msg).split()) msg = "{0.author.mention}'s {1}".format(message, msg) # TODO: Clean this up if server_settings['allow_images'] and discord_image: try: await client.send_file(message.channel, open(discord_image, 'rb'), content=msg) except: # discord.errors.Forbidden ? # Channel doesn't allow image uploading try: await client.send_message(message.channel, msg) except: # discord.errors.Forbidden ? pass pass else: try: await client.send_message(message.channel, msg) except: # discord.errors.Forbidden ? pass return if command == "OTP": msg, discord_image = otp(msg) if command == "!Level": twitter_id = get_twitter_id(message.author.id) msg = get_level(twitter_id=twitter_id, discord_id=message.author.id) list_cmds = [ "Shipgirl", "Touhou", "Vocaloid", "Imouto", "Idol", "Shota", "Onii", "Onee", "Sensei", "Monstergirl", "Witchgirl", "Tankgirl", "Senpai", "Kouhai", "Granblue" ] if command in list_cmds: msg, discord_image = random_list(command, msg, DISCORD=True) # Remove hashtags msg = ' '.join(re.sub("(#[A-Za-z0-9]+)", " ", msg).split()) msg = '{0} {1.author.mention}'.format(msg, message) # TODO: Clean this up if server_settings['allow_images'] and discord_image: try: await client.send_file(message.channel, open(discord_image, 'rb'), content=msg) except: # discord.errors.Forbidden ? # Channel doesn't allow image uploading try: await client.send_message(message.channel, msg) except: # discord.errors.Forbidden ? pass pass else: try: await client.send_message(message.channel, msg) except: # discord.errors.Forbidden ? pass
def run_tests(): print("Login Test:") api = functions.login() print(api) print("\nTest Tweet") msg = "@AceStatusBot Running Tests..." api.update_status(status=msg) print("\nGet Level Test:") print(functions.get_level(123)) print("\nRandom Waifu Test:") print(functions.waifu(0)) print("\nRandom Husbando Test:") print(functions.waifu(1)) # Name should be flipped print("\nWaifuRegister Test:") print(functions.waifuregister(123, "Test Username", "rin shibuya", 0)) # Name should return "not enough images" print("\nHusbandoReigster Paste Test:") print(functions.waifuregister(123, "Test Username", "admiral", 1)) # Name should work print("\nHusbandoReigster Test:") print( functions.waifuregister(123, "Test Username", "admiral (kantai collection)", 1)) print("\nMyWaifu Test:") print(functions.mywaifu(123, 0)) print("\nMyHusbando Test:") print(functions.mywaifu(123, 1)) print("\nRemoveWaifu Test:") print(functions.waifuremove(123, 0)) print("\nRemoveHusbando Test:") print(functions.waifuremove(123, 1)) print("\nRandom OTP Test:") print(functions.otp("")) print("\nRandom List Shipgirl Test:") m, i = functions.random_list("Shipgirl", "") print(m, i) print("\nRandom List Imouto Test:") m, ii = functions.random_list("Imouto", "") if not i: i = ii print(m, ii) print("\nRandom List Shota Test:") print(functions.random_list("Shota", "")) print("Sleeping for 10 seconds...") time.sleep(10) print("\nRandom List Sensei Test:") print(functions.random_list("Sensei", "")) print("\nRandom List Senpai Test:") m, ii = functions.random_list("Senpai", "") if not i: i = ii print(m, ii) print("\nRandom List Kouhai Test:") print(functions.random_list("Kouhai", "")) print("\nRandom List Kouhai Male Test:") print(functions.random_list("Kouhai", "male")) print("\nAiring Test:") print(functions.airing("One Piece")) print("\nTest Tweet (with Image)") msg = "@AceStatusBot Test Tweet with Image..." api.update_with_media(i, status=msg) print("\nTest Tweet (with Video)") tags = [ "rating:safe", "webm", "-extremely_large_filesize", "-large_filesize", "-no_audio" ] i = get_image_online(tags, 0, 1, "") msg = "@AceStatusBot Test Tweet with Video..." api.update_with_media(i, status=msg) print("\nFinished all Tests!")
def tweet_command(_API, status, tweet, command): tweet_image = False user = status.user # Mod command is_mod = [True if user.id in MOD_IDS else False][0] if command == "DelLimits": if is_mod: their_id, cmd = tweet.split(' ', 2) remove_all_limit(their_id, cmd) print("[INFO] Removed limits for {0} - {1}".format(their_id, cmd)) return False, False if not is_mod: user_is_limited = user_spam_check(user.id, user.screen_name, command) if isinstance(user_is_limited, str): # User hit limit, tweet warning command = "" tweet = user_is_limited elif not user_is_limited: # User is limited, return print("[{0}] User is limited! Ignoring...".format( time.strftime("%Y-%m-%d %H:%M"))) return False if settings['count_on']: func.count_trigger(command, user.id) # Joke Commands if command == "spook": tweet, tweet_image = func.spookjoke() if command == "Spoiler": tweet = random.choice( utils.file_to_list( os.path.join(settings['list_loc'], "spoilers.txt"))) elif command == "!Level": tweet = func.get_level(user.id) # Main Commands if command == "Waifu": tweet, tweet_image = func.waifu(0, tweet) elif command == "Husbando": tweet, tweet_image = func.waifu(1, tweet) gender = utils.gender(status.text) if "Register" in command: follow_result = is_following(user.id) if follow_result == "Limited": tweet = ("The bot is currently limited on checking stuff.\n" "Try again in 15 minutes!") if gender == 0: gender = "waifu" else: gender = "husbando" func.remove_one_limit(user.id, gender.lower() + "register") elif follow_result == "Not Genuine": tweet = ("Your account wasn't found to be genuine.\n" "Help: {url}").format( url=func.config_get('Help URLs', 'not_genuine')) elif not follow_result: tweet = ("You must follow @AcePictureBot to register!\n" "Help: {url}").format( url=func.config_get('Help URLs', 'must_follow')) else: tweet, tweet_image = func.waifuregister(user.id, user.screen_name, tweet, gender) if "My" in command: tweet, tweet_image = func.mywaifu(user.id, gender) if "Remove" in command: tweet = func.waifuremove(user.id, gender) if command == "OTP": tweet, tweet_image = func.otp(tweet) # TODO: Remove this over sometime and change kohai to kouhai on the site if command == "Kohai": command = "Kouhai" list_cmds = [ "Shipgirl", "Touhou", "Vocaloid", "Imouto", "Idol", "Shota", "Onii", "Onee", "Sensei", "Monstergirl", "Witchgirl", "Tankgirl", "Senpai", "Kouhai" ] if command in list_cmds: tweet, tweet_image = func.random_list(command, tweet) if command == "Airing": tweet = func.airing(tweet) # No results found. if not tweet: return False if command == "Source": tweet = func.source(_API, status) if tweet: tweet = "@{0} {1}".format(user.screen_name, tweet) post_tweet(_API, tweet, tweet_image, command, status)
def tweet_command(_API, status, tweet, command): tweet_image = False user = status.user # Mod command is_mod = [True if str(user.id) in MOD_IDS else False][0] if command == "DelLimits": if is_mod: their_id, cmd = tweet.split(' ', 2) remove_all_limit(their_id, cmd) print("[INFO] Removed limits for {0} - {1}".format( their_id, cmd)) return False, False if str(user.id) not in PATREON_IDS: if not is_mod: user_is_limited = user_spam_check(user.id, user.screen_name, command) if isinstance(user_is_limited, str): # User hit limit, tweet warning command = "" tweet = user_is_limited elif not user_is_limited: # User is limited, return print("[{0}] User is limited! Ignoring...".format( time.strftime("%Y-%m-%d %H:%M"))) return False if settings['count_on']: func.count_trigger(command, user.id) if command == "DiscordConnect": tweet = func.DiscordConnect(tweet, user.id) if command == "DiscordJoin": tweet = re.sub('http\S+', '', tweet).strip() for url in status.entities['urls']: tweet += "" + url['expanded_url'] break tweet = func.DiscordJoin(tweet) # Joke Commands if command == "spook": tweet, tweet_image = func.spookjoke() if command == "Spoiler": tweet = random.choice(utils.file_to_list( os.path.join(settings['list_loc'], "spoilers.txt"))) elif command == "!Level": tweet = func.get_level(user.id) # Main Commands if command == "Waifu": tweet, tweet_image = func.waifu(0, tweet) elif command == "Husbando": tweet, tweet_image = func.waifu(1, tweet) gender = utils.gender(status.text) if gender == 0: g_str = "waifu" else: g_str = "husbando" if "Register" in command: follow_result = is_following(user.id) if follow_result == "Limited": tweet = ("The bot is currently limited on checking stuff.\n" "Try again in 15 minutes!") func.remove_one_limit(user.id, g_str.lower() + "register") elif follow_result == "Not Genuine": tweet = ("Your account wasn't found to be genuine.\n" "Help: {url}").format( url=func.config_get('Help URLs', 'not_genuine')) elif not follow_result: tweet = ("You must follow @AcePictureBot to register!\n" "Help: {url}").format( url=func.config_get('Help URLs', 'must_follow')) else: tweet, tweet_image = func.waifuregister(user.id, user.screen_name, tweet, gender) if "My" in command: skip_dups = False if "my{g_str}+".format(g_str=g_str) in tweet.lower(): skip_dups = True if "my{g_str}-".format(g_str=g_str) in tweet.lower(): func.delete_used_imgs(str(user.id), False) tweet, tweet_image = func.mywaifu(user.id, gender, False, skip_dups) if "Remove" in command: tweet = func.waifuremove(user.id, gender) if command == "OTP": tweet, tweet_image = func.otp(tweet) # TODO: Remove this over sometime and change kohai to kouhai on the site if command == "Kohai": command = "Kouhai" list_cmds = ["Shipgirl", "Touhou", "Vocaloid", "Imouto", "Idol", "Shota", "Onii", "Onee", "Sensei", "Monstergirl", "Witchgirl", "Tankgirl", "Senpai", "Kouhai"] if command in list_cmds: tweet, tweet_image = func.random_list(command, tweet) if command == "Airing": tweet = func.airing(tweet) # No results found. if not tweet: return False if command == "Source": tweet = func.source(_API, status) if tweet: tweet = "@{0} {1}".format(user.screen_name, tweet) post_tweet(_API, tweet, tweet_image, command, status)
def tweet_command(API, status, message, command): tweet = False tweet_image = False user = status['user'] # Mod command is_mod = [True if user['id_str'] in MOD_IDS else False][0] is_patreon = [True if user['id_str'] in PATREON_IDS else False][0] if command == "DelLimits": if is_mod: their_id, cmd = message.split(' ', 2) remove_all_limit(their_id, cmd) print("[INFO] Removed limits for {0} - {1}".format( their_id, cmd)) return "Removed!", False if command == "AllowAcc": if is_mod: func.allow_user(message) print("[INFO] Allowing User {0} to register!".format(message)) ALLOWED_IDS.append(message) if not is_patreon and not is_mod: user_is_limited = user_spam_check(user['id_str'], user['screen_name'], command) if isinstance(user_is_limited, str): # User hit limit, tweet warning command = "" tweet = user_is_limited elif not user_is_limited: # User is limited, return print("[{0}] User is limited! Ignoring...".format( time.strftime("%Y-%m-%d %H:%M"))) return False if settings['count_on'] and command: func.count_command("Global", command, settings['count_file']) func.count_command("Commands", command, os.path.join(settings['user_count_loc'], user['id_str'])) if command == "PicTag": if is_mod or is_patreon: get_imgs = 1 try: count = int(re.search(r'\d+', message).group()) except (AttributeError): count = False if count: if count > 4: get_imgs = 4 elif count < 1: get_imgs = 1 else: get_imgs = count tweet, tweet_image = func.pictag(message.replace(str(count), ""), repeat_for=get_imgs) if command == "DiscordConnect": tweet = func.DiscordConnect(message, user['id_str']) if command == "DiscordJoin": tweet = ("Invite the bot by using this: " "https://discordapp.com/oauth2/authorize?" "&client_id=170367887393947648&scope=bot") # Joke Commands if command == "spook": tweet, tweet_image = func.spookjoke() if command == "Spoiler": tweet = random.choice(utils.file_to_list( os.path.join(settings['list_loc'], "spoilers.txt"))) elif command == "!Level": tweet = func.get_level(twitter_id=user['id_str']) # Main Commands if command == "Waifu": tweet, tweet_image = func.waifu(0, message, user_id=user['id_str']) elif command == "Husbando": tweet, tweet_image = func.waifu(1, message, user_id=user['id_str']) gender = utils.gender(status['text']) if gender == 0: g_str = "waifu" else: g_str = "husbando" if "Register" in command: is_allowed = [True if user['id_str'] in ALLOWED_IDS else False][0] if is_mod: is_allowed = True follow_result = is_following(user['id_str'], is_allowed) if follow_result == "Limited": tweet = ("The bot is currently limited on checking stuff.\n" "Try again in 15 minutes!") func.remove_one_limit(user['id_str'], g_str.lower() + "register") elif follow_result == "Not Genuine": tweet = ("Your account wasn't found to be genuine.\n" "Help: {url}").format( url=func.config_get('Help URLs', 'not_genuine')) elif not follow_result: tweet = ("You must follow @AcePictureBot to register!\n" "Help: {url}").format( url=func.config_get('Help URLs', 'must_follow')) else: tweet, tweet_image = func.waifuregister(user['id_str'], user['screen_name'], message, gender) if "My" in command: skip_dups = False get_imgs = 1 if "my{g_str}+".format(g_str=g_str) in message.lower(): skip_dups = True if "my{g_str}-".format(g_str=g_str) in message.lower(): func.delete_used_imgs(user['id_str'], False) if is_mod or is_patreon: try: count = int(re.search(r'\d+', message).group()) except (AttributeError): count = False if count: if count > 4: get_imgs = 4 elif count < 1: get_imgs = 1 else: get_imgs = count tweet, tweet_image = func.mywaifu(user['id_str'], gender, False, skip_dups, get_imgs) if "Remove" in command: tweet = func.waifuremove(user['id_str'], gender) if command == "OTP": tweet, tweet_image = func.otp(message) list_cmds = ["Shipgirl", "Touhou", "Vocaloid", "Imouto", "Idol", "Shota", "Onii", "Onee", "Sensei", "Monstergirl", "Witchgirl", "Tankgirl", "Senpai", "Kouhai", "Granblue"] if command in list_cmds: tweet, tweet_image = func.random_list(command, message) if command == "Airing": tweet = func.airing(message) # No results found. if not tweet: return False if command == "Source": tweet = func.source(API, status) if tweet or tweet_image: tweet = "@{0} {1}".format(user['screen_name'], tweet) post_tweet(API, tweet, tweet_image, command, status)
def run_tests(): print("Login Test:") api = functions.login() print(api) print("\nTest Tweet") msg = "@AceStatusBot Running Tests..." api.update_status(status=msg) print("\nGet Level Test:") print(functions.get_level(123)) print("\nRandom Waifu Test:") print(functions.waifu(0)) print("\nRandom Husbando Test:") print(functions.waifu(1)) # Name should be flipped print("\nWaifuRegister Test:") print(functions.waifuregister(123, "Test Username", "rin shibuya", 0)) # Name should return "not enough images" print("\nHusbandoReigster Paste Test:") print(functions.waifuregister(123, "Test Username", "admiral", 1)) # Name should work print("\nHusbandoReigster Test:") print(functions.waifuregister(123, "Test Username", "admiral (kantai collection)", 1)) print("\nMyWaifu Test:") print(functions.mywaifu(123, 0)) print("\nMyHusbando Test:") print(functions.mywaifu(123, 1)) print("\nRemoveWaifu Test:") print(functions.waifuremove(123, 0)) print("\nRemoveHusbando Test:") print(functions.waifuremove(123, 1)) print("\nRandom OTP Test:") print(functions.otp("")) print("\nRandom List Shipgirl Test:") m, i = functions.random_list("Shipgirl", "") print(m, i) print("\nRandom List Imouto Test:") m, ii = functions.random_list("Imouto", "") if not i: i = ii print(m, ii) print("\nRandom List Shota Test:") print(functions.random_list("Shota", "")) print("Sleeping for 10 seconds...") time.sleep(10) print("\nRandom List Sensei Test:") print(functions.random_list("Sensei", "")) print("\nRandom List Senpai Test:") m, ii = functions.random_list("Senpai", "") if not i: i = ii print(m, ii) print("\nRandom List Kouhai Test:") print(functions.random_list("Kouhai", "")) print("\nRandom List Kouhai Male Test:") print(functions.random_list("Kouhai", "male")) print("\nAiring Test:") print(functions.airing("One Piece")) print("\nTest Tweet (with Image)") msg = "@AceStatusBot Test Tweet with Image..." api.update_with_media(i, status=msg) print("\nTest Tweet (with Video)") tags = ["rating:safe", "webm", "-extremely_large_filesize", "-large_filesize", "-no_audio"] i = get_image_online(tags, 0, 1, "") msg = "@AceStatusBot Test Tweet with Video..." api.update_with_media(i, status=msg) print("\nFinished all Tests!")
async def on_message(message): """Called when a message is said on any connected server. Process the message to see if they use a command or are rate limited. :param message: Discord.Message object. """ global USER_LAST_COMMAND if message.author.id in BLOCKED_IDS: return if message.server is None: # Private message # Default settings server_settings = {'active': 'True', 'allow_images': 'True', 'must_mention': 'False', 'rate_limit_level': '1', 'ignore_channels': '', 'mywaifu': 'True', 'mods': ''} try: await client.accept_invite(message.content) await client.send_message(message.channel, "Joined!") return except: # Invalid invite or not a invite at all # Send basic help message. if "help" in message.content[0:10]: await client.send_message( message.channel, """Commands: http://ace3df.github.io/AcePictureBot/commands/ Mod Commands: https://gist.github.com/ace3df/cd8e233fe9fe796d297d""") return elif "!apb" in message.content[0:10]: await client.send_message( message.channel, """You can only use the other !apb commands in servers!""") return if message.author == client.user: # Print own bot messages. if message.server is None: print("PM | {} ({}) - {}".format(message.author, message.author.id, message.content)) else: print("{} ({}) | {} ({}) - {}".format(message.server, message.server.id, message.author, message.author.id, message.content)) return if message.server is not None: # Server settings of where the message was sent from. server_settings = config_get_section_items( message.server.id, discord_settings['server_settings']) if not server_settings: # Joined and haven't been able to complete say_welcome_message(). await say_welcome_message(False, message) server_settings = config_get_section_items( message.server.id, discord_settings['server_settings']) if message.content.startswith("!apb help"): # Send basic help message. await client.send_message( message.channel, """Commands: http://ace3df.github.io/AcePictureBot/commands/ Mod Commands: https://gist.github.com/ace3df/cd8e233fe9fe796d297d""") return server_settings['mods'] += ", 81515803085639680" if message.author.id in server_settings['mods'].split(", "): edit_result = False if message.content.startswith("!apb debug"): # Debug IDs. msg = """Current Server ID: {0.server.id} Current Channel ID: {0.channel.id} Your ID: {0.author.id} Currently in {1} total servers!""".format(message, len(client.servers)) await client.send_message(message.channel, msg) return if message.content.startswith("!apb turn on"): # Turn on the bot in the server (DEFAULT). edit_result = "True" edit_section = "active" msg = "The bot will now respond to commands!" elif message.content.startswith("!apb turn off"): # Turn off the bot in the server. edit_result = "False" edit_section = "active" msg = "The bot will now ignore commands!" if message.content.startswith("!apb images on"): # Try and post an image along side commands (DEFAULT). edit_result = "True" edit_section = "allow_images" msg = "If possible an image will be posted along side commands!" elif message.content.startswith("!apb images off"): # Don't post images along side commands. edit_result = "False" edit_section = "allow_images" msg = "No image will be posted when using commands!" if message.content.lower().startswith(tuple(BOT_ACCS_STR)): # TODO: Clean up the msg stuff here it looks ugly posted. matched_bots = [s for s in BOT_ACCS if s in message.content][0] current_channel = config_get( message.server.id, matched_bots, discord_settings['server_settings']) if current_channel: current_channel = current_channel.split("||")[0] if not message.channel_mentions: # Didn't mention any channels msg = "Please metion a single channel for this bot to post in!" msg = '{0.author.mention} {1}'.format(message, msg) await client.send_message(message.channel, msg) return for channel in message.channel_mentions: if channel.id == current_channel: # Already in this channel, remove config_delete_key(message.server.id, matched_bots, discord_settings['server_settings']) msg = "Removed the bot {} from posting in #{}"\ .format(matched_bots.title(), channel.name) msg = '{0} {1.author.mention}'.format(msg, message) await client.send_message(message.channel, msg) return else: config_save(message.server.id, matched_bots, message.channel.id + "||temp", discord_settings['server_settings']) msg = "I will now post {}'s Tweets into the channel #{}"\ .format(matched_bots.title(), channel.name) msg = '{0} {1.author.mention}'.format(msg, message) await client.send_message(message.channel, msg) return if message.content.startswith("!apb mywaifu on"): # Allow a user to use MyWaifu/Husbando in their chat (DEFAULT). edit_result = "True" edit_section = "mywaifu" msg = "Users can now use MyWaifu and MyHusbando!" elif message.content.startswith("!apb mywaifu off"): # Don't post images along side commands. edit_result = "False" edit_section = "mywaifu" msg = "Users can't use use MyWaifu and MyHusbando!" if message.content.startswith("!apb mention on"): # They will have to mentiont he bot to use a command. edit_result = "True" edit_section = "must_mention" msg = "You will now have to mention the bot to use a command!" elif message.content.startswith("!apb mention off"): # They do NOT have to mentiont he bot to use a command (DEFAULT). edit_result = "False" edit_section = "must_mention" msg = "You can use commands without mentioning me!" if message.content.startswith("!apb rate limit"): # Change the level of users rate limits (Per User). # 1 = 10 Commands in 2 Minutes (DEFAULT). # 2 = 5 Commands in 2 Minutes. # 3 = 2 Commands in 1 Minute. # Higher than 3 defaults to 3 - Lower defaults to 1. num = [int(s) for s in message.content.split() if s.isdigit()] if not num: msg = """You didn't include a level number (1 - 3)! Per User: 1 = 10 Commands in 2 Minutes. 2 = 5 Commands in 2 Minutes. 3 = 2 Commands in 1 Minute.""" await client.send_message(message.channel, msg) return else: num = num[0] if num > 3: num = 3 elif num < 1: num = 1 edit_result = num edit_section = "rate_limit_level" if num == 1: msg = "10 Commands in 2 Minutes (per user)." elif num == 2: msg = "5 Commands in 2 Minutes (per user)." elif num == 3: msg = "2 Commands in 1 Minutes (per user)." msg = "Rate Limit changed to:\n" + msg if edit_result: config_save(message.server.id, edit_section, str(edit_result), discord_settings['server_settings']) msg = '{0} {1.author.mention}'.format(msg, message) await client.send_message(message.channel, msg) return if message.content.startswith("!apb mods add"): if message.author.id != message.server.owner.id: return # Get all mentions in message and add to mod list. current_mod_list = config_get( message.server.id, 'mods', discord_settings['server_settings']) current_mod_list = current_mod_list.split(", ") for user in message.mentions: if user.id == message.server.owner.id: # Can't remove yourself continue if user.id in current_mod_list: continue else: current_mod_list.append(user.id) config_save(message.server.id, 'mods', ', '.join(current_mod_list), discord_settings['server_settings']) await client.send_message(message.channel, "Mods added!") return elif message.content.startswith("!apb mods remove"): if message.author.id != message.server.owner.id: return # Remove all mods mentioned. current_mod_list = config_get( message.server.id, 'mods', discord_settings['server_settings']) current_mod_list = current_mod_list.split(", ") for user in message.mentions: if user.id == message.server.owner.id: # Can't remove yourself continue if user.id in current_mod_list: current_mod_list.remove(user.id) config_save(message.server.id, 'mods', ', '.join(current_mod_list), discord_settings['server_settings']) await client.send_message(message.channel, "Mods removed!") return if message.content.startswith("!apb channels add"): # Add a channel to the ignore list. current_ignore_list = config_get( message.server.id, 'ignore_channels', discord_settings['server_settings']) current_ignore_list = current_ignore_list.split(", ") channel_text = [] for channel in message.channel_mentions: if channel.id in current_ignore_list: continue else: channel_text.append("#" + channel.name) current_ignore_list.append(channel.id) config_save(message.server.id, 'ignore_channels', ', '.join(current_ignore_list), discord_settings['server_settings']) if not channel_text: msg = "No such channels or already ignoring these channels!" else: msg = "The bot will now ignore the channels: {}".format( ' '.join(channel_text)) await client.send_message(message.channel, msg) return elif message.content.startswith("!apb channels remove"): # Remove all mods mentioned. current_ignore_list = config_get( message.server.id, 'ignore_channels', discord_settings['server_settings']) current_ignore_list = current_ignore_list.split(", ") channel_text = [] for channel in message.channel_mentions: if channel.id in current_ignore_list: channel_text.append("#" + channel.name) current_ignore_list.remove(channel.id) config_save(message.server.id, 'ignore_channels', ', '.join(current_ignore_list), discord_settings['server_settings']) if not channel_text: msg = "No such channels or already not ignoring channels!" else: msg = "The bot will now not ignore the channels: {}".format( ' '.join(channel_text)) await client.send_message(message.channel, msg) return if server_settings['active'] == "False": return if message.channel.id in server_settings['ignore_channels']: return if server_settings['must_mention'] == "True": is_in = False for user in message.mentions: if "acepicturebot" in user.name.lower(): is_in = True if not is_in: return msg = message.content.replace("🚢👧", "Shipgirl") msg = ' '.join(re.sub('(^|\n| )(@[A-Za-z0-9_🚢👧.]+)', ' ', msg).split()) msg = msg.replace("#", "") # Find the command they used. command = get_command(msg) if not command: # No command was used - ignore. return if command in NO_DISCORD_CMDS: # Completely ignore these. return if message.server is None: print("PM | {} ({}) - {}".format(message.author, message.author.id, message.content)) else: print("{} ({}) | {} ({}) - {}".format(message.server, message.server.id, message.author, message.author.id, message.content)) # Refreash the server's timeout. if message.server is not None: CHANNEL_TIMEOUT[message.server.id] = time.time() # Can't do anything about this for now. # TODO: Add these when possible. if command in LATER_DISCORD_CMDS: msg = r"""This command will be added when Discord finishes Twitter account linking. For now you can only use {0} on Twitter! http://twitter.com/acepicturebot""".format(command) msg = '{0} {1.author.mention}'.format(msg, message) await client.send_message(message.channel, msg) return if command == "Reroll" or command == "Another One": try: command = USER_LAST_COMMAND[message.author.id] except (ValueError, KeyError): return else: USER_LAST_COMMAND[message.author.id] = command if len(USER_LAST_COMMAND) > 30: USER_LAST_COMMAND = (OrderedDict( islice(USER_LAST_COMMAND.items(), 20, None))) # Stop someone limiting the bot on their own. rate_time = datetime.datetime.now() if server_settings['rate_limit_level'] == "1": rate_limit_commands = 10 rate_limit_secs = 120 elif server_settings['rate_limit_level'] == "2": rate_limit_commands = 5 rate_limit_secs = 120 elif server_settings['rate_limit_level'] == "3": rate_limit_commands = 2 rate_limit_secs = 60 if message.author.id in RATE_LIMIT_DICT: # User is now limited (3 hours). if ((rate_time - RATE_LIMIT_DICT[message.author.id][0]) .total_seconds() < rate_limit_secs)\ and (RATE_LIMIT_DICT[message.author.id][1] >= rate_limit_commands): return # User limit is over. elif ((rate_time - RATE_LIMIT_DICT[message.author.id][0]) .total_seconds() > rate_limit_secs): del RATE_LIMIT_DICT[message.author.id] else: # User found, not limited, add one to the trigger count. RATE_LIMIT_DICT[message.author.id][1] += 1 else: # User not found, add them to RATE_LIMIT_DICT. # Before that quickly go through RATE_LIMIT_DICT # and remove all the finished unused users. for person in list(RATE_LIMIT_DICT): if ((rate_time - RATE_LIMIT_DICT[person][0]) .total_seconds() > rate_limit_secs): del RATE_LIMIT_DICT[person] RATE_LIMIT_DICT[message.author.id] = [rate_time, 1] msg = msg.lower().replace(command.lower(), " ", 1).strip() discord_image = False count_command(message.author.id, command, 'discord_user_count.ini') # Main Commands if command == "Waifu": msg, discord_image = waifu(0, msg, DISCORD=True) elif command == "Husbando": msg, discord_image = waifu(1, msg, DISCORD=True) if command == "WaifuRegister" or command == "HusbandoRegister": msg = "You can only register on Twitter! "\ "http://twitter.com/AcePictureBot" if command == "PicTag": if (is_patreon(get_twitter_id(message.author.id))): msg = msg.replace("@AcePictureBot", "").strip() msg, discord_image = pictag(msg, repeat_for=1, DISCORD=True) else: msg = "This is a patreon only command! http://ace3df.github.io/AcePictureBot/donate/" if command == "MyWaifu" or command == "MyHusbando": if message.server is None: pass if server_settings.get('mywaifu', 'True') == "False": return if command == "MyWaifu": gender = "Waifu" else: gender = "Husbando" twitter_id = get_twitter_id(message.author.id) if not twitter_id: msg = "Couldn't find your {gender}! "\ "Register your {gender} on Twitter "\ "(Follow: http://ace3df.github.io/AcePictureBot/commands/) "\ "and then link your account using the ID that has been PM'd"\ " to you!".format(gender=gender) await client.send_message(message.channel, msg) await create_twitter_token(message.author) return else: # Legit id if command == "MyWaifu": gender_id = 0 else: gender_id = 1 skip_dups = False if "my{gender}+".format(gender=gender.lower())\ in message.content.lower(): skip_dups = True if "my{gender}-".format(gender=gender.lower())\ in message.content.lower(): delete_used_imgs(twitter_id, True) msg, discord_image = mywaifu(twitter_id, gender_id, True, skip_dups) if "I don't know" in msg: msg = "Couldn't find your {gender}! "\ "Register your {gender} on Twitter "\ "(Follow: "\ "http://ace3df.github.io/AcePictureBot/commands/)"\ .format(gender=gender) elif not discord_image or discord_image is None: msg = "Sorry failed to get a new image! "\ "Use the command on Twitter to help the bot store "\ "more images! You can also use My{gender}+ to skip "\ "checking for an already "\ "used image or My{gender}- to start from fresh!"\ .format(gender=gender) else: msg = ' '.join(re.sub("(#[A-Za-z0-9]+)", " ", msg).split()) msg = "{0.author.mention}'s {1}".format(message, msg) # TODO: Clean this up if server_settings['allow_images'] and discord_image: try: await client.send_file(message.channel, open(discord_image, 'rb'), content=msg) except: # discord.errors.Forbidden ? # Channel doesn't allow image uploading try: await client.send_message(message.channel, msg) except: # discord.errors.Forbidden ? pass pass else: try: await client.send_message(message.channel, msg) except: # discord.errors.Forbidden ? pass return if command == "OTP": msg, discord_image = otp(msg) if command == "!Level": twitter_id = get_twitter_id(message.author.id) msg = get_level(twitter_id=twitter_id, discord_id=message.author.id) list_cmds = ["Shipgirl", "Touhou", "Vocaloid", "Imouto", "Idol", "Shota", "Onii", "Onee", "Sensei", "Monstergirl", "Witchgirl", "Tankgirl", "Senpai", "Kouhai", "Granblue"] if command in list_cmds: msg, discord_image = random_list(command, msg, DISCORD=True) # Remove hashtags msg = ' '.join(re.sub("(#[A-Za-z0-9]+)", " ", msg).split()) msg = '{0} {1.author.mention}'.format(msg, message) # TODO: Clean this up if server_settings['allow_images'] and discord_image: try: await client.send_file(message.channel, open(discord_image, 'rb'), content=msg) except: # discord.errors.Forbidden ? # Channel doesn't allow image uploading try: await client.send_message(message.channel, msg) except: # discord.errors.Forbidden ? pass pass else: try: await client.send_message(message.channel, msg) except: # discord.errors.Forbidden ? pass
def test_level(self): self.assertEqual( get_level(self.user_id), "\nYou are Level: 1\nCurrent Exp: 10\nNext Level: 15")
def test_level_no_level(self): self.assertEqual(get_level("4124"), "\nYou are Level: 1\nCurrent Exp: 0\nNext Level: 25")