Deployment options for a Flask/PyMongo Discord Bot? - python-3.x
I've seen many tutorials on how to deploy just a Discord Bot or just a basic Flask Web App, however, this project packages both together where the Bot relies completely on interfacing with a REST api.
Presentation Layer: Discord UI. Handles user command input and bot output
Application Layer: Discord handlers that interface with and formats output based on data received from requests (bot.py)
Business Logic Layer: Flask interface that handles data from arguments passed from Discord user input. Handles CRUD requests (app.py)
Data Access Layer: MongoDB database
I currently have it set up to run the app using the routine in this answer for threading: run discord bot inside flask
$ python3 app.py
I've tried uploading the project to a google VM which works but when I exit, it goes offline. I've also tried following a digital ocean tutorial on Dockerizing a Flask and MongoDB app to deploy it to the cloud but it throws errors like
File "/var/www/wsgi.py", line 1, in <module>
flask | from app import app
flask | File "/var/www/app.py", line 5
flask | from import client
flask | ^
flask | SyntaxError: invalid syntax
even though there is no such error...
db.py
from flask import Flask
import pymongo
from pymongo import MongoClient
from app import app
CONNECTION_STRING = "mongodb+srv://<user>:<password>#cluster0.43ypd.mongodb.net/book-bot?retryWrites=true&w=majority"
client = MongoClient(CONNECTION_STRING)
db = client["book-bot"]
books_collection = db["books"]
app.py
from flask import Flask, render_template, request
from flask_mail import *
from random import *
from json import dumps, loads
from bot import client
from threading import Thread
from functools import partial
import time
import os
import db
from book import Book, Seller
app = Flask(__name__)
app.config["MAIL_SERVER"]='smtp.gmail.com'
app.config["MAIL_PORT"] = 465
app.config["MAIL_USERNAME"] = 'email#gmail.com'
app.config['MAIL_PASSWORD'] = 'password'
app.config['MAIL_USE_TLS'] = False
app.config['MAIL_USE_SSL'] = True
mail = Mail(app)
otp = randint(000000,999999)
# localhost:8000/book?course=<course>&number=<course-number>
#app.route('/book/<department>/<course_num>', methods=['GET'])
def book(department, course_num):
query = {"department": department, "course": course_num}
document = db.db.books_collection.find(query, {"_id": 0})
for book in document:
return dumps(book)
return dumps(None)
#app.route('/book/insert/<dep>/<cnum>/<name>/<link>/<buy_price>/<rent_price>', methods=["POST"])
def insert_book(dep, cnum, name, link, buy_price, rent_price):
# Initialize book with the GMU bookstore seller information
seller = Seller("GMU Bookstore", link, float(buy_price), float(rent_price), "Fairfax Campus", True)
# Initialize seller and book data with request params and seller data
bookstore_dict = {"name": seller.name, "link": seller.link,
"buy": seller.buy,
"buy_price": seller.buy_price,
"rent": seller.rent,
"rent_price": seller.rent_price,
"location": seller.location,
"verified": seller.verified
}
book = Book(name, dep, cnum, bookstore_dict)
# create dict struct to insert proper format into Mongo collection
book_dict = {
"name": book.name,
"department": book.department,
"course": book.course_name,
"sellers": book.sellers
}
# Insert book into Database
db.db.books_collection.insert_one(book_dict)
return f"Book name: {book.name} | Department: {book.department} | Course: {book.course_name}"
# example: localhost:8000/book/insert/seller/Mostafa/mostaf#gmu.edu/true/false/Faifax
# "http://localhost:8000/book/insert/seller/{dep}/{cnum}/{name}/{link}/{buy_price}/{rent_price}/{location}
#app.route('/book/insert/seller/<dep>/<cnum>/<name>/<link>/<buy_price>/<rent_price>/<location>', methods=["POST"])
def insert_seller(dep, cnum, name, link, buy_price, rent_price, location, methods=["POST"]):
# temporary solution to interrupted POST request due to # tag in string passed into request
name_arg = name.split("#")
name = name_arg[0] + "#" + name_arg[1]
seller = Seller(name, link, float(buy_price), float(rent_price), location, False)
seller_dict = {
"name": seller.name,
"link": seller.link,
"buy": seller.buy,
"buy_price": seller.buy_price,
"rent": seller.rent,
"rent_price": seller.rent_price,
"location": seller.location,
"verified": seller.verified,
}
query = {"department": dep, "course": cnum}
book = db.db.books_collection.find(query)
for doc in book:
db.db.books_collection.update_one({"_id": doc["_id"]}, {"$push": {"sellers": seller_dict}})
return "Added seller to book"
#app.route('/verify/<email>/<dep>/<cnum>', methods=["POST"])
def verify(email, dep, cnum):
send_otp = f"{otp}-{dep}-{cnum}"
msg = Message('Verify Student Seller', sender='email#gmail.com', recipients=[email])
msg.body = f"Please click on the link to verify your student seller status\n {send_otp}\nhttp://localhost:8000/"
mail.send(msg)
return "Success"
#app.route('/', methods=["GET"])
def load_validation():
return render_template("email.html")
#app.route('/validate', methods=["POST"])
def validate():
user_email = request.form['email']
user_otp = request.form['otp']
args = user_otp.split('-')
# check if numbervalue is equal to otp generated
if int(args[0]) == otp:
query = {"department": args[1], "course": args[2]}
book = db.db.books_collection.find(query)
for doc in book:
db.db.books_collection.update_one(query, {"$set": {"sellers.$[t].verified": True}},
array_filters=[{"t.link": user_email}])
return "<h3>Your seller status has been activated.</h3>"
return "<h3>failure, OTP does not match</h3>"
def flask_thread(func):
thread = Thread(target=func)
print('Start Separate Thread From Bot')
thread.start()
def run():
app.run(host='0.0.0.0', port=8000, use_reloader=False)
if __name__ == '__main__':
flask_thread(func=run)
client.run('bot-token', bot=True)
bot.py
import discord
from discord.ext import commands
import os
import requests
import json
import asyncio
client = discord.Client()
# client = commands.Bot(command_prefix="!", case_insensitive=True)
def insert_seller(dep, cnum, link, buy_price, rent_price, location, name):
response = requests.post(f"http://localhost:8000/book/insert/seller/{dep}/{cnum}/{name}/{link}/{buy_price}/{rent_price}/{location}")
return response
def verify_student(email, dep, cnum):
if ('#gmu.edu' in email):
response = requests.post(f"http://localhost:8000/verify/{email}/{dep}/{cnum}")
return response
return "Please enter a valid email"
def get_book(department, course_num):
response = requests.get(f"http://localhost:8000/book/{department}/{course_num}")
if (response == None):
return None
json_data = json.loads(response.text)
return json_data
#client.event
async def on_ready():
print("We have logged in as {0.user}".format(client))
#client.event
async def on_message(message):
if (message.content.startswith("!bot help")):
msg = "To check for a course textbook enter:\n'!bot check <department> <course_num>\n\nTo add yourself as a seller enter:\n'!bot add <department> <course_num> <your_name> <gmu_email> <buy_price> <rent_price> <city>'"
await message.channel.send(msg)
if (message.content.startswith('!bot check')):
args = message.content.split()
book = get_book(args[2], args[3])
embed = discord.Embed(title=f"{book['department']} {book['course']} Textbook", description=f"\n{book['name']}\n", color=0xFFD700)
for i in book['sellers']:
# hyperlink the bookstore link because it's long
if i['buy']:
options = "buy"
if i['rent']:
options += "/rent"
if i['name'] == "GMU Bookstore":
embed.add_field(name=f"{i['name']}", value=f"[Bookstore link]({i['link']})\nOptions: {options}\nLocation: {i['location']}",inline=False)
else:
embed.add_field(name=f"{i['name']} ({i['link']})", value=f"Options: {options}\nLocation: {i['location']}",inline=False)
embed.set_author(name=message.author.display_name, icon_url=message.author.avatar_url)
embed.set_footer(text="Powered by students. This is not an official GMU service.")
await message.channel.send(embed=embed)
if (message.content.startswith('!bot add')):
args = message.content.split()
book = get_book(args[2], args[3])
if (book == None):
await message.channel.send("Course requirements not found.")
return
def check(msg):
return msg.author == message.author and 'Y' in msg.content
embed = discord.Embed(title=f"Is this your book (enter Y/N)?\n\n", description=f"\n{book['name']}\n", color=0xFFD700)
await message.channel.send(embed=embed)
try:
msg = await client.wait_for("message", check=check, timeout=30)
except asyncio.TimeoutError:
await message.channel.send("Sorry, you didn't reply in time.")
if (msg.content):
# name will save the seller with their discord user handle
name = f"{message.author}"
# Request will 404 because of '#' symbol
regs = name.split('#')
insert_seller(args[2], args[3], args[4], args[5], args[6], args[7], regs[0] + "#" + regs[1])
verify_student(args[4], args[2], args[3])
await message.channel.send("Sending verification email!")
else:
await message.channel.send("Sorry you can't add that.")
Related
Telegram API Bot to invite subscribers of other channels to my own
I want to invite the members of other telegram channels to my channel, but getting this error THE Error is: raise ConnectionError('Connection to Telegram failed {} time(s)'.format(self._retries)) ConnectionError: Connection to Telegram failed 5 time(s) I do not know why I am getting this error, I have checked it with a proxy but it remained the same. If anyone of you know about this type of error please let me know. I have no idea about this. I do know about this. This is basically a telegram api bot that I want to be completely done with no errors. import configparser import json import asyncio import sys import socks from telethon import TelegramClient from telethon.tl.types import InputPhoneContact from telethon.tl.functions.contacts import ImportContactsRequest from telethon.errors import FloodWaitError, RPCError #import proxy from telethon import TelegramClient from telethon.errors import SessionPasswordNeededError from telethon.tl.functions.channels import GetParticipantsRequest from telethon.tl.types import ChannelParticipantsSearch from telethon.tl.types import ( PeerChannel ) # Reading Configs config = configparser.ConfigParser() config.read("config.ini") # Setting configuration values api_id = 1234567 api_hash = 'abcdefghjklmnopqrstuvwxyz1234567' phone = '+123456789000' username = '#abcdef' #proxy='180.179.98.22:3128' # Create the client and connect client = TelegramClient(username, api_id, api_hash, proxy=(socks.SOCKS5, '166.62.80.198', 18726)) async def main(phone): await client.start() print("Client Created") # Ensure you're authorized if await client.is_user_authorized() == False: await client.send_code_request(phone) try: await client.sign_in(phone, input('Enter the code: ')) except SessionPasswordNeededError: await client.sign_in(password=input('Password: ')) me = await client.get_me() user_input_channel = input("enter entity(telegram URL or entity id):") if user_input_channel.isdigit(): entity = PeerChannel(int(user_input_channel)) else: entity = user_input_channel my_channel = await client.get_entity(entity) offset = 0 limit = 100 all_participants = [] while True: participants = await client(GetParticipantsRequest( my_channel, ChannelParticipantsSearch(''), offset, limit, hash=0 )) if not participants.users: break all_participants.extend(participants.users) offset += len(participants.users) all_user_details = [] for participant in all_participants: all_user_details.append( {"id": participant.id, "first_name": participant.first_name, "last_name": participant.last_name, "user": participant.username, "phone": participant.phone, "is_bot": participant.bot}) with open('user_data.json', 'w') as outfile: json.dump(all_user_details, outfile) with client: client.loop.run_until_complete(main(phone))
While multithreading, Flask Error sugests using app_context--but that doesn't work
I've created a route to test sending an email. When visiting /book_blast, i get a runtime error. I've messed around with adding app_context() in different ways, but none of them has eliminated the error. If i remove the sending of the emails, it works. If i remove the threaded sending of emails, and send them normally, it works. #books.route("/book_blast") def book_blast(): all_free_books = Book.query.all() # pass in: username, books, token subscribers = Mail_Subscription.query.filter(Mail_Subscription.daily_promotions == True).all() # send emails one-by-one based on user preferences for subscriber in subscribers: user = subscriber.user email = subscriber.email name = 'Subscriber' if user: # if there is a connected user and not just an email subscriber name = user.first_name + " " + user.last_name if user.first_name and user.last_name else user.username token_url = url_for('books.email_preferences') + "/" + get_unsubscribe_token_for_email_list(email) books = [book for book in all_free_books if book.stars >= subscriber.rating and book.reviews >= subscriber.reviews and book.genre in subscriber.genres.split( ',')] print(f'{name} ({email}) will receive: {books}') html = render_template('book_blast.html', name=name, token_url=token_url, books=books) msg = Message(f'Free Books {datetime.datetime.now().date()}', sender=current_app.config['MAIL_DEFAULT_SENDER'], recipients=[email]) msg.body = f'We have {len(books)} for free today. Visit {url_for("books.email_preferences")} to read more.' msg.html = html # send emails """ app = current_app with app.app_context(): """ app = current_app app.app_context() with concurrent.futures.ThreadPoolExecutor() as executor: future = executor.submit(mail.send, msg) future.result() return html
Id suggest you to create something like send_email.py that looks something like this. from flask import current_app, render_template from flask_mail import Message from threading import Thread from . import mail def send_async_mail(app, msg): with app.app_context(): mail.send(msg) def send_email(to, subject, template, **kwargs): app = current_app._get_current_object() msg = Message(app.config['MAIL_SUBJECT_PREFIX'] + subject, sender=app.config['MAIL_SENDER'], recipients=[to]) msg.body = render_template(template + '.txt', **kwargs) msg.html = render_template(template + '.html', **kwargs) thr = Thread(target=send_async_mail, args=[app, msg]) thr.start() return thr After this inside your view just import the send_mail function send_email(TO, 'TITLE!','template/location') Also i was using flask mail here too... hope it will help you out a bit.
Discord.py creates error by reactions. How do I fix?
I have a problem with the discord.py Python library. I'm getting an error, every time someone reacts to messages. Python do not show me, where the error comes from, but I think it's an error in the discord.py. Last week the code worked but now its not working anymore. I'm becoming a headache about it. Can someone help my please? Here is my code: import discord from emojis import emojis import msg from discord.utils import get from discord.ext import commands import json from uuid import uuid4 shop_channels = [] orders = {} cmd_prefix = "dsb!" def check_perms(user): for role in user.roles: if role.name == "𝗢𝘄𝗻𝗲𝗿": return True return False def check_command(message, command): if message.startswith(cmd_prefix + command): return True else: return False def save_settings(): settings = {} settings["shop_channels"] = shop_channels settings["orders"] = orders with open("data.json", "w") as file: json.dump(settings, file) def load_settings(): global shop_channels global orders with open("data.json") as file: settings = json.load(file) orders = settings["orders"] shop_channels = settings["shop_channels"] print(shop_channels) class MyClient(discord.Client): async def on_ready(self): self.msg = msg.Msg(True, "DiShoBo") print('-------') print('Logged in as') print('Username:', self.user.name) print('ID:', self.user.id) print('------', end="\n\n") load_settings() print("Settings loaded!\n\n") async def on_reaction_add(self, reaction, user): print("hello") if shop_channels is None or reaction.message.channel.id not in shop_channels: return try: if reaction.emoji == emojis["shopping_cart"]: order_id = uuid4() item = reaction.message.content.partition('\n')[0] amount = reaction.message.content.partition('\n')[2] self.msg.debug("Got new order from user {0} for Item: {1}! OrderID: {2}".format( user, item, order_id)) if user.dm_channel == None: await user.create_dm() message = await user.dm_channel.send("You ({2}) added the Item: '{0}' for {1} to your shopping cart!\nClick on :white_check_mark: to finish your order then go back to Angels 2B2T shop, or if you want to keep shopping, just go back to the shop and add more items.\nIf you want to clear your shopping cart, click the {3} below.\n\nYour OrderID is: {4}".format(item, amount, user.mention, emojis["cross_mark"], order_id)) # await message.add_reaction(emojis["check_mark_box"]) # await message.add_reaction(emojis["cross_mark"]) if not message.author in orders: orders.update( {message.author: {"user": message.author, "ID": order_id, "items": []}}) print(orders[message.author]["items"]) orders[message.author]["items"].append({item, amount}) print(orders[message.author]["items"]) if not reaction.me and reaction.count > 1: await reaction.remove(user) else: await reaction.remove(user) except: pass # * CONNECT client = MyClient() client.run('TOKEN')
This was a bug on the hand of the discord.py library but was fixed in the update 1.3.2.
How to store websockets at the server side with aiohttp?
I'm trying to make simple web-chat with rooms using aiohttp. Can you please advise me how to store my websockets connections? Some code below are simplified a bit. I'm getting an EOF error from socket time by time (and I can reproduce it), but i don't know why. So, i got a question, am i do it right? Should i close websockets everytime when i reload or follow to link? If not, so, how i will connect client with my already opened socket? Sorry for my eng ^^ thanks. app.py import asyncio import aiohttp_jinja2 import jinja2 import hashlib import collections import os from aiohttp_session import session_middleware from aiohttp_session.cookie_storage import EncryptedCookieStorage from aiohttp import web from routes import routes from middlewares import authorize from motor import motor_asyncio as ma from settings import * basedir = os.path.dirname(os.path.realpath(__file__)) photo_dir = os.path.join(basedir, 'static/photo/') async def on_shutdown(app): for ws in app['websockets']: await ws.close(code=1001, mesage='Server shutdown') middle = [ session_middleware(EncryptedCookieStorage(hashlib.sha256(bytes(SECRET_KEY, 'utf-8')).digest())), authorize ] app = web.Application(middlewares=middle) aiohttp_jinja2.setup(app, loader=jinja2.FileSystemLoader('templates')) for route in routes: app.router.add_route(*route[:3], name=route[3]) app['static_root_url'] = '/static' app.router.add_static('/static', 'static', name='static') app.client = ma.AsyncIOMotorClient(MONGO_HOST) app.db = app.client[MONGO_DB_NAME] app.on_cleanup.append(on_shutdown) app['websockets'] = collections.defaultdict(list) app['online'] = {} app['photo_dir'] = photo_dir web.run_app(app) and websocket handler class CompanyWebSocket(web.View): async def get(self): ws = web.WebSocketResponse() await ws.prepare(self.request) session = await get_session(self.request) self_id = session.get('user') login = session.get('login') company_id = self.request.rel_url.query.get('company_id') message = Message(self.request.app.db) company = Company(self.request.app.db) my_companys = await company.get_company_by_user(self_id) for c in my_companys: self.request.app['websockets'][str(c['_id'])].append(ws) async for msg in ws: if msg.type == WSMsgType.TEXT: if msg.data == 'close': await ws.close() else: await message.save_for_company({'data': 'data'}) mess = { 'data': 'data' } # send mess to users in company for company_ws in self.request.app['websockets'][company_id]: await company_ws.send_json(mess) elif msg.type == WSMsgType.ERROR: log.debug('ws connection closed with exception %s' % ws.exception()) try: self.request.app['websockets'][company_id].remove(ws) except: pass for _ws in self.request.app['websockets'][company_id]: await _ws.send_json({'user': login, 'type': 'left'}) return ws
Easy integration of chatbot with slack-app
I have a ChatBot application running, just want to hook this application with Slack-api as it's interface. I used Slack RTM and maintained user-session with its slack user-id. finally solved and written a client(API) which can easily connect to any conversation engine. Github repo link- https://github.com/csemanmohan/Slack_api_client import time import re from slackclient import SlackClient import requests # 'url', chatbot endpoint and 'slack_token' is slack application user-access-token url = "http://127.0.0.1:****/*******/v2/api" slack_token = "xoxb-**********-***********-*************lipO8hoI" # instantiate Slack client slack_client = SlackClient(slack_token) # starterbot's user ID in Slack: value is assigned after the bot starts up starterbot_id = None # constants RTM_READ_DELAY = 1 # 1 second delay between reading from RTM EXAMPLE_COMMAND = "do" MENTION_REGEX = "^<#(|[WU].+?)>(.*)" def parse_bot_commands(slack_events): """ Parses a list of events coming from the Slack RTM API to find bot commands. If a bot command is found, this function returns a tuple of command and channel. If its not found, then this function returns None, None. """ # below var msg and channel_var will be used/ # when no trigger(#app-name) passed from application msg = "" channel_def = "" for event in slack_events: if event["type"] == "message" and not "subtype" in event: msg = event["text"] channel_def = event["channel"] user_id, message = parse_direct_mention(event["text"]) print("there is an event here...", user_id, message) if user_id == starterbot_id: return message, event["channel"] channel_def = channel_def return msg, channel_def def parse_direct_mention(message_text): """ Finds a direct mention (a mention that is at the beginning) in message text and returns the user ID which was mentioned. If there is no direct mention, returns None """ matches = re.search(MENTION_REGEX, message_text) # the first group contains the username, the second group contains the remaining message return (matches.group(1), matches.group(2).strip()) if matches else (None, None) def handle_command(command, channel): """ Executes bot command if the command is known """ # Default response is help text for the user default_response = "Not sure what you mean. Try *{}*.".format(EXAMPLE_COMMAND) # Implemented below code-snippet for making API call to ChatBot input_text = command payload = {"text": input_text, "email": "manmohan#m******.com"} headers = {'content-type': "application/json"} resp = requests.request("POST", url, json=payload, headers=headers) result = eval(resp.json()) print("result is: ", result) response = result['text'] # Sends the response back to the channel slack_client.api_call( "chat.postMessage", channel=channel, text=response or default_response ) if __name__ == "__main__": if slack_client.rtm_connect(with_team_state=False): print("Starter Bot connected and running!") # Read bot's user ID by calling Web API method `auth.test` starterbot_id = slack_client.api_call("auth.test")["user_id"] while True: command, channel = parse_bot_commands(slack_client.rtm_read()) if command: handle_command(command, channel) time.sleep(RTM_READ_DELAY) else: print("Connection failed. Exception traceback printed above.")