Skip to content
This repository has been archived by the owner on Jun 27, 2019. It is now read-only.

Commit

Permalink
Merge pull request #173 from NabDev/dev
Browse files Browse the repository at this point in the history
Version 2.3.0
  • Loading branch information
Galarzaa90 committed Apr 19, 2019
2 parents dd6d1e5 + dbbf77e commit 13a547b
Show file tree
Hide file tree
Showing 31 changed files with 366 additions and 169 deletions.
16 changes: 15 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,21 @@
- 🐛 Fixed bug
- ❌ Removed feature

### Version 2.2.0 (2019-03-07)
## Version 2.3.0 (2019-04-19)
- ✔ New subcommand `/unregistered guild`, checks which members of a guild are not registered in the server.
- ✔ New owner command `/logs` to upload log files.
- ✔ New subcommand `/news ticker`, displays recent news ticker messages.
- ✔ New ticker messages are now announced along with news articles and featured articles.
- 🔧 `/quote` now shows a link to the original message.
- 🔧 Added auto sharding.
- 🔧 No longer using a development version of `discord.py`, now using version v1.0.0
- 🐛 Fixed error in `/event subscribe`.
- 🐛 Fixed bug not allowing to check characters with `.` in their names.
- 🐛 Fixed bug that duplicates certain server-log messages.
- 🐛 Fixed with time strings (`2d`, `1d4h`, etc) not working with spaces around them.
- 🐛 Updated TibiaWiki database.

## Version 2.2.0 (2019-03-07)
- ✔ Added option to disable custom messages for deaths and level ups. `/settings simpleannouncements`
- ✔ New `/purge` owner command, cleans settings for servers where the bot is no longer in.
- ✔ Added option to set how long ago was killed, to reduce that from the cooldown timer. e.g. `/boss set Lloyd,Tschas,1h30m`.
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.

Copyright 2018 Allan Galarza
Copyright 2019 Allan Galarza

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ You can also host your own instance of NabBot.
### Requirements
- Python 3.6 and up
- Python modules:
- [discord.py (rewrite branch)](https://github.com/Rapptz/discord.py/tree/rewrite)
- discord.py
- psutil
- pillow
- BeautifulSoup
Expand Down Expand Up @@ -49,4 +49,4 @@ If you like NabBot, you can donate to this project. NabBot and the developers wi



*[Tibia](http://tibia.com) is made by [CipSoft](https://www.cipsoft.com/), all game related images are copyrighted by [CipSoft GmbH](https://www.cipsoft.com/).*
*[Tibia](http://tibia.com) is made by [CipSoft](https://www.cipsoft.com/), all game related images are copyrighted by [CipSoft GmbH](https://www.cipsoft.com/).*
2 changes: 1 addition & 1 deletion cogs/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ async def game_update(self):
# Entries starting with "l:" are prefixed with "Listening to "
presence_list = [
# Playing _____
"Half-Life 3", "Tibia on Steam", "DOTA 3", "Human Simulator 2018", "Russian roulette",
"Half-Life 3", "Tibia on Steam", "DOTA 3", "Human Simulator 2019", "Russian roulette",
"with my toy humans", "with fire🔥", "God", "innocent", "the part", "hard to get",
"with my human minions", "Singularity", "Portal 3", "Dank Souls", "you", "01101110", "dumb",
"with GLaDOS 💙", "with myself", "with your heart", "League of Dota", "my cards right",
Expand Down
12 changes: 6 additions & 6 deletions cogs/general.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import logging
import random
from typing import List
from typing import List, Optional

import discord
from discord.ext import commands
Expand Down Expand Up @@ -90,7 +90,7 @@ async def quote(self, ctx: NabCtx, message_id: int):
Note that the bot won't attempt to search in channels you can't read.
Additionally, messages in NSFW channels can't be quoted in regular channels."""
channels: List[discord.TextChannel] = ctx.guild.text_channels
message: discord.Message = None
message: Optional[discord.Message] = None
with ctx.typing():
for channel in channels:
bot_perm = ctx.bot_permissions
Expand All @@ -100,7 +100,7 @@ async def quote(self, ctx: NabCtx, message_id: int):
auth_perm.read_message_history and auth_perm.read_messages):
continue
try:
message = await channel.get_message(message_id)
message = await channel.fetch_message(message_id)
except discord.HTTPException:
continue
if message is not None:
Expand All @@ -114,13 +114,13 @@ async def quote(self, ctx: NabCtx, message_id: int):
if message.channel.nsfw and not ctx.channel.nsfw:
await ctx.error("I can't quote messages from NSFW channels in regular channels.")
return
embed = discord.Embed(description=message.content, timestamp=message.created_at)
embed = discord.Embed(description=f"{message.content}\n\n[Jump to original]({message.jump_url})",
timestamp=message.created_at)
try:
embed.colour = message.author.colour
except AttributeError:
pass
embed.set_author(name=message.author.display_name, icon_url=get_user_avatar(message.author),
url=message.jump_url)
embed.set_author(name=message.author.display_name, icon_url=get_user_avatar(message.author))
embed.set_footer(text=f"In #{message.channel.name}")
if len(message.attachments) >= 1:
attachment: discord.Attachment = message.attachments[0]
Expand Down
16 changes: 10 additions & 6 deletions cogs/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@


class Info(commands.Cog, utils.CogUtils):
"""Commands that disploy general information."""
"""Commands that display general information."""
def __init__(self, bot: NabBot):
self.bot = bot

Expand Down Expand Up @@ -60,16 +60,18 @@ async def about(self, ctx: NabCtx):

@checks.can_embed()
@commands.command(name="botinfo")
async def bot_info(self, ctx: NabCtx):
async def _bot_info(self, ctx: NabCtx):
"""Shows advanced information about the bot."""
async with ctx.pool.acquire() as conn:
char_count = await conn.fetchval('SELECT COUNT(*) FROM "character" WHERE user_id != 0')
deaths_count = await conn.fetchval('SELECT COUNT(*) FROM character_death')
levels_count = await conn.fetchval('SELECT COUNT(*) FROM character_levelup')

used_ram = psutil.Process().memory_full_info().uss / 1024 ** 2
bot_ram = psutil.Process().memory_full_info().uss / 1024 ** 2
bot_percentage_ram = psutil.Process().memory_percent()
used_ram = psutil.virtual_memory().used / 1024 ** 2
percentage_ram = psutil.virtual_memory().percent
total_ram = psutil.virtual_memory().total / 1024 ** 2
percentage_ram = psutil.Process().memory_percent()

def ram(value):
if value >= 1024:
Expand All @@ -89,13 +91,14 @@ def ram(value):
embed.description = f"🔰 Version: **{self.bot.__version__}**\n" \
f"⏱ Uptime **{parse_uptime(self.bot.start_time)}**\n" \
f"🖥️ OS: **{platform.system()} {platform.release()}**\n" \
f"📉 RAM: **{ram(used_ram)}/{ram(total_ram)} ({percentage_ram:.2f}%)**\n"
f"📉 RAM: **{ram(bot_ram)} ({bot_percentage_ram:.2f}%)**\n" \
f"📈 Total RAM: **{ram(used_ram)}/{ram(total_ram)} ({percentage_ram:.2f}%)**\n"
try:
embed.description += f"⚙ CPU: **{psutil.cpu_count()} @ {psutil.cpu_freq().max} MHz**\n"
except AttributeError:
pass
embed.description += f"🏓 Ping: **{ping} ms**\n" \
f"👾 Servers: **{len(self.bot.guilds):,}**\n" \
f"👾 Servers: **{len(self.bot.guilds):,}** (**{self.bot.shard_count}** shards)\n" \
f"💬 Channels: **{len(list(self.bot.get_all_channels())):,}**\n" \
f"👨 Users: **{len(self.bot.users):,}** \n" \
f"👤 Characters: **{char_count:,}**\n" \
Expand Down Expand Up @@ -275,6 +278,7 @@ async def emoji_info(self, ctx: NabCtx, *, emoji: discord.Emoji = None):
embed.add_field(name=name, value=value.replace("\n", ""))
await ctx.send(embed=embed)

# TODO: Implement this command the proper discord.py way
@checks.can_embed()
@commands.command(name='help')
async def _help(self, ctx, *, command: str = None):
Expand Down
7 changes: 3 additions & 4 deletions cogs/loot.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,10 +223,10 @@ async def loot(self, ctx: NabCtx):

# Send on ask_channel or PM
if await ctx.is_long():
await ctx.send(short_message, embed=embed, file=discord.File(loot_image_overlay, "results.png"))
await ctx.send(short_message, embed=embed, file=discord.File(io.BytesIO(loot_image_overlay), "results.png"))
else:
try:
await ctx.author.send(file=discord.File(loot_image_overlay, "results.png"), embed=embed)
await ctx.author.send(file=discord.File(io.BytesIO(loot_image_overlay), "results.png"), embed=embed)
except discord.Forbidden:
await ctx.error(f"{ctx.author.mention}, I tried pming you to send you the results, "
f"but you don't allow private messages from this server.\n"
Expand All @@ -239,7 +239,6 @@ async def loot_legend(self, ctx: NabCtx):
"""Shows the meaning of the overlayed icons."""
with open("./images/legend.png", "r+b") as f:
await ctx.send(file=discord.File(f))
f.close()

@checks.owner_only()
@loot.command(name="show")
Expand All @@ -265,7 +264,7 @@ async def loot_show(self, ctx, *, item: str):
if len(fields) > 5:
embed.set_footer(text="Too many frames to display all information.")
embed.set_image(url="attachment://results.png")
await ctx.send(embed=embed, file=discord.File(result, "results.png"))
await ctx.send(embed=embed, file=discord.File(io.BytesIO(result), "results.png"))

@classmethod
def load_image(cls, image_bytes: bytes) -> Image.Image:
Expand Down
46 changes: 41 additions & 5 deletions cogs/mod.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from cogs import utils
from cogs.utils import converter
from cogs.utils.tibia import get_guild, get_voc_emoji, get_voc_abb
from nabbot import NabBot
from .utils import checks, config, safe_delete_message
from .utils.context import NabCtx
Expand Down Expand Up @@ -183,14 +184,10 @@ async def unignore(self, ctx: NabCtx, *entries: converter.ChannelOrMember):

@checks.channel_mod_only()
@checks.tracking_world_only()
@commands.command()
@commands.group(invoke_without_command=True, case_insensitive=True)
async def unregistered(self, ctx: NabCtx):
"""Shows a list of users with no registered characters."""
entries = []
if ctx.world is None:
await ctx.send("This server is not tracking any worlds.")
return

results = await ctx.pool.fetch('SELECT user_id FROM "character" WHERE world = $1 GROUP BY user_id', ctx.world)
# Flatten list
users = [i["user_id"] for i in results]
Expand All @@ -210,6 +207,45 @@ async def unregistered(self, ctx: NabCtx):
await pages.paginate()
except CannotPaginate as e:
await ctx.send(e)

@checks.channel_mod_only()
@checks.tracking_world_only()
@unregistered.command(name="guild")
async def unregistered_guild(self, ctx: NabCtx, *, name: str):
"""Shows a list of unregistered guild members.
Unregistered guild members can be either characters not registered to NabBot or
registered to users not in the server."""
guild = await get_guild(name)
if guild is None:
return await ctx.error("There's no guild with that name.")
if guild.world != ctx.world:
return await ctx.error(f"**{guild.name}** is not in **{ctx.world}**")

names = [m.name for m in guild.members]
registered = await ctx.pool.fetch("""SELECT name FROM "character" T0
INNER JOIN user_server T1 ON T0.user_id = T1.user_id
WHERE name = any($1) AND server_id = $2""", names, ctx.guild.id)
registered_names = [m['name'] for m in registered]

entries = []
for member in guild.members:
if member.name in registered_names:
continue
emoji = get_voc_emoji(member.vocation.value)
voc_abb = get_voc_abb(member.vocation.value)
entries.append(f'{member.rank} — **{member.name}** (Lvl {member.level} {voc_abb} {emoji})')
if len(entries) == 0:
await ctx.send("There are no unregistered users.")
return

pages = Pages(ctx, entries=entries, per_page=10)
pages.embed.set_author(name=f"Unregistered members from {guild.name}", icon_url=guild.logo_url)
try:
await pages.paginate()
except CannotPaginate as e:
await ctx.send(e)

# endregion

@classmethod
Expand Down
58 changes: 44 additions & 14 deletions cogs/owner.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import inspect
import os
import platform
import textwrap
import traceback
Expand All @@ -24,7 +25,6 @@
log = logging.getLogger("nabbot")

req_pattern = re.compile(r"([\w.]+)([><=]+)([\d.]+),([><=]+)([\d.]+)")
dpy_commit = re.compile(r"a(\d+)\+g([\w]+)")


class Owner(commands.Cog, CogUtils):
Expand Down Expand Up @@ -70,7 +70,7 @@ def check(m):
@checks.owner_only()
@commands.command()
async def announcement(self, ctx: NabCtx, *, message):
"""Sends an announcement to all servers with a sererlog."""
"""Sends an announcement to all servers with a serverlog."""
embed = discord.Embed(title="📣 Owner Announcement", colour=discord.Colour.blurple(),
timestamp=dt.datetime.now())
embed.set_author(name="Support Server", url="https://discord.gg/NmDvhpY", icon_url=self.bot.user.avatar_url)
Expand Down Expand Up @@ -256,6 +256,48 @@ async def load_cog(self, ctx: NabCtx, cog: str):
except Exception as e:
await ctx.send('{}: {}'.format(type(e).__name__, e))

@commands.command(name="logs")
@checks.owner_only()
async def logs(self, ctx: NabCtx, log_name: str = None):
base_dir = "logs"
if log_name is None:
def file_size(size):
if size < 1024:
return f"{size:,} B"
size /= 1024
if size < 1024:
return f"{size:,.2f} kB"
size /= 1024
if size < 1024:
return f"{size:,.2f} mB"

entries = []
for log_file in os.listdir(base_dir):
path = os.path.join(base_dir, log_file)
if os.path.isfile(path):
entries.append(f"{log_file} (*{file_size(os.path.getsize(path))}*)")
entries[1:] = sorted(entries[1:], reverse=True)
pages = Pages(ctx, entries=entries, per_page=10)
pages.embed.title = f"Log files"
try:
await pages.paginate()
except CannotPaginate as e:
await ctx.error(e)
return

if log_name and ctx.guild:
return await ctx.error("For security reasons, I can only upload logs on private channels.")
if ".." in log_name:
return await ctx.error("You're not allowed to get files from outside the log folder.")
try:
with open(os.path.join("logs", log_name), "rb") as f:
await ctx.send("Here's your log file", file=discord.File(f, log_name))
except FileNotFoundError:
return await ctx.error("There's no log file with that name.")
except discord.HTTPException:
return await ctx.error("Error uploading file. It is currently not possible to read the current log file.")


@commands.command(usage="<old world> <new world>")
@checks.owner_only()
async def merge(self, ctx: NabCtx, old_world: str, new_world: str):
Expand Down Expand Up @@ -665,19 +707,7 @@ def comp(operator, object1, object2):
elif operator == "<=":
return object1 <= object2

discordpy_version = pkg_resources.get_distribution("discord.py").version
m = dpy_commit.search(discordpy_version)
dpy = f"v{discordpy_version}"
if m:
revision, commit = m.groups()
is_valid = int(revision) >= self.bot.__min_discord__
discordpy_url = f"https://github.com/Rapptz/discord.py/commit/{commit}"
dpy = f"{ctx.tick(is_valid)}[v{discordpy_version}]({discordpy_url})"
if not is_valid:
dpy += f"\n`{self.bot.__min_discord__ - int(revision)} commits behind`"

embed = discord.Embed(title="NabBot", description="v"+self.bot.__version__)
embed.add_field(name="discord.py", value=dpy)
embed.set_footer(text=f"Python v{platform.python_version()} on {platform.platform()}",
icon_url="https://www.python.org/static/apple-touch-icon-precomposed.png")

Expand Down
17 changes: 5 additions & 12 deletions cogs/serverlog.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,13 +344,10 @@ async def on_member_remove(self, member: discord.Member):
@commands.Cog.listener()
async def on_member_update(self, before: discord.Member, after: discord.Member):
"""Called every time a member is updated"""
embed = discord.Embed(description=f"{after.mention}: ", colour=COLOUR_MEMBER_UPDATE)
embed.set_author(name=f"{after.name}#{after.discriminator} (ID: {after.id})", icon_url=get_user_avatar(after))
changes = True
if f"{before.name}#{before.discriminator}" != f"{after.name}#{after.discriminator}":
embed.description += "Name changed from **{0.name}#{0.discriminator}** to **{1.name}#{1.discriminator}**." \
.format(before, after)
elif before.nick != after.nick:
if before.nick != after.nick:
embed = discord.Embed(description=f"{after.mention}: ", colour=COLOUR_MEMBER_UPDATE)
embed.set_author(name=f"{after.name}#{after.discriminator} (ID: {after.id})",
icon_url=get_user_avatar(after))
if before.nick is None:
embed.description += f"Nickname set to **{after.nick}**"
elif after.nick is None:
Expand All @@ -361,9 +358,6 @@ async def on_member_update(self, before: discord.Member, after: discord.Member):
if entry and entry.user.id != after.id:
icon_url = get_user_avatar(entry.user)
embed.set_footer(text=f"{entry.user.name}#{entry.user.discriminator}", icon_url=icon_url)
else:
changes = False
if changes:
await self.bot.send_log_message(after.guild, embed=embed)

@commands.Cog.listener()
Expand All @@ -377,7 +371,6 @@ async def on_member_unban(self, guild: discord.Guild, user: discord.User):
embed.set_footer(text="{0.name}#{0.discriminator}".format(entry.user),
icon_url=get_user_avatar(entry.user))
await self.bot.send_log_message(guild, embed=embed)

# endregion

@staticmethod
Expand All @@ -396,7 +389,7 @@ async def get_audit_entry(guild: discord.Guild, action: discord.AuditLogAction,
return
now = dt.datetime.utcnow()
after = now - dt.timedelta(0, 5)
async for entry in guild.audit_logs(limit=10, reverse=False, action=action, after=after):
async for entry in guild.audit_logs(limit=10, oldest_first=False, action=action, after=after):
if abs((entry.created_at - now)) >= dt.timedelta(seconds=5):
break
if target is not None and entry.target.id == target.id:
Expand Down
Loading

0 comments on commit 13a547b

Please sign in to comment.