Telegram Automation

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
gregster
Posts: 8886
Joined: 30 Sep 2013, 06:48

Re: Telegram Automation

Post by gregster » 19 Sep 2021, 21:59

rc76 wrote:
16 Sep 2021, 16:08
However I did experience lag on the bot running on Windows, not sure whether it is because the loop script is taking alot of load.

I did some search and found the following optimization code that can be added on the script. I think it sort of helped to reduce some lag on the script. But honestly I can't find all the explanation about each of the optimization code. Here is what I have:
I doubt that the hotkey settings will have any influence for this particular script, since it doesn't use hotkeys (neither a window).
Generally, these could have some effect on a script like this:

Code: Select all

#keyhistory 0
listlines off
setbatchlines -1
(especially the last one, I guess)

But the actual reason for the lag is most likely something different - if you haven't changed it already ;). Not sure if you have noticed that the update timer in the example script is only running once every 1000 milliseconds (= 1 second): SetTimer, UpdateTimer, 1000
For my limited applications this was okay most of the time (but I think I actually use 500 ms in my private scripts). But you could certainly try to reduce this bottleneck by setting a shorter update period (perhaps 200, 100 or 50 ms) - not sure, if it will have any effect on system load (taskmanager can tell you).
Of course, a slow phone or slow wifi connection could also have some impact. Large amounts of messages to be processed could be another problem - but you probably don't have many people using this (yet ?) at the same time. So my best guess would be the timer period.

rc76
Posts: 144
Joined: 07 Nov 2020, 01:45

Re: Telegram Automation

Post by rc76 » 21 Sep 2021, 22:40

Yes @gregster make it 500ms definitely make it faster!

I originally thought 1000ms the CPU may not able to take it (or Telegram API), but it seems it can so far!

rc76
Posts: 144
Joined: 07 Nov 2020, 01:45

Re: Telegram Automation

Post by rc76 » 23 Sep 2021, 00:43

rc76 wrote:
16 Sep 2021, 16:03
This nice little bottom will work well as root commands (where you can some some hint about the command as well). Root commands such as:

/start
/status
/clear

For /clear, it be ideal if we can sort of clear up the chat screen (i.e. make it scroll such that you do not see any historical message?). Any chance you may have tried something like it? Or maybe its a redundant feature, should just return to /start.
A very minor question @gregster , any chance you have tried to "clear" the Telegram chat?

Not deleting the historical message, but just simply make the chat "cleaner"?

gregster
Posts: 8886
Joined: 30 Sep 2013, 06:48

Re: Telegram Automation

Post by gregster » 24 Sep 2021, 21:40

rc76 wrote:
23 Sep 2021, 00:43
A very minor question @gregster , any chance you have tried to "clear" the Telegram chat?

Not deleting the historical message, but just simply make the chat "cleaner"?
I don't think that I have, but tbh I am not sure what you have in mind, except clearing the history (and that action is probably limited to the user).

rc76
Posts: 144
Joined: 07 Nov 2020, 01:45

Re: Telegram Automation

Post by rc76 » 25 Sep 2021, 00:24

gregster wrote:
24 Sep 2021, 21:40
rc76 wrote:
23 Sep 2021, 00:43
A very minor question @gregster , any chance you have tried to "clear" the Telegram chat?

Not deleting the historical message, but just simply make the chat "cleaner"?
I don't think that I have, but tbh I am not sure what you have in mind, except clearing the history (and that action is probably limited to the user).
I did a quick search and found this official support of deleteMessage method in Bot API 3.0: https://core.telegram.org/bots/api#deletemessage

Code: Select all

https://api.telegram.org/botTOKEN/deleteMessage?chat_id=CID&message_id=MID
Would like to seek your wisdom again @gregster .

If we want to clear/delete "All" of chat history with a command "/clear", how can we do that in our Telegram loop code? It be so cool if we can do that, with a inline command something like "Confirm Clear All History".

gregster
Posts: 8886
Joined: 30 Sep 2013, 06:48

Re: Telegram Automation

Post by gregster » 25 Sep 2021, 12:12

rc76 wrote:
25 Sep 2021, 00:24
Would like to seek your wisdom again @gregster .

If we want to clear/delete "All" of chat history with a command "/clear", how can we do that in our Telegram loop code? It be so cool if we can do that, with a inline command something like "Confirm Clear All History".
If I understand the docs correctly, a bot can only delete messages which are not older than 48 hours. In general, you would have to keep track of the sent messages, because you'll need their message IDs to delete them, but that's certainly possible (I would use an array or an object to store the IDs). But you can only delete one message after another, afaics, not multiple messages in one go.

If the user wants to clear the complete chat history, this is already available in the Telegram App - two clicks and you are done (see three points button at the top of the chat window - there, in that dialog, at least on the phone and the desktop client, users can also choose automatic deletion after 24 hours, one week, or one month; a bot can only get a notification about a changed setting, afaik, but can't change it by itself).

rc76
Posts: 144
Joined: 07 Nov 2020, 01:45

Re: Telegram Automation

Post by rc76 » 27 Sep 2021, 16:50

Yes there is the clear message button with the three dot icons! Thank you @gregster !

A quick question regarding the Telegram bot API and the AHK script, is it possible to use it to create inter-computer-user communication, all in remote locations?

For Example, we have:

User A in Location 1.
Computer B, running AHK script AHK_B in Location 2. (Perform some interactions with a Windows app)
Computer C, running AHK script AHK_C in Location 3. (Perform some AHK actions require alot of computation, so requires to be on separate computer)
Location 2 and 3 are not in the same network.

Is it possible:
a. User A send a Telegram message to Computer B's AHK_B. AHK_B do some processing.
b. AHK_B send a telegram message to AHK_C, to request it to do further processing. Once AHK_C is done, send a Telegram message back to AHK_B.
c. AHK_B confirmed the processing is done.
d. Send User A a Telegram notification.

So, is it possible for Computer B and Computer C's AHK script to communicate via Telegram bot?

gregster
Posts: 8886
Joined: 30 Sep 2013, 06:48

Re: Telegram Automation

Post by gregster » 28 Sep 2021, 21:33

rc76 wrote:
27 Sep 2021, 16:50
So, is it possible for Computer B and Computer C's AHK script to communicate via Telegram bot?
This seems mainly like a Telegram Bot API question. So, you might want to also look for an answer at other suited places on the internet (that's what I have done ;) ).

In the past, based on my more or less limited knowledge as a casual Telegram Bot API user, but also based on older API versions (which might or might not have changed substantially since then), I did say 'no' to that question. But at least nowadays, there seems to be an exception to the rule that bots cannot see messages sent by other bots (or their own).

This means, two bots can't have 1:1 chats, and can't see bot messages in group chats. The only exceptions seem to be "channels" (a special kind of group - you probably know what it is).
In channels, bots can see the messages of other bots, as long as these bots are all channel admins with sufficient rights.

Based on a quick test, this really seems to work. So, you need at least two separate bot admins, which then can exchange messages (ideally, you also check the option to let them sign their messages with their names). Afaik, it is even possible to keep such a channel completely private and unlisted. I can post some example code later...

This seems also easier than automating a regular (non-bot) user account (which might be against some Telegram TOS anyway), or using other techniques to communicate between two computers via the internet - at least if one already knows the Telegram bot API basics.

rc76
Posts: 144
Joined: 07 Nov 2020, 01:45

Re: Telegram Automation

Post by rc76 » 28 Sep 2021, 22:56

Yes it definitely seems like a very valuable approach, that:

(1) you can communicate between two computers across internet without static IP for each
(2) To a degree, some level of security that your computers will not get hacked (since you only send/receive messages between Telegram).
(3) Also, Telegram channel can act as a way to check if the computers are connected/disconnected to internet (i.e. constantly send msg ping to channel). If any of the computers do not send ping in regular time interval, user can be notified from the smartphone.

Or... am I mistaken on the above? :crazy:

AterEX
Posts: 1
Joined: 29 Sep 2021, 04:51

Re: Telegram Automation

Post by AterEX » 29 Sep 2021, 04:59

Hello!
I've made a script, that makes bot read my messages, but it doesn't work properly. Bot takes information about user, sends it to ahk script. However, bot can't read users input.
Image

Code: Select all

#include json.ahk					; include to process JSON strings as objects

token := "MYTOKEN"

q::		; press q to get updates
update := GetUpdates(token, , 1)				; limit result to one message
oUpd := JSON.Load(update)		; load JSON response string as an AHK object
msgbox % update
return

Esc::ExitApp

GetUpdates(botToken, offset="", updlimit=100, timeout=0){							
	If (updlimit>100)
		updlimit := 100
	; Offset = Identifier of the first update to be returned.
	url := "https://api.telegram.org/bot" botToken "/getupdates?offset=" offset "&limit=" updlimit "&timeout=" timeout
	updjson := URLDownloadToVar(url)					
	return updjson
}

URLDownloadToVar(url,ByRef variable=""){			
	try
	{	
		hObject:=ComObjCreate("WinHttp.WinHttpRequest.5.1")
		hObject.Open("GET",url)
		hObject.Send()
		variable:=hObject.ResponseText
		return variable
	}
}

rc76
Posts: 144
Joined: 07 Nov 2020, 01:45

Re: Telegram Automation

Post by rc76 » 04 Oct 2021, 20:06

Hi @AterEX , I think you can start by trying out @gregster 's amazing Telegram ahk example that will perform an infinite loop to listen and answer telegram messages from user.

It will be a very good starting point for you to understand and try out various many other things.
gregster wrote:
03 Jan 2018, 00:14
Click the link go to gregster's earlier post.

rc76
Posts: 144
Joined: 07 Nov 2020, 01:45

Re: Telegram Automation

Post by rc76 » 10 Oct 2021, 01:57

gregster wrote:
28 Sep 2021, 21:33
rc76 wrote:
27 Sep 2021, 16:50
So, is it possible for Computer B and Computer C's AHK script to communicate via Telegram bot?
This seems mainly like a Telegram Bot API question. So, you might want to also look for an answer at other suited places on the internet (that's what I have done ;) ).

In the past, based on my more or less limited knowledge as a casual Telegram Bot API user, but also based on older API versions (which might or might not have changed substantially since then), I did say 'no' to that question. But at least nowadays, there seems to be an exception to the rule that bots cannot see messages sent by other bots (or their own).

This means, two bots can't have 1:1 chats, and can't see bot messages in group chats. The only exceptions seem to be "channels" (a special kind of group - you probably know what it is).
In channels, bots can see the messages of other bots, as long as these bots are all channel admins with sufficient rights.

Based on a quick test, this really seems to work. So, you need at least two separate bot admins, which then can exchange messages (ideally, you also check the option to let them sign their messages with their names). Afaik, it is even possible to keep such a channel completely private and unlisted. I can post some example code later...

This seems also easier than automating a regular (non-bot) user account (which might be against some Telegram TOS anyway), or using other techniques to communicate between two computers via the internet - at least if one already knows the Telegram bot API basics.
Dear @gregster , any chance you may have gave this a try?

gregster
Posts: 8886
Joined: 30 Sep 2013, 06:48

Re: Telegram Automation

Post by gregster » 18 Oct 2021, 19:28

rc76 wrote:
10 Oct 2021, 01:57
Dear @gregster , any chance you may have gave this a try?
Well, the general concept seemed to work.
gregster wrote: Based on a quick test, this really seems to work.
I hoped you might give it a try, based on the outline I posted. I didn't really touch the code again since I posted, because I was quite busy (and will now be travelling until the end of october - without access to my computer).
gregster wrote:So, you need at least two separate bot admins, which then can exchange messages (ideally, you also check the option to let them sign their messages with their names).
This setup has to be done manually in Telegram.

To get the channel ID (which you should use as the chat ID in this case), this might help:
https://github.com/GabrielRF/telegram-id#web-channel-id :
Web Channel ID

Click on the channel you want and see the url displayed on your browser.

If it's a public group, the ID is @name of the group.

If it's a private group then the url must be similar to:

Code: Select all

https://web.telegram.org/#/im?p=c1018013852_555990343349619165
If this is the case, then the channel ID would be 1018013852. It's important to know that channel's IDs are always negative and 13 characters long! So add -100 to it, making the correct ID -1001018013852.
So, you will have one chat (channel) ID, and two (or more) different bot tokens to send from - but probably on different computers, in your use case. It's mainly the same code you would use for a normal 1:1 chat (user:bot).

gregster
Posts: 8886
Joined: 30 Sep 2013, 06:48

Re: Telegram Automation

Post by gregster » 18 Oct 2021, 19:30

AterEX wrote:
29 Sep 2021, 04:59
I've made a script, that makes bot read my messages, but it doesn't work properly. Bot takes information about user, sends it to ahk script. However, bot can't read users input.
I am not totally sure what you are asking, but if this is the code you are actually running, I can see a few potential problems.

With the line

Code: Select all

update := GetUpdates(token, , 1)				; limit result to one message
you are limiting message updates to only 1 message, while not handling the offset. This means, you will read the same message again and again (the first unconfirmed one), no matter how many (new) messages are actually being sent, because no message will be confirmed as read. The offset is used to determine which message will be considered old/confirmed, or new/unconfirmed. If you don't handle it right, you will miss messages or get the same message multiple times.

This might also explain why the msgbox in your picture shows the text \u0430 which stands for the unicode character cyrillic small letter a. If you load this whole json response string via json.ahk into an object, and read the corresponding field, you will see that it actually will get transformed to the cyrillic character а. I assume that this is the content of the one message you are actually reading, and you are not getting updates for these TEXT... messages - because you are not handling the offset, like mentioned above.

inseption86
Posts: 198
Joined: 19 Apr 2018, 00:24

Re: Telegram Automation

Post by inseption86 » 31 Oct 2021, 04:53

Hi, gregster. how to add a check so that only certain chatids work with the bot?

Code: Select all

list_chat_id .= "976342721, 1799943559, 135051128"
if InStr(list_chat_id, from_id)
     somefunction(botToken, msg, from_id, mtext)
else
     SendSticker(botToken, sticker := "CAACAgIAAxkBAAIF9mF9C_RtYeA8lYwdQKdPJkDSMPJUAAIUDwACj8H5S80R-nTuFhAAASEE", from_id)	



gregster wrote:
03 Jan 2018, 00:14
Happy new year!
Here is a cobbled together and superficially tested script that demonstrates some possibilities in Telegram messenger like getting 'Updates' (messages from users), simple parsing and calling corresponding functions, sending messages and creating custom keyboards or custom 'inline' keyboards (to be handled differently). My original script is much longer and more complex by now, but still chaotic and very specific for my needs (for example, I use a more complex parsing algorithm because I also implemented commands with multiple parameters). But I think that this might be a good starting point for you, if you are not there already.

Some things that you might want to add by and by:
*) user/customer management (--> scheduling individual messages)
*) log files
*) better parsing
*) ability to send pictures
*) (better) error-handling
*) 'help' command or an 'assistant'

I can probably help you with most of these things - I am working on it anyway for my own use cases.
I added some commentary to the script below, but if you have further questions, don't hesitate to ask.

To use the script, you have to include Coco's JSON lib (https://autohotkey.com/boards/viewtopic.php?t=627) and add the personal token of your Telegram bot at the top of your script. Also, for testing purposes you will have to add your own chat ID, because I added a (very basic) check for known/registered users.
In general, you can get all the updates from your bot (from everyone who starts a chat with it) and decide if you will answer all users/chat IDs or just your registered users/customers. It is generally possible to send something like personalized (registration) links to your customers, afaik.
Hope that helps.

Code: Select all

#include json.ahk																		; Coco's JSON library, get it here:   https://autohotkey.com/boards/viewtopic.php?t=627

botToken  :=  "xxxxxxxxx:yyyyyyyyyyyyyyyyyyy"					; add your Telegram bot token
chatID := xyz000001																		; add your chat ID for testing porposes

oCustomers := {}																; Object of user ids of registered customers -> add your customers, you can send them a personal registration link
oCustomers[chatID] := "My Name"											; add your chat id (and name) to the customer object for testing purposes
offset := ""																			; Telegram message offset

cmds := {	"Command1" : "somefunction"										; bundle commands (case insensitive) and corresponding script functions to call 
				, 	"Command2" : "somefunction"
				,   "Command3" : "somefunction"
				, 	"Show inline buttons" : "inlinebuttons"
				, 	"Remove keyboard" : "RemoveKeyb"  }

; add custom keyboard for testing
keyb=																		; json formatted string
( 
	{"keyboard":[ ["Command1", "Command2"], ["Show inline buttons", "Remove Keyboard"] ], "resize_keyboard" : true } 
)																			
url := "https://api.telegram.org/bot" botToken "/sendMessage?text=Keyboard added&chat_id=" chatID "&reply_markup=" keyb
json_message := URLDownloadToVar(url)   
	; msgbox % json_message

; Check for new updates 
 SetTimer, UpdateTimer, 1000							; every 1000 ms = 1 second
return
;---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Esc::ExitApp													; hit Escape to stop the script
;----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

UpdateTimer:																									; checks constantly for user input in your bot
stack := {}																										; message stack			

updates := GetUpdates(botToken, (offset+1))											; get (new) updates from your bot as JSON string; keep track of old messages  
	;msgbox % updates
oUpdates := JSON.Load(updates)											; create an AHK object from the JSON string
If oUpdates.ok																			; check if json answer was "ok" : true
{
		
	loop % oUpdates.result.MaxIndex()		; determine number of new messages (updates) 
			stack.Push(oUpdates.result[A_index])		; add all updates (=messages) to stack
	For key, msg in stack
	{
		from_id := first_name := mtext := last_name := username := ""
		if (msg.callback_query.data!="")									; inline buttons have to be handled differently than normal messages and normal keyboards
		{	
			from_id := msg.callback_query.from.id
			mtext := msg.callback_query.data
		}	
		else 																					; get message text or message from normal keyboard
		{		
			from_id := msg.message.from.id									; which ID sent the message?
			mtext := msg.message.text
					;first_name := msg.message.from.first_name
					;last_name := msg.message.from.last_name
					;username ;=  msg.message.from.username
		}
		offset := msg.update_id													; keep track of processed messages
	
		if oCustomers.Haskey(from_id)				; check for known users...   optional
		{	
				if cmds.Haskey(mtext)														; is message a known command?
				{
					fun := cmds[mtext]									;look up which function to call for a specific command
					%fun%(botToken, msg, from_id, mtext)			; call function
				}
				else
				{
					text := "Sorry... I don't know this command."				  
					;    url encoding for messages :  %0A = newline (\n)   	%2b = plus sign (+)		%0D for carriage return \r        single Quote ' : %27			%20 = space
					SendText(botToken, text, from_id)
				}
		}
		else
				Traytip, No, Unknown user, 3		
	}  ; end 'for' 
}
return

;--------------------------------------------------------  functions for user commands ---------------------------------------------------------------------------------------------------------
somefunction(botToken, msg, from_id, mtext) 
{
		if (msg.callback_query.id!="")						;  After the user presses a callback button, Telegram clients will display a progress bar until you call answerCallbackQuery 
		{	
			cbQuery_id := msg.callback_query.id
			; Notification and alert are optional
			url := "https://api.telegram.org/bot" botToken "/answerCallbackQuery?text=Notification&callback_query_id=" cbQuery_id "&show_alert=true"
			json_message := URLDownloadToVar(url)  
				;msgbox % json_message
		}
		text := "You chose " mtext				  
		SendText(botToken, text, from_id)        ;  url encoding for messages :  %0A = newline (\n)   	%2b = plus sign (+)		%0D for carriage return \r        single Quote ' : %27			%20 = space
		return
}
;--------------------------------------------------------------------
; add inline buttons
inlinebuttons(botToken, msg,  from_id, mtext="") 
{
	keyb=																	; json string:
	( 
		{"inline_keyboard":[ [{"text": "Command One" , "callback_data" : "Command1"}, {"text" : "Some button (Cmd3)",  "callback_data" : "Command3"} ] ], "resize_keyboard" : true } 
	)
	url := "https://api.telegram.org/bot" botToken "/sendMessage?text=Keyboard added&chat_id=" from_id "&reply_markup=" keyb
	json_message := URLDownloadToVar(url)   ; find this function at the next code box below
	return json_message		
}
;---------------------------------------------------------------------
; Remove custom keyboard 
 RemoveKeyb(botToken, msg, from_id, mtext="") 
 {
	keyb=
	( 
		{"remove_keyboard" : true } 
	)
	url := "https://api.telegram.org/bot" botToken "/sendMessage?text=Keyboard removed&chat_id=" from_id "&reply_markup=" keyb
	json_message := URLDownloadToVar(url)  
		;msgbox % json_message
	return
}	
;------------------------------------------  Telegram functions  --------------------------------------------------------------------------------------------------------
GetUpdates(token, offset="", updlimit=100, timeout=0)     
{							
	If (updlimit>100)
		updlimit := 100
	; Offset = Identifier of the first update to be returned.
	url := "https://api.telegram.org/bot" token "/getupdates?offset=" offset "&limit=" updlimit "&timeout=" timeout
	updjson := URLDownloadToVar(url)					
	return updjson
}
;------------------------------------------------
SendText(token, text, from_id, replyMarkup="",  parseMode="" )
{
		url := "https://api.telegram.org/bot" token "/sendmessage?chat_id=" from_ID "&text=" text "&reply_markup=" replyMarkup "&parse_mode=" parseMode
		json_message := URLDownloadToVar(url)
		return json_message
}
;----------------------------------- additional functions ------------------------------------------------------------------------------------------------------------------
URLDownloadToVar(url,ByRef variable=""){						; function originally by Maestrith, I think
	try																						; keep script from breaking if API is down or not reacting
	{	
		hObject:=ComObjCreate("WinHttp.WinHttpRequest.5.1")
		hObject.Open("GET",url)
		hObject.Send()
		variable:=hObject.ResponseText
		return variable
	}
}
(Hit 'Escape' key to stop the script)

rc76
Posts: 144
Joined: 07 Nov 2020, 01:45

Re: Telegram Automation

Post by rc76 » 07 Nov 2021, 08:18

Dear @gregster , if possible, may I seek your guidance and advice on how you able to figure out how to implement the Telegram bot API?

From the API website:

https://core.telegram.org/bots/api

It didn't provide any examples of how the endpoint looks like. For example on the bot menu, there is nothing mentioned how the new menu api endpoint would look like, yet you can figure it out it is like:

Code: Select all

str := "https://api.telegram.org/bot" botToken "/setMyCommands?" 
How did you do it?

I tried to discover what else we can do with the Telegram API using your msg loop as basis, but I just can't figure out how the endpoint will look like when reading the docs. So I am wondering is there other websites that teaches examples on the bot functionality vs telegram endpoint?

Thank you gregster!

rc76
Posts: 144
Joined: 07 Nov 2020, 01:45

Re: Telegram Automation

Post by rc76 » 26 Feb 2022, 23:47

Dear @gregster , do you have any experience with AHK automation using Signal app/api?

gregster
Posts: 8886
Joined: 30 Sep 2013, 06:48

Re: Telegram Automation

Post by gregster » 27 Feb 2022, 10:32

rc76 wrote:
26 Feb 2022, 23:47
Dear @gregster , do you have any experience with AHK automation using Signal app/api?
No, sorry, so far never used the Signal messenger.

rc76 wrote:
07 Nov 2021, 08:18
It didn't provide any examples of how the endpoint looks like. For example on the bot menu, there is nothing mentioned how the new menu api endpoint would look like, yet you can figure it out it is like:

Code: Select all

str := "https://api.telegram.org/bot" botToken "/setMyCommands?"
How did you do it?
I am very sorry for not answering this at the time. At the end of last year, I was quite busy (and then ill), and I totally forgot about this. Getting old, I guess... :shifty:

It's not a Telegram-specific format, but the usual way how these query strings get constructed, not only for Telegram (compare eg https://en.wikipedia.org/wiki/Query_string)
Nevertheless, the Telegram API docs say this
https://core.telegram.org/bots/api#making-requests wrote:Making requests

All queries to the Telegram Bot API must be served over HTTPS and need to be presented in this form: https://api.telegram.org/bot<token>/METHOD_NAME. Like this for example:
https://api.telegram.org/bot123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11/getMe
We support GET and POST HTTP methods. We support four ways of passing parameters in Bot API requests:

* URL query string
* application/x-www-form-urlencoded
* application/json (except for uploading files)
* multipart/form-data (use to upload files)
[...]
and
https://core.telegram.org/bots/api#available-methods wrote:Available methods

All methods in the Bot API are case-insensitive. We support GET and POST HTTP methods. Use either URL query string or application/json or application/x-www-form-urlencoded or multipart/form-data for passing parameters in Bot API requests.
On successful call, a JSON-object containing the result will be returned.
If you don't need to upload an image, a file, or something else, you can still use the URL query string method. It's similar to what you see in the browser's address field, eg

Code: Select all

https://www.autohotkey.com/boards/search.php?search_id=newposts
But in case of more complex strings (with json strings), I prefer the application/x-www-form-urlencoded method, which I showed in the setMyCommands example.
For uploading images or other stuff, you need to encode the file (via multipart/form-data) - there should be an example somewhere...

Sometimes, when you are unsure, it also helps to google for code examples from other programming languages... but these are recurring patterns, and common formats which also can be found elsewhere.

Any specific methods you are still having problems with ? Or did you move on to other messengers?

rc76
Posts: 144
Joined: 07 Nov 2020, 01:45

Re: Telegram Automation

Post by rc76 » 28 Feb 2022, 05:46

Thank you so much @gregster ! Absolutely no worries, so great to hear from you!

Yes I am still all in on Telegram. If nothing changes with Telegram's current status, I really wish not to change the platform and throw away previous works.

Yes, actually I would like to seek your guidance and advice on two items, if possible:

(1) How to structure the ahk query for sendMediaGroup
https://core.telegram.org/bots/api#sendmediagroup
I have been using sendMessage, sendPhoto, and they are working great. But when there are many screenshots I wish to send, then they become ugly (it will simply flood the chatroom). So an example of how to use sendMediaGroup will be super helpful and make the delivery of related screenshots much cleaner to read/reference.

(2) Bot-to-Bot channel chat
This application will be super helpful for managing different telegram applications/bots across an external network. It will be awesome if gregster can shred some lights on how to begin on this, a basic example to illustrate how the channel chat can be setup will be super helpful!
rc76 wrote:
10 Oct 2021, 01:57
gregster wrote:
28 Sep 2021, 21:33
rc76 wrote:
27 Sep 2021, 16:50
So, is it possible for Computer B and Computer C's AHK script to communicate via Telegram bot?
This seems mainly like a Telegram Bot API question. So, you might want to also look for an answer at other suited places on the internet (that's what I have done ;) ).

In the past, based on my more or less limited knowledge as a casual Telegram Bot API user, but also based on older API versions (which might or might not have changed substantially since then), I did say 'no' to that question. But at least nowadays, there seems to be an exception to the rule that bots cannot see messages sent by other bots (or their own).

This means, two bots can't have 1:1 chats, and can't see bot messages in group chats. The only exceptions seem to be "channels" (a special kind of group - you probably know what it is).
In channels, bots can see the messages of other bots, as long as these bots are all channel admins with sufficient rights.

Based on a quick test, this really seems to work. So, you need at least two separate bot admins, which then can exchange messages (ideally, you also check the option to let them sign their messages with their names). Afaik, it is even possible to keep such a channel completely private and unlisted. I can post some example code later...

This seems also easier than automating a regular (non-bot) user account (which might be against some Telegram TOS anyway), or using other techniques to communicate between two computers via the internet - at least if one already knows the Telegram bot API basics.
Dear @gregster , any chance you may have gave this a try?

gregster
Posts: 8886
Joined: 30 Sep 2013, 06:48

Re: Telegram Automation

Post by gregster » 02 Mar 2022, 12:01

rc76 wrote:
28 Feb 2022, 05:46
(1) How to structure the ahk query for sendMediaGroup
https://core.telegram.org/bots/api#sendmediagroup
I have been using sendMessage, sendPhoto, and they are working great. But when there are many screenshots I wish to send, then they become ugly (it will simply flood the chatroom). So an example of how to use sendMediaGroup will be super helpful and make the delivery of related screenshots much cleaner to read/reference.
Okay, let's start with the sendMediaGroup method "to send a group of photos, videos, documents or audios as an album". Since you are mainly sending images, as far as I understand, let's focus on sending groups of images/screenshots/photos. Grouping other media types will hopefully work similarly (not tested).

The required media parameter needs to be an
Array of InputMediaAudio, InputMediaDocument, InputMediaPhoto and InputMediaVideo[...]
[...]
A JSON-serialized array describing messages to be sent, must include 2-10 items
An array... so I expect a structure similar to this: [ { } , { } , { } ] - with each { } part holding the necessary data for a single image.
(It looks like even a single item works, contrary to the description, but then you could use the simpler SendPhoto method.)

Now let's look at the InputMediaPhoto type ("Represents a photo to be sent."). Mandatory parameters are:

Code: Select all

type 		String 		Type of the result, must be photo

media 		String 		File to send. 
						Pass a file_id to send a file that exists on the Telegram servers (recommended), 
						pass an HTTP URL for Telegram to get a file from the Internet, 
						or pass “attach://<file_attach_name>” to upload a new one using multipart/form-data under <file_attach_name> name. 
						More info on Sending Files »
type is no problem; I (successfully) tried all three approaches mentioned for media:


1. Using URLs:

Code: Select all

botToken := ""			; add your Telegram bot token
chat_id := 				; add your ID

media = 			; use URLs
(
	[ {"type" : "photo", "media" : "https://www.autohotkey.com/logos/ahk_logo-winter.png", "caption" : "Hello AHK"} 
	, {"type" : "photo", "media" : "https://www.autohotkey.com/logos/ahk_logo-stpat2.png"} 
	, {"type" : "photo", "media" : "https://www.autohotkey.com/logos/ahk_logo.png"} 	]
)
; sendMediaGroup	https://core.telegram.org/bots/api#sendmediagroup
param := "chat_id=" chat_id "&media=" media				
str := "https://api.telegram.org/bot" botToken "/sendMediaGroup?" 
msgbox % response := url_tovar(str, param)

;---------------------------------------------------------------------------------------
url_tovar(URL, param) {

	WebRequest := ComObjCreate("WinHttp.WinHttpRequest.5.1")
	WebRequest.Open("POST", URL)
	WebRequest.SetRequestHeader("Content-Type", "application/x-www-form-urlencoded")
	WebRequest.Send(param)
	res := WebRequest.ResponseText
	return res
}

2. When you upload a picture to Telegram via URL or from your computer, you'll get file_ids in the response texts for each picture which you can reuse:

Code: Select all

media = 
(
	[ {"type" : "photo", "media" : "some file_id of an already used picture", "caption" : "via file_id"} ]
)

3. Now, the last method - uploading pictures from a computer - was kind of a head-scratcher:
or pass “attach://<file_attach_name>” to upload a new one using multipart/form-data under <file_attach_name> name
Seemed at first a bit different than the multipart upload of the SendPhoto method. But finally, the contents of the error messages and googling around led me to structure the call like this:

Code: Select all

media = 
(
	[ {"type" : "photo", "media" : "attach://photo_1"} 
	, {"type" : "photo", "media" : "attach://photo_2"} 	]
)

url_str := "https://api.telegram.org/bot" botToken "/sendMediaGroup?media=" media				; ?caption=" caption
objParam := {	 	"chat_id" : chat_id	
				, 	"photo_1" : ["202013.jpg"]  
				,	"photo_2" : ["202012.jpg"]}

msgbox % UploadFormData(url_str, objParam)	
ExitApp

;---------------------------------------------
UploadFormData(url_str, objParam)									; Upload multipart/form-data
{
		CreateFormData(postData, hdr_ContentType, objParam)
		whr := ComObjCreate("WinHttp.WinHttpRequest.5.1")
		whr.Open("POST", url_str, true)
		whr.SetRequestHeader("Content-Type", hdr_ContentType)
				; whr.SetRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko")				; ???????
		whr.Option(6) := False ; No auto redirect
		whr.Send(postData)
		whr.WaitForResponse()
		json_resp := whr.ResponseText
		whr :=												; free COM object
		return json_resp							; will return a JSON string that contains, among many other things, the file_id of the uploaded file
}

;###################################################################################################################
; by tmplinshi  https://www.autohotkey.com/boards/viewtopic.php?f=6&t=7647
/*
	CreateFormData - Creates "multipart/form-data" for http post

	Usage: CreateFormData(ByRef retData, ByRef retHeader, objParam)

		retData   - (out) Data used for HTTP POST.
		retHeader - (out) Content-Type header used for HTTP POST.
		objParam  - (in)  An object defines the form parameters.

		            To specify files, use array as the value. Example:
		                objParam := { "key1": "value1"
		                            , "upload[]": ["1.png", "2.png"] }

	Requirement: BinArr.ahk -- https://gist.github.com/tmplinshi/a97d9a99b9aa5a65fd20
	Version    : 1.20 / 2016-6-17 - Added CreateFormData_WinInet(), which can be used for VxE's HTTPRequest().
	             1.10 / 2015-6-23 - Fixed a bug
	             1.00 / 2015-5-14
*/

; Used for WinHttp.WinHttpRequest.5.1, Msxml2.XMLHTTP ...
CreateFormData(ByRef retData, ByRef retHeader, objParam) {
	New CreateFormData(retData, retHeader, objParam)
}

; Used for WinInet
CreateFormData_WinInet(ByRef retData, ByRef retHeader, objParam) {
	New CreateFormData(safeArr, retHeader, objParam)

	size := safeArr.MaxIndex() + 1
	VarSetCapacity(retData, size, 1)
	DllCall("oleaut32\SafeArrayAccessData", "ptr", ComObjValue(safeArr), "ptr*", pdata)
	DllCall("RtlMoveMemory", "ptr", &retData, "ptr", pdata, "ptr", size)
	DllCall("oleaut32\SafeArrayUnaccessData", "ptr", ComObjValue(safeArr))
}

Class CreateFormData {

	__New(ByRef retData, ByRef retHeader, objParam) {

		CRLF := "`r`n"

		Boundary := this.RandomBoundary()
		BoundaryLine := "------------------------------" . Boundary

		; Loop input paramters
		binArrs := []
		For k, v in objParam
		{
			If IsObject(v) {
				For i, FileName in v
				{
					str := BoundaryLine . CRLF
					     . "Content-Disposition: form-data; name=""" . k . """; filename=""" . FileName . """" . CRLF
					     . "Content-Type: " . this.MimeType(FileName) . CRLF . CRLF
					binArrs.Push( BinArr_FromString(str) )
					binArrs.Push( BinArr_FromFile(FileName) )
					binArrs.Push( BinArr_FromString(CRLF) )
				}
			} Else {
				str := BoundaryLine . CRLF
				     . "Content-Disposition: form-data; name=""" . k """" . CRLF . CRLF
				     . v . CRLF
				binArrs.Push( BinArr_FromString(str) )
			}
		}

		str := BoundaryLine . "--" . CRLF
		binArrs.Push( BinArr_FromString(str) )

		retData := BinArr_Join(binArrs*)
		retHeader := "multipart/form-data; boundary=----------------------------" . Boundary
	}

	RandomBoundary() {
		str := "0|1|2|3|4|5|6|7|8|9|a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z"
		Sort, str, D| Random
		str := StrReplace(str, "|")
		Return SubStr(str, 1, 12)
	}

	MimeType(FileName) {
		n := FileOpen(FileName, "r").ReadUInt()
		Return (n        = 0x474E5089) ? "image/png"
		     : (n        = 0x38464947) ? "image/gif"
		     : (n&0xFFFF = 0x4D42    ) ? "image/bmp"
		     : (n&0xFFFF = 0xD8FF    ) ? "image/jpeg"
		     : (n&0xFFFF = 0x4949    ) ? "image/tiff"
		     : (n&0xFFFF = 0x4D4D    ) ? "image/tiff"
		     : "application/octet-stream"
	}

}
;#############################################################################################################
; Update: 2015-6-4 - Added BinArr_ToFile()

BinArr_FromString(str) {
	oADO := ComObjCreate("ADODB.Stream")

	oADO.Type := 2 ; adTypeText
	oADO.Mode := 3 ; adModeReadWrite
	oADO.Open
	oADO.Charset := "UTF-8"
	oADO.WriteText(str)

	oADO.Position := 0
	oADO.Type := 1 ; adTypeBinary
	oADO.Position := 3 ; Skip UTF-8 BOM
	return oADO.Read, oADO.Close
}

BinArr_FromFile(FileName) {
	oADO := ComObjCreate("ADODB.Stream")

	oADO.Type := 1 ; adTypeBinary
	oADO.Open
	oADO.LoadFromFile(FileName)
	return oADO.Read, oADO.Close
}

BinArr_Join(Arrays*) {
	oADO := ComObjCreate("ADODB.Stream")

	oADO.Type := 1 ; adTypeBinary
	oADO.Mode := 3 ; adModeReadWrite
	oADO.Open
	For i, arr in Arrays
		oADO.Write(arr)
	oADO.Position := 0
	return oADO.Read, oADO.Close
}

BinArr_ToString(BinArr, Encoding := "UTF-8") {
	oADO := ComObjCreate("ADODB.Stream")

	oADO.Type := 1 ; adTypeBinary
	oADO.Mode := 3 ; adModeReadWrite
	oADO.Open
	oADO.Write(BinArr)

	oADO.Position := 0
	oADO.Type := 2 ; adTypeText
	oADO.Charset  := Encoding 
	return oADO.ReadText, oADO.Close
}

BinArr_ToFile(BinArr, FileName) {
	oADO := ComObjCreate("ADODB.Stream")

	oADO.Type := 1 ; adTypeBinary
	oADO.Open
	oADO.Write(BinArr)
	oADO.SaveToFile(FileName, 2)
	oADO.Close
}
(With CreateFormData() by tmplinshi.)
Here, photo_1 and photo_2 are random names which you assign to specific files (please adjust filepaths). You can then attach them via the "attach://..." syntax.


I hope this helps. For your second question, I will return... :)

Post Reply

Return to “Ask for Help (v1)”