How do I mark a message as read? - telegram

How do I mark a message as read?
app = Client(session_name, api_id, api_hash)
#app.on_message()
async def my_handler(client, message):
await app.send_message(message.from_user.username, "ok boss")
await app.read_chat_history(message.from_user.username)
app.run()
I expected the bot's message to be ticked that he had read it

Client.read_chat_history()
Mark a chat’s message history as read.
Usable by
[X] Users
[ ] Bots
Parameters:
chat_id (int | str) – Unique identifier (int) or username (str) of the target chat. For your personal cloud (Saved Messages) you can simply use “me” or “self”. For a contact that exists in your Telegram address book you can use his phone number (str).
max_id (int, optional) – The id of the last message you want to mark as read; all the messages before this one will be marked as read as well. Defaults to 0 (mark every unread message as read).
Returns:
bool - On success, True is returned.
EXAMPLE
# Mark the whole chat as read
await app.read_chat_history(chat_id)
# Mark messages as read only up to the given message id
await app.read_chat_history(chat_id, 12345)

I expected the bot's message to be ticked that he had read it
This is not possible, messages send by a Telegram Bot will never get those checkmarks.
Those marks are only for messages send from a user-account

Related

Telegram Bot -UnhandledPromiseRejectionWarning: Error: 400: Bad Request: chat not found

I am trying to get chat from one user using my telegram bot. Below is my code so far
const { Telegraf } = require('telegraf')
const bot = new Telegraf(process.env.BOT_TOKEN);
bot.telegram.getChat('username', async (ctx) =>{'incoming message ', console.log(ctx.message.tesx)})
from Doument
(method) Telegram.getChat(chatId: string | number): Promise<ChatFromGetChat>
Get up to date information about the chat (current name of the user for one-on-one conversations, current username of a user, group or channel, etc.).
#param chatId — Unique identifier for the target chat or username of the target supergroup or channel (in the format
#channelusername — )
so in my parameter i use the username of that particular chat (not group chat, only 1 person) but it return chat not found.
Any advice is appreciated
You have to pass the chat and chat id for the getChat instead of just passing a string.
bot.telegram.getChat(ctx.message.chat.id, ctx.message.chat, async (ctx) =>
{'incoming message ', console.log(ctx.message.tesx)})

Python-Telegram-Bot ConversationHandler unable to receive Message

I am using the Python-Telegram-Bot Package. My telegram bot is unable to receive a message at state FIRST. After sending my bot /start , I am prompted the text message as shown in start_command. However, after sending a url to the bot, the bot is unable to receive the message as seen in the 'single tick' at the bottom right of my message.
# Stages
FIRST, SECOND = range(2)
def start_command(update: Update, context: CallbackContext):
update.message.reply_text("""
To use:
1. Send the google sheets URL and sheet name to this telegram bot in the following format:
your_google_sheets_url (your_sheet_name)
""")
global user_id
user_id = update.message.from_user['id']
return FIRST
def select(update: Update, context: CallbackContext):
if update.message.text:
user_input = update.message.text
update.message.reply_text('I have received your Google Sheets!')
reply_markup = InlineKeyboardMarkup(build_menu(button_list, n_cols=1))
update.message.reply_text('Please choose the headers:', reply_markup=reply_markup)
return SECOND
dp = updater.dispatcher
dp.add_handler(CommandHandler("start", start_command))
conv_handler = ConversationHandler(
entry_points=[CommandHandler('start', start_command)],
states={
FIRST: [MessageHandler(Filters.text, select)],
SECOND: [CallbackQueryHandler(display)],
},
fallbacks=[CommandHandler('cancel', cancel)],
)
dp.add_handler(conv_handler)
the CommandHandler for start should not be added. This is because when the user inputs /start, the CommandHandler is called to handle the command instead of the ConversationHandler.

EditBannedRequest doesn't work for bot in private channel

I have tried use the EditBannedRequest method for a private channel,
when used it with a client works fine, however, when i use a bot
with administrator permission over the channel, I got this error
Main.telethon.errors.rpcerrorlist.BotMethodInvalidError: The API
access for bot users is restricted. The method you tried to invoke
cannot be executed as a bot (caused by CheckChatInviteRequest)
I handled EditBannedRequest method for receive directly valid
entities without execute get_input_channel and get_input_entity
methods... Then, I printed the values generated for the bot and
compared with values generated for the client, and were equals.
For example:
In the Telegram method, I modified resolve function so:
async def resolve(self, client, utils):
if isinstance(self.channel, InputChannel) and isinstance(self.user_id, InputUser):
self.channel = self.channel
self.user_id = self.user_id
else:
self.channel = utils.get_input_channel(await client.get_input_entity(self.channel))
self.user_id = utils.get_input_user(await client.get_input_entity(self.user_id))
values generated by the client:
InputChannel(channel_id=XXXXXXX, access_hash=XXXXXX)
<class 'Main.telethon.tl.types.InputChannel'>
values sended by the bot:
INVITE_ACCESS = [InputChannel(channel_id=XXXXXXX, access_hash=XXXXXXX)]
USER = [InputUser(user_id=XXXXXXX, access_hash=-XXXXXXX)]
And the function is this:
with TelegramClient(phone, api_id, api_hash).start(bot_token=bot_token) as bot:
result = bot(functions.channels.EditBannedRequest(
channel=INVITE_ACCESS[0],
user_id=USER[0],
banned_rights=types.ChatBannedRights(
until_date=None,
view_messages=True,
send_messages=True
)
))
print(result.stringify())
The final error is this:
Traceback (most recent call last):
File "banneduser.py", line 41, in <module>
client(EditBannedRequest(
File "/usr/local/lib/python3.8/dist-packages/telethon/sync.py", line 39, in syncified
return loop.run_until_complete(coro)
File "/usr/lib/python3.8/asyncio/base_events.py", line 616, in run_until_complete
return future.result()
File "/usr/local/lib/python3.8/dist-packages/telethon/client/users.py", line 30, in _call_
return await self._call(self._sender, request, ordered=ordered)
File "/usr/local/lib/python3.8/dist-packages/telethon/client/users.py", line 77, in _call
result = await future
telethon.errors.rpcerrorlist.ChannelInvalidError: Invalid channel object. Make sure to pass the right types, for instance making sure that the request is designed for channels or otherwise look for a different one more suited (caused by EditBannedRequest)
Any way to make the bot run the EditBannedRequest method without problems in a private channel?
EditBannedRequest can be used by bots just fine, but bots (like the error indicates) cannot use CheckChatInviteRequest.
The access_hash is unique to each account (account A will see person C with hash 123, account B will see person C with has 456).
You should use the channel peer (or marked ID) to let Telethon know you're referring to a channel. Additionally, you should use client.edit_permissions, which is nicer to use than raw API:
chat = types.PeerChannel(123)
# chat = -100123 # equivalent, bot-API style channel ID
# Banning `user` from `chat` forever
await client.edit_permissions(chat, user, view_messages=False)

ASAuthorizationAppleIDRequest with name and mail scope returns nil values

I'm implementing Sign in with Apple and noticed that the email and fullName properties of the returned ASAuthorizationAppleIDCredential are only filled on the very first Sign-In for this Apple ID. On all subsequent Sign-Ins those properties are nil.
Is this a bug on iOS 13 or expected behaviour?
Here is the code I'm using to start the request:
#available(iOS 13.0, *)
dynamic private func signInWithAppleClicked() {
let request = ASAuthorizationAppleIDProvider().createRequest()
request.requestedScopes = [.fullName, .email]
let controller = ASAuthorizationController(authorizationRequests: [request])
controller.delegate = self
controller.presentationContextProvider = self
controller.performRequests()
}
I'm receiving the credential in this delegate method:
public func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
guard let credential = authorization.credential as? ASAuthorizationAppleIDCredential else { return }
let userIdentifier = credential.user
let token = credential.identityToken
let authCode = credential.authorizationCode
let realUserStatus = credential.realUserStatus
let mail = credential.email // nil
let name = credential.fullName // nil
}
Seems like a bug but after reading different posts on apple forums it looks like this seems to be the expected behaviour.
So some takeaways.
On the first time sign in with apple (signup) make sure to create user account on your backend.
In case of any connection error with your servers, make sure you save the user details locally (because you are not getting this next time) and keep retrying to create account on your backend.
For testing on device you can revoke your apple ID login for your app. After revoking it will work like a signup next time and you will get the details like (email, name, etc).
To revoke access on your device with IOS 13.
iPhone Settings > Apple Id > Password & Security > Apple ID logins > {YOUR APP} > Stop using Apple ID
In case you're wondering how to retrieve email second and subsequent times, here's a hint: use identityToken which contains encoded in JWT user authorisation data including email.
Import this library to decode JWT: https://github.com/auth0/JWTDecode.swift
try this code
import JWTDecode
// ...
if let identityTokenData = appleIDCredential.identityToken,
let identityTokenString = String(data: identityTokenData, encoding: .utf8) {
print("Identity Token \(identityTokenString)")
do {
let jwt = try decode(jwt: identityTokenString)
let decodedBody = jwt.body as Dictionary<String, Any>
print(decodedBody)
print("Decoded email: "+(decodedBody["email"] as? String ?? "n/a") )
} catch {
print("decoding failed")
}
Or decode it at PHP backend like this:
print_r(json_decode(base64_decode(str_replace('_', '/', str_replace('-','+',explode('.', $identityTokenString)[1])))));
It is a correct behavior when implementing SignIn with Apple.
This behaves correctly, user info is only sent in the
ASAuthorizationAppleIDCredential upon initial user sign up. Subsequent
logins to your app using Sign In with Apple with the same account do
not share any user info and will only return a user identifier in the
ASAuthorizationAppleIDCredential. It is recommended that you securely
cache the initial ASAuthorizationAppleIDCredential containing the user
info until you can validate that an account has successfully been
created on your server.
To overcome this issue we can store all the required information in Keychain. I have created Singleton class for SignIn With Apple. I am sure it will help you.
Git source: https://github.com/IMHitesh/HSAppleSignIn
You need to follow below steps:
Step:1
Add the AppleSignIn folder into your project.
Step:2
Enable SignIn with Apple in Capabilities.
Step:3 -> IMPORTANT
Goto your UIViewController and Add Container view for SignIn with
apple.
if #available(iOS 13.0, *) {
appleSignIn.loginWithApple(view:viewAppleButton, completionBlock: { (userInfo, message) in
if let userInfo = userInfo{
print(userInfo.email)
print(userInfo.userid)
print(userInfo.firstName)
print(userInfo.lastName)
print(userInfo.fullName)
}else if let message = message{
print("Error Message: \(message)")
}else{
print("Unexpected error!")
}
})
}else{
viewAppleButton.isHidden = true
}
This seems to be the expected behaviour:
This behaves correctly, user info is only sent in the
ASAuthorizationAppleIDCredential upon initial user sign up. Subsequent
logins to your app using Sign In with Apple with the same account do
not share any user info and will only return a user identifier in the
ASAuthorizationAppleIDCredential. It is recommened that you securely
cache the initial ASAuthorizationAppleIDCredential containing the user
info until you can validate that an account has succesfully been
created on your server.
Source https://forums.developer.apple.com/thread/121496#379297
This is not bug but it will indicate that your authentication successfully store to your device setting.
if you want to that all information again then you need to following this states.
go to your device -> Settings -> Apple ID -> Password & Security
-> Apps Using your Apple ID -> you get list of apps used sign in with apple {find your app} -> swift left of your apps row {show Delete option} -> click on Delete
restart your app or repress sign in with apple button
Now you can get all information
In https://developer.apple.com/documentation/signinwithapplerestapi/authenticating_users_with_sign_in_with_apple it says:
Because the user’s information isn’t shared with your app in any subsequent API calls, your app should store it locally, immediately after you receive it from the API response. In case of subsequent failures in your process or network, you can read the information from local storage and try processing it again.
The email would be given on the first time sign in. If the user do not "revoke" the apple sign in of your app (which is in the user's Apple ID of system setting page) the callback for signing in would be returned with a nil email value. You could save the user id and email info of the first time sign-in successful result, and when the next time sign in to judge the difference between the return and the saved info.
A better practice is to judge the the value of ASAuthorizationAppleIDProvider.getCredentialState while your app is being "active" for syncing the sign-in state with back-end server in time.
Please refer to: How to Sign Out of Apple After Being Authenticated
I wrote a Helper class specific for this issue. This Helper class can help to save and retrieve the user info securely to and from keyChain.
I am using SwiftKeychainWrapper library to do the heavy task for me. Feel free to copy paste the helper class in your code.You might need to add any other extra information depending on your need.
import Foundation
import SwiftKeychainWrapper
/// A Helper class which abstract Keychain API related calls.
final class KeyChainService {
// MARK: - Properties
static let shared = KeyChainService()
/// Returns previous saved user name if available.
var appleUserName: String? {
return KeychainWrapper
.standard
.string(forKey: .appAppleUserName)
}
/// Returns previous saved user appleId/email if available.
var appleUserEmail: String? {
return KeychainWrapper
.standard
.string(forKey: .appAppleEmailId)
}
/// Saves the apple user name into keychain.
/// - Parameter name: Apple user name retrieved form AppleLogin.
/// - Returns: true if succeed otherwise false.
#discardableResult
func saveAppleUserName(name: String?) -> Bool {
guard let name = name else {return false}
return KeychainWrapper.standard.set(
name,
forKey: KeychainWrapper.Key.appAppleUserName.rawValue
)
}
/// Saves the apple user email into keychain.
/// - Parameter email: Apple userId/email retrieved form AppleLogin.
/// - Returns: true if succeed otherwise false.
#discardableResult
func saveAppleEmail(email: String?) -> Bool {
guard let email = email else {return false}
return KeychainWrapper.standard.set(
email,
forKey: KeychainWrapper.Key.appAppleEmailId.rawValue
)
}
/// Deletes both apple user name and saved Id from keyChain.
func deleteSavedAppleUserInfo(){
KeychainWrapper.standard.remove(forKey: .appAppleUserName)
KeychainWrapper.standard.remove(forKey: .appAppleEmailId)
}
}
// MARK: - KeychainWrapper + Extensions
extension KeychainWrapper.Key {
/// A random string used to identify saved user apple name from keychain.
static let appAppleUserName: KeychainWrapper.Key = "appAppleUserName"
/// A random string used to identify saved user apple email /Id from keychain.
static let appAppleEmailId:KeychainWrapper.Key = "appAppleEmailId"
}

Messenger bot with checkbox plugin how to keep track of the conversation with user ref and user id

We'd like to add the facebook messenger checkbox plugin at the end of a request form so users can opt-in for notifications via messenger.
When the user opts in, our webhook receives a callback with the user_ref that we set in the form.
We send a confirmation of opt-in to this user_ref
But other messages we receive like delivery, read receipt or actual messages from the user do contain the user ref anymore but the user id.
This is the official documentation of facebook:
After you receive the callback event, you can call the Send API to start messaging the user using the user_ref identifier in recipient as shown below. Note that this field is the same as the unique user_ref param used before when the plugin was rendered and in confirming the opt-in.
If the call to the Send API is successful, the response will contain a recipient_id parameter, which is a stable user ID that you can now use in future API calls.
Therefore it's impossible to keep track between the initial message and new ones. Does anyone found a solution for this?
Thank you very much in advance.
You can, for example, send additional information when the user opts in using the optional ref parameter. You can send the username of the user logged on my website:
function confirmOptIn() {
FB.AppEvents.logEvent('MessengerCheckboxUserConfirmation', null, {
'app_id':'APP_ID',
'page_id':'PAGE_ID',
'ref': 'myuser#mywebsite.com',
'user_ref':'UNIQUE_REF_PARAM'
});
You will receive the username within optin in your webhook event:
{
"recipient":{
"id":"PAGE_ID"
},
"timestamp":1234567890,
"optin":{
"ref":"myuser#mywebsite.com",
"user_ref":"UNIQUE_REF_PARAM"
}
}
Then, you can call the Send API to start messaging the user using the user_ref.
If the call to the Send API is successful, the response will contain a recipient_id parameter, which is a stable user ID that you can now use in future API calls.
...so you will received the Messenger ID which you can map to the username of your website you already have. Here, I modified a little the example from the official developers site to call the send API with user_ref and map the user ID I get in the response to the username of my website:
function callSendAPICheckbox(messageData, userApplicationId) {
((userApplicationId) => {
request({
uri: 'https://graph.facebook.com/v2.6/me/messages',
qs: {
access_token: PAGE_ACCESS_TOKEN
},
method: 'POST',
json: messageData
},
function(error, response, body) {
if (!error && response.statusCode == 200) {
var recipientId = body.recipient_id;
var messageId = body.message_id;
if (messageId) {
console.log("Map messenger user ID %s with the username of my website %s", recipientId, userApplicationId);
} else {
console.log("Successfully called Send API for recipient %s",
recipientId);
}
} else {
console.error("Failed calling Send API for userId " +
recipientId, response.statusCode, response.statusMessage, body.error);
}
});
})(userApplicationId)
}
Why don't you make use of metadata field of sendTextMessage. Each and every message you send to your user, you send the metadata too, and when you receive response of the message being delivered, you find the metadata field in it.
Here is what I do:
When user select the checkbox plugin and event is triggered I receive the call on my server, check if it contains user_ref. If it does, then I send a text message to user with a custom metadata using user_ref. When user receives the message, the webhook send me a json data as mentioned in the documentation. To identify for which user_ref I have received this response, I set custom metadata which is combination of some string + user_ref before sending the message to user using user_ref. Using this custom metadata I identify the sender.id of the user for which I previously sent message using user_ref. The sender.id is my pageid and recipient.id the the user id which you are trying to get and using which we generally send message to the user and is also know as psid.
Above if just the logical part mentioned which I usually do.
For detail solution along with code, I have already posted it here:

Resources