Skip to content

Commit

Permalink
Merge pull request #47 from jediswaplabs/46-log-in-with-discord-to-pr…
Browse files Browse the repository at this point in the history
…event-abuse-of-bot

46 log in with discord to prevent abuse of bot
  • Loading branch information
al-matty authored Jan 13, 2023
2 parents 913848a + 7333874 commit 626b4ac
Show file tree
Hide file tree
Showing 6 changed files with 292 additions and 63 deletions.
100 changes: 71 additions & 29 deletions discord_bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,33 @@ async def send_to_TG(self, telegram_user_id, msg, parse_mode='Markdown') -> None
Sends a message a specific Telegram user id.
Defaults to Markdown V1 for inline link capability.
"""
signature = "| _back to /menu_ |"
escape_d = {
'.': '\.',
'!': '\!',
'-': '\-',
'#': '\#',
'>': '\>',
'<': '\<',
'.': '\.',
'_': '\_',
'`': '\`',
'*': '*',
}

# Escape markdown characters for main part of TG msg
header_end = msg.find("):\n")

no_header = msg[header_end+2:]
footer_start = no_header.find(22*"~")
discord_content = no_header[:footer_start]

header = msg[:header_end+2]
footer = no_header[footer_start:]
escaped_msg = discord_content.translate(msg.maketrans(escape_d))

msg = header + escaped_msg + footer

await self.telegram_bot.send_message(
chat_id=telegram_user_id,
text=msg,
Expand Down Expand Up @@ -139,6 +166,10 @@ async def get_user(self, guild_id, username) -> discord.User:
guild = await self.get_guild(guild_id)
return guild.get_member_named(username)

async def get_user_id(self, guild_id, username) -> str:
"""Takes guild id & username, str(<Discord ID>)."""
user = await self.get_user(guild_id, username)
return user.id

async def get_guild_roles(self, guild_id) -> list:
"""Takes guild id returns list of names of all roles on guild."""
Expand Down Expand Up @@ -239,10 +270,11 @@ async def on_message(message):

line = "\n"+("~"*22)+"\n"


# If no user mentions in message -> Skip this part
if message.mentions != []:

if self.debug_mode: log(f"USER MENTIONS IN MESSAGE: {message.mentions}")
if self.debug_mode: log(f"{len(message.mentions)} USER MENTIONS IN {message.channel.name}.")

channel = message.channel.name
whitelist = self.channel_whitelist
Expand Down Expand Up @@ -275,22 +307,28 @@ async def on_message(message):
f" {guild.id == target_guild_id}"
)

# Condition 1: msg guild matches guild set up by user
if guild.id == target_guild_id:

if self.debug_mode:
log(
f"CHANNEL CHECK: {type(channel)} {channel} in"
f" {whitelist[_id]}: {channel in whitelist[_id]}\n"
f"SET UP CHANNELS: {whitelist[_id]}"
)

# Condition 2: Channel matches or no channels set up
if whitelist[_id] == set():
await self.send_to_TG(_id, out_msg)
else:
if channel in whitelist[_id]:
# Condition 1: User Discord is verified
if self.users[_id]["verified discord"]:

# Condition 2: msg guild matches guild set up by user
if guild.id == target_guild_id:

if self.debug_mode:
log(
f"CHANNEL CHECK: {type(channel)} {channel} in"
f" {whitelist[_id]}: {channel in whitelist[_id]}\n"
f"SET UP CHANNELS: {whitelist[_id]}"
)

# Condition 3: Channel matches or no channels set up
if whitelist[_id] == set():
await self.send_to_TG(_id, out_msg)
else:
if channel in whitelist[_id]:
await self.send_to_TG(_id, out_msg)

else:
if self.debug_mode: log("UNVERIFIED DISCORD. NO HANDLE NOTIFICATION SENT.")


# If no role mentions in message -> Skip this part
Expand Down Expand Up @@ -331,22 +369,26 @@ async def on_message(message):
f" {guild.id == target_guild_id}"
)

# Condition 1: msg guild matches guild set up by user
if guild.id == target_guild_id:
# Condition 1: User Discord is verified
if self.users[_id]["verified discord"]:

log(
f"CHANNEL CHECK: {type(channel)} {channel} in"
f" {whitelist[_id]}: {channel in whitelist[_id]}\n"
f"SET UP CHANNELS: {whitelist[_id]}"
)
# Condition 2: msg guild matches guild set up by user
if guild.id == target_guild_id:
if self.debug_mode:
log(
f"CHANNEL CHECK: {type(channel)} {channel} in"
f" {whitelist[_id]}: {channel in whitelist[_id]}\n"
f"SET UP CHANNELS: {whitelist[_id]}"
)

# Condition 2: Channel matches or no channels set up
if whitelist[_id] == set():
await self.send_to_TG(_id, out_msg)
# Condition 3: Channel matches or no channels set up
if whitelist[_id] == set():
await self.send_to_TG(_id, out_msg)
else:
if channel in whitelist[_id]:
await self.send_to_TG(_id, out_msg)
else:
if channel in whitelist[_id]:
await self.send_to_TG(_id, out_msg)

if self.debug_mode: log("UNVERIFIED DISCORD. NO ROLE NOTIFICATION SENT.")

DISCORD_TOKEN = os.getenv("DISCORD_TOKEN")
await client.start(DISCORD_TOKEN)
11 changes: 7 additions & 4 deletions docs/diagrams/src/state_diagram.mmd
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ flowchart TB
%% Documentation: https://mermaid-js.github.io/mermaid/#/flowchart
%% To generate png file: mmdc -i <source>.mmd -o <target>.png

MENU(("/menu")):::entryPoint -->|Current active notifications...| CHOOSING((CHOOSING)):::state



CHOOSING ==> guild("Discord guild"):::userInput
Expand All @@ -20,13 +20,16 @@ flowchart TB
done --> |"Bring back the /menu anytime!"| End(("END")):::termination

submenu ---> |"[save button press] <br /> Please enter the name <br /> of a role / channel ..."| TYPING_REPLY((TYPING_REPLY)):::state
submenu --> |"[if 'Back']"| MENU
submenu --> |"[if 'Back']"| MENU(("/menu")):::entryPoint

TYPING_REPLY --> |"[if coming from inline button menu: <br /> store/delete data according to logic in <br /> received_callback()]"| add_remove_more("Inline button menu: <br /> | Add/Remove more? | Back |"):::inlineMenu
TYPING_REPLY --> |"[save choice: text] <br /> [store/delete data according to logic in <br /> received_information()] "| MENU

add_remove_more --> |"[if 'Add/Remove more']"| TYPING_REPLY
add_remove_more --> |"[if 'Back']"| MENU

TYPING_REPLY --> |"[if coming from inline button menu: <br /> store/delete data according to logic in <br /> received_callback()]"| add_remove_more("Inline button menu: <br /> | Add/Remove more? | Back |"):::inlineMenu
TYPING_REPLY --> |"[save choice: text] <br /> [store/delete data according to logic in <br /> received_information()] "| MENU
MENU ---->|Current active notifications...| CHOOSING((CHOOSING)):::state


classDef userInput fill:#2a5279, color:#ffffff, stroke:#ffffff
classDef inlineMenu fill:#ffff66, color:#000000, stroke:#ffffff
Expand Down
Binary file modified example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
39 changes: 26 additions & 13 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
![Preview](https://github.com/jediswaplabs/discord-alert-bot/blob/main/example.png)


A Telegram bot sending out a real time notification each time your handle is mentioned on the JediSwap Discord server. To use, start a conversation with [@JediSwapAlertBot](https://t.me/JediSwapAlertBot) on Telegram. This will bring up the bot menu, where you can set up your Discord handle. After entering it, the bot will forward any message mentioning your Discord handle or any of your roles to your Telegram. Notifications can be deactivated for any role or channel using the bot menu.
A Telegram bot sending out a real time notification each time your handle is mentioned on the JediSwap Discord server. To use, start a conversation with [@JediSwapAlertBot](https://t.me/JediSwapAlertBot) on Telegram. This will bring up the bot menu, where you can set up your Discord handle. After entering it and verifying via Discord, the bot will forward any message mentioning your Discord handle or any of your roles to your Telegram. Notifications can be deactivated for any role or channel using the bot menu.

## Running the bot on your Discord server

Expand All @@ -16,6 +16,9 @@ A Telegram bot sending out a real time notification each time your handle is men
- Rename `sample.env` to `.env` and enter the following information to the file without any spaces or quotes (1 exception):
* `DISCORD_TOKEN=`your Discord bot token
* `TELEGRAM_BOT_TOKEN=`your Telegram bot token
* `OAUTH_DISCORD_CLIENT_ID=`your Discord application ID
* `OAUTH_DISCORD_CLIENT_SECRET=`your Discord client secret
* `OAUTH_REDIRECT_URI=`see 'Discord Authentication' below
* `DEFAULT_GUILD=`your Discord [server ID](https://support.discord.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID-)
* `ALLOWED_CHANNEL_CATEGORIES=`"[channel ID,channel ID,channel ID, ...]" (enter the [category channel IDs](https://support.discord.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID-) containing the channels the user is supposed to see in the bot's channels menu)
- Add the bot to your Discord server as shown [here](https://www.writebots.com/discord-bot-token/) or set up an [invite link](https://discordapi.com/permissions.html#66560).
Expand All @@ -25,21 +28,31 @@ A Telegram bot sending out a real time notification each time your handle is men

## Requirements & Installation

The bot requires python >= 3.7 (3.9 is recommended) and uses the packages listed in `requirements.txt`.
The best practice would be to install a virtual environment and install the
requirements afterwards using `pip`:
The bot requires python 3.9 and uses the packages listed in [requirements.txt](./requirements.txt). The best practice would be to install a virtual environment.

```
pip install -r requirements.txt
```
* Install & activate a virtual environment using either `venv`:

If you're using [anaconda](https://www.anaconda.com), you can create a virtual environment and install the requirements using this code:
```
python -m venv venv
source venv/bin/activate # `deactivate` to leave again
```
```
conda create -n discord-alert-bot python=3.9
conda activate discord-alert-bot
pip install -r requirements.txt
```
or [anaconda](https://www.anaconda.com):
```
conda create -n venv python=3.9
conda activate venv # `conda deactivate` to leave again
```
* Install dependencies:
```
pip install -r requirements.txt
```
## Discord Authentication
This bot uses [Oauth2 authentication](https://discord.com/developers/docs/topics/oauth2), which requires a whitelisted redirect url to send back the verification info safely. Sending oauth data back to a Telegram bot instead of a website requires a workaround. To enable users to verify their Discord handle, you can set up an aws api gateway as described [here](https://stackoverflow.com/a/42457831). Add its url to the `.env` file under `OAUTH_REDIRECT_URI=`, and don't forget to also add it to the whitelist on the [Discord developer portal](https://discord.com/developers/) under Applications -> OAuth2 -> Redirects. These two entered urls need to match exactly.
## State Diagram
Expand Down
5 changes: 4 additions & 1 deletion sample.env
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
DISCORD_TOKEN=
TELEGRAM_BOT_TOKEN=
OAUTH_DISCORD_CLIENT_ID=
OAUTH_DISCORD_CLIENT_SECRET=
OAUTH_REDIRECT_URI=
DEFAULT_GUILD=
DEBUG_ID=<Telegram ID permissioned to call the /debug function (optional)>
ALLOWED_CHANNEL_CATEGORIES="[<int>, <int>, <int>, ...]"
DEBUG_ID=<Telegram ID permissioned to call the /debug function (optional)>
Loading

0 comments on commit 626b4ac

Please sign in to comment.