commit be4ac63db7cf59c4003e99a6875363bb3e525bc2 Author: Jiri Karlik Date: Sat Aug 10 09:10:28 2024 +0200 Transfer to Gitea diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2483976 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea/ +__pycache__/ diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..f626319 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Karlik Jiri + +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: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..97cfe53 --- /dev/null +++ b/README.md @@ -0,0 +1,211 @@ + + + + + + +[![Contributors][contributors-shield]][contributors-url] +[![Forks][forks-shield]][forks-url] +[![Stargazers][stars-shield]][stars-url] +[![Issues][issues-shield]][issues-url] +[![MIT License][license-shield]][license-url] +[![LinkedIn][linkedin-shield]][linkedin-url] + + + + +
+

+ + Logo + + +

RandBot

+

PROJECT DISCONTINUED due to lack of interest

+ +

+ Discord bot randomizer. Users can create a list that is stored in MongoDB and the bot can select a random item from this list. + · + Report Bug + · + Request Feature +

+

+ + + + +
+

Table of Contents

+
    +
  1. + About The Project + +
  2. +
  3. + Getting Started + +
  4. +
  5. Bot Commands
  6. +
  7. Roadmap
  8. +
  9. Contributing
  10. +
  11. License
  12. +
  13. Contact
  14. +
+
+ + + + +## About The Project + +Discord bot created in Python using [discord.py](https://discordpy.readthedocs.io/). +The bot can create custom lists using the ?list command and stores those lists in MongoDB. +Those lists can also be deleted using the command ?delete. +Bot returns 1 random item from the selected list when command ?random is used. + +The project is now in the beta version. This is the first working version that I am using on my Discord server to randomize drop location in Call of Duty: Warzone. +It is currently working, but error handling should be updated and new features can be added. This was a fun project to use on my Discord, but I have decided to share it with others. I am currently hosting the bot as well, so you can also invite the bot to your server instead of running it on your own. + + +### Built With + +* [Python](https://www.python.org/) +* [MongoDB](https://www.mongodb.com/) + + + + +## Getting Started + + +Download the repository and create the MongoDB Atlas database. More details in the Installation section. + +### Prerequisites + +- [Python 3](https://www.python.org/) +- Following Python modules are needed. I recommend installing them with [pip](https://pip.pypa.io/en/stable/installing/): + - [discord](https://discordpy.readthedocs.io/) + - [pymongo](https://pymongo.readthedocs.io/) + - [asyncio](https://docs.python.org/3/library/asyncio.html) + - [dnspython](https://www.dnspython.org/) +- [MongoDB Atlas](https://www.mongodb.com/cloud/atlas) - There is free database option. I am currently using following DB structure "bot.lists", which is also part of the code in commands.py. It can be changed there: + ```sh + db = clientDB.bot + collection = db.lists + ``` + + +### Installation + +1. Clone the repo + ```sh + git clone https://github.com/karlji/RandBot-Discord-Randomizer.git + ``` +2. Create Discord bot on [Discord Developer Portal](https://discord.com/developers/docs/intro) & store the bot token to tokens.py + - Logo +4. Create [MongoDB Atlas cluster](https://www.mongodb.com/cloud/atlas) with DB structure "bot.lists", Document structure visible below, add IP adress of your bot machine, copy "connection string" to tokens.py + - Logo + - Logo +5. Run randbot.py +6. Create OAuth2 link on [Discord Developer Portal](https://discord.com/developers/). Options "bot" and "send messages" should be enough. This link can be used to invite bot to Discord servers. + - Logo + + + +## Commands + +Following commands are available: + +- ?commands + - Lists all available commands. +- ?list {ListName} + - Creates new list. +- ?shuffle {ListName} + - Randomly selects one item from list. +- ?delete {ListName} + - Deletes existing list. +- ?showlists + - Shows existing lists. +- ?yesno + - Randomly answers Yes/No. +- ?8ball + - Answers like The Magic 8 Ball. + + + +## Roadmap + +See the [open issues](https://github.com/github_username/repo_name/issues) for a list of proposed features (and known issues). + + + + +## Contributing + +Contributions are what makes the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. + +1. Fork the Project +2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) +3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) +4. Push to the Branch (`git push origin feature/AmazingFeature`) +5. Open a Pull Request + + + + +## License + +Distributed under the MIT License. See `LICENSE` for more information. + + + + +## Contact + +Jiri Karlik- [Linkedin](https://www.linkedin.com/in/jiri-karlik/) + +Project Link: [https://github.com/karlji/RandBot-Discord-Randomizer](https://github.com/karlji/RandBot-Discord-Randomizer) + + + + + + + + + + +[contributors-shield]: https://img.shields.io/github/contributors/karlji/RandBot-Discord-Randomizer.svg?style=for-the-badge +[contributors-url]: https://github.com/karlji/RandBot-Discord-Randomizer/graphs/contributors +[forks-shield]: https://img.shields.io/github/forks/karlji/RandBot-Discord-Randomizer.svg?style=for-the-badge +[forks-url]: https://github.com/karlji/RandBot-Discord-Randomizer/network/members +[stars-shield]: https://img.shields.io/github/stars/karlji/repo.svg?style=for-the-badge +[stars-url]: https://github.com/karlji/RandBot-Discord-Randomizer/stargazers +[issues-shield]: https://img.shields.io/github/issues/karlji/RandBot-Discord-Randomizer.svg?style=for-the-badge +[issues-url]: https://github.com/karlji/RandBot-Discord-Randomizer/issues +[license-shield]: https://img.shields.io/github/license/karlji/RandBot-Discord-Randomizer.svg?style=for-the-badge +[license-url]: https://github.com/karlji/RandBot-Discord-Randomizer/blob/master/LICENSE.txt +[linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555 +[linkedin-url]: https://www.linkedin.com/in/jiri-karlik/ diff --git a/commands.py b/commands.py new file mode 100644 index 0000000..7ee612d --- /dev/null +++ b/commands.py @@ -0,0 +1,157 @@ +import random +import json +import asyncio +import pymongo +import messages as mes +import tokens as tok +import time + +clientDB = pymongo.MongoClient(tok.mongo_token) +db = clientDB.bot + + +# Base class for other exceptions +class Error(Exception): + pass + + +# Raised when the list format is wrong +class ListFormatError(Error): + pass + + +class ListExistsError(Error): + pass + + +async def list_command(client, message): + server_name = message.guild.name + collection = db[server_name] + list_name = message.content.replace('?list ', '') + title = "Creating list: " + list_name + full_user = message.author.name + "#" + message.author.discriminator + await message.channel.send(embed=mes.list_message(title)) + + try: + def check(m): + # checking if awaited message is from the same user that used the command + if full_user == m.author.name + "#" + m.author.discriminator: + # checking if user input contains ; ... else returning Format error + if m.content.find(";") != -1: + return True + else: + raise ListFormatError + else: + return False + + if collection.find_one({"User": full_user, "List_Name": list_name}, {"List": 1, "_id": False}) is None: + message = await client.wait_for('message', timeout=60.0, check=check) + else: + raise ListExistsError + # Format error + timeout error + except asyncio.TimeoutError: + await message.channel.send(embed=mes.timeout_message()) + except ListFormatError: + await message.channel.send(embed=mes.format_error_message()) + except ListExistsError: + await message.channel.send(embed=mes.list_exists_error_message()) + + # if no errors occurred, create new list + else: + now = int(time.time()) + collection.insert_one( + {"User": full_user, "List_Name": list_name, "List": message.content, "Timestamp": now}) + title = list_name + " created!" + await message.channel.send(embed=mes.list_created_message(title)) + return + + +async def random_command(message): + list_name = message.content.replace('?random ', '') + server_name = message.guild.name + full_user = message.author.name + "#" + message.author.discriminator + # calling randomize function to get random item from list + item = randomize(server_name, full_user, list_name) + await message.channel.send(embed=mes.random_message(item)) + return + + +async def delete_command(message): + list_name = message.content.replace('?delete ', '') + server_name = message.guild.name + collection = db[server_name] + full_user = message.author.name + "#" + message.author.discriminator + collection.delete_one({"User": full_user, "List_Name": list_name}) + title = list_name + " deleted!" + await message.channel.send(embed=mes.delete_message(title)) + return + + +async def commands_command(message): + await message.channel.send(embed=mes.commands_message()) + return + + +# retrieving random item from list +def randomize(server_name, full_user, list_name): + collection = db[server_name] + # query based on listname & username + output = collection.find_one({"User": full_user, "List_Name": list_name}, {"List": 1, "_id": False}) + # adding timestamp of the latest use to the list + now = int(time.time()) + timestamp = {"$set": {"Timestamp": now}} + collection.update_one(output, timestamp) + # formatting the query output + output = json.dumps(output) + output = output.replace('{"List": "', '') + output = output.replace('"}', '') + output = output.split(";") + # selecting random item from the output + output = random.choice(tuple(output)) + # check for when query returns null + if output == "null": + output = "List not found!" + return output + + +async def eightball_command(message): + answers = ("It is certain.", "It is decidedly so.", "Without a doubt.", "Yes – definitely.", "You may rely on it.", + "As I see it, yes.", "Most likely.", "Outlook good.", "Yes.", "Signs point to yes.", + "Reply hazy, try again.", "Ask again later.", "Better not tell you now.", "Cannot predict now.", + "Concentrate and ask again.", "Don't count on it.", "My reply is no.", "My sources say no.", + "Outlook not so good.", "Very doubtful.") + item = random.choice(answers) + await message.channel.send(embed=mes.random_message(item)) + return + + +async def yesno_command(message): + answers = ("yes", "no") + item = random.choice(answers) + await message.channel.send(embed=mes.random_message(item)) + return + + +async def clean(): + col_list = db.list_collection_names() + now = int(time.time()) + difference = now - 2592000 + + # Loops through all collections and deletes those that are older than 30 days from now + for i in range(len(col_list)): + col = col_list[i] + collection = db[col] + myquery = {"Timestamp": {"$lt": difference}} + collection.delete_many(myquery) + return + + +async def print_lists(message): + server_name = message.guild.name + full_user = message.author.name + "#" + message.author.discriminator + collection = db[server_name] + query = collection.find({"User": full_user}, {"List_Name": 1, "_id": False}) + array = list(query) + length = len(array) + await message.channel.send(embed=mes.print_lists_message(array,length)) + return diff --git a/images/discord_token.png b/images/discord_token.png new file mode 100644 index 0000000..8a4e28c Binary files /dev/null and b/images/discord_token.png differ diff --git a/images/logo.png b/images/logo.png new file mode 100644 index 0000000..c5f369e Binary files /dev/null and b/images/logo.png differ diff --git a/images/mongo_document.png b/images/mongo_document.png new file mode 100644 index 0000000..1ccbd40 Binary files /dev/null and b/images/mongo_document.png differ diff --git a/images/mongo_token.png b/images/mongo_token.png new file mode 100644 index 0000000..f6df8cc Binary files /dev/null and b/images/mongo_token.png differ diff --git a/images/oauth.png b/images/oauth.png new file mode 100644 index 0000000..568da4e Binary files /dev/null and b/images/oauth.png differ diff --git a/messages.py b/messages.py new file mode 100644 index 0000000..22570e5 --- /dev/null +++ b/messages.py @@ -0,0 +1,142 @@ +import discord +import json + +# List of embeded messages to clean up the code + +def list_message(title): + embed = discord.Embed(title=title, + description="Please enter your list items separated by ; ", + color=0xFF5733) + embed.add_field(name="Example", value="Item1;Item2;Item3;Item4", inline=False) + embed.add_field(name="Support this project", + value="[Donate](https://www.paypal.com/donate?hosted_button_id=QY9QSBC63TL34)", + inline=False) + return embed + + +def timeout_message(): + embed = discord.Embed(title="No list provided within timeout!", + description="There is 60s timeout. ", + color=0xFF5733) + embed.add_field(name="Example", value="Please start again with ?list command.", inline=False) + return embed + + +def format_error_message(): + embed = discord.Embed(title="Format Error!", + description="Use ; separator between items! ", + color=0xFF5733) + embed.add_field(name="Example", value="Please start again with ?list command.", inline=False) + return embed + + +def list_exists_error_message(): + embed = discord.Embed(title="List already exists!", + description="Please use unique list name.", + color=0xFF5733) + embed.add_field(name="Example", value="Please start again with ?list command.", inline=False) + return embed + + +def guild_join_message(): + embed = discord.Embed(title="Thanks for inviting me:", + description="Following commands are available:", + color=0xFF5733) + + embed.add_field(name="?list {ListName}", + value="Creates new list.", + inline=False) + + embed.add_field(name="?random {ListName}", + value="Randomly selects one item from list.", + inline=False) + + embed.add_field(name="?delete {ListName}", + value="Deletes existing list.", + inline=False) + + embed.add_field(name="?commands", + value="Lists all available commands.", + inline=False) + return embed + + +def list_created_message(title): + embed = discord.Embed(title=title, + description="New list created!", + color=0xFF5733) + embed.add_field(name="Support this project", + value="[Donate](https://www.paypal.com/donate?hosted_button_id=QY9QSBC63TL34)", + inline=False) + return embed + + +def random_message(item): + embed = discord.Embed(title=item, + description="[Support this project](https://www.paypal.com/donate?hosted_button_id=QY9QSBC63TL34)", + color=0xFF5733) + return embed + + +def delete_message(title): + embed = discord.Embed(title=title, + description="List deleted!", + color=0xFF5733) + embed.add_field(name="Support this project", + value="[Donate](https://www.paypal.com/donate?hosted_button_id=QY9QSBC63TL34)", + inline=False) + return embed + + +def commands_message(): + embed = discord.Embed(title="Commands:", + description="Following commands are available:", + color=0xFF5733) + + embed.add_field(name="?commands", + value="Lists all available commands.", + inline=False) + + embed.add_field(name="?delete {ListName}", + value="Deletes existing list.", + inline=False) + + embed.add_field(name="?list {ListName}", + value="Creates new list.", + inline=False) + + embed.add_field(name="?random {ListName}", + value="Randomly selects one item from the list.", + inline=False) + + embed.add_field(name="?showlists", + value="Prints all available lists for the user.", + inline=False) + + embed.add_field(name="?yesno", + value="Gives Yes or No answer.", + inline=False) + + embed.add_field(name="?8ball", + value="Gives random 8ball answer.", + inline=False) + + return embed + + +def print_lists_message(array, length): + embed = discord.Embed(title="Show all lists", + description="Following lists are availible to you:", + color=0xFF5733) + for i in range(length): + item = array[i] + item = json.dumps(item) + item = item.replace('{"List_Name": "', '') + item = item.replace('"}', '') + embed.add_field(name=item, + value="\u200b", + inline=False) + embed.add_field(name="Support this project", + value="[Donate](https://www.paypal.com/donate?hosted_button_id=QY9QSBC63TL34)", + inline=False) + return embed diff --git a/randbot.py b/randbot.py new file mode 100644 index 0000000..18c406d --- /dev/null +++ b/randbot.py @@ -0,0 +1,54 @@ +import discord +import commands as com +import messages as mes +import tokens as tok +from discord.ext import tasks +import datetime + +client = discord.Client() + + +# Information Embed message when bot joins the server for the first time. +@client.event +async def on_guild_join(guild): + await guild.text_channels[0].send(embed=mes.guild_join_message()) + + +# Information to console when bot successfully logs in the server. +@client.event +async def on_ready(): + print('We have logged in as {0.user}'.format(client)) + + +# Reactions to message commands. +@client.event +async def on_message(message): + if message.author == client.user: + return + elif message.content.startswith('?random'): + await com.random_command(message) + elif message.content.startswith('?8ball'): + await com.eightball_command(message) + elif message.content.startswith('?yesno'): + await com.yesno_command(message) + elif message.content.startswith('?list'): + await com.list_command(client,message) + elif message.content.startswith('?showlists'): + await com.print_lists(message) + elif message.content.startswith('?delete'): + await com.delete_command(message) + elif message.content.startswith('?commands'): + await com.commands_command(message) + + +# daily DB clean loop +@tasks.loop(hours=24) +async def cleandb(): + await com.clean() + + print("DB cleaned " + datetime.datetime.utcnow().strftime('%B %d %Y - %H:%M:%S')) + +cleandb.start() + +# Connects the client using Discord bot token +client.run(tok.discord_token) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..a797332 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +discord +pymongo +asyncio +dnspython \ No newline at end of file diff --git a/tokens.py b/tokens.py new file mode 100644 index 0000000..2ace5fb --- /dev/null +++ b/tokens.py @@ -0,0 +1,2 @@ +mongo_token = '' +discord_token = ''