initial commit
This commit is contained in:
commit
5c253575aa
39
NTFConfig.go
Normal file
39
NTFConfig.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NTFConfig struct {
|
||||||
|
HubAddr string
|
||||||
|
BotAPIKey string
|
||||||
|
GroupChatID int64
|
||||||
|
|
||||||
|
// Map of telegram user IDs to NMDC nicks
|
||||||
|
KnownUsers map[int64]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadConfig(configFile string) (NTFConfig, error) {
|
||||||
|
b, err := ioutil.ReadFile(configFile)
|
||||||
|
if err != nil {
|
||||||
|
return NTFConfig{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := NTFConfig{}
|
||||||
|
err = json.Unmarshal(b, &ret)
|
||||||
|
if err != nil {
|
||||||
|
return NTFConfig{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *NTFConfig) Save(configFile string) error {
|
||||||
|
b, err := json.MarshalIndent(this, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ioutil.WriteFile(configFile, b, 0644)
|
||||||
|
}
|
178
NTFServer.go
Normal file
178
NTFServer.go
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"code.ivysaur.me/libnmdc"
|
||||||
|
telegram "github.com/go-telegram-bot-api/telegram-bot-api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NTFServer methods all run on the same thread, so no mutexes are needed for field access
|
||||||
|
type NTFServer struct {
|
||||||
|
bot *telegram.BotAPI
|
||||||
|
upstream chan upstreamMessage
|
||||||
|
chatName string
|
||||||
|
inviteLink string
|
||||||
|
configFile string
|
||||||
|
config NTFConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type upstreamMessage struct {
|
||||||
|
telegramUserId int64
|
||||||
|
evt libnmdc.HubEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNTFServer(configFile string) (*NTFServer, error) {
|
||||||
|
ret := NTFServer{
|
||||||
|
configFile: configFile,
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := LoadConfig(configFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.config = cfg
|
||||||
|
|
||||||
|
if len(cfg.BotAPIKey) == 0 {
|
||||||
|
return nil, errors.New("No bot API key supplied (register with BotFather first)")
|
||||||
|
}
|
||||||
|
|
||||||
|
bot, err := telegram.NewBotAPI(cfg.BotAPIKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Connecting to Telegram: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.bot = bot
|
||||||
|
|
||||||
|
bot.Debug = true
|
||||||
|
|
||||||
|
log.Printf("Connected to telegram as '%s'", bot.Self.UserName)
|
||||||
|
|
||||||
|
if ret.IsSetupMode() {
|
||||||
|
log.Println("Group chat ID unknown, running in setup mode only - find the groupchat ID then re-run")
|
||||||
|
|
||||||
|
} else {
|
||||||
|
chatInfo, err := bot.GetChat(telegram.ChatConfig{ChatID: ret.config.GroupChatID})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Couldn't get supergroup properties: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
inviteLink, err := bot.GetInviteLink(telegram.ChatConfig{ChatID: ret.config.GroupChatID})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Couldn't get supergroup invite link: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Group chat: %s", chatInfo.Title)
|
||||||
|
log.Printf("Invite link: %s", inviteLink)
|
||||||
|
|
||||||
|
ret.chatName = chatInfo.Title
|
||||||
|
ret.inviteLink = inviteLink
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spawn upstream connections for all known users
|
||||||
|
ret.upstream = make(chan upstreamMessage, 0)
|
||||||
|
for k, v := range ret.config.KnownUsers {
|
||||||
|
ret.LaunchUpstreamWorker(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *NTFServer) LaunchUpstreamWorker(telegramUserId int64, hubUsername string) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Open NMDC connection
|
||||||
|
go upstreamWorker()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *NTFServer) IsSetupMode() bool {
|
||||||
|
return this.config.GroupChatID == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *NTFServer) Run() error {
|
||||||
|
updateProps := telegram.NewUpdate(0)
|
||||||
|
updateProps.Timeout = 60 // seconds
|
||||||
|
|
||||||
|
updateChan, err := this.bot.GetUpdatesChan(updateProps)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for update := range updateChan {
|
||||||
|
this.HandleMessage(update)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil // Update channel was closed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *NTFServer) HandleMessage(update telegram.Update) {
|
||||||
|
if update.Message == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if this.IsSetupMode() {
|
||||||
|
log.Printf("Message from '%s': '%s', chat ID '%d'\n", update.Message.From.UserName, update.Message.Text, update.Message.Chat.ID)
|
||||||
|
|
||||||
|
} else if update.Message.Chat.ID == this.config.GroupChatID {
|
||||||
|
err := this.HandleGroupMessage(update)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Handling group message: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if update.Message.Chat.IsPrivate() {
|
||||||
|
err := this.HandleDirectMessage(update)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Handling private message: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
log.Printf("Message from unknown chat %d (not our supergroup, not a PM, ...)", update.Message.Chat.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%#v\n", update.Message)
|
||||||
|
|
||||||
|
log.Printf("[%s] %s", update.Message.From.UserName, update.Message.Text)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *NTFServer) HandleGroupMessage(update telegram.Update) error {
|
||||||
|
|
||||||
|
// Joins: ????
|
||||||
|
if update.Message.NewChatMembers != nil && len(*update.Message.NewChatMembers) > 0 {
|
||||||
|
// Users joining
|
||||||
|
// Ensure that they have a valid user mapping
|
||||||
|
// Create upstream NMDC connection for them
|
||||||
|
}
|
||||||
|
|
||||||
|
if update.Message.LeftChatMember != nil {
|
||||||
|
// User parted
|
||||||
|
// Close upstream NMDC connection for them
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parts:
|
||||||
|
/*
|
||||||
|
&tgbotapi.Message{
|
||||||
|
MessageID:9, From:(*tgbotapi.User)(0xc420304000), Date:1527989178, Chat:(*tgbotapi.Chat)(0xc4201dc120), [...]
|
||||||
|
NewChatMembers:(*[]tgbotapi.User)(nil),
|
||||||
|
LeftChatMember:(*tgbotapi.User)(0xc420304050),
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *NTFServer) HandleDirectMessage(update telegram.Update) error {
|
||||||
|
|
||||||
|
// Registration workflow
|
||||||
|
// Find out the current status for this chat ID... etc.
|
||||||
|
|
||||||
|
msg := telegram.NewMessage(update.Message.Chat.ID, "Hi user, join the group chat at "+this.inviteLink)
|
||||||
|
msg.ReplyToMessageID = update.Message.MessageID
|
||||||
|
this.bot.Send(msg)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
20
README.txt
Normal file
20
README.txt
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
A bot to synchronise an NMDC hub with a Telegram supergroup.
|
||||||
|
|
||||||
|
## USAGE
|
||||||
|
|
||||||
|
Create a new bot
|
||||||
|
- Use BotFather to create a new bot
|
||||||
|
- Use BotFather to change its privacy mode for group chats
|
||||||
|
|
||||||
|
Create a group
|
||||||
|
- Manually create a group chat and add the bot to it
|
||||||
|
- Convert group chat to supergroup
|
||||||
|
- Grant bot to be an administrator (including ability to add more administrators)
|
||||||
|
- Settings > "Who can add members" > Only administrators
|
||||||
|
- Create an invite link
|
||||||
|
|
||||||
|
Start nmdc-telegramfrontend
|
||||||
|
- Run this bot with no -GroupChatID, to learn the groupchat ID
|
||||||
|
- Post a test message in the group chat, to discover the groupchat ID
|
||||||
|
- Leave the group chat
|
||||||
|
- Run this bot with -GroupChatID for normal operation
|
24
main.go
Normal file
24
main.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
configFile := flag.String("ConfigFile", "config.json", "")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
svr, err := NewNTFServer(*configFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
err = svr.Run()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Server shutting down")
|
||||||
|
}
|
44
upstreamWorker.go
Normal file
44
upstreamWorker.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"code.ivysaur.me/libnmdc"
|
||||||
|
)
|
||||||
|
|
||||||
|
// upstreamWorker handles an NMDC connection.
|
||||||
|
// It blocks on the current thread, caller can background it with 'go'.
|
||||||
|
func upstreamWorker(ctx context.Context, hubAddress, username string, responseChan chan<- libnmdc.HubEvent) {
|
||||||
|
|
||||||
|
interiorChan := make(chan libnmdc.HubEvent, 0)
|
||||||
|
|
||||||
|
conn := libnmdc.ConnectAsync(
|
||||||
|
&libnmdc.HubConnectionOptions{
|
||||||
|
Address: libnmdc.HubAddress(hubAddress),
|
||||||
|
Self: &libnmdc.UserInfo{
|
||||||
|
Nick: username,
|
||||||
|
ClientTag: AppName,
|
||||||
|
ClientVersion: AppVersion,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
interiorChan,
|
||||||
|
)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
conn.Disconnect()
|
||||||
|
ctx = nil // prevent hitting this case again repeatedly - closed channel reads immediately, nil channel blocks forever
|
||||||
|
|
||||||
|
case evt := <-interiorChan:
|
||||||
|
responseChan <- evt // forward
|
||||||
|
|
||||||
|
if evt.StateChange == libnmdc.CONNECTIONSTATE_DISCONNECTED && ctx.Err() != nil {
|
||||||
|
// We're done here
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
6
version.go
Normal file
6
version.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
const (
|
||||||
|
AppName = "nmdc-telegramfrontend"
|
||||||
|
AppVersion = "1.0.0"
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user