Delete outgoing message sent by telegram bot (Telegram, python) - python-telegram-bot

i am stuck in my code as i do not know how to input/derive the message_id of the outgoing message forwarded by my bot.
Background: This is just a part of my code which i would subsequently integrate into the main code. Here, i am testing the functionality of forwarding messages + deleting them. I am able to successfully forward them out but i am stuck at deleting them. i am able to give the input of the chat_id but not able to do so for the message_id to delete. Is there a way to do it especially when i am gonna integrate to my main script which can have a few groups to manage. Please assist the noob in me. Thank you!
My script:
import logging
from telegram import ReplyKeyboardMarkup, ReplyKeyboardRemove, Update
from telegram.ext import (
Updater,
CommandHandler,
MessageHandler,
Filters,
ConversationHandler,
CallbackContext,
)
TOKEN = "PUT TOKEN HERE" #INPUT HERE
# Enable logging
logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO)
logger = logging.getLogger(__name__)
MSG, DELETE_MSG = range(2)
def start(update: Update, context: CallbackContext) -> int:
update.message.reply_text(
'Hi! Please post the message you would like to share:')
return MSG
def send(update: Update, context: CallbackContext) -> int:
user = update.message.from_user
logger.info("Message of %s: %s", user.first_name, update.message.text)
print(update.message.message_id)
send = update.message.forward(chat_id= 'PUT CHAT ID OF OUTGOING GROUP HERE') #INPUT HERE
update.message.reply_text("Please delete")
return DELETE_MSG
def delete_msg(update: Update, context: CallbackContext) -> int:
user = update.message.from_user
logger.info("edit of %s: %s", user.first_name, update.message.text)
update.message.delete(chat_id='PUT CHAT ID OF OUTGOING GROUP HERE',
message_id='IM STUCK HERE') #INPUT HERE
return ConversationHandler.END
def cancel(update: Update, context: CallbackContext) -> int:
user = update.message.from_user
logger.info("User %s canceled the conversation.", user.first_name)
update.message.reply_text('Bye! I hope we can talk again some day.', reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END
def main() -> None:
updater = Updater(TOKEN, use_context=True)
dispatcher = updater.dispatcher
conv_handler = ConversationHandler(
entry_points=[CommandHandler('start', start)],
states={
MSG: [MessageHandler(~Filters.command, send)],
DELETE_MSG: [MessageHandler(~Filters.command, delete_msg)]
},
fallbacks=[CommandHandler('cancel', cancel)],
)
dispatcher.add_handler(conv_handler)
updater.start_polling()
updater.idle()
if __name__ == '__main__':
main()

update.message.reply_text("Please delete") must be a variable and, then, you'll be able to context.bot.deleteMessage the message_id of that. Just like this:
must_delete = update.message.reply_text("Please delete")
context.bot.deleteMessage (message_id = must_delete.message_id,
chat_id = update.message.chat_id)
Give it a try and let me know if this worked for you!!

Related

How to test fastapi with oracle, sql alchemy?

I have a fastapi application where I use sqlalchemy and stored procedures.
Now I want to test my endpoints like in the documentation
import pytest
from fastapi.testclient import TestClient
from fastapi import FastAPI
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from ..dependencies import get_db
import cx_Oracle
host = 'xxxx'
port = 1111
sid = 'FUU'
user = 'bar'
password = 'fuubar'
sid = cx_Oracle.makedsn(host, port, sid=sid)
database_url = 'oracle://{user}:{password}#{sid}'.format(
user=user,
password=password,
sid=sid,
)
engine = create_engine(database_url, connect_args={"check_same_thread": False})
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
app = FastAPI()
init_router(app)
#pytest.fixture()
def session():
db = TestingSessionLocal()
try:
yield db
finally:
db.close()
#pytest.fixture()
def client(session):
# Dependency override
def override_get_db():
try:
yield session
finally:
session.close()
app.dependency_overrides[get_db] = override_get_db
yield TestClient(app)
def test_index(client):
res = client.get("/")
assert res.text
assert res.status_code == 200
def test_search_course_by_verid_exist():
response = client.get(
'search', params={"search_query": "1111", "semester": "S2022"})
# course exist
assert response.status_code == 200
I've tried it with creating a new app and/or importing it via getting the app from the main.py
from ..main import app
The method is in my courses router.
#router.get("/search", status_code=status.HTTP_200_OK)
async def search_course(
response: Response,
search_query: Union[str, None] = None,
semester: Union[int, None] = None,
db: Session = Depends(get_db),
):
.....
return response
The index test already failes by returning assert 400 == 200. For the 2nd (test_search_course_by_verid_exist) I'll get
AttributeError: 'function' object has no attribute 'get'
My main has some middleware settings like
app.add_middleware(
SessionMiddleware, secret_key="fastAPI"
) # , max_age=300 this should match Login action timeout in token-settings of a realm
app.add_middleware(
TrustedHostMiddleware,
allowed_hosts=settings.ALLOWED_HOSTS,
)
# MIDDLEWARE
#app.middleware("http")
async def check_route(request: Request, call_next):
....
I'm clueless what I'm missing or if things are just different with cx_Oracle
I've tried changing the testclient from fastapi to the starlette one. I've tried not overriding the db and just import the original db settings (which are basically the same). But nothing works.
I'm not sure if this is the proper way to test FastAPI application, https://fastapi.tiangolo.com/tutorial/testing/
Why you didn't declare client as :
client = TestClient(app)
?
Idk if this was the root problem. But naming my fixtures solved the problem and the db connection is working.
conftest.py
#pytest.fixture(name="db_session", scope="session")
def db_session(app: FastAPI) -> Generator[TestingSessionLocal, Any, None]:
Also created the app fixture
#pytest.fixture(name="app", scope="session")
def app() -> Generator[FastAPI, Any, None]:
"""
Create a fresh database on each test case.
"""
_app = start_application()
yield _app

How to fix "ERROR - No error handlers are registered, logging exception." with telegram.ext.dispatcher?

I downloaded bot which copy messages from telegram group and sending to my discord channel.
But when I am trying to send a message in a group, I am getting this error.
2022-12-31 08:25:19,877 - telegram.ext.dispatcher - ERROR - No error handlers are registered, logging exception.
Traceback (most recent call last):
File "C:\Python310\lib\site-packages\telegram\ext\dispatcher.py", line 557, in process_update
handler.handle_update(update, self, check, context)
File "C:\Python310\lib\site-packages\telegram\ext\handler.py", line 199, in handle_update
return self.callback(update, context)
File "C:\Discord-Telegram-Bot-main 1\main.py", line 46, in getTgAnnouncement
textUpdate = update.channel_post.text
AttributeError: 'NoneType' object has no attribute 'text'
I am using python 3.10
Python-telegram-bot 13.15
Below i will add the code
import requests
import json
import asyncio
import os
import json
import logging
from dotenv import load_dotenv
import discord
from discord.ext import commands
from telegram import Update
from telegram.ext import Updater,CallbackContext,MessageHandler,Filters
from telegram.utils import helpers
from telegram.ext.dispatcher import run_async
load_dotenv('token.env')
discordToken = os.getenv('DCTOKEN')# discord bot token
telegramToken = os.getenv('TGTOKEN')# telegram bot token
discordChannelId = os.getenv('DCCID')# discord announcement channel id
loop = asyncio.get_event_loop()
#--------------------------------------------------------------------------------------------------------------
bot = commands.Bot(
command_prefix="k!",
case_insensitive=True,
intents=discord.Intents.all(),
help_command=None
)
async def sendDcAnnouncement(textUpdate,nonTextUpdate):
announcementChannel = bot.get_channel(int(discordChannelId))
if textUpdate != None and nonTextUpdate != None:
await announcementChannel.send(textUpdate,file=discord.File(nonTextUpdate))
os.remove(nonTextUpdate)
elif textUpdate == None:
await announcementChannel.send(file=discord.File(nonTextUpdate))
os.remove(nonTextUpdate)
elif nonTextUpdate == None:
await announcementChannel.send(textUpdate)
def getTgAnnouncement(update: Update, context: CallbackContext):
textUpdate = None
nonTextUpdate = None
updateType = helpers.effective_message_type(update)
if updateType == 'text':
textUpdate = update.channel_post.text
else:
textUpdate = update.channel_post.caption
if updateType == 'photo':
nonTextUpdate = update.channel_post.photo[-1].get_file()['file_path']
elif updateType == 'video':
nonTextUpdate = update.channel_post.video.get_file()['file_path']
elif updateType == 'document':
nonTextUpdate = update.channel_post.document.get_file()['file_path']
elif updateType == 'voice':
nonTextUpdate = update.channel_post.voice.get_file()['file_path']
loop.create_task(sendDcAnnouncement(textUpdate,nonTextUpdate))
#bot.event
async def on_ready():
print(f'logged in as {bot.user}')
updater = Updater(token=telegramToken,use_context=True)
dispatcher = updater.dispatcher
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO)
getTgAnnouncement_handler = MessageHandler((~Filters.command),getTgAnnouncement)
dispatcher.add_handler(getTgAnnouncement_handler)
updater.start_polling()
bot.run(discordToken)
#--------------------------------------------------------------------------------------------------------------
I expect that this bot will send messages and that I will not get these errors.
MessageHandler handles all updates that have one of the fields message, edited_message, channel_post or edited_channel_post. So in getTGAnnouncement, it may very well be the case that update.channel_post is None.
If you want that handler to only handle channel_post update, please use Filters.update.channel_post.
As a side note: I see that you're using python-telegram-bot v13.x. Support for that version is ending soon in favor of the new v20.x.
Disclaimer: I'm currently the maintainer of python-telegram-bot.

ConversationHandler State did not pass in Python telegram bot

I am trying out the python telegram bot. I am trying to pass the state from button to questionTwo. However, when I run it, it can successfully pass through /start and answerOne. Yet, it will stop at answerOne by displaying it.
It will not display the second question and inline keyboard for me to select the answer, I believe the state didn't pass to questionTwo, but I am not sure which part is wrong. Thank you in advanceb
import logging
from telegram import (ReplyKeyboardMarkup, ReplyKeyboardRemove,InlineKeyboardButton, InlineKeyboardMarkup)
from telegram.ext import (Updater, CommandHandler, MessageHandler, Filters,
ConversationHandler,CallbackQueryHandler)
answerOne, answerTwo = range(2)
def start(update, context):
keyboard = [
[
InlineKeyboardButton("Option 1", callback_data='1'),
InlineKeyboardButton("Option 2", callback_data='2'),
],
[InlineKeyboardButton("Option 3", callback_data='3')],
]
reply_markup = InlineKeyboardMarkup(keyboard)
update.message.reply_text('Please choose:', reply_markup=reply_markup)
return answerOne
def button(update, context):
query = update.callback_query
# CallbackQueries need to be answered, even if no notification to the user is needed
# Some clients may have trouble otherwise. See https://core.telegram.org/bots/api#callbackquery
query.answer()
query.edit_message_text(text="Selected option: {}".format(query.data))
return questionTwo
def questionTwo(update, context):
keyboard = [
[
InlineKeyboardButton("Option 4", callback_data='4'),
InlineKeyboardButton("Option 5", callback_data='5'),
],
[InlineKeyboardButton("Option 6", callback_data='6')],
]
reply_markup = InlineKeyboardMarkup(keyboard)
update.message.reply_text('Please choose:', reply_markup=reply_markup)
return answerTwo
def answerTwo(update, context):
query = update.callback_query
# CallbackQueries need to be answered, even if no notification to the user is needed
# Some clients may have trouble otherwise. See https://core.telegram.org/bots/api#callbackquery
query.answer()
query.edit_message_text(text="Selected option: {}".format(query.data))
def cancel(update, context):
user = update.message.from_user
logger.info("User %s canceled the conversation.", user.first_name)
update.message.reply_text('Bye! I hope we can talk again some day.',
reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END
def main():
# Create the Updater and pass it your bot's token.
# Make sure to set use_context=True to use the new context based callbacks
# Post version 12 this will no longer be necessary
updater = Updater('TOKEN', use_context=True)
# Get the dispatcher to register handlers
dp = updater.dispatcher
conv_handler = ConversationHandler(
entry_points=[CommandHandler('start', start)],
states={
answerOne: [CallbackQueryHandler(button)],
questionTwo:[MessageHandler(Filters.text & ~Filters.command, questionTwo)],
answerTwo:[CallbackQueryHandler(answerTwo)],
},
fallbacks=[CommandHandler('cancel', cancel)]
)
dp.add_handler(conv_handler)
# Start the Bot
updater.start_polling()
# Run the bot until you press Ctrl-C or the process receives SIGINT,
# SIGTERM or SIGABRT. This should be used most of the time, since
# start_polling() is non-blocking and will stop the bot gracefully.
updater.idle()
if __name__ == '__main__':
main()
import logging
from telegram import (ReplyKeyboardMarkup, ReplyKeyboardRemove,InlineKeyboardButton, InlineKeyboardMarkup)
from telegram.ext import (Updater, CommandHandler, MessageHandler, Filters,
ConversationHandler,CallbackQueryHandler)
answerOne, questionTwo = range(2)
logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
)
logger = logging.getLogger(__name__)
def start(update, context):
keyboard = [
[
InlineKeyboardButton("Option 1", callback_data='1'),
InlineKeyboardButton("Option 2", callback_data='2'),
],
[InlineKeyboardButton("Option 3", callback_data='3')],
]
reply_markup = InlineKeyboardMarkup(keyboard)
update.message.reply_text('Please choose:', reply_markup=reply_markup)
return answerOne
def button(update, context):
query = update.callback_query
# CallbackQueries need to be answered, even if no notification to the user is needed
# Some clients may have trouble otherwise. See https://core.telegram.org/bots/api#callbackquery
query.answer()
query.edit_message_text(text="Selected option: {}".format(query.data))
return questionTwo
def questionTwo(update, context):
keyboard = [
[
InlineKeyboardButton("Option 4", callback_data='4'),
InlineKeyboardButton("Option 5", callback_data='5'),
],
[InlineKeyboardButton("Option 6", callback_data='6')],
]
reply_markup = InlineKeyboardMarkup(keyboard)
update.message.reply_text('Please choose:', reply_markup=reply_markup)
return answerOne
def cancel(update, context):
user = update.message.from_user
logger.info("User %s canceled the conversation.", user.first_name)
update.message.reply_text('Bye! I hope we can talk again some day.',
reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END
def main():
# Create the Updater and pass it your bot's token.
# Make sure to set use_context=True to use the new context based callbacks
# Post version 12 this will no longer be necessary
updater = Updater('Token', use_context=True)
# Get the dispatcher to register handlers
dp = updater.dispatcher
conv_handler = ConversationHandler(
entry_points=[CommandHandler('start', start)],
states={
answerOne: [CallbackQueryHandler(button)],
questionTwo:[MessageHandler(Filters.text & ~Filters.command, questionTwo)]
},
fallbacks=[CommandHandler('cancel', cancel)]
)
dp.add_handler(conv_handler)
# Start the Bot
updater.start_polling()
# Run the bot until you press Ctrl-C or the process receives SIGINT,
# SIGTERM or SIGABRT. This should be used most of the time, since
# start_polling() is non-blocking and will stop the bot gracefully.
updater.idle()
if __name__ == '__main__':
main()

How to get the course ID for Google Classroom API

I'm trying to use Google Classroom API, I've read through their documentation, and the course ID is used for basically everything, but they never explained where to find the course ID for a course.
It also seems like when you create a course, the function would return the course ID, but I'm wondering if it's possible to get the course ID for courses that already exist.
As shown in the quickstart page for the documentation (https://developers.google.com/classroom/quickstart/python), you can run a piece of code to list the first 10 courses the user has access to with their credentials. You can then add a print(course['id']) statement whilst iterating through the courses to print the id of the courses you have retrieved. The python example is shown below
from __future__ import print_function
import pickle
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
# If modifying these scopes, delete the file token.pickle.
SCOPES = ['https://www.googleapis.com/auth/classroom.courses.readonly']
def main():
"""Shows basic usage of the Classroom API.
Prints the names of the first 10 courses the user has access to.
"""
creds = None
# The file token.pickle stores the user's access and refresh tokens, and is
# created automatically when the authorization flow completes for the first
# time.
if os.path.exists('token.pickle'):
with open('token.pickle', 'rb') as token:
creds = pickle.load(token)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
'credentials.json', SCOPES)
creds = flow.run_local_server(port=0)
# Save the credentials for the next run
with open('token.pickle', 'wb') as token:
pickle.dump(creds, token)
service = build('classroom', 'v1', credentials=creds)
# Call the Classroom API
results = service.courses().list(pageSize=10).execute()
courses = results.get('courses', [])
if not courses:
print('No courses found.')
else:
print('Courses:')
for course in courses:
print(course['name'])
print(course['id'])
if __name__ == '__main__':
main()
I use this in nodejs/javascript to retrieve all classroom
const { google } = require("googleapis");
const classroom = google.classroom('v1');
const SCOPES = [
"https://www.googleapis.com/auth/classroom.rosters",
"https://www.googleapis.com/auth/classroom.profile.emails",
"https://www.googleapis.com/auth/classroom.profile.photos",
"https://www.googleapis.com/auth/classroom.courses"
];
google.options({
auth: client,
});
//retrieve all classroom
async function getClassroom() {
try {
const res = await classroom.courses.list(
// {
// pageSize: 10,
// pageToken: "",
// }
);
console.log(res.data, "res");
} catch (error) {
console.error("Error:", error.message,);
}
}
Note: The client is my preferred authorization method

Using side effects in Akka Streams to implement commands received from a websocket

I want to be able to click a button on a website, have it represent a command, issue that command to my program via a websocket, have my program process that command (which will produce a side effect), and then return the results of that command to the website to be rendered.
The websocket would be responsible for updating state changes applied by different actors that are within the users view.
Example: Changing AI instructions via the website. This modifies some values, which would get reported back to the website. Other users might change other AI instructions, or the AI would react to current conditions changing position, requiring the client to update the screen.
I was thinking I could have an actor responsible for updating the client with changed information, and just have the receiving stream update the state with the changes?
Is this the right library to use? Is there a better method to achieve what I want?
You can use akka-streams and akka-http for this just fine. An example when using an actor as a handler:
package test
import akka.actor.{Actor, ActorRef, ActorSystem, Props, Stash, Status}
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.ws.{Message, TextMessage}
import akka.http.scaladsl.server.Directives._
import akka.stream.scaladsl.{Flow, Sink, Source, SourceQueueWithComplete}
import akka.stream.{ActorMaterializer, OverflowStrategy, QueueOfferResult}
import akka.pattern.pipe
import scala.concurrent.{ExecutionContext, Future}
import scala.io.StdIn
object Test extends App {
implicit val actorSystem = ActorSystem()
implicit val materializer = ActorMaterializer()
implicit def executionContext: ExecutionContext = actorSystem.dispatcher
val routes =
path("talk") {
get {
val handler = actorSystem.actorOf(Props[Handler])
val flow = Flow.fromSinkAndSource(
Flow[Message]
.filter(_.isText)
.mapAsync(4) {
case TextMessage.Strict(text) => Future.successful(text)
case TextMessage.Streamed(textStream) => textStream.runReduce(_ + _)
}
.to(Sink.actorRefWithAck[String](handler, Handler.Started, Handler.Ack, Handler.Completed)),
Source.queue[String](16, OverflowStrategy.backpressure)
.map(TextMessage.Strict)
.mapMaterializedValue { queue =>
handler ! Handler.OutputQueue(queue)
queue
}
)
handleWebSocketMessages(flow)
}
}
val bindingFuture = Http().bindAndHandle(routes, "localhost", 8080)
println("Started the server, press enter to shutdown")
StdIn.readLine()
bindingFuture
.flatMap(_.unbind())
.onComplete(_ => actorSystem.terminate())
}
object Handler {
case object Started
case object Completed
case object Ack
case class OutputQueue(queue: SourceQueueWithComplete[String])
}
class Handler extends Actor with Stash {
import context.dispatcher
override def receive: Receive = initialReceive
def initialReceive: Receive = {
case Handler.Started =>
println("Client has connected, waiting for queue")
context.become(waitQueue)
sender() ! Handler.Ack
case Handler.OutputQueue(queue) =>
println("Queue received, waiting for client")
context.become(waitClient(queue))
}
def waitQueue: Receive = {
case Handler.OutputQueue(queue) =>
println("Queue received, starting")
context.become(running(queue))
unstashAll()
case _ =>
stash()
}
def waitClient(queue: SourceQueueWithComplete[String]): Receive = {
case Handler.Started =>
println("Client has connected, starting")
context.become(running(queue))
sender() ! Handler.Ack
unstashAll()
case _ =>
stash()
}
case class ResultWithSender(originalSender: ActorRef, result: QueueOfferResult)
def running(queue: SourceQueueWithComplete[String]): Receive = {
case s: String =>
// do whatever you want here with the received message
println(s"Received text: $s")
val originalSender = sender()
queue
.offer("some response to the client")
.map(ResultWithSender(originalSender, _))
.pipeTo(self)
case ResultWithSender(originalSender, result) =>
result match {
case QueueOfferResult.Enqueued => // okay
originalSender ! Handler.Ack
case QueueOfferResult.Dropped => // due to the OverflowStrategy.backpressure this should not happen
println("Could not send the response to the client")
originalSender ! Handler.Ack
case QueueOfferResult.Failure(e) =>
println(s"Could not send the response to the client: $e")
context.stop(self)
case QueueOfferResult.QueueClosed =>
println("Outgoing connection to the client has closed")
context.stop(self)
}
case Handler.Completed =>
println("Client has disconnected")
queue.complete()
context.stop(self)
case Status.Failure(e) =>
println(s"Client connection has failed: $e")
e.printStackTrace()
queue.fail(new RuntimeException("Upstream has failed", e))
context.stop(self)
}
}
There are lots of places here which could be tweaked, but the basic idea remains the same. Alternatively, you could implement the Flow[Message, Message, _] required by the handleWebSocketMessages() method by using GraphStage. Everything used above is also described in detail in akka-streams documentation.

Resources