lyricsgenius lyrics sometimes end with "EmbedShare URLCopyEmbedCopy" - python

I am making a Discord lyrics bot and to receive the lyrics. I am using genius API (lyricsgenius API wrapper). But when I receive the lyrics, it ends with this:
"away" is the last word in the song but it is accompanied with EmbedShare URLCopyEmbedCopy. Sometimes it is just the plain lyrics without the EmbedShare text.
With the same song:
Is there anyway to prevent that?
Source code for the lyrics command:
#commands.command(help="Gives the lyrics of the song XD! format //lyrics (author) (song name)")
async def lyrics(self, ctx, arg1, arg2):
song = genius.search_song(arg1, arg2)
print(song.lyrics)
name = ("Lyrics for " + arg2.capitalize() + " by " + arg1.capitalize())
gembed = discord.Embed(title=name.capitalize(), description=song.lyrics)
await ctx.send(embed=gembed)

This is a known bug with lyricsgenius and there's an open PR to address this issue: https://github.com/johnwmillr/LyricsGenius/pull/215.
This is because lyricsgenius web scrapes the lyrics from Genius' website, which means if their website updates, lyricsgenius would fail to fetch the lyrics. This library hasn't been updated in 6 months; itself being a web scraping library means that kind of inactivity would render the library severely unstable. Since the library is licensed under MIT, you can fork the library and maintain an up-to-date version for your project/bot. However, it would be much better to use a dedicated API to fetch songs lyrics to guarantee stability.
Also, lyricsgenius uses the synchronous requests library, which means it'll "block" your asynchronous bot while it fetches the lyrics. This is definitely undesirable for a Discord Bot since your bot would be completely unresponsive while it fetches the lyrics. Consider rewriting it using aiohttp or use run_in_executor when calling blocking functions.

Some Random API is something easy to deal with when you are creating a command that will send you the song lyrics.
This is how to do it with some random api,
# these imports are used for this particular lyrics command. the essential import here is aiohttp, which will be used to fetch the lyrics from the API
import textwrap
import urllib
import aiohttp
import datetime
#bot.command(aliases = ['l', 'lyrc', 'lyric']) # adding aliases to the command so they they can be triggered with other names
async def lyrics(ctx, *, search = None):
"""A command to find lyrics easily!"""
if not search: # if user hasnt given an argument, throw a error and come out of the command
embed = discord.Embed(
title = "No search argument!",
description = "You havent entered anything, so i couldnt find lyrics!"
)
return await ctx.reply(embed = embed)
# ctx.reply is available only on discord.py version 1.6.0, if you have a version lower than that use ctx.send
song = urllib.parse.quote(search) # url-encode the song provided so it can be passed on to the API
async with aiohttp.ClientSession() as lyricsSession:
async with lyricsSession.get(f'https://some-random-api.ml/lyrics?title={song}') as jsondata: # define jsondata and fetch from API
if not 300 > jsondata.status >= 200: # if an unexpected HTTP status code is recieved from the website, throw an error and come out of the command
return await ctx.send(f'Recieved poor status code of {jsondata.status}')
lyricsData = await jsondata.json() # load the json data into its json form
error = lyricsData.get('error')
if error: # checking if there is an error recieved by the API, and if there is then throwing an error message and returning out of the command
return await ctx.send(f'Recieved unexpected error: {error}')
songLyrics = lyricsData['lyrics'] # the lyrics
songArtist = lyricsData['author'] # the author's name
songTitle = lyricsData['title'] # the song's title
songThumbnail = lyricsData['thumbnail']['genius'] # the song's picture/thumbnail
# sometimes the song's lyrics can be above 4096 characters, and if it is then we will not be able to send it in one single message on Discord due to the character limit
# this is why we split the song into chunks of 4096 characters and send each part individually
for chunk in textwrap.wrap(songLyrics, 4096, replace_whitespace = False):
embed = discord.Embed(
title = songTitle,
description = chunk,
color = discord.Color.blurple(),
timestamp = datetime.datetime.utcnow()
)
embed.set_thumbnail(url = songThumbnail)
await ctx.send(embed = embed)

Related

How do I get the user ID of the person who issued the command in Discord

So im making a discord Bot that will notify me when my favorite food is avalable in the cafiteria at my school. Im trying to make it so multiple people can use the bot so im storing the users favorite food in a dictionary with their user id as the key. I've never used slash commands in discord before, normally I just use a command.prefix but decided to try it this way instead.
So far this is what I have:
client = aclient()
tree = app_commands.CommandTree(client)
#tree.command(guild = discord.Object(id=guild_id), name = 'favorite_add', description='Add a food item to your favorite food list') #guild specific slash command
async def addFavorite(interaction: discord.Interaction, content : str = ""):
await interaction.response.send_message(f"{content} has been added to your favorite food list", ephemeral = True)
userFavorites[content.author.id] += [content]
The problem im having is that the str: content doesnt have the author.id attrubite. Is there any way to aquire the id of who posted the command so I can add the content to their favorite food list or potentially add a new key to my dictionary
async def addFavorite(interaction: discord.Interaction, content : str = ""):
^^^^^^^
That's because the content argument is a string since you're telling the library that you wanted it to be. If you want to get the ID of the user who triggered the command, you have to get the user from interaction.user then get the ID.
userFavorites[interaction.user.id] += [content]
discord.py isn't beginner-friendly; you should know the basics before trying it. You can look at some examples here.

'The application did not respond' error while making a slash command using discord.py

While making a discord bot in Python using discord-ui and discord.py, I wanted to make a command which does specific stuff for specific roles. Like if I have the att_role and aot_role, it would execute the code for both of these roles, and if I add another role like est_role, it would execute its code too. I am using PyCharm as the IDE here.
I am a novice programmer, so I don't know if this is the best way to do this, but when I do it I get the error of 'The application did not respond'. Here is when it works and when not:
When I have the att_role, it works perfectly even if I add more roles from the list. If I remove the att_role and add two other roles from the list, it would work for those too. If I have the rs_role only, it would work for it too. However, if I have only the aot_role or only the est_role, I get this error after running:- The application did not respond in the discord channel. If I add any other role with these, it would work perfectly. It doesn't show any error in my IDE(PyCharm).
If you don't understand correctly what I mean, just tell me the best way for doing this: Making different stuff for different roles.
Here is my code:
import discord
from discord.ext import commands
from discord_ui import UI, SelectOption, SelectMenu
import asyncio
# Clients
client = commands.Bot(' ')
client.remove_command('help')
ui = UI(client)
#ui.slash.command()
async def log(ctx):
"""Use this command to log trainings/tryouts"""
# These are the roles allowed to use this command
allowed_roles = [
'Advanced Trooper Training Section',
'Advanced Officer Training Section,',
'Recruiting Staff',
'Academy Instructor'
'Executive Sergeant Training Section',
'TB Bot Manager'
]
author_roles = ctx.author.roles
# If statement to check if the user can use this command
if any(role.name in allowed_roles for role in author_roles):
att_role = discord.utils.get(author_roles, name='Advanced Trooper Training Section')
aot_role = discord.utils.get(author_roles, name='Advanced Officer Training Section')
rs_role = discord.utils.get(author_roles, name='Recruiting Staff')
ai_role = discord.utils.get(author_roles, name='Academy Instructor')
est_role = discord.utils.get(author_roles, name='Executive Sergeant Training Section')
manager_role = discord.utils.get(author_roles, name='TB Bot Manager')
# Different stuff for different roles
if att_role is not None and att_role.name == 'Advanced Trooper Training Section':
await ctx.send('att') # Sample Stuff
if rs_role is not None and rs_role.name == 'Recruiting Staff':
await ctx.send('rs') # Sample Stuff
if ai_role is not None and ai_role.name == 'Academy Instructor':
await ctx.send('ai') # Sample Stuff
if est_role is not None and est_role.name == 'Executive Sergeant Training Section':
await ctx.send('est') # Sample Stuff
if manager_role is not None and manager_role.name == 'TB Bot Manager':
await ctx.send('manager') # Sample Stuff
if aot_role is not None and aot_role.name == 'Advanced Officer Training Section':
await ctx.send('aot') # Sample Stuff
client.run(TOKEN)
If your bot does not respond to an interaction within 3 seconds, the interaction will fail (although not necessarily an error has occured in your application). You can defer if you know it will take longer time.
In Pycord, it is located under InteractionResponse.defer but since you didn't mention which library you're using, I don't know which method you have to call.
You should use
await ctx.respond()
instead of
await ctx.send()
when dealing with slash commands. If this doesn't work, it'll be due to an error in the code (It should appear in terminal)
A previous answer on here suggests using ctx.respond() instead of ctx.send(), this solution will throw an error in the current version of discord.py:
Instead use:
await ctx.reply()

Faust example of publishing to a kafka topic

I'm curious about how you are supposed to express that you want a message delivered to a Kafka topic in faust. The example in their readme doesn't seem to write to a topic:
import faust
class Greeting(faust.Record):
from_name: str
to_name: str
app = faust.App('hello-app', broker='kafka://localhost')
topic = app.topic('hello-topic', value_type=Greeting)
#app.agent(topic)
async def hello(greetings):
async for greeting in greetings:
print(f'Hello from {greeting.from_name} to {greeting.to_name}')
#app.timer(interval=1.0)
async def example_sender(app):
await hello.send(
value=Greeting(from_name='Faust', to_name='you'),
)
if __name__ == '__main__':
app.main()
I would expect hello.send in the above code to publish a message to the topic, but it doesn't appear to.
There are many examples of reading from topics, and many examples of using the cli to push an ad-hoc message. After combing through the docs, I don't see any clear examples of publishing to topics in code. Am I just being crazy and the above code should work?
You can use sink to tell Faust where to deliver the results of an agent function. You can also use multiple topics as sinks at once if you want.
#app.agent(topic_to_read_from, sink=[destination_topic])
async def fetch(records):
async for record in records:
result = do_something(record)
yield result
The send() function is the correct one to call to write to topics. You can even specify a particular partition, just like the equivalent Java API call.
Here is the reference for the send() method:
https://faust.readthedocs.io/en/latest/reference/faust.topics.html#faust.topics.Topic.send
If you want a Faust producer only (not combined with a consumer/sink), the original question actually has the right bit of code, here's a fully functional script that publishes messages to a 'faust_test' Kafka topic that is consumable by any Kafka/Faust consumer.
Run the code below like this: python faust_producer.py worker
"""Simple Faust Producer"""
import faust
if __name__ == '__main__':
"""Simple Faust Producer"""
# Create the Faust App
app = faust.App('faust_test_app', broker='localhost:9092')
topic = app.topic('faust_test')
# Send messages
#app.timer(interval=1.0)
async def send_message(message):
await topic.send(value='my message')
# Start the Faust App
app.main()
So we just ran into the need to send a message to a topic other than the sink topics.
The easiest way we found was: foo = await my_topic.send_soon(value="wtfm8").
You can also use send directly like below using the asyncio event loop.
loop = asyncio.get_event_loop()
foo = await ttopic.send(value="wtfm8??")
loop.run_until_complete(foo)
Dont know how relevant this is anymore but I came across this issue when trying to learn Faust. From what I read, here is what is happening:
topic = app.topic('hello-topic', value_type=Greeting)
The misconception here is that the topic you have created is the topic you are trying to consume/read from. The topic you created currently does not do anything.
await hello.send(
value=Greeting(from_name='Faust', to_name='you'),
)
this essentially creates an intermediate kstream which sends the values to your hello(greetings) function. def hello(...) will be called when there is a new message to the stream and will process the message that is being sent.
#app.agent(topic)
async def hello(greetings):
async for greeting in greetings:
print(f'Hello from {greeting.from_name} to {greeting.to_name}')
This is receiving the kafka stream from hello.send(...) and simply printing it to the console (no output to the 'topic' created). This is where you can send a message to a new topic. so instead of printing you can do:
topic.send(value = "my message!")
Alternatively:
Here is what you are doing:
example_sender() sends a message to hello(...) (through intermediate kstream)
hello(...) picks up the message and prints it
NOTICE: no sending of messages to the correct topic
Here is what you can do:
example_sender() sends a message to hello(...) (through intermediate kstream)
hello(...) picks up the message and prints
hello(...) ALSO sends a new message to the topic created(assuming you are trying to transform the original data)
app = faust.App('hello-app', broker='kafka://localhost')
topic = app.topic('hello-topic', value_type=Greeting)
output_topic = app.topic('test_output_faust', value_type=str)
#app.agent(topic)
async def hello(greetings):
async for greeting in greetings:
new_message = f'Hello from {greeting.from_name} to {greeting.to_name}'
print(new_message)
await output_topic.send(value=new_message)
I found a solution to how to send data to kafka topics using Faust, but I don't really understand how it works.
There are several methods for this in Faust: send(), cast(), ask_nowait(), ask(). In the documentation they are called RPC operations.
After creating the sending task, you need to run the Faust application in the mode Client-Only Mode. (start_client(), maybe_start_client())
The following code (the produce() function) demonstrates their application (pay attention to the comments):
import asyncio
import faust
class Greeting(faust.Record):
from_name: str
to_name: str
app = faust.App('hello-app', broker='kafka://localhost')
topic = app.topic('hello-topic', value_type=Greeting)
result_topic = app.topic('result-topic', value_type=str)
#app.agent(topic)
async def hello(greetings):
async for greeting in greetings:
s = f'Hello from {greeting.from_name} to {greeting.to_name}'
print(s)
yield s
async def produce(to_name):
# send - universal method for sending data to a topic
await hello.send(value=Greeting(from_name='SEND', to_name=to_name), force=True)
await app.maybe_start_client()
print('SEND')
# cast - allows you to send data without waiting for a response from the agent
await hello.cast(value=Greeting(from_name='CAST', to_name=to_name))
await app.maybe_start_client()
print('CAST')
# ask_nowait - it seems to be similar to cast
p = await hello.ask_nowait(
value=Greeting(from_name='ASK_NOWAIT', to_name=to_name),
force=True,
reply_to=result_topic
)
# without this line, ask_nowait will not work; taken from the ask implementation
await app._reply_consumer.add(p.correlation_id, p)
await app.maybe_start_client()
print(f'ASK_NOWAIT: {p.correlation_id}')
# blocks the execution flow
# p = await hello.ask(value=Greeting(from_name='ASK', to_name=to_name), reply_to=result_topic)
# print(f'ASK: {p.correlation_id}')
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(produce('Faust'))
Starting Fast worker with the command faust -A <example> worker
Then we can launch the client part of the application and check that everything is working: python <example.py>
<example.py> output:
SEND
CAST
ASK_NOWAIT: bbbe6795-5a99-40e5-a7ad-a9af544efd55
It is worth noting that you will also see a traceback of some error that occurred after delivery, which does not interfere with the program (it seems so)
Faust worker output:
[2022-07-19 12:06:27,959] [1140] [WARNING] Hello from SEND to Faust
[2022-07-19 12:06:27,960] [1140] [WARNING] Hello from CAST to Faust
[2022-07-19 12:06:27,962] [1140] [WARNING] Hello from ASK_NOWAIT to Faust
I don't understand why it works this way, why it's so difficult and why very little is written about in the documentation 😓.

Pulling historical channel messages python

I am attempting to create a small dataset by pulling messages/responses from a slack channel I am a part of. I would like to use python to pull the data from the channel however I am having trouble figuring out my api key. I have created an app on slack but I am not sure how to find my api key. I see my client secret, signing secret, and verification token but can't find my api key
Here is a basic example of what I believe I am trying to accomplish:
import slack
sc = slack.SlackClient("api key")
sc.api_call(
"channels.history",
channel="C0XXXXXX"
)
I am willing to just download the data manually if that is possible as well. Any help is greatly appreciated.
messages
See below for is an example code on how to pull messages from a channel in Python.
It uses the official Python Slack library and calls
conversations_history with paging. It will therefore work with
any type of channel and can fetch large amounts of messages if
needed.
The result will be written to a file as JSON array.
You can specify channel and max message to be retrieved
threads
Note that the conversations.history endpoint will not return thread messages. Those have to be retrieved additionaly with one call to conversations.replies for every thread you want to retrieve messages for.
Threads can be identified in the messages for each channel by checking for the threads_ts property in the message. If it exists there is a thread attached to it. See this page for more details on how threads work.
IDs
This script will not replace IDs with names though. If you need that here are some pointers how to implement it:
You need to replace IDs for users, channels, bots, usergroups (if on a paid plan)
You can fetch the lists for users, channels and usergroups from the API with users_list, conversations_list and usergroups_list respectively, bots need to be fetched one by one with bots_info (if needed)
IDs occur in many places in messages:
user top level property
bot_id top level property
as link in any property that allows text, e.g. <#U12345678> for users or <#C1234567> for channels. Those can occur in the top level text property, but also in attachments and blocks.
Example code
import os
import slack
import json
from time import sleep
CHANNEL = "C12345678"
MESSAGES_PER_PAGE = 200
MAX_MESSAGES = 1000
# init web client
client = slack.WebClient(token=os.environ['SLACK_TOKEN'])
# get first page
page = 1
print("Retrieving page {}".format(page))
response = client.conversations_history(
channel=CHANNEL,
limit=MESSAGES_PER_PAGE,
)
assert response["ok"]
messages_all = response['messages']
# get additional pages if below max message and if they are any
while len(messages_all) + MESSAGES_PER_PAGE <= MAX_MESSAGES and response['has_more']:
page += 1
print("Retrieving page {}".format(page))
sleep(1) # need to wait 1 sec before next call due to rate limits
response = client.conversations_history(
channel=CHANNEL,
limit=MESSAGES_PER_PAGE,
cursor=response['response_metadata']['next_cursor']
)
assert response["ok"]
messages = response['messages']
messages_all = messages_all + messages
print(
"Fetched a total of {} messages from channel {}".format(
len(messages_all),
CHANNEL
))
# write the result to a file
with open('messages.json', 'w', encoding='utf-8') as f:
json.dump(
messages_all,
f,
sort_keys=True,
indent=4,
ensure_ascii=False
)
This is using the slack webapi. You would need to install requests package. This should grab all the messages in channel. You need a token which can be grabbed from apps management page. And you can use the getChannels() function. Once you grab all the messages you will need to see who wrote what message you need to do id matching(map ids to usernames) you can use getUsers() functions. Follow this https://api.slack.com/custom-integrations/legacy-tokens to generate a legacy-token if you do not want to use a token from your app.
def getMessages(token, channelId):
print("Getting Messages")
# this function get all the messages from the slack team-search channel
# it will only get all the messages from the team-search channel
slack_url = "https://slack.com/api/conversations.history?token=" + token + "&channel=" + channelId
messages = requests.get(slack_url).json()
return messages
def getChannels(token):
'''
function returns an object containing a object containing all the
channels in a given workspace
'''
channelsURL = "https://slack.com/api/conversations.list?token=%s" % token
channelList = requests.get(channelsURL).json()["channels"] # an array of channels
channels = {}
# putting the channels and their ids into a dictonary
for channel in channelList:
channels[channel["name"]] = channel["id"]
return {"channels": channels}
def getUsers(token):
# this function get a list of users in workplace including bots
users = []
channelsURL = "https://slack.com/api/users.list?token=%s&pretty=1" % token
members = requests.get(channelsURL).json()["members"]
return members

aiohttp: client.get() returns html tag rather than file

I'm trying to downloads bounding box files (stored as gzipped tar archives) from image-net.org. When I print(resp.read()), rather than a stream of bytes representing the archive, I get the HTML b'<meta http-equiv="refresh" content="0;url=/downloads/bbox/bbox/[wnid].tar.gz" />\n where [wnid] refers to a particular wordnet identification string. This leads to the error tarfile.ReadError: file could not be opened successfully. Any thoughts on what exactly is the issue and/or how to fix it? Code is below (images is a pandas data frame).
def get_boxes(images, nthreads=1000):
def parse_xml(xml):
return 0
def read_tar(data, wnid):
bytes = io.BytesIO(data)
tar = tarfile.open(fileobj=bytes)
return 0
async def fetch_boxes(wnid, client):
url = ('http://www.image-net.org/api/download/imagenet.bbox.'
'synset?wnid={}').format(wnid)
async with client.get(url) as resp:
res = await loop.run_in_executor(executor, read_tar,
await resp.read(), wnid)
return res
async def main():
async with aiohttp.ClientSession(loop=loop) as client:
tasks = [asyncio.ensure_future(fetch_boxes(wnid, client))
for wnid in images['wnid'].unique()]
return await asyncio.gather(*tasks)
loop = asyncio.get_event_loop()
executor = ThreadPoolExecutor(nthreads)
shapes, boxes = zip(*loop.run_until_complete(main()))
return pd.concat(shapes, axis=0), pd.concat(boxes, axis=0)
EDIT: I understand now that this is a meta refresh used as a redirect. Would this be considered a "bug" in `aiohttp?
This is ok.
Some services have redirects from user-friendly web-page to a zip-file. Sometimes it is implemented using HTTP status (301 or 302, see example below) or using page with meta tag that contains redirect like in your example.
HTTP/1.1 302 Found
Location: http://www.iana.org/domains/example/
aiohttp can handle first case - automatically (when allow_redirects = True by default).
But in the second case library retrieves simple HTML and can't handle that automatically.
I run into the same problem \n
when I tried to download using wget from the same url as you did
http://www.image-net.org/api/download/imagenet.bbox.synset?wnid=n01729322
but it works if you input this directly
www.image-net.org/downloads/bbox/bbox/n01729322.tar.gz
ps. n01729322 is the wnid

Categories

Resources