Simplified Chinese document is here
a simple telegram robot that helps (especially with vmshell server)
go 1.18 is required
git clone git@github.com:ZinkLu/TGRobot.git && cd TGRobot && go build
to start the robot, a proper config file is required,check config part to get more details.
command:
./TGRobot -c config.yaml
a config file is a valid yaml format document or a json format document.
yaml can have comments, which I suggest.
apiToken: xxx
debug: false
handlers:
message_handler:
vmShell: xxx
anotherMessageHandler: xxx
inline_keyboard_handler:
xxx: Xxx
picture_handler:
xxx: xxx
...
-
apiToken
: robot token which can be get from @botfather -
debug
: verbose message will be logged if set to true. -
handlers
section contains different handlers configs.
there are some handlers which can be used out of box:
vmshell handler helps you to get your vmshell server info or control your server for conveniently.
vmshell is a message handler yet a inline-keyboard handler,but since it process raw dialog messages, let's put it in message_handler
:
handlers:
message_handler:
vmShell:
username: vmshellAccount
password: vmshellAccountPassword
serverIds:
- xxx
- xxx
handers.message_handler.vmShell
:username
: vmshell accountpassword
: vmshell passwordserverIds
: servers which you wants to control.
warning!
a two step Authenticator should not be activated!
until vmshell servers can be access through apiToken which is under developed according to their customer service.
-
open your services list
-
press
F12
to open develop console -
remember to select
preserve log
and filterFetch/XHR
-
select a server, just like the picture below:
-
then your console should trace a XHR request which contains
serverId
:
currently, valid messages are:
-
服务器流量
: get server bandwidth usage -
服务器信息
: get server info
just send any message with theses keyword above to robot, it will retrieve the information for you.
-
make serverId to serverIds so we can control multiple servers.
-
if serverIds is empty then robot can get all servers automatically for you to select.
hitokoto handler doesn't need any configuration.
send 一句话
to robot to get your hitokoto.
thanks to hitokoto.cn
Tgo Handler can communicate with Tgo API through gRPC.
Currently only support user statistics query.
Tgo Handler is also a message_handler
:
handlers:
message_handler:
Tgo:
api_addr: "127.0.0.1"
api_port: 1444
use_cert: true
sni: example.com
ca_cert_path: []
cert_path: client.crt
cert_key: client.key
verify: true
handers.message_handler.Tgo
:api_addr
: grpc API's hostapi_port
: grpc API's portuse_cert
: use cert to connect to server or not, but have cert_path and cert_key filledsni
: sni, if your api_addr is a IP address(like127.0.0.1
), it will be used to verify cert's Domain informationca_cert_path
: if you use a self signed cert, put your ca cert herecert_path
: client cert. You should also add the cert to trojan server trust list, see herecert_key
: client keyverify
: verify server client or not,true
is alway recommanded
Currently only insecure connection is supported (connection with no client certificate.)
Send 我的流量
to the bot, and a query-for-password message will be sent from the bot.
Select the message and choose reply
and send you password to the bot.
If your password is correct, user's info will be sent.
USAGE ⏳184.61MB(0.18Gb).
SPEED ⬆️0.00kb/s, ⬇️0.00kb/s
SPEED LIMIT ⬆️0.00kb/s, ⬇️0.00kb/s
ONLINE DEVICE 💻 0(current) 0(total)
Password will be cached by the bot. So you can text 我的流量
next time for this information.
- Add Secure gRPC connection.
- Add Command for user CURD.
since telegram have many message types, the source codes are structured to handle different types of message, we call the true handler App Handler
.
├── handlers
│ ├── handlers.go
| ├── register.go
│ └── message_handler
│ ├── message_handler.go
│ └── vmshell
│ ├── config.go
│ ├── server_info.go
│ ├── vmshell_client.go
│ ├── vmshell_client_test.go
│ └── vmshell_handler.go # this is a App Handler
| ├── inline_keyboard_handler
| ├── video_handler(not implement)
| ├── command_handler(not implement)
Assume you want to add a message handler to get the local weather.
First, create a weather
folder under handlers/message_handler/
We want to specify a country in the config file so that we could get the city's weather.
let's add a mapping under handler.message_handler
section in config.yaml
apiToken: xxx
debug: false
handlers:
message_handler:
weather: # APP Handler Name
city: Shanghai
Config under message_handler
will be injected to App Handler automatically.
An App Handler must implements common.AppHandlerInterface
.
type AppHandlerInterface interface {
Handle(*tgbotapi.Update, *tgbotapi.BotAPI) // Handler function
When(*tgbotapi.Update) bool // true means the handler can handler current message, or fallback next handler
Init(*config.ConfigUnmarshaler) // Init function can be called automatically with config as it's parameters
Order() int // less is earlier
Help() string // Help String, If all App can't handler current message, a combination of help messages is sent by bot
Name() string // Name of the App Handler , can't duplicate.
}
In order to use the configuration file in the yaml, we define a struct that corresponds to it.
package weather
type Config struct {
City string `configKey:"city"`
}
Since we can take json and yaml as our config file, a new struct tag named configKey
is used to unmarshal a config object.
func (w *WeatherHandler) Init(conf *config.ConfigUnmarshaler) {
wConf := &Config{}
conf.UnmarshalConfig(wConf, w.Name())
w.City = wConf.City
}
func (w *WeatherHandler) Name() string {
return "weather"
}
func (w *WeatherHandler) Order() int {
return 999
}
*config.ConfigUnmarshaler
's UnmarshalConfig
method will pass the config under handler.message_handler
section for you.
By the way, we set Order()
function, This method also affects the order of help messages.
Let's set When()
can this App Handler handler messages.
Let's say, the handler can handle messages which contains word "weather".
func (w *WeatherHandler) When(u *tgbotapi.Update) bool {
return strings.Contains(u.Message.Text, "weather")
}
func (w *WeatherHandler) Help() string {
return "ask me the 'weather'"
}
u.Message
is a pointer, but you should not worry it will be a nil, cause message handler only can handle a non-nilu.Message
Now that the Handle()
method can be implements , let's write a pseudo-code
func (w *WeatherHandler) Handle(u *tgbotapi.Update, bot *tgbotapi.BotAPI) {
URL, _ := url.Parse(WEATHER_PROVIDER_URL)
URL.Query().Set("City", w.City)
resp, _ := http.Get(URL.String())
content, _ := ioutil.ReadAll(resp.Body)
m := tgbotapi.NewMessage(u.Message.Chat.ID, string(content))
bot.Send(m)
}
In order to enable the Handler, you need to register the Handler to its parent Handler, our weather handler's parent handler is message_handler
, call message_handler.Register
directly in init
.
func init() {
message_handler.Register(&WeatherHandler{})
}
So that we can import the package in handlers.go
to enable the handler.
package handlers
import (
_ "github.com/ZinkLu/TGRobot/handlers/message_handler/weather"
)
Sometimes an App Handler can depend on other handlers.
for example an inline_keyboard_message
may be triggered by a message handler.
So a inline_keyboard_message handler
may need the trigger's config to process further.
with GetAppHandlerByName()
function, you can get a handler that has been registered.
if the handler is not registered, the program may panic.
message_handler := pool.GetAppHandlerByName[*vm_message.VmShellHandler]("vmShell")
message_handler.Config.serverIds // get info