Related
I have written a discord bot in python in VS code. The code consists of 1 init.py file, and 9 Cog files.
example of my init.py:
#bot.event
async def on_ready():
guild_count = 0
for guild in bot.guilds:
if guild.id == 941021482144903178:
guild_count += 1
for jsonguild in guilds["guilds"]:
if jsonguild["id"] == str(guild.id):
break
else:
guilds["guilds"].append({
"id": str(guild.id),
"prefix": "!",
"tornapikey": "",
"tornapikey2": "",
"tornapikey3": ""
})
dbutils.write("guilds", guilds)
if str(guild.id) not in aractions:
aractions[guild.id] = {
"lastscan": 0,
"requests": []
}
dbutils.write("armory", aractions)
for member in guild.members:
if str(member.id) in users:
continue
if member.bot:
continue
users[member.id] = {
"tornid": "",
"tornapikey": "",
"generaluse": False
}
dbutils.write("users", users)
print(f'Bot is in {guild_count} guilds.')
print(f'{bot.user} has connected to Discord!')
await bot.add_cog(admin.Admin(botlogger, bot, client))
example of admin.py:
class Admin(commands.Cog):
def __init__(self, logger, bot, client):
self.logger = logger
self.bot = bot
self.client = client
#commands.command()
#commands.cooldown(1, 5, commands.BucketType.user)
async def config(self, ctx, arg=None, value=None):
'''
Returns the current configuration of the bot
'''
if not check_admin(ctx.message.author) and dbutils.get_superuser() != ctx.message.author.id:
embed = discord.Embed()
embed.title = "Permission Denied"
embed.description = f'This command requires {ctx.message.author.name} to be an Administrator. ' \
f'This interaction has been logged.'
await ctx.send(embed=embed)
self.logger.warning(f'{ctx.message.author.name} has attempted to run config, but is not an Administrator')
return None
embed = discord.Embed()
if not value:
embed.title = "Value Error"
embed.description = "A value must be passed"
elif arg == "key":
data = dbutils.read("guilds")
for guild in data["guilds"]:
if guild["id"] == str(ctx.guild.id):
guild["tornapikey"] = str(value)
dbutils.write("guilds", data)
self.logger.info(f'{ctx.message.author.name} has set the primary Torn API Key.')
embed.title = "Torn API Key"
embed.description = f'The Torn API key for the primary faction has been set by {ctx.message.author.name}.'
await ctx.message.delete()
elif arg == "key2":
data = dbutils.read("guilds")
for guild in data["guilds"]:
if guild["id"] == str(ctx.guild.id):
guild["tornapikey2"] = str(value)
dbutils.write("guilds", data)
self.logger.info(f'{ctx.message.author.name} has set the secondary Torn API Key.')
embed.title = "Torn API Key"
embed.description = f'The Torn API key for the secondary faction has been set by {ctx.message.author.name}.'
await ctx.message.delete()
elif arg == "key3":
data = dbutils.read("guilds")
for guild in data["guilds"]:
if guild["id"] == str(ctx.guild.id):
guild["tornapikey3"] = str(value)
dbutils.write("guilds", data)
self.logger.info(f'{ctx.message.author.name} has set the secondary Torn API Key.')
embed.title = "Torn API Key"
embed.description = f'The Torn API key for the secondary faction has been set by {ctx.message.author.name}.'
await ctx.message.delete()
else:
embed.title = "Configuration"
embed.description = "This key is not a valid configuration key."
if embed is not None:
await ctx.send(embed=embed)
These codes run perfectly in VS code, connects to Discord and all of the commands work as they should. When I try and run them on my Discord Bot Hosting Server (Something.Host) I get the following error with no code changes:
Ignoring exception in on_ready
Traceback (most recent call last):
File "/home/container/.local/lib/python3.8/site-packages/discord/client.py", line 343, in _run_event
await coro(*args, **kwargs)
File "__init__.py", line 85, in on_ready
TypeError: object NoneType can't be used in 'await' expression
await bot.add_cog(admin.Admin(botlogger, bot, client))
I have uninstalled and reinstalled numerous packages. My python verion on VS is 3.10.7, and on the server it is python 3.10x.
I have also tried adding this in admin.py
async def setup(logger, bot, client):
await bot.add_cog(Admin(logger, bot, client))
and this in init.py
files = ['admin']
for cogs in files:
bot.load_extension(cogs)
but this didn't work either.
Please could you help me find out why my Cog is returning a NoneType in my server, but not in VS code?
My bot logged a whole bunch of DEBUG stuff when trying to run, will try and provide a link to the file
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.")
I am in the process of moving a python process to Spark. In python we are using ftplib to connect and download a file to a EC2 instance. Once file is downloaded, we are uploading to S3. We are transitioning to severless infrastructure and would like to load file in spark via AWS Glue and then use mulit-part upload to move it to S3. I have tried to just run the current code in a in a larger glue instance type but the machine still runs out of memory (20gb file).
old python code
"""
This script will get the backup file
"""
import sys
from datetime import datetime
import re
import ftplib
from retry import retry
import shutil
from tools.python.s3_functions import s3_upload
from python_scripts.get import *
def get_ftp_connector(path, user, password):
ftp = ftplib.FTP_TLS(path)
ftp.login(user, password)
ftp.prot_p()
return ftp
def get_ftp_files_list(ftp, dir):
ftp.cwd(dir)
files = ftp.nlst()
print(str("-".join(files)))
if "filecompleted.txt" not in files:
print("Failed to find filescompleted.txt file in ftp server.")
raise Exception("Failed to find filescompleted.txt file in ftp server.")
regex_str = 'Backup_File_Mask_Goes_here([\d]{8}).bak'
find_date_regex = re.compile(regex_str)
searched = [(f, find_date_regex.match(f)) for f in files if find_date_regex.match(f)]
searched = \
[(file_name, datetime.strptime(regex_result.groups()[0], '%Y%m%d')) for file_name, regex_result in searched]
searched = sorted(searched, key=lambda elem: elem[1], reverse=True)
if not searched:
print("Failed to find appropriate file in ftp server.")
raise Exception("Failed to find appropriate file in ftp server.")
return searched[0]
class FtpUploadTracker:
size_written = 0
total_size = 0
last_shown_percent = "X"
def __init__(self, total_size, bk_file):
self.total_size = total_size
self.bk_file = bk_file
self.output_file = open(self.bk_file, 'wb')
self.start_time = datetime.now()
def handle(self, block):
self.size_written += len(block)
percent_complete = str(round((self.size_written / self.total_size) * 100, 1))
self.output_file.write(block)
time_elapsed = (datetime.now() - self.start_time).total_seconds()
speed = round(self.size_written / (1000 * 1000 * time_elapsed), 2)
msg = "{percent}% complete # average speed of {speed}MB/s : total run time {minutes}m".\
format(percent=percent_complete, speed=speed, minutes=round(time_elapsed/60))
if time_elapsed > 600 and speed < 1:
print("Zombie connection, failing dl.")
raise Exception("Zombie connection, failing dl.")
if self.last_shown_percent != percent_complete:
self.last_shown_percent = percent_complete
print(msg)
def close(self):
self.output_file.close()
#retry(tries=4, delay=300)
def retrieve_db():
"""
This function will retrieve via FTP the backup
:return: None
"""
ftp = get_ftp_connector(FTP_PATH, FTP_USER, FTP_PASSWORD)
# return back the most recent entry
file_name, file_date = get_ftp_files_list(ftp, 'database')
file_epoch = (file_date - datetime(1970, 1, 1)).total_seconds()
new_file_name = "backup_{epoch}.bak".format(epoch=str(int(file_epoch)))
if os.path.exists(DATAFILEPATH):
shutil.rmtree(DATAFILEPATH)
if not os.path.exists(DATAFILEPATH):
os.makedirs(DATAFILEPATH)
temp_backup_file_location = os.path.join(DATAFILEPATH + new_file_name)
print("Found file {file_name}, and downloading it to {loc}".
format(file_name=file_name, loc=temp_backup_file_location))
ftp_handler = FtpUploadTracker(ftp.size(file_name), temp_backup_file_location)
ftp.retrbinary("RETR " + file_name, ftp_handler.handle)
ftp.quit()
ftp_handler.close()
print("Finished download. Uploading to S3.")
s3_upload(DATAFILEPATH, new_file_name, bucket, "db_backup")
os.remove(temp_backup_file_location)
def main():
try:
retrieve_db()
except Exception as e:
print("Failed to download backup after 4 tries with error {e}.".format(e=e))
return 1
return 0
if __name__ == "__main__":
rtn = main()
sys.exit(rtn)
New Spark Code (in progress): The username has a | character that made me encode the uri. When I run the code, I get a connection refused. I am able to use same connection info for python.
from pyspark import SparkContext
from pyspark import SparkFiles
import urllib
sc = SparkContext()
ftp_path = "ftp://Username:password#ftplocation.com/path_to_file"
file_path_clean = urllib.parse.urlencode(ftp_path, safe='|')
print(f"file_path_clean: {file_path_clean}")
sc.addFile(ftp_path)
filename = SparkFiles.get(file_path.split('/')[-1])
print(f"filename: {filename}")
rdd = sc.textFile("file://" + filename)
print("We got past rdd = sc.textFile(file:// + filename)")
rdd.take(10)
rdd.collect()
print(rdd)
There are three ways to approach the problem:
Use a mounted file system backed by FTP and write to it from Spark.
Use a Spark to SFTP connector such as spark-sftp.
Write the files with Spark somewhere else and copy to SFTP as a separate step. Due to the various reliability issues with SFTP and the fact that Spark leaves partial output during failed write operations, which is the path that we've taken. We write terabytes to SFTP endpoints using code that looks like the following in Scala. I hope it can be helpful for you Python work.
/** Defines some high-level operations for interacting with remote file protocols like FTP, SFTP, etc.
*/
trait RemoteFileOperations extends Closeable {
var backoff: BlockingRetry.Backoff = Backoff.linear(3000)
var retry: BlockingRetry.Retry = Retry.maxRetries(3)
var recover: Recovery = recoverable(this)
var ignore: Ignored = nonRecoverable
def listFiles(path: String = ""): Seq[FInfo]
def uploadFile(localPath: String, remoteDirectory: String): Unit
def downloadFile(localPath: String, remotePath: String): Unit
def deleteAll(path: String): Unit
def connect(): Unit = {}
def disconnect(): Unit = {}
def reconnect(): Unit = {
disconnect()
connect()
}
override def close(): Unit = disconnect()
/** Wraps a block of code and allows it to be retried when [[recoverable()]] conditions
* are met. [[BlockingRetry.retry()]] is called with the var fields
* [[backoff]], [[retry]], [[recover]], and [[ignore]], which can all be reconfigured.
*/
def retryable[A](f: => A): A = {
BlockingRetry.retry(retry, backoff, recover, ignore) {
f
}
}
def recoverable(fileOp: RemoteFileOperations): Recovery = {
case (_: SocketTimeoutException, _: Int) =>
fileOp.reconnect()
None
}
def nonRecoverable: Ignored = {
case _: UnknownHostException |
_: SSLException |
_: SocketException |
_: IllegalStateException =>
}
}
class SSHJClient(host: String, username: String, password: String) extends RemoteFileOperations {
import net.schmizz.keepalive.KeepAliveProvider
import net.schmizz.sshj.connection.ConnectionException
import net.schmizz.sshj.sftp.SFTPClient
import net.schmizz.sshj.transport.verification.PromiscuousVerifier
import net.schmizz.sshj.xfer.FileSystemFile
import net.schmizz.sshj.{DefaultConfig, SSHClient}
override def listFiles(path: String): Seq[FInfo] = {
import collection.JavaConverters._
retryable {
sftpSession(sftp => {
sftp.ls(path).asScala
.filter(f => f.getName != "." && f.getName != "..")
.map(f => FInfo(f.getPath, f.getParent, f.isDirectory, f.getAttributes.getSize, f.getAttributes.getMtime))
})
}
}
override def uploadFile(localPath: String, remoteDirectory: String): Unit = {
retryable {
sftpSession(sftp => {
sftp.getFileTransfer.setPreserveAttributes(false)
sftp.put(new FileSystemFile(localPath), remoteDirectory)
})
}
}
override def downloadFile(localPath: String, remotePath: String): Unit = {
retryable {
sftpSession(sftp => {
sftp.getFileTransfer.setPreserveAttributes(false)
sftp.get(remotePath, new FileSystemFile(localPath))
})
}
}
override def deleteAll(path: String): Unit =
throw new UnsupportedOperationException("#deleteAll is unsupported for SSHJClient")
private def sftpSession[A](f: SFTPClient => A): A = {
val defaultConfig = new DefaultConfig()
defaultConfig.setKeepAliveProvider(KeepAliveProvider.KEEP_ALIVE)
val ssh = new SSHClient(defaultConfig)
try {
// This is equivalent to StrictHostKeyChecking=no which is disabled since we don't usually know
// the SSH remote host key ahead of time.
ssh.addHostKeyVerifier(new PromiscuousVerifier())
ssh.connect(host)
ssh.authPassword(username, password)
val sftp = ssh.newSFTPClient()
try {
f(sftp)
} finally {
sftp.close()
}
} finally {
ssh.disconnect()
}
}
override def recoverable(fileOp: RemoteFileOperations): Recovery = {
super.recoverable(fileOp).orElse {
case (e: ConnectionException, _: Int) =>
println(s"Recovering session from exception: $e")
None
}
}
}
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.
I am new to python. I am trying to send a response showing a custom keyboard with two buttons to the user in Telegram app but it gives me an error about encode.
I would like to know where is my mistake.
import json
from urllib.request import urlopen
from urllib.parse import quote, unquote
import time
def Decode(telegram_response):
decoded=''
for line in telegram_response:
decoded=decoded+line.decode('utf-8')
return decoded
TOKEN = 'mytoken'
URL = 'https://api.telegram.org/bot{}/'.format(TOKEN)
cmd = 'getme'
telegram_response = urlopen(URL + cmd)
decoded = Decode(telegram_response)
gtm = json.loads(decoded)
status = True
while status:
cmd = 'getUpdates'
telegram_response = urlopen(URL + cmd)
decoded = Decode(telegram_response)
upds = json.loads(decoded)
new_message = len(upds['result'])
if new_message !=0:
msg = upds['result'][0]['message']
chat_id = str(msg['chat']['id'])
reply_markup = {'keyboard': [[{'text':'first button'}],[{'text':'second button'}]], 'resize_keyboard': True, 'one_time_keyboard': True}
reply_markup = json.dumps(reply_markup)
params = ({'chat_id': chat_id, 'reply_markup': reply_markup, 'disable_web_page_preview': 'true'})
myresponse =urlopen(URL + 'sendMessage' + quote((params).encode('utf-8')))
Easy way to build powerful bots is to use python-telegram-bot library.
I re-writed your code with few major fixes and features. Hope it help you to learn bots more deeper.
my version of bot:
###############################################################################
!#/usr/bin/python3
from sys import exc_info as error
from urllib.request import urlopen
from urllib.parse import urlencode
import json
TOKEN = 'XXXXXXXXXXXXXXXXXXXXXXXXXXX'
URL = 'https://api.telegram.org/bot{}'.format(TOKEN)
STATUS = True
OFFSET = 0
def call_api_method(method='getMe', data=None):
# Call API method with data.
data = urlencode(data).encode("utf-8") if data else data
response = urlopen('{}/{}'.format(URL, method), data)
return json.loads(response.read())
def get_me():
# Get bot info.
bot = call_api_method()
return type('Bot', (), dict(bot['result']))
def get_updates():
# Get new updates from Telegram.
data = {'offset': OFFSET, 'limit': 0, 'timeout': 0}
return type('Updates', (), call_api_method('getUpdates', data))
def handle(update):
# Make usefull objects.
message = type('Message', (object,), dict(update['message']))
user = type('User', (), dict(update['message']['from']))
chat = type('Chat', (), dict(update['message']['chat']))
return message, user, chat
def send_message(chat_id, message):
# Send message to specific chat.
data = {'text': message,
'chat_id': chat_id,
'parse_mode': 'Markdown',
'disable_web_page_preview': True}
call_api_method('sendMessage', data)
def send_keyboard(chat_id, message, keyboard):
# Send message and keyboard to specific chat.
data = {'text': message,
'chat_id': chat_id,
'parse_mode': 'Markdown',
'reply_markup': reply_markup(keyboard),
'disable_web_page_preview': 'true'}
call_api_method('sendMessage', data)
def reply_markup(keyboard):
# Serialize keyboard data to JSON.
return json.dumps({'keyboard': keyboard,
'resize_keyboard': True,
'one_time_keyboard': True,
'selective': True})
def main_keyboard():
# Main menu.
return [first_button(), second_button()]
def one_line_keyboard():
# Menu with buttons in one line.
return [two_buttons()]
def first_button():
# Single keyboard button.
return ['first button']
def second_button():
# Single keyboard button.
return ['second button']
def two_buttons():
# Two buttons on one line.
return ['left button', 'right button']
while STATUS:
# Get updates forever. Except if get Error.
try:
if not OFFSET:
OFFSET = 1
# Print bot info on the start.
bot = get_me()
print('Bot #{} is running...'.format(bot.username))
updates = get_updates()
for update in updates.result:
# Handle last update.
OFFSET = update['update_id'] + 1
message, user, chat = handle(update)
# Greeting user by full name.
greeting = 'Hello, {} {}!'.format(user.first_name, user.last_name)
#send_message(chat.id, greeting)
send_keyboard(chat.id, greeting, one_line_keyboard())
except:
STATUS = False
print('\nERROR:\t', error()[1])
###############################################################################
you can this code: I hope useful for you .
i change :
params = ({'chat_id': chat_id, 'reply_markup': reply_markup, 'disable_web_page_preview': 'true'})
myresponse =urlopen(URL + 'sendMessage' + quote((params).encode('utf-8')))
with:
params = ({'text': 'ss', 'chat_id': chat_id, 'reply_markup': reply_markup, 'disable_web_page_preview': 'true'})
data = urllib.parse.urlencode(params).encode("utf-8")
myresponse = urlopen(URL + 'sendMessage', data)
complate code :
import json
import urllib
from urllib.parse import quote
from urllib.request import urlopen
def Decode(telegram_response):
decoded = ''
for line in telegram_response:
decoded = decoded + line.decode('utf-8')
return decoded
TOKEN = 'XXXXXXXXXXXXXXXXXXXXXXXXXXX'
URL = 'https://api.telegram.org/bot{}/'.format(TOKEN)
cmd = 'getme'
telegram_response = urlopen(URL + cmd)
decoded = Decode(telegram_response)
gtm = json.loads(decoded)
status = True
while status:
cmd = 'getUpdates'
telegram_response = urlopen(URL + cmd)
decoded = Decode(telegram_response)
upds = json.loads(decoded)
new_message = len(upds['result'])
if new_message != 0:
msg = upds['result'][0]['message']
chat_id = str(msg['chat']['id'])
reply_markup = {'keyboard': [[{'text': 'first button'}], [{'text': 'second button'}]], 'resize_keyboard': True,
'one_time_keyboard': True}
reply_markup = json.dumps(reply_markup)
params = ({'text': 'ss', 'chat_id': chat_id, 'reply_markup': reply_markup, 'disable_web_page_preview': 'true'})
data = urllib.parse.urlencode(params).encode("utf-8")
myresponse = urlopen(URL + 'sendMessage', data)