Gisle Aune
6 years ago
commit
0ecbc16bdb
15 changed files with 686 additions and 0 deletions
-
BINdebug
-
125internal/api/client.go
-
35internal/api/error.go
-
122internal/bot/bot.go
-
34internal/bot/handler.go
-
53internal/config/config.go
-
49internal/models/change.go
-
9internal/models/channel.go
-
40internal/models/channels/find.go
-
40internal/models/channels/list-logged.go
-
40internal/models/channels/set-logged.go
-
30internal/models/channels/subscribe-logged.go
-
26internal/models/user.go
-
42internal/models/users/check-token.go
-
41main.go
@ -0,0 +1,125 @@ |
|||||
|
package api |
||||
|
|
||||
|
import ( |
||||
|
"bytes" |
||||
|
"context" |
||||
|
"encoding/json" |
||||
|
"errors" |
||||
|
"fmt" |
||||
|
"io/ioutil" |
||||
|
"net/http" |
||||
|
"time" |
||||
|
|
||||
|
"git.aiterp.net/rpdata/logbot3/internal/config" |
||||
|
jwt "github.com/dgrijalva/jwt-go" |
||||
|
) |
||||
|
|
||||
|
type queryData struct { |
||||
|
Query string `json:"query"` |
||||
|
Variables map[string]interface{} `json:"variables"` |
||||
|
} |
||||
|
|
||||
|
type queryResponse struct { |
||||
|
Errors QueryErrorList `json:"errors"` |
||||
|
Data json.RawMessage `json:"data"` |
||||
|
} |
||||
|
|
||||
|
// A Client is a client for the rpdata API
|
||||
|
type Client struct { |
||||
|
http *http.Client |
||||
|
endpoint string |
||||
|
username string |
||||
|
keyID string |
||||
|
keySecret string |
||||
|
} |
||||
|
|
||||
|
func (client *Client) sign(permissions []string) (token string, err error) { |
||||
|
jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ |
||||
|
"kid": "bar", |
||||
|
"user": client.username, |
||||
|
"permissions": permissions, |
||||
|
"exp": time.Now().Add(time.Second * 30).Unix(), |
||||
|
}) |
||||
|
jwtToken.Header["kid"] = client.keyID |
||||
|
|
||||
|
return jwtToken.SignedString([]byte(client.keySecret)) |
||||
|
} |
||||
|
|
||||
|
// Query sends a query or mutation to the server. The returned value is the data element.
|
||||
|
func (client *Client) Query(ctx context.Context, query string, variables map[string]interface{}, permissions []string) (data json.RawMessage, err error) { |
||||
|
// Get token
|
||||
|
token, err := client.sign(permissions) |
||||
|
if err != nil { |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// Make body
|
||||
|
qdata := queryData{ |
||||
|
Query: query, |
||||
|
Variables: variables, |
||||
|
} |
||||
|
buffer := bytes.NewBuffer(make([]byte, 0, 256)) |
||||
|
err = json.NewEncoder(buffer).Encode(&qdata) |
||||
|
if err != nil { |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// Make request
|
||||
|
req, err := http.NewRequest("POST", client.endpoint, buffer) |
||||
|
if err != nil { |
||||
|
return |
||||
|
} |
||||
|
req = req.WithContext(ctx) |
||||
|
req.Header.Set("Content-Type", "application/json") |
||||
|
req.Header.Set("Authorization", "Bearer "+token) |
||||
|
|
||||
|
// Run request
|
||||
|
res, err := client.http.Do(req) |
||||
|
if err != nil { |
||||
|
return |
||||
|
} |
||||
|
defer res.Body.Close() |
||||
|
|
||||
|
if res.StatusCode >= 500 { |
||||
|
stuff, _ := ioutil.ReadAll(res.Body) |
||||
|
fmt.Println(string(stuff)) |
||||
|
return nil, errors.New("Internal srever error") |
||||
|
} |
||||
|
|
||||
|
// Parse response
|
||||
|
resData := queryResponse{} |
||||
|
err = json.NewDecoder(res.Body).Decode(&resData) |
||||
|
if err != nil { |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
if len(resData.Errors) > 0 { |
||||
|
err = resData.Errors |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
return resData.Data, nil |
||||
|
} |
||||
|
|
||||
|
// New makes a new client.
|
||||
|
func New(endpoint, username, keyID, keySecret string) *Client { |
||||
|
return &Client{ |
||||
|
http: &http.Client{Timeout: 60 * time.Second}, |
||||
|
endpoint: endpoint, |
||||
|
username: username, |
||||
|
keyID: keyID, |
||||
|
keySecret: keySecret, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Global gets the global client, from the config file.
|
||||
|
func Global() *Client { |
||||
|
conf := config.Get() |
||||
|
return &Client{ |
||||
|
http: &http.Client{Timeout: 60 * time.Second}, |
||||
|
endpoint: conf.API.Endpoint, |
||||
|
username: conf.API.Username, |
||||
|
keyID: conf.API.Key.ID, |
||||
|
keySecret: conf.API.Key.Secret, |
||||
|
} |
||||
|
} |
@ -0,0 +1,35 @@ |
|||||
|
package api |
||||
|
|
||||
|
import ( |
||||
|
"fmt" |
||||
|
"strings" |
||||
|
) |
||||
|
|
||||
|
// A QueryError is a GraphQL error.
|
||||
|
type QueryError struct { |
||||
|
Message string `json:"message"` |
||||
|
Path []string `json:"path"` |
||||
|
} |
||||
|
|
||||
|
func (err QueryError) Error() string { |
||||
|
return fmt.Sprintf("GraphQL: %s: %s", strings.Join(err.Path, "."), err.Message) |
||||
|
} |
||||
|
|
||||
|
func (err QueryError) String() string { |
||||
|
return err.Error() |
||||
|
} |
||||
|
|
||||
|
// QueryErrorList is a list of query errors
|
||||
|
type QueryErrorList []QueryError |
||||
|
|
||||
|
func (errs QueryErrorList) Error() string { |
||||
|
if len(errs) == 1 { |
||||
|
return errs[0].Error() |
||||
|
} |
||||
|
|
||||
|
return fmt.Sprintf("%s (+%d others)", errs[0].Error(), len(errs)-1) |
||||
|
} |
||||
|
|
||||
|
func (errs QueryErrorList) String() string { |
||||
|
return errs.Error() |
||||
|
} |
@ -0,0 +1,122 @@ |
|||||
|
package bot |
||||
|
|
||||
|
import ( |
||||
|
"context" |
||||
|
"log" |
||||
|
"strings" |
||||
|
"time" |
||||
|
|
||||
|
"git.aiterp.net/gisle/irc" |
||||
|
"git.aiterp.net/rpdata/logbot3/internal/models/channels" |
||||
|
) |
||||
|
|
||||
|
var botKey = "git.aiterp.net/rpdata/logbot.Bot.key" |
||||
|
|
||||
|
// The Bot is the IRC client.
|
||||
|
type Bot struct { |
||||
|
commandChannel string |
||||
|
client *irc.Client |
||||
|
ctx context.Context |
||||
|
ctxCancel context.CancelFunc |
||||
|
} |
||||
|
|
||||
|
// New creates a new Bot.
|
||||
|
func New(ctx context.Context, nick string, alternatives []string) *Bot { |
||||
|
client := irc.New(ctx, irc.Config{ |
||||
|
Nick: nick, |
||||
|
Alternatives: alternatives, |
||||
|
SendRate: 2, |
||||
|
SkipSSLVerification: false, |
||||
|
}) |
||||
|
|
||||
|
bot := &Bot{ |
||||
|
client: client, |
||||
|
} |
||||
|
|
||||
|
client.SetValue(botKey, bot) |
||||
|
|
||||
|
return bot |
||||
|
} |
||||
|
|
||||
|
// Connect connects the bot to the IRC server. This will disconnect already
|
||||
|
// established connections.
|
||||
|
func (bot *Bot) Connect(server string, ssl bool) error { |
||||
|
if bot.ctxCancel != nil { |
||||
|
bot.ctxCancel() |
||||
|
} |
||||
|
bot.ctx, bot.ctxCancel = context.WithCancel(bot.client.Context()) |
||||
|
|
||||
|
return bot.client.Connect(server, ssl) |
||||
|
} |
||||
|
|
||||
|
func (bot *Bot) loop() { |
||||
|
ticker := time.NewTicker(time.Second * 10) |
||||
|
defer ticker.Stop() |
||||
|
|
||||
|
log.Println("Client ready.") |
||||
|
|
||||
|
for { |
||||
|
select { |
||||
|
case <-ticker.C: |
||||
|
{ |
||||
|
bot.syncChannels() |
||||
|
} |
||||
|
|
||||
|
case <-bot.ctx.Done(): |
||||
|
{ |
||||
|
log.Println("Spinning down bot main loop.") |
||||
|
return |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func (bot *Bot) syncChannels() { |
||||
|
channels, err := channels.ListOpen(bot.ctx) |
||||
|
if err != nil { |
||||
|
log.Println("Failed to update channel-list:", err) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
names := make([]string, 0, len(channels)) |
||||
|
joins := make([]string, 0, len(channels)) |
||||
|
leaves := make([]string, 0, len(names)) |
||||
|
|
||||
|
// Add new channels to join list.
|
||||
|
for _, channel := range channels { |
||||
|
name := channel.Name |
||||
|
names = append(names, name) |
||||
|
|
||||
|
if bot.client.Channel(name) == nil { |
||||
|
joins = append(joins, name) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Add no longer logged channels to leave list.
|
||||
|
LeaveLoop: |
||||
|
for _, channel := range bot.client.Channels() { |
||||
|
for _, name := range names { |
||||
|
if strings.ToLower(channel.Name()) == strings.ToLower(name) { |
||||
|
continue LeaveLoop |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Join channels.
|
||||
|
if len(joins) > 0 { |
||||
|
log.Println("Joining", strings.Join(joins, ", ")) |
||||
|
bot.client.SendQueuedf("JOIN %s", strings.Join(joins, ",")) |
||||
|
} |
||||
|
|
||||
|
// leave channels.
|
||||
|
if len(leaves) > 0 { |
||||
|
log.Println("Leaving", strings.Join(leaves, ", ")) |
||||
|
bot.client.SendQueuedf("PART %s :Channel removed.", strings.Join(leaves, ",")) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func (bot *Bot) stopLoop() { |
||||
|
if bot.ctxCancel != nil { |
||||
|
bot.ctxCancel() |
||||
|
} |
||||
|
} |
@ -0,0 +1,34 @@ |
|||||
|
package bot |
||||
|
|
||||
|
import ( |
||||
|
"log" |
||||
|
"strings" |
||||
|
|
||||
|
"git.aiterp.net/gisle/irc" |
||||
|
) |
||||
|
|
||||
|
func handler(event *irc.Event, client *irc.Client) { |
||||
|
bot, ok := client.Value(botKey).(*Bot) |
||||
|
if !ok { |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
if event.Kind() == "packet" && len(event.Verb()) == 3 { |
||||
|
log.Printf("(%s) %s %s\n", event.Verb(), strings.Join(event.Args[1:], " "), event.Text) |
||||
|
} |
||||
|
|
||||
|
switch event.Name() { |
||||
|
case "hook.ready": |
||||
|
{ |
||||
|
go bot.loop() |
||||
|
} |
||||
|
case "client.disconnect": |
||||
|
{ |
||||
|
bot.stopLoop() |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func init() { |
||||
|
irc.Handle(handler) |
||||
|
} |
@ -0,0 +1,53 @@ |
|||||
|
package config |
||||
|
|
||||
|
import ( |
||||
|
"encoding/json" |
||||
|
"fmt" |
||||
|
"os" |
||||
|
"sync" |
||||
|
) |
||||
|
|
||||
|
// Config represents the logbot's configuration.
|
||||
|
type Config struct { |
||||
|
Bot struct { |
||||
|
Nicks []string `json:"nicks"` |
||||
|
Server string `json:"server"` |
||||
|
CommandChannel string `json:"commandChannel"` |
||||
|
} `json:"bot"` |
||||
|
API struct { |
||||
|
Endpoint string `json:"endpoint"` |
||||
|
Username string `json:"username"` |
||||
|
Key struct { |
||||
|
ID string `json:"id"` |
||||
|
Secret string `json:"secret"` |
||||
|
} `json:"key"` |
||||
|
} `json:"api"` |
||||
|
} |
||||
|
|
||||
|
var mutex sync.Mutex |
||||
|
var config *Config |
||||
|
|
||||
|
// Get lazy-loads the configuration in a thread-safe manner.
|
||||
|
func Get() Config { |
||||
|
mutex.Lock() |
||||
|
defer mutex.Unlock() |
||||
|
|
||||
|
if config == nil { |
||||
|
config = &Config{} |
||||
|
|
||||
|
file, err := os.Open("/etc/aiterp/logbot3.json") |
||||
|
if err != nil { |
||||
|
fmt.Fprintf(os.Stderr, "Failed to load config: %s\n", err) |
||||
|
os.Exit(1) |
||||
|
} |
||||
|
defer file.Close() |
||||
|
|
||||
|
err = json.NewDecoder(file).Decode(&config) |
||||
|
if err != nil { |
||||
|
fmt.Fprintf(os.Stderr, "Failed to parse config: %s\n", err) |
||||
|
os.Exit(1) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return *config |
||||
|
} |
@ -0,0 +1,49 @@ |
|||||
|
package models |
||||
|
|
||||
|
import ( |
||||
|
"encoding/json" |
||||
|
"errors" |
||||
|
"time" |
||||
|
) |
||||
|
|
||||
|
// A Change is a change in the timeline.
|
||||
|
type Change struct { |
||||
|
ID string `json:"id"` |
||||
|
Model string `json:"model"` |
||||
|
Op string `json:"op"` |
||||
|
Author string `json:"author"` |
||||
|
Date time.Time `json:"date"` |
||||
|
Objects []ChangeObject `json:"objects"` |
||||
|
} |
||||
|
|
||||
|
// A ChangeObject is an object affected by the change.
|
||||
|
type ChangeObject struct { |
||||
|
Data json.RawMessage `json:"-"` |
||||
|
TypeName string `json:"__typename"` |
||||
|
} |
||||
|
|
||||
|
// Channel parses the ChangeObject as a channel.
|
||||
|
func (co *ChangeObject) Channel() (Channel, error) { |
||||
|
if co.TypeName != "Channel" { |
||||
|
return Channel{}, errors.New("Incorrect Type Name") |
||||
|
} |
||||
|
|
||||
|
channel := Channel{} |
||||
|
err := json.Unmarshal(co.Data, &channel) |
||||
|
|
||||
|
return channel, err |
||||
|
} |
||||
|
|
||||
|
// UnmarshalJSON implements json.Unmarshaller
|
||||
|
func (co *ChangeObject) UnmarshalJSON(b []byte) error { |
||||
|
data := make([]byte, len(b)) |
||||
|
copy(data, b) |
||||
|
co.Data = data |
||||
|
|
||||
|
err := json.Unmarshal(data, co) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
return nil |
||||
|
} |
@ -0,0 +1,9 @@ |
|||||
|
package models |
||||
|
|
||||
|
// Channel represents
|
||||
|
type Channel struct { |
||||
|
Name string |
||||
|
Logged bool |
||||
|
EventName string |
||||
|
LocationName string |
||||
|
} |
@ -0,0 +1,40 @@ |
|||||
|
package channels |
||||
|
|
||||
|
import ( |
||||
|
"context" |
||||
|
"encoding/json" |
||||
|
|
||||
|
"git.aiterp.net/rpdata/logbot3/internal/api" |
||||
|
"git.aiterp.net/rpdata/logbot3/internal/models" |
||||
|
) |
||||
|
|
||||
|
type findResult struct { |
||||
|
Channel models.Channel `json:"channel"` |
||||
|
} |
||||
|
|
||||
|
var findGQL = ` |
||||
|
query FindChannel($name: String!) { |
||||
|
channel(name: $name) { |
||||
|
name |
||||
|
logged |
||||
|
eventName |
||||
|
locationName |
||||
|
} |
||||
|
} |
||||
|
` |
||||
|
|
||||
|
// Find finds a channel by name
|
||||
|
func Find(ctx context.Context, name string) (models.Channel, error) { |
||||
|
data, err := api.Global().Query(ctx, findGQL, map[string]interface{}{"name": name}, nil) |
||||
|
if err != nil { |
||||
|
return models.Channel{}, err |
||||
|
} |
||||
|
|
||||
|
result := findResult{} |
||||
|
err = json.Unmarshal(data, &result) |
||||
|
if err != nil { |
||||
|
return models.Channel{}, err |
||||
|
} |
||||
|
|
||||
|
return result.Channel, nil |
||||
|
} |
@ -0,0 +1,40 @@ |
|||||
|
package channels |
||||
|
|
||||
|
import ( |
||||
|
"context" |
||||
|
"encoding/json" |
||||
|
|
||||
|
"git.aiterp.net/rpdata/logbot3/internal/api" |
||||
|
"git.aiterp.net/rpdata/logbot3/internal/models" |
||||
|
) |
||||
|
|
||||
|
type listLoggedResult struct { |
||||
|
Channels []models.Channel `json:"channels"` |
||||
|
} |
||||
|
|
||||
|
var listLoggedGQL = ` |
||||
|
query ListLogged { |
||||
|
channels(filter: {logged: true}) { |
||||
|
name |
||||
|
logged |
||||
|
eventName |
||||
|
locationName |
||||
|
} |
||||
|
} |
||||
|
` |
||||
|
|
||||
|
// ListLogged gets all open channels
|
||||
|
func ListLogged(ctx context.Context) ([]models.Channel, error) { |
||||
|
data, err := api.Global().Query(ctx, listLoggedGQL, nil, nil) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
result := listLoggedResult{} |
||||
|
err = json.Unmarshal(data, &result) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
return result.Channels, nil |
||||
|
} |
@ -0,0 +1,40 @@ |
|||||
|
package channels |
||||
|
|
||||
|
import ( |
||||
|
"context" |
||||
|
"encoding/json" |
||||
|
|
||||
|
"git.aiterp.net/rpdata/logbot3/internal/api" |
||||
|
"git.aiterp.net/rpdata/logbot3/internal/models" |
||||
|
) |
||||
|
|
||||
|
type setLoggedResult struct { |
||||
|
EditChannel models.Channel `json:"editChannel"` |
||||
|
} |
||||
|
|
||||
|
var setLoggedGQL = ` |
||||
|
mutation SetChanelLogged($name: String!, $logged: Boolean!) { |
||||
|
editChannel(input: {name: $name, logged: $logged}) { |
||||
|
name |
||||
|
logged |
||||
|
eventName |
||||
|
locationName |
||||
|
} |
||||
|
} |
||||
|
` |
||||
|
|
||||
|
// SetLogged sets a channel as logged
|
||||
|
func SetLogged(ctx context.Context, name string, logged bool) (models.Channel, error) { |
||||
|
data, err := api.Global().Query(ctx, setLoggedGQL, map[string]interface{}{"name": name, "logged": logged}, []string{"channel.edit"}) |
||||
|
if err != nil { |
||||
|
return models.Channel{}, err |
||||
|
} |
||||
|
|
||||
|
result := setLoggedResult{} |
||||
|
err = json.Unmarshal(data, &result) |
||||
|
if err != nil { |
||||
|
return models.Channel{}, err |
||||
|
} |
||||
|
|
||||
|
return result.EditChannel, nil |
||||
|
} |
@ -0,0 +1,30 @@ |
|||||
|
package channels |
||||
|
|
||||
|
import ( |
||||
|
"context" |
||||
|
"time" |
||||
|
|
||||
|
"git.aiterp.net/rpdata/logbot3/internal/models" |
||||
|
) |
||||
|
|
||||
|
// SubscribeLogged returns a channel that gets all the channel changes.
|
||||
|
func SubscribeLogged(ctx context.Context) (<-chan models.Channel, error) { |
||||
|
channels, err := ListLogged(ctx) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
output := make(chan models.Channel, len(channels)+8) |
||||
|
for _, channel := range channels { |
||||
|
output <- channel |
||||
|
} |
||||
|
|
||||
|
go func() { |
||||
|
for { |
||||
|
time.Sleep(time.Second * 20) |
||||
|
|
||||
|
} |
||||
|
}() |
||||
|
|
||||
|
return output, nil |
||||
|
} |
@ -0,0 +1,26 @@ |
|||||
|
package models |
||||
|
|
||||
|
// A User is the information about ID and permission for a user, usually
|
||||
|
// the caller.
|
||||
|
type User struct { |
||||
|
ID string `json:"id"` |
||||
|
Permissions []string `json:"permissions"` |
||||
|
} |
||||
|
|
||||
|
// LoggedIn returns true if the user is logged-in.
|
||||
|
func (user *User) LoggedIn() bool { |
||||
|
return user.ID != "" |
||||
|
} |
||||
|
|
||||
|
// Permitted gets whether the user has this permission.
|
||||
|
func (user *User) Permitted(permissions ...string) bool { |
||||
|
for _, userPermission := range user.Permissions { |
||||
|
for _, permission := range permissions { |
||||
|
if permission == userPermission { |
||||
|
return true |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return false |
||||
|
} |
@ -0,0 +1,42 @@ |
|||||
|
package users |
||||
|
|
||||
|
import ( |
||||
|
"context" |
||||
|
"encoding/json" |
||||
|
|
||||
|
"git.aiterp.net/rpdata/logbot3/internal/api" |
||||
|
"git.aiterp.net/rpdata/logbot3/internal/models" |
||||
|
) |
||||
|
|
||||
|
type checkTokenResult struct { |
||||
|
Token struct { |
||||
|
User models.User `json:"user"` |
||||
|
} `json:"token"` |
||||
|
} |
||||
|
|
||||
|
var checkTokenGQL = ` |
||||
|
query CheckTokenQuery { |
||||
|
token { |
||||
|
user { |
||||
|
id |
||||
|
permissions |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
` |
||||
|
|
||||
|
// CheckToken checks the own token and returns the user information.
|
||||
|
func CheckToken(ctx context.Context) (models.User, error) { |
||||
|
data, err := api.Global().Query(ctx, checkTokenGQL, nil, []string{"member"}) |
||||
|
if err != nil { |
||||
|
return models.User{}, err |
||||
|
} |
||||
|
|
||||
|
result := checkTokenResult{} |
||||
|
err = json.Unmarshal(data, &result) |
||||
|
if err != nil { |
||||
|
return models.User{}, err |
||||
|
} |
||||
|
|
||||
|
return result.Token.User, nil |
||||
|
} |
@ -0,0 +1,41 @@ |
|||||
|
package main |
||||
|
|
||||
|
import ( |
||||
|
"context" |
||||
|
"log" |
||||
|
"os" |
||||
|
"os/signal" |
||||
|
"strings" |
||||
|
"syscall" |
||||
|
|
||||
|
"git.aiterp.net/rpdata/logbot3/internal/bot" |
||||
|
"git.aiterp.net/rpdata/logbot3/internal/config" |
||||
|
|
||||
|
"git.aiterp.net/rpdata/logbot3/internal/models/users" |
||||
|
) |
||||
|
|
||||
|
func main() { |
||||
|
conf := config.Get() |
||||
|
|
||||
|
user, err := users.CheckToken(context.Background()) |
||||
|
if user.LoggedIn() { |
||||
|
log.Printf("Logged in: %s (%s)", user.ID, strings.Join(user.Permissions, ", ")) |
||||
|
} else { |
||||
|
log.Println("Warning: API key did not gain us access:", err) |
||||
|
os.Exit(1) |
||||
|
} |
||||
|
|
||||
|
bot := bot.New(context.Background(), conf.Bot.Nicks[0], conf.Bot.Nicks[1:]) |
||||
|
err = bot.Connect(conf.Bot.Server, false) |
||||
|
if err != nil { |
||||
|
log.Println("Warning:", err) |
||||
|
os.Exit(1) |
||||
|
} |
||||
|
|
||||
|
// Listen for a quit signal.
|
||||
|
interrupt := make(chan os.Signal, 1) |
||||
|
signal.Notify(interrupt, os.Interrupt) |
||||
|
signal.Notify(interrupt, syscall.SIGTERM) |
||||
|
|
||||
|
log.Println("Interrupt received, stopping...") |
||||
|
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue