I am needing help in adding parameters in API command for api's such Urban Dictionary (for searching definitions) and open weather map (to get weather of certain location) I understand the fact that a lot of these are provided with code for querystring then ("GET", url, headers=headers, params=querystring) but I don't understand how to allow something such as $urban yo-yo.
#commands.command(
name="urban",
description="Allows the user to get a definition from Urban Dictionary",
aliases=['urbandict']
)
async def urban(self, ctx):
url = "https://mashape-community-urban-dictionary.p.rapidapi.com/define"
headers = {
'x-rapidapi-key': self.bot.quote_api_key,
'x-rapidapi-host': "mashape-community-urban-dictionary.p.rapidapi.com"
}
async with ClientSession() as session:
async with session.get(url, headers=headers) as response:
r = await response.json()
# print(r)
embed = discord.Embed(title="Term:", description=f"{r['']}")
embed.add_field(name="Definition:", value=f"||{r['']}||")
embed.set_author(name=ctx.author.display_name, icon_url=ctx.message.author.avatar_url)
await ctx.send(embed=embed)
Looking at the Urban Dictionnary API, querystring must a dictionnary that has a term key.
Then, to add parameters to commands, you simply have to add a parameter to your function and discord.py will parse the command automatically. If you want everything after $urban in a single parameter, you have to add a * before the term parameter.
It would look like this :
#commands.command()
async def urban(self, ctx, *, term):
url = "https://mashape-community-urban-dictionary.p.rapidapi.com/define"
querystring = {"term": term}
headers = {
'x-rapidapi-key': self.bot.quote_api_key,
'x-rapidapi-host': "mashape-community-urban-dictionary.p.rapidapi.com"
}
async with ClientSession() as session:
async with session.get(url, headers=headers, params=querystring) as response:
r = await response.json()
embed = discord.Embed(title="Term:", description=f"{r['']}")
embed.add_field(name="Definition:", value=f"||{r['']}||")
embed.set_author(name=ctx.author.display_name, icon_url=ctx.message.author.avatar_url)
await ctx.send(embed=embed)
For the Open Weather Map API, you need to pass the argument in the url. For instance, if you want Paris' 5 days forecast, you'd have to use this url :
http://api.openweathermap.org/data/2.5/forecast?q=Paris&units=metric&APPID=your_token
For more informations, you can look at the documentations :
Discord.py's command arguments handler
Urban Dictionnary API
Open Weather Map API
Related
Today I was trying to speed up my script and found great example code from another stackoverflow post. Basically I found a way to make async requests using aiohttp to web instead of using requests. Here is the link to that post (I copied code from DragonBobZ's answer).
Link to other stackoverflow post from which I copied code
The issue is that I am trying to get it to return 2 values (url, response) instead of just the response from the request made. Here is the code I took.
def async_aiohttp_get_all(urls, cookies):
async def get_all(urls):
async with aiohttp.ClientSession(cookies=cookies) as session:
async def fetch(url):
async with session.get(url) as response:
return await response.json()
return await asyncio.gather(*[
fetch(url) for url in urls
])
return sync.async_to_sync(get_all)(urls)
for x in async_aiohttp_get_all(urls_list, s.cookies.get_dict()):
print(x)
Now I am successfully able to get responses from all urls within fraction of time it was taking with requests but I want the function to also return the url with:
return await response.json()
I tried this but nothing works and this is my first day to ever use async practices in python so I am not even able to search for a solution as nothing makes sense.
return await url, response.json()
return await (url, response.json())
I could not run the code exactly how you do, but I returned a tuple with no problem. Also removed the sync call, since asyncio gives you enough flexibility.
import asyncio
import aiohttp
urls_list = [
"https://www.google.com",
"https://www.facebook.com",
"https://www.twitter.com",
]
async def async_aiohttp_get_all(urls, cookies):
async with aiohttp.ClientSession(cookies=cookies) as session:
async def fetch(url):
async with session.get(url) as response:
return await response.text(), url
return await asyncio.gather(*[
fetch(url) for url in urls
])
results = asyncio.run(async_aiohttp_get_all(urls_list, None))
for res in results:
print(res[0][:10], res[1])
Output:
<!doctype https://www.google.com
<!DOCTYPE https://www.facebook.com
<!DOCTYPE https://www.twitter.com
So, in your case, return await response.json(), url should work.
I am having an error thrown at me when I try to search a term using the API, so what I am trying to accomplish is if the term is a noun it would print the definition for the noun portion, if its an verb then it would print the verb section.
#commands.command(
name="dict",
description="Allows the user to get a definition from Urban Dictionary",
aliases=['dictionary']
)
async def dict(self, ctx, *, entry):
url = "https://twinword-word-graph-dictionary.p.rapidapi.com/definition/"
querystring = {"entry": entry}
headers = {
'x-rapidapi-key': self.bot.quote_api_key,
'x-rapidapi-host': "twinword-word-graph-dictionary.p.rapidapi.com"
}
async with ClientSession() as session:
async with session.get(url, headers=headers, params=querystring) as response:
r = await response.json()
print(r)
if r['meaning']['noun'] in entry:
embed = discord.Embed(title="Term:", description=f"{entry}")
embed.add_field(name="Definition:", value=f'{r["meaning"]["noun"]}')
embed.set_author(name=ctx.author.display_name, icon_url=ctx.message.author.avatar_url)
await ctx.send(embed=embed)
elif r['meaning']['verb'] in entry:
embed = discord.Embed(title="Term:", description=f"{entry}")
embed.add_field(name="Definition:", value=f"{r['meaning']['verb']}")
embed.set_author(name=ctx.author.display_name, icon_url=ctx.message.author.avatar_url)
await ctx.send(embed=embed)
elif r['meaning']['adverb'] in entry:
embed = discord.Embed(title="Term:", description=f"{entry}")
embed.add_field(name="Definition:", value=f"{r['meaning']['adverb']}")
embed.set_author(name=ctx.author.display_name, icon_url=ctx.message.author.avatar_url)
await ctx.send(embed=embed)
elif r['meaning']['adjective'] in entry:
embed = discord.Embed(title="Term:", description=f"{entry}")
embed.add_field(name="Definition:", value=f"{r['meaning']['adjective']}")
embed.set_author(name=ctx.author.display_name, icon_url=ctx.message.author.avatar_url)
await ctx.send(embed=embed)
In embed.fields.0.value: This field is required
The error gets when it tries to throw the first embed of ['meaning']['noun'], then it doesn't send any more embeds when that happens. The issue, without doing the if and elif I will only be able to search if its a noun or verb I'm trying to make sure that if the word has is a noun or verb i don't have to make another command for it.
I want my discord.py bot to send a meme from hot posts of r/memes via PRAW. After this issue, I tried searching in the web and in the documentations, but I didn't find any method to view the image. Here is my code:
import praw
import discord
from discord.ext import commands
from discord import client
reddit = praw.Reddit(client_id="d",
client_secret="d",
user_agent="automoderatoredj by /u/taskuratik")
#boot
print("il bot si sta avviando... ")
token = "token"
client = commands.Bot(command_prefix=("/"))
#bot online
#client.event
async def on_ready():
print("il bot e' ora online")
#client.command()
async def meme(submission):
if reddit:
channel = client.get_channel(722491234991472742)
submission = reddit.subreddit("memes").hot(limit=1)
await channel.send(submission.url)
client.run(token)
Your code says:
submission = reddit.subreddit("memes").hot(limit=1)
await channel.send(submission.url)
Here, you assign a listing of one post to submission. As listing is an iterable (somewhat like a list) that contains one submission, rather than the submission itself. Unlike a list, you can't use an index to access a specific item, but there are other ways to get it. One way to get the submission is
for submission in reddit.subreddit("memes").hot(limit=1):
await channel.send(submission.url)
This allows you to change the limit and send more posts if you want.
Or, you could use next() to get the next (and only) item from the post listing:
submission = next(reddit.subreddit("memes").hot(limit=1))
await channel.send(submission.url)
This will always send just one submission, even if you change the limit parameter.
PRAW is blocking, aiohttp is not and frankly discord.py comes with aiohttp. Reddit offers an endpoint to return json data that you can prase with the json.loads() method to get raw json.
This is something I wrote to fetch images from subreddits
from aiohttp import ClientSession
from random import choice as c
from json import loads
async def get(session: object, url: object) -> object:
async with session.get(url) as response:
return await response.text()
async def reddit(sub: str):
type = ['new', 'top', 'hot', 'rising']
url = f"https://www.reddit.com/r/{sub}/{c(type)}.json?sort={c(type)}&limit=10"
async with ClientSession() as session:
data = await get(session, url)
data = loads(data)
data = data['data']['children']
url = [d['data']['url'] for d in data]
return c(url)
All you need to do is await reddit(sub= 'memes') to get the required url.
I would simply like to associate responses from aiohttp asynchronous HTTP requests with an identifier. I am using the following code to hit the API and extract contactproperty object which requires an external field (contacid) in order to call its API:
def get_contact_properties(self, office_name, api_key, ids, chunk_size=100, **params):
properties_pages = []
batch = 0
while True:
chunk_ids = [ids[i] for i in range(batch * chunk_size + 1, chunk_size * (1 + batch) + 1)]
urls = ["{}/{}".format(self.__get_base_url(), "contacts/{}/properties?api_key={}".format(contactid, api_key))
for contactid in chunk_ids]
responses_raw = self.get_responses(urls, self.get_office_token(office_name), chunk_size)
try:
responses_json = [json.loads(response_raw) for response_raw in responses_raw]
except Exception as e:
print(e)
valid_responses = self.__get_valid_contact_properties_responses(responses_json)
properties_pages.append(valid_responses)
if len(valid_responses) < chunk_size: # this is how we know there are no more pages with data
break
else:
batch = batch + 1
ids is a list of ids. The problem is that I do not know which response corresponds to which id so that later I can link it to contact entity using contacid. This is my fetch() function so I was wondering how to edit this function to return the contactid along with output.
async def __fetch(self, url, params, session):
async with session.get(url, params=params) as response:
output = await response.read()
return (output)
async def __bound_fetch(self, sem, url, params, session):
# Getter function with semaphore.
async with sem:
output = await self.__fetch(url, params, session)
return output
You can return the url (or whatever key identifies your request) together with the output.
Regarding using the data, I think you should read the response directly as JSON, especially since aiohttp can do this for you automatically.
async def __fetch(self, url, params, session):
async with session.get(url, params=params) as response:
try:
data = await response.json()
except ValueError as exc:
print(exc)
return None
return data
async def __bound_fetch(self, sem, url, params, session):
# Getter function with semaphore.
async with sem:
output = await self.__fetch(url, params, session)
return {"url": url, "data": data}
You did not post the get_responses function but I'm guessing something like this should work:
responses = self.get_responses(urls, self.get_office_token(office_name), chunk_size)
Responses will be a list of {"url": url, data: "data"} (data can be None for invalid responses); however with the code above one invalid request will not affect the others.
I have a Python (3.6) web server using aiohttp with a lot of routes in it. The handler in my routes usually looks like this:
async def __call__(self, request):
response1 = await call_service_1('aaa')
response2 = await call_service_2('bbb')
response3 = await call_service_3('ccc')
return [response1, response2, response3]
And a service call looks like:
async def call_service1(self, arg):
url = 'http://localhost/' + arg
headers = {'Content-Type': 'application/json'}
async with self.client_session.get(url, headers=headers) as response:
return response
I have a new requirement in which I need to read a header value in the request, and pass that on as a header in my subsequent service requests. I have this requirement for all of my routes and all of my service calls. My initial attempt to fulfil this requirement would be to change the routes to this:
async def __call__(self, request):
my_header_value = request.headers['my_header_value'] # new header value
response1 = call_service_1('aaa', my_header_value)
response2 = call_service_2('bbb', my_header_value)
response3 = call_service_3('ccc', my_header_value)
And to change the service request to this:
async def call_service1(self, arg, my_header_value): # new parameter
url = 'http://localhost/' + arg
headers = {
'Content-Type': 'application/json',
'my_header_value': my_header_value # new header value
}
async with self.client_session.get(url, headers=headers) as response:
return response
I don't think this is the Pythonic way to make a change like this. I think it's a bad design to pass this parameter around like this, from endpoint to endpoint. I know that this is a cross-cutting concern, but I don't know the proper way to design something to handle this in Python. I'd ultimately be copying and pasting code all over the place. There's got to be a better way, but I don't know it and my Python isn't that strong.
For instance, is there some way I can decorate the service calls so that I don't have to add a new parameter all over the place? I don't think I have access to some session variables that I can set so that I don't have to add parameters at all. If I did however, I'd be worried about unit testing.
What do you recommend?