├── .pylintrc ├── .travis.yml ├── LICENSE.md ├── README.md ├── config.ini ├── selena.py └── selena.service /.pylintrc: -------------------------------------------------------------------------------- 1 | [BASIC] 2 | good-names=i,j,k,m,n,x,y,fg,bg,r,g,b,i3,r1,r2,r3,g1,g2,g3,b1,b2,b3,h,s,v 3 | 4 | [MESSAGES CONTROL] 5 | # inconsistent-return-statements: 6 | # Disabled as it's a false-positive and a bug in pylint. 7 | # too-many-branches: 8 | # Disabled as it's a non-issue and only occurs in the 9 | # process_args() function. 10 | # too-many-statements: 11 | # Disabled as it's a non-issue and only occurs in the 12 | # process_args() function. 13 | disable=inconsistent-return-statements,too-many-branches,too-many-statements,global-statement 14 | 15 | [SIMILARITIES] 16 | ignore-imports=y 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | matrix: 4 | include: 5 | - os: linux 6 | python: 3.5 7 | 8 | install: 9 | - pip install flake8 pylint discord 10 | 11 | script: 12 | - flake8 selena.py 13 | - pylint selena.py 14 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Dylan Araps 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Selena 2 | 3 | [![Discord](https://img.shields.io/discord/440354555197128704.svg)](https://discord.gg/BtnTPFF) 4 | [![Build Status](https://travis-ci.org/dylanaraps/pywal.svg?branch=master)](https://travis-ci.org/dylanaraps/pywal) 5 | [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE.md) 6 | [![Donate](https://img.shields.io/badge/donate-patreon-yellow.svg)](https://www.patreon.com/dyla) 7 | 8 | A minimal bot to log all Discord messages for 9 | transparency. 10 | 11 | This bot logs every message and sends them to another 12 | channel. It’s like the audit log but for messages. The 13 | messages can also be sent to a different server if 14 | you’d like to keep logging separate. 15 | 16 | 17 | ## Dependencies 18 | 19 | - python 3.5+ 20 | - discord-rewrite 21 | 22 | 23 | ## Getting Started 24 | 25 | **Setup** 26 | 27 | ```sh 28 | git clone https://dylanaraps.com/discord-selena` 29 | cd discord-selena 30 | mkdir -p ~/.config/selena 31 | cp config.ini ~/.config/selena 32 | ``` 33 | 34 | **Bot Token** 35 | 36 | - Add your bot token to the config file. 37 | 38 | ```ini 39 | token = 40 | ``` 41 | 42 | **Log Channel** 43 | 44 | - Change the value to the Channel ID of your logging 45 | channel. 46 | 47 | ```ini 48 | log_channel = 447547444566163457 49 | ``` 50 | -------------------------------------------------------------------------------- /config.ini: -------------------------------------------------------------------------------- 1 | [auth] 2 | token = 3 | 4 | [channel] 5 | log_channel = 447547444566163457 6 | -------------------------------------------------------------------------------- /selena.py: -------------------------------------------------------------------------------- 1 | """ 2 | Selena - A bot for Discord. 3 | """ 4 | import configparser 5 | import os 6 | import shutil 7 | import subprocess 8 | import sys 9 | 10 | import discord 11 | 12 | 13 | CONFIG = configparser.ConfigParser() 14 | BOT = discord.Client() 15 | LOG_CHANNEL = "" 16 | 17 | 18 | @BOT.event 19 | async def on_ready(): 20 | """On bot start.""" 21 | global LOG_CHANNEL 22 | LOG_CHANNEL = BOT.get_channel(int(CONFIG.get("channel", "log_channel"))) 23 | 24 | 25 | @BOT.event 26 | async def on_message(m): 27 | """Log all messages.""" 28 | if not m.author.bot and not m.channel.is_nsfw(): 29 | await LOG_CHANNEL.send(embed=make_embed_m(m, "sent")) 30 | 31 | 32 | @BOT.event 33 | async def on_message_delete(m): 34 | """Log deleted messages.""" 35 | if not m.author.bot and not m.channel.is_nsfw(): 36 | await LOG_CHANNEL.send(embed=make_embed_m(m, "deleted")) 37 | 38 | 39 | @BOT.event 40 | async def on_message_edit(r, m): 41 | """Log edited messages.""" 42 | if not m.author.bot and not m.channel.is_nsfw(): 43 | m.content = "%s\n=\n%s" % (r.content, m.content) 44 | await LOG_CHANNEL.send(embed=make_embed_m(m, "edited")) 45 | 46 | 47 | @BOT.event 48 | async def on_member_ban(_, user): 49 | """Log bans.""" 50 | await LOG_CHANNEL.send(embed=make_embed_b(user, "banned")) 51 | 52 | 53 | @BOT.event 54 | async def on_member_unban(_, user): 55 | """Log unbans.""" 56 | await LOG_CHANNEL.send(embed=make_embed_b(user, "unbanned")) 57 | 58 | 59 | def msg_icon(msg_type): 60 | """Get the icon to use for the msg type.""" 61 | return { 62 | "sent": ":e_mail:", 63 | "edited": ":pencil:", 64 | "deleted": ":wastebasket:", 65 | }.get(msg_type, ":shrug:") 66 | 67 | 68 | def msg_color(msg_type): 69 | """Get the color to use for the msg type.""" 70 | return { 71 | "sent": 0x4caf50, 72 | "edited": 0xffc107, 73 | "deleted": 0xff5252, 74 | }.get(msg_type, 0x000000) 75 | 76 | 77 | def make_embed_m(m, msg_type="sent"): 78 | """Create an embed for messages.""" 79 | title = "%s Message %s by __%s__ in #%s" \ 80 | % (msg_icon(msg_type), msg_type, m.author, m.channel) 81 | conte = "```fix\n%s %s\n```" \ 82 | % (m.content, ", ".join([attach.url for attach in m.attachments])) 83 | 84 | embed = discord.Embed(title=title, 85 | description=conte, 86 | color=msg_color(msg_type)) 87 | return embed.set_footer(text="MSG ID: %s, ID: %s" % (m.id, m.author.id)) 88 | 89 | 90 | def make_embed_b(m, msg_type="banned"): 91 | """Create an embed for bans.""" 92 | title = "%s User __%s__ %s" % (msg_icon(msg_type), m.name, msg_type) 93 | 94 | embed = discord.Embed(title=title, 95 | description="", 96 | color=msg_color(msg_type)) 97 | return embed.set_footer(text="ID: %s" % m.id) 98 | 99 | 100 | def get_config(): 101 | """Find the config file.""" 102 | home = os.path.expanduser("~") 103 | user_config = os.path.join(home, ".config", "selena", "config.ini") 104 | 105 | if os.path.isfile(user_config): 106 | return user_config 107 | 108 | return "config.ini" 109 | 110 | 111 | def main(): 112 | """Main function.""" 113 | CONFIG.read(get_config()) 114 | 115 | if not CONFIG.get("auth", "token"): 116 | print("error: Token not found.") 117 | sys.exit(1) 118 | 119 | if shutil.which("git"): 120 | print("info: Updating bot.") 121 | subprocess.run(["git", "pull"]) 122 | 123 | BOT.run(CONFIG.get("auth", "token")) 124 | 125 | 126 | main() 127 | -------------------------------------------------------------------------------- /selena.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=discord-selena 3 | After=multi-user.target 4 | [Service] 5 | WorkingDirectory=/home/selena/discord-selena 6 | User=selena 7 | Group=selena 8 | ExecStart=/usr/bin/python3 /home/selena/discord-selena/selena.py 9 | Type=idle 10 | Restart=always 11 | RestartSec=15 12 | 13 | [Install] 14 | WantedBy=multi-user.target 15 | --------------------------------------------------------------------------------