Python Telegram bot on Heroku can't access Google Spreadsheet - heroku

I am deplying a python program on Heroku. Everything works fine locally, but it doesn't seem to run on Heroku
The bot.py file contains two variables which are saved as config in Heroku: TOKEN and SPREADSHEET, these are the Token of the telegram bot and the ID of the google Spreadsheet I am trying to access.
When the code is running on Heroku, I get this:
The code seems to be running on the Heroku server, but when I try to send the command on the Telegram bot, it doesn't work
at=info method=POST path="/" host=fff-transparency-wg.herokuapp.com request_id=d883bfc2-24a3-4cb5-b638-36b310726780 fwd="91.108.6.81" dyno=web.1 connect=1ms service=5ms status=200 bytes=172 protocol=https
The program contains the files:
bot.py
client_secret.json
Procfile
requirements.txt
runtime.txt
BOT.PY
from telegram.ext import Updater, CommandHandler, CallbackQueryHandler
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
import logging
import os
import json
import gspread
from oauth2client.service_account import ServiceAccountCredentials
from datetime import datetime
from itertools import permutations
import re
logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO)
logger = logging.getLogger("telegram.bot")
def start(update, context):
context.bot.send_message(
chat_id=update.effective_chat.id, text="I'm a bot, please talk to me!")
# def help(update, context):
def call(update, context):
"""
Save Message ID, Title, Date, Time, Duration, Description, Link, Group ID, Group name, Saved by Name, Username
Expected inputs (in order): Date, Time, Duration, Title, Description, Link
Send variables to Google Sheet
Send variables to Trello Board
Send variables to Google Calendar
OPTIONAL:
Notification System
"""
message_id = update.message.message_id
user = update.message.from_user
username = user['username']
full_name = "{} {}".format(user['first_name'], user['last_name'])
groupchat = update.message.chat
message_text = update.message.text
text = format_string(message_text, "/call")
if (text == -1):
groupchat.send_message(
text="Please make sure all arguments are inserted in the correct order and separated by semicolomns:\n\n- Date (dd/mm/yy) \n- Time (GMT) \n- Duration (hour:min) \n- Title \n- Description(optional) \n- Agenda Link(optional)")
call_date = text[0]
call_time = text[1]
call_duration = text[2]
call_title = text[3]
#if text[4]: call_description = text[4]
#if text[5]: call_agenda = text[5]
calls.append_row(
[message_id, call_title, call_date, call_time, call_duration])
# def group(update, context):
def str2date(string):
"Parse a string into a datetime object."
for fmt in dateformats():
try:
return datetime.strptime(string, fmt)
except ValueError:
pass
raise ValueError("'%s' is not a recognized date/time" % string)
def format_string(message, command):
message = re.sub(command, '', message)
message.strip()
message = message.split(';')
if not(len(message) >= 4):
return -1
if command == "/call":
message[0: 1] = [' '.join(message[0: 1])]
s = message[0].strip()
try:
message[0] = str2date(s)
except ValueError:
print("invalid date/time")
return message
def dateformats():
"Yield all combinations of valid date formats."
years = ("%Y",)
months = ("%b", "%B")
days = ("%d",)
times = ("%I%p", "%I:%M%p", "%H:%M", "")
for year in years:
for month in months:
for day in days:
for args in ((day, month), (month, day)):
date = " ".join(args)
for time in times:
for combo in permutations([year, date, time]):
yield " ".join(combo).strip()
def error(update, context):
logger.warning('Update "%s" caused error "%s"', update, context.error)
def main():
TOKEN = os.environ['TOKEN']
updater = Updater(token=TOKEN, use_context=True)
dp = updater.dispatcher
PORT = int(os.environ.get('PORT', '8443'))
updater.start_webhook(listen="0.0.0.0", port=PORT, url_path=TOKEN)
updater.bot.set_webhook("https://fff-transparency-wg.herokuapp.com/" + TOKEN)
updater.idle()
# use creds to create a client to interact with the Google Drive API
scope = ['https://spreadsheets.google.com/feeds', 'https://www.googleapis.com/auth/drive', 'https://www.googleapis.com/auth/drive.file', 'https://www.googleapis.com/auth/drive']
creds = ServiceAccountCredentials.from_json_keyfile_name(
'client_secret.json', scope)
client = gspread.authorize(creds)
# Find a workbook by name and open the first sheet
SPREADSHEET = os.environ['SPREADSHEET']
spreadsheet = client.open_by_key(
SPREADSHEET)
groupchats = spreadsheet.get_worksheet(0)
calls = spreadsheet.get_worksheet(1)
# Commands
dp.add_handler(CommandHandler("start", start))
#dp.add_handler(CommandHandler("help", help))
dp.add_handler(CommandHandler("call", call))
#dp.add_handler(CommandHandler("group", group))
dp.add_error_handler(error)
if __name__ == '__main__':
main()
PROCFILE
web: python3 bot.py
worker: python3 bot.py
REQUIREMENTS.TXT
certifi==2019.11.28
cffi==1.14.0
chardet==3.0.4
cryptography==2.9
decorator==4.4.2
future==0.18.2
gspread==3.3.1
httplib2==0.17.1
idna==2.9
oauth2client==4.1.3
pyasn1==0.4.8
pyasn1-modules==0.2.8
pycparser==2.20
python-telegram-bot==12.5.1
requests==2.23.0
rsa==4.0
six==1.14.0
tornado==6.0.4
urllib3==1.25.8
RUNTIME.TXT
python-3.8.2

I had the same issue with my telegram bot.
I added the actual bot token string to the TOKEN variable and uploaded it to Heroku.
Then I used os.environ.get('TOKEN') command for the version that I pushed on GitHub.
I am still just a beginner in python, I don't know if this posses security issues but it removed the error code and allowed my bot to work

Related

how to add an image to user playing activity?(discord.py)

Iam trying to add image user activity status in discord.py and basically, the problem is that I can't seem to add an image so I looked up discord.py API but couldn't find how to do it. So if you know do answer.
Would like to know how to add one. (NOT FOR BOTS)
Here is the code
import discord, os, keep_alive, datetime
import discord.ext
from discord.ext import tasks
intents = discord.Intents.default()
client = discord.Client()
launch_time = datetime.datetime.utcnow()
#client.event
async def on_ready():
print("Account Activated")
print(launch_time)
if not Status_loop.is_running():
Status_loop.start()
Put it in a loop to constantly update the activity
#tasks.loop(seconds=1, count=None, reconnect=True)
async def Status_loop():
delta_uptime = datetime.datetime.utcnow() - launch_time
hours, remainder = divmod(int(delta_uptime.total_seconds()), 3600)
minutes, seconds = divmod(remainder, 60)
days, hours = divmod(hours, 24)
print(f"Online Time: {days:02d}d | {hours:02d}h | {minutes:02d}m | {seconds:02d}s")
await client.change_presence(status = discord.Status.dnd,
activity = discord.Activity(type=discord.ActivityType.playing,
large_image = "https://i.imgur.com/Kk2BvJg.jpg",
large_text = "This is Game Icon",
name = "Sleeping Simulatorđź’¤",
details = "Dreams of Desires(Easy)",
state = f"{days:02d}d | {hours:02d}h | {minutes:02d}m Passed"))
Here is the Status on a user account
If you are wondering why I am even doing this, well I just wanted a custom status
It seems that after trying sites and other kinds of ways to do it, I finally got it using replit uploaded file link... and it worked...
large_image = "https://e8a593e1-79cc-4f08-9992-2b588af90e7f.id.repl.co/static/sleepingface.jpg",
large_text = "This is Game Icon",
small_image = "https://e8a593e1-79cc-4f08-9992-2b588af90e7f.id.repl.co/static/sleepingface.jpg",
small_text = "This is Game Icon",
The result:

Telegram bot is running, but does not retrieve data from the database

trying to output data from sql to telegram bot. bot is
running(sending message.answer), but after that nothing happens and
shows no error. using pycharm and xampp. it is my first code
import logging
import mysql.connector
from aiogram import Bot, Dispatcher, executor, types
API_TOKEN = 'token'
logging.basicConfig(level=logging.INFO)
bot = Bot(token=API_TOKEN)
dp = Dispatcher(bot)
mydb = mysql.connector.connect(
host="localhost",
user="root",
password="",
database="php"
)
mycursor = mydb.cursor()
#dp.message_handler(commands=['start'])
async def send_welcome(message: types.Message):
await message.answer("Hi!\nEnter name:")
# works up to this line
#dp.message_handler(content_types=['text'])
async def send_msg(message: types.Message):
mycursor.execute(
"SELECT surname, age, year, city, phone FROM list WHERE name ='{}'".format(message.from_user.id))
# fetching the rows from the cursor object
result = mycursor.fetchone()
for x in result:
await message.answer(x)
if __name__ == '__main__':
executor.start_polling(dp, skip_updates=True)
Rewrite your code like this
#dp.message_handler(commands=['start'])
async def send_welcome(message: types.Message, state: FSMContext):
await message.answer("Hi!\nEnter name:")
# works up to this line
await state.set_state("get_name")
#dp.message_handler(state="get_name")
async def send_msg(message: types.Message, state: FSMContext):
mycursor.execute(
"SELECT name FROM users WHERE name ='{}'".format(message.from_user.id)) # message.text should be here?
result = mycursor.fetchone()
for x in result:
await message.answer(x)
await state.finish()
You could use FSM to get data from user

Cloud web scraping with requests returns nothing

I'm trying to create a cog for my Discord bot that scrapes Indeed and returns info on job postings (position, company, location, etc). My bot is hosted on Heroku, which is where the issues start. I've tested my web scraper by itself and when implemented as a cog for my Discord bot locally. It works both times. However, when I tried to deploy it on Heroku, the cog stopped working.
I read that this was because cloud-hosting services have blacklists or something for web scraping apps and functions. So I tried to use rq as suggested in this post:
https://devcenter.heroku.com/articles/python-rq
I did all the steps, added an additional worker, a worker.py file, and installed the Redis To Go addon. However, when I try to use the following, I receive nothing back:
url = get_url(job_title, location)
response = requests.get(url)
soup = BeautifulSoup(response.text, "html.parser")
# soup.find() returns None
I'm sure I just implemented something wrong, but can someone help me please? The full code is below:
import discord
from discord.ext import commands
import random
import requests
import time
from bs4 import BeautifulSoup
from rq import Queue
from worker import conn
ret = []
def get_url(position, location):
'''Generate url from position and location'''
template = 'https://www.indeed.com/jobs?q={}&l={}'
position = position.replace(" ", "+")
location = location.replace(" ", "+")
url = template.format(position, location)
return url
def get_jobs(job_title, location):
'''Max returned number of jobs is 15 per page.'''
global ret
url = get_url(job_title, location)
response = requests.get(url)
print(f"Responses: {response}")
### This returns <Response [200]>
soup = BeautifulSoup(response.text, "html.parser")
job_names = []
for job_name in soup.find_all("h2", class_="jobTitle"):
job_names.append(job_name.get_text())
### Each one just returns an empty list []
companies = []
for company in soup.find_all("span", class_="companyName"):
companies.append(company.get_text())
locations = []
for location in soup.find_all("div", class_="companyLocation"):
locations.append(location.get_text())
salaries = []
for salary in soup.find_all("div", class_="attribute_snippet"):
if salary.get_text().startswith("$"):
salaries.append(salary.get_text())
else:
salaries.append("Unknown")
links = []
for link in soup.find_all("a", class_=lambda value: value and value.startswith("tapItem fs-unmask result"), href=True):
link = link["href"]
link = "https://indeed.com" + link
links.append(link)
ret = [job_names, companies, locations, salaries, links]
print(ret)
### This returns [[], [], [], [], []]
class JobScraper(commands.Cog):
def __init__(self, client): # References whatever is passed through the client from discord
self.client = client
self.q = Queue(connection=conn)
#commands.command(aliases=["job", "find_job", "find_jobs", "get_job", "get_jobs"])
async def jobs(self, ctx, *, query):
'''Scrapes Indeed.com for jobs and returns them.
The input format should be "eve jobs [job title], [job location], [num returned]
e.g. eve jobs ai researcher, san francisco, 3'''
key_terms = query.split(",")
key_terms = [term.strip() for term in key_terms]
if len(key_terms) == 3:
num_jobs = int(key_terms[2])
else:
num_jobs = 15
# ret = get_jobs(key_terms[0], key_terms[1])
job = self.q.enqueue(get_jobs, key_terms[0], key_terms[1])
await ctx.send("Here is what I found:")
for i in range(num_jobs):
await ctx.send("```" +
f"\nTitle: {ret[0][i]}" +
f"\nCompany: {ret[1][i]}" +
f"\nLocation: {ret[2][i]}" +
f"\nSalary: {ret[3][i]}" +
f"\nLink: {ret[4][i]}" +
"\n```")
def setup(client):
client.add_cog(JobScraper(client))

Discord.py Rewrite Giphy Cog Error: Unclosed Connector

I'm trying to build a Giphy cog for my discord.py bot and it's throwing the following error
Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0x000001CA509CDA60>
Unclosed connector
connections: ['[(<aiohttp.client_proto.ResponseHandler object at 0x000001CA51897BE0>, 77055.671)]']
connector: <aiohttp.connector.TCPConnector object at 0x000001CA509CDA30>
Does anyone know what is causing this and how I would fix it?
Here is my code:
import os
import aiohttp
import random
import discord
from discord.ext import commands
from dotenv import load_dotenv
load_dotenv()
prefix = os.getenv("CLIENT_PREFIX")
giphy_api = os.getenv("GIPHY_API_KEY")
command_attrs = {'hidden': False}
class FunCog(commands.Cog, name='Fun Commands', command_attrs=command_attrs):
def __init__(self, client):
self.client = client
#commands.command(name='gif')
async def _gif(self, ctx, *, search, json=None):
embed = discord.Embed(colour=discord.Colour.blue())
session = aiohttp.ClientSession()
if search == '':
response = await session.get('https://api.giphy.com/v1/gifs/random?api_key=' + giphy_api)
data = json.loads(await response.text())
embed.set_image(url=data['data']['images']['original']['url'])
else:
search.replace(' ', '+')
response = await session.get(
'http://api.giphy.com/v1/gifs/search?q=' + search + '&api_key=' + giphy_api + '&limit=10')
data = json.loads(await response.text())
gif_choice = random.randint(0, 9)
embed.set_image(url=data['data'][gif_choice]['images']['original']['url'])
await session.close()
await ctx.send(embed=embed)
def setup(client):
client.add_cog(FunCog(client))
I omitted my Giphy API Key for security reasons.
I'm using discord.py rewrite and python 3.8.6if it helps.
Basically I want to be able to search a gif with it by tag and it will respond with a random gif from giphy for the specified tag.
--EDIT--
Moved the error logging to my Events.py file
Moved the API Keys to my .env file
Removed the tenor_api env since it won't be getting used and has nothing to do with this question
Updated the code a bit to resolve a few missing parameters.
---EDIT---
Thanks to Fixator10 for the response that fixed the issue I was having in this post.
Here is my working code if anyone wants to use it:
import os
import aiohttp
import random
import discord
import json
from discord.ext import commands
from dotenv import load_dotenv
load_dotenv()
prefix = os.getenv("CLIENT_PREFIX")
giphy_api = os.getenv("GIPHY_API_KEY")
command_attrs = {'hidden': False}
class FunCog(commands.Cog, name='Fun Commands', command_attrs=command_attrs):
def __init__(self, client):
self.client = client
self.session = aiohttp.ClientSession()
def cog_unload(self):
self.client.loop.create_task(self.session.close())
#commands.command(name='gif')
async def _gif(self, ctx, *, search):
session = self.session
embed = discord.Embed(colour=discord.Color.dark_gold())
if search == '':
response = await session.get('https://api.giphy.com/v1/gifs/random?api_key=' + giphy_api)
data = json.loads(await response.text())
embed.set_image(url=data['data']['images']['original']['url'])
else:
search.replace(' ', '+')
response = await session.get(
'http://api.giphy.com/v1/gifs/search?q=' + search + '&api_key=' + giphy_api + '&limit=10')
data = json.loads(await response.text())
gif_choice = random.randint(0, 9)
embed.set_image(url=data['data'][gif_choice]['images']['original']['url'])
await ctx.send(embed=embed)
def setup(client):
client.add_cog(FunCog(client))
It's probably not the best since it only uses a single tag but I'll probably improve that at another time xD
This is caused by your session behavior. You dont close session if search is empty. In two words: you should close session after usage.
The most simple solution - is to use context manager:
# https://docs.aiohttp.org/en/stable/#client-example
async with aiohttp.ClientSession() as session:
async with session.get('http://python.org') as response:
Otherwise, you can create a session for your Cog and close it on unload:
class MyCog(commands.Cog):
def __init__(client):
self.client = client
self.session = aiohttp.ClientSession()
# https://discordpy.readthedocs.io/en/stable/ext/commands/api.html#discord.ext.commands.Cog.cog_unload
def cog_unload(self):
# since close is coroutine,
# and we need to call it in sync func
# lets create asyncio task
self.client.loop.create_task(self.session.close())
# or, we can use detach:
# self.session.detach()
#commands.command()
async def mycmd(ctx):
async with self.session.get("https://example.com") as response:
await ctx.send(response.status)

Attempting to rewrite a Discord.py GIF Bot from an old outdated guide

Ok, so I'm attempting to rebuild a GIF bot in Discord.py.
It is based off of some code found on a website that gave a guide on making a discord.py gif bot.
The problem is, the code is old and severely outdated with over 37 errors on it's own.
How would I go about converting this to discord.py-rewrite and basically making it a gif searching bot that can pick up random gifs by tag from GIPHY?
Here is the code I currently have:
import discord.utils
import os
import giphy_client
from giphy_client.rest import ApiException
from pprint import pprint
from discord.ext import commands
import random
from dotenv import load_dotenv
load_dotenv()
API_KEY = os.getenv("GIPHY_API_KEY")
API_TOKEN = os.getenv("GIPHY_API_TOKEN")
api_instance = giphy_client.DefaultApi()
config = {
'api_key': API_KEY,
'token': API_TOKEN,
'limit': 1,
'rating': 'g'
}
try:
api_response = api_instance.gifs_trending_get(
config['token'], limit=config['limit'], rating=config['rating'])
pprint(api_response)
except ApiException as e:
print("Exception when calling DefaultApi->gifs_trending_get: %s\n" % e)
Client = commands.Bot(command_prefix=os.getenv("CLIENT_PREFIX"))
class DiscordClient(discord.Client):
#Client.event
async def on_ready(self):
print("Connected to Discord Client as")
print(f'{self.user.name}#{self.user.discriminator}')
print("-------")
#Client.command(name="gif")
async def search_gifs(query):
try:
giphy_token = API_TOKEN
response = api_instance.gifs_search_get(giphy_token, query, limit=3, rating='g')
lst = list(response.data)
gif = random.choices(lst)
return gif[0].url
except ApiException as e:
return "Exception when calling DefaultApi->gifs_search_get: %s\n" % e
async def on_ready(self):
print("Connected to Discord Client as")
print(Client.user.name)
print("-------")
#Client.command(name='gif')
async def search_gifs(query):
try:
response = api_instance.gifs_search_get(
API_TOKEN, query, limit=3, rating='g')
lst = list(response.data)
gif = random.choices(lst)
return gif[0].url
except ApiException as e:\
return "Exception when calling DefaultApi->gifs_search_get: %s\n" % e\
#Client.command(name='8ball')
async def magic_eight_ball(ctx):
response = [
'Without a doubt.',
'Outlook good.',
'Better not tell you now.',
'Cannot predict now.',
'My reply is no.',
'Outlook not so good.',
]
gif = await search_gifs('cheese')
await ctx.send(random.choice(response))
await ctx.send('Gif URL : ' + gif)
Client.run(os.getenv("CLIENT_TOKEN"))
Here is the website I got the code I'm building off of from:
https://www.maxongzb.com/serving-gifs-with-discord-bot-reading-time-12-mins/
Here is what I currently have for the requirements.txt file:
discord.py
giphy_client
python-dotenv
Any help would be much appreciated.
I host via Heroku due to my VPS taking a dump, so that's why I have the os.getenv("...") lines in there.
I've gotten it down to around 7 errors, 8 warnings and 8 weak warnings. But that's the furthest I was able to get to.

Resources