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