prevent bot from replying to an already replied tweet? - python

Is it possible to have a list to store the ID's of recent tweets that the bot replied to instead of saving it in a text file?
def on_status(self, status):
timelineTweets = api.user_timeline(screen_name="USERNAME")
alreadyReplied = [-1]
if timelineTweets[0].id not in alreadyReplied:
lastTweet = timelineTweets[0]
api.update_status('#USERNAME' + gettext(), in_reply_to_status_id=lastTweet.id)
alreadyReplied.append(lastTweet.id)
if len(alreadyReplied) == 20:
alreadyReplied.pop()
return True

Related

Extract timestamp and username when streaming tweets using tweepy

I have the following class, in order to extract tweets in real time containing a given hashtag #Today:
class TweetListener(StreamingClient):
def on_data(self, raw_data):
logging.info(raw_data)
producer.send(topic_name, value=raw_data)
return True
def on_error(self, status_code):
if status_code == 420:
return False
def start_streaming_tweets(self):
rule = StreamRule(value="#Today lang:en")
self.add_rules(rule)
self.filter()`
However, in this way, the object sent is something like:
ConsumerRecord(topic='twitter', partition=0, offset=46, timestamp=1675201799030, timestamp_type=0, key=None, value=b'{"data":{"edit_history_tweet_ids":["16205398989347923"],"id":"16205398989347923","text":"#Today is a great day!"},"matching_rules":[{"id":"16238748236833856","tag":""}]}', headers=[], checksum=None, serialized_key_size=-1, serialized_value_size=196, serialized_header_size=-1
And so, I don't have any info about the user, the time of publication, the number of likes... Is there any way to get this info?

How do you get a user avatar from a mentioned with discord.py?

i made an avatar command for my bot but it doest work with mentioions, im trying to not use ' #client.command ' because it causes problems for the rest of the code
if message.content.startswith('+avatar'):
sender = message.author
name = sender.display_name
avatar_url = sender.avatar_url
embedVar = discord.Embed(title="**{}**".format(sender), description="Username: {}".format(name), color=0xec9e36)
embedVar.set_image(url = avatar_url)
await message.channel.send(embed=embedVar)
is what i have written and i cant find any ways of making it work, any kind of help would be appreciated!
I am guessing you are trying to do something like
+avatar #mention
A mention is basically a string marking that contains ID of the user.
import re
def is_mention(s):
'''Check if the string is a valid 'mention' string for Discord'''
try:
return bool(re.match(r'<#!?(\d+)>', s))
except Exception:
return None
def mention_to_id(s):
'''Extract user-id from the mention'''
if is_mention(s):
return int(re.sub(r'[<>!#]', '', s))
return None
You can first extract the mention string using mention_str = message.content.split()[1], and then call mention_to_id function to extract the ID from mention string.
After that, call get_user on your bot (client) object to get the user, which will return a User object which has avatar_url field.
To summarize,
if message.content.startswith('+avatar'):
message_parts = message.content.split()
if len(message_parts) > 1:
mention_str = message.content.split()[1]
if is_mention(mention_str):
user = client.get_user(mention_to_id(mention_str)
# do whatever with user.avatar_url
else:
# Warn about invalid mention
else:
# Whatever you want to do when there's no mention
References:
get_user: https://discordpy.readthedocs.io/en/latest/api.html#discord.Client.get_user
User: https://discordpy.readthedocs.io/en/latest/api.html#discord.User
I used message.mentions to find if someone is mentioned in the message and if mentioned i took the mentioned user's name and avatar_url else i took the sender's name and avatar_url
if message.content.startswith('+avatar'):
if( len(message.mentions) > 0 ): #checking if someone is mentioned
#mesage.mentions[0] is the first mentioned user in the message
name = message.mentions[0].display_name
avatar_url = message.mentions[0].avatar_url
else: #no one is mentioned
name = sender.display_name
avatar_url = sender.avatar_url
embedVar = discord.Embed(title="Username: {}".format(name), color=0xec9e36)
embedVar.set_image(url = avatar_url)
await message.channel.send(embed=embedVar)

How do I count how often a command was executed?

I would like to display via bot how many times a command has been executed.
For this I have already inquired here on the platform and have tried different things. About my code:
e = discord.Embed(color=discord.Colour.green())
user = ctx.author
e.title = f "New suggestion!"
e.set_footer(text=f"{user} | {user.id}")
e.description = f "**__Submitter:__**\n {ctx.author}"
e.add_field(name="__Suggestion:__", value=f"{text}")
e.set_thumbnail(url=ctx.message.author.avatar_url)
e.timestamp = datetime.utcnow()
await channel.send(embed=e)
await ctx.message.add_reaction("✅")
The bot should add Suggestion no. #number in the title, which will go up one at a time when the command is executed. I have already tried the following:
def __init__(self, bot):
self.bot = bot
self.counter = 0
#Shortened
e.title = f "Suggestion no. {self.counter}"
Or just as well:
e.title = f "Suggestion no. {self.counter + 1}
But that didn't help, so how do I make sure that a correct number is displayed and that in case of a bot restart this number continues to rise and doesn't start again from the beginning.
Note that a global event did not work for me somehow!
You actually need to update the counter variable
self.counter += 1
e.title = f"Suggestion no. {self.counter}"
You can save it in a JSON or text file if you want the variable to continue after the bot restarts

Python Telegram Bot how to wait for user answer to a question And Return It

Context:
I am using PyTelegramBotAPi or Python Telegram Bot
I have a code I am running when a user starts the conversation.
When the user starts the conversation I need to send him the first picture and a question if He saw something in the picture, the function needs to wait for the user input and return whether he saw it or not.
After that, I will need to keep sending the picture in a loop and wait for the answer and run a bisection algorithm on it.
What I have tried so far:
I tried to use reply markup that waits for a response or an inline keyboard with handlers but I am stuck because my code is running without waiting for the user input.
The code:
#bot.message_handler(func=lambda msg: msg in ['Yes', 'No'])
#bot.message_handler(commands=['start', 'help'])
def main(message):
"""
This is my main function
"""
chat_id = message.chat.id
try:
reply_answer = message.reply_to_message.text
except AttributeError:
reply_answer = '0'
# TODO : should wait for the answer asynchnonossly
def tester(n, reply_answer):
"""
Displays the current candidate to the user and asks them to
check if they see wildfire damages.
"""
print('call......')
bisector.index = n
bot.send_photo(
chat_id=chat_id,
photo=bisector.image.save_image(),
caption=f"Did you see it Yes or No {bisector.date}",
reply_markup=types.ForceReply(selective=True))
# I SHOUL WAIT FOR THE INPUT HERE AND RETURN THE USER INPUT
return eval(reply_answer)
culprit = bisect(bisector.count, lambda x: x, partial(tester, reply_answer=reply_answer) )
bisector.index = culprit
bot.send_message(chat_id, f"Found! First apparition = {bisector.date}")
bot.polling(none_stop=True)
The algorithm I am running on the user input is something like this :
def bisect(n, mapper, tester):
"""
Runs a bisection.
- `n` is the number of elements to be bisected
- `mapper` is a callable that will transform an integer from "0" to "n"
into a value that can be tested
- `tester` returns true if the value is within the "right" range
"""
if n < 1:
raise ValueError('Cannot bissect an empty array')
left = 0
right = n - 1
while left + 1 < right:
mid = int((left + right) / 2)
val = mapper(mid)
tester_values = tester(val) # Here is where I am using the ouput from Telegram bot
if tester_values:
right = mid
else:
left = mid
return mapper(right)
I hope I was clear explaining the problem, feel free to ask any clarification.
If you know something that can point me in the right direction in order to solve this problem, let me know.
I have tried a similar question but I am not getting answers.
You should save your user info in a database. Basic fields would be:
(id, first_name, last_name, username, menu)
What is menu?
Menu keeps user's current state. When a user sends a message to your bot, you check the database to find out about user's current sate.
So if the user doesn't exist, you add them to your users table with menu set to MainMenu or WelcomeMenu or in your case PictureMenu.
Now you're going to have a listener for update function, let's assume each of these a menu.
#bot.message_handler(commands=['start', 'help'])
so when the user sends start you're going to check user's menu field inside the function.
#bot.message_handler(commands=['start', 'help'])
def main(message):
user = fetch_user_from_db(chat_id)
if user.menu == "PictureMenu":
if message.photo is Not None:
photo = message.photo[0].file_id
photo_file = download_photo_from_telegram(photo)
do_other_things()
user.menu = "Picture2Menu";
user.save();
else:
send_message("Please send a photo")
if user.menu == "Picture2Menu":
if message.photo is Not None:
photo = message.photo[0].file_id
photo_file = download_photo_from_telegram(photo)
do_other_things()
user.menu = "Picture3Menu";
user.save();
else:
send_message("Please send a photo")
...
I hope you got it.
I have found the answer:
the trick was to use next_step_handler, and message_handler_function to handle command starting with start and help
Then as suggested by #ALi in his answer, I will be saving the user input answer as well as the question id he replied to in a dictionary where keys are questions and id are the answer.
Once the user has answered all questions, I can run the algorithms on his answer
Here is how it looks like in the code :
user_dict = {}
# Handle '/start' and '/help'
#bot.message_handler(commands=['help', 'start'])
def send_welcome(message):
# initialise the the bisector and
bisector = LandsatBisector(LON, LAT)
indice = 0
message = send_current_candidate(bot, message, bisector, indice)
bot.register_next_step_handler(
message, partial(
process_step, indice, bisector))
def process_step(indice, bisector, message):
# this run a while loop and will that send picture and will stop when the count is reached
response = message.text
user = User.create_get_user(message, bisector=bisector)
if indice < bisector.count - 1:
indice += 1
try:
# get or create
user.responses[bisector.date] = response # save the response
message = send_current_candidate(bot, message, bisector, indice)
bot.register_next_step_handler(
message, partial(
process_step, indice, bisector))
except Exception as e:
print(e)
bot.reply_to(message, 'oooops')
else:
culprit = bisect(bisector.count,
lambda x: x,
partial(
tester_function,
responses=list(user.responses.values())))
bisector.index = culprit
bot.reply_to(message, f"Found! First apparition = {bisector.date}")

Set tweets counts for each items in tweepy stream

I have a problem and cant get to a solution..
I have written a python script to Stream twitter tweets.
My issue is I need to read 5 tweets for each words in the given list.
Below is the code:
class TweetListener(StreamListener):
def on_status(self,status):
print "TWEET ARRIVED!!!"
print "Tweet Text : %s" % status.text
print "Author's name : %s" % status.author.screen_name
print "Time of creation : %s" % status.created_at
print "Source of Tweet : %s" % status.source
time.sleep(10)
return True
def on_error(self, status):
print status
if status == 420:
print "Too soon reconnected, Exiting!!"
return False
sys.exit()
def search_tweets():
twitterStream = Stream(connect().auth, TweetListener())
twitterStream.filter(track=['Cricket','Maths','Army','Sports'],languages = ["en"],async=True)
Here I need to get 5 tweets each for Cricket, Maths, Army & Sports
What I am getting is an infinite number of tweets for the above elements.
Any help will be highly appreciated.
Thanks & regards.
class TweetListener(StreamListener):
def __init__(self, list_=None,dict_= None):
self.keys_= list_
self.dict = dict_
def on_status(self, status):
str_ = status.text.lower()
for key in self.dict.keys():
if key.lower() in str_.lower():
if self.dict[key] <= 0:
return True
else:
self.dict[key] -=1
self.performAction(key,status)
if all(value == 0 for value in self.dict.values()):
return False
def on_error(self, status):
print status
if status == 420:
print "Too soon reconnected . Will terminate the program"
return False
sys.exit()
def create_dict(list_):
no_of_tweets = 5
dict_ = {k:no_of_tweets for k in list_ }
return dict_
def search_tweets():
search_word = ['Cricket','Maths','Army','Sports']
twitterStream = Stream(connect().auth, TweetListener(list_=search_word , dict_=create_dict(search_word)))
twitterStream.filter(track=search_word ,languages = ["en"],async=True)
Here I initialize a list with all the required words that are to be searched for tweets, then I create a dictionary with key:value as word_to_be_searched:count_as_5 in the create_dict(list_) function, like Cricket:5, Maths:5, Army:5, Sports:5 and so on. Then I pass the list along with the dictionary to the TweetListener class.
I override the on_status function to retrieve tweets and then compare the tweets with the key field of my dictionary. It is obvious there will be a match and then, in that case, I decrease the value(as counter here) by 1.
When all the values become 0, then I return false to break the loop and close the thread.
[Note, if any value corresponding to a key has become zero, it indicates that the required no of tweets are already captured so we will not proceed with any more tweets on that word.]
Then in the performAction(key, status) function {key=one of the searched words and status = tweet captured} I perform my required task.

Categories

Resources