You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

335 lines
8.4 KiB

package bot
import (
"bytes"
"encoding/json"
"errors"
"flag"
"fmt"
"log"
"os"
"strings"
"time"
"git.lalonde.me/matth/AltVRBot/pkg/altvr"
"git.lalonde.me/matth/AltVRBot/pkg/discord"
)
const (
tickerIntervalFriendshipOnline = 2
tickerIntervalCheckIncomingConversations = 1
tickerIntervalCheckIncomingFriendshipRequest = 5
tickerIntervalUpdateGuildEmojis = 24 * 60
)
// UserEmoji holds a guild emoji information for a user
type UserEmoji struct {
ID string
Name string
}
// User is the structure for a single bot user
type User struct {
AltVRUserID string
DiscordID string
DiscordName string
DiscordEmoji UserEmoji
Role Roles
MsgMode msgMode
}
// GetDiscordEmoji If it exists, builds a discord emoji for the user
func (u *User) GetDiscordEmoji() string {
if u.DiscordEmoji.ID != "" {
return fmt.Sprintf("<:%s:%s> ", u.DiscordEmoji.Name, u.DiscordEmoji.ID)
}
return ""
}
// Roles determines the bot user roles
type Roles uint
// Bot user roles
const (
RoleUser Roles = iota
RoleModerator
RoleAdmin
)
// FriendshipRequestPending data for a pending friendship request
type FriendshipRequestPending struct {
Friendship altvr.Friendship
MessageID string
}
// Type the AltVR bot type
type Type struct {
DGToken string
DGsID string
DGcID string
AltVRUsername string
AltVRPassword string
dg *discord.Discord
avr altvr.AltVR
users []User
msgMode msgMode
isQuiet bool
frPending []FriendshipRequestPending
convos map[string]string
}
func (b *Type) setFlags() {
b.DGToken = os.Getenv("DG_TOKEN")
if b.DGToken == "" {
flag.StringVar(&b.DGToken, "t", "", "Discord Authentication Token")
}
b.DGsID = os.Getenv("DG_SERVER_ID")
if b.DGsID == "" {
flag.StringVar(&b.DGsID, "s", "", "Discord Server ID")
}
b.DGcID = os.Getenv("DG_CHANNEL_ID")
if b.DGcID == "" {
flag.StringVar(&b.DGcID, "c", "", "Discord Channel ID")
}
b.AltVRUsername = os.Getenv("ALTVR_USERNAME")
if b.AltVRUsername == "" {
flag.StringVar(&b.AltVRUsername, "u", "", "AltVR Username")
}
b.AltVRPassword = os.Getenv("ALTVR_PASSWORD")
if b.AltVRPassword == "" {
flag.StringVar(&b.AltVRPassword, "p", "", "AltVR Password")
}
var mode string
ms := fmt.Sprintf("%s|%s|%s|%s", msgModeNormal, msgModeRude, msgModeFlirty, msgModeRandom)
flag.StringVar(&mode, "m", "", "Select the reply message mode <"+ms+">")
if mode == "" {
if value, ok := os.LookupEnv("DG_MSG_MODE"); ok {
mode = value
}
}
switch strings.ToLower(mode) {
case msgModeRandom.String():
b.msgMode = msgModeRandom
case msgModeFlirty.String():
b.msgMode = msgModeFlirty
case msgModeRude.String():
b.msgMode = msgModeRude
case msgModeNormal.String():
b.msgMode = msgModeNormal
default:
if mode != "" {
fmt.Printf("Unknown message mode `%s` defaulting to normal\n", mode)
}
b.msgMode = msgModeNormal
}
flag.BoolVar(&b.isQuiet, "q", false, "Turn on quiet mode (don't be verbose on the discord chat)")
flag.Parse()
}
// Start launches the bot
func (b *Type) Start() {
b.setFlags()
var err error
b.avr = altvr.New(b.AltVRUsername, b.AltVRPassword)
b.dg, err = discord.New(b.DGToken, b.DGsID, b.DGcID)
if err != nil {
log.Fatal(err)
}
b.dg.UpdateAvatar(b.avr.GetAvatarURL())
b.convos = make(map[string]string)
b.loadUserFile()
b.loadDiscordHandlers()
if !b.isQuiet {
s := b.getMessageString("online_welcome")
if _, err := b.dg.Session.ChannelMessageSend(b.DGcID, s); err != nil {
log.Printf("Error sending welcome message: %+v\n", err)
}
}
//b.avr.FetchOnlineFriendships(b.userConnected, b.userDisconnected)
go b.periodicTicker()
}
// Close closes any open session
func (b *Type) Close() {
b.dg.Close()
}
func (b *Type) periodicTicker() {
onlineFriendships := time.NewTimer(tickerIntervalFriendshipOnline * time.Minute)
checkConversations := time.NewTimer(tickerIntervalCheckIncomingConversations * time.Minute)
checkIncomingFriendshipRequests := time.NewTimer(tickerIntervalCheckIncomingFriendshipRequest * time.Minute)
updateGuildEmojis := time.NewTimer(tickerIntervalUpdateGuildEmojis * time.Minute)
for {
select {
case <-onlineFriendships.C:
b.avr.FetchOnlineFriendships(b.userConnected, b.userDisconnected)
onlineFriendships.Reset(tickerIntervalFriendshipOnline * time.Minute)
case <-checkConversations.C:
log.Println("Checking for conversations")
pm, _ := b.avr.FetchPendingConversations()
if len(pm) > 0 {
b.handleNewPendingConversation(pm)
}
checkConversations.Reset(tickerIntervalCheckIncomingConversations * time.Minute)
case <-checkIncomingFriendshipRequests.C:
log.Println("Checking for incoming friendship request")
fr, _ := b.avr.FetchPendingFriendshipRequests()
if len(fr) > 0 {
b.handleNewFriendshipRequests(fr)
}
checkIncomingFriendshipRequests.Reset(tickerIntervalCheckIncomingFriendshipRequest * time.Minute)
case <-updateGuildEmojis.C:
log.Println("Updating guild emojis")
b.dg.Emojis, _ = b.dg.Session.GuildEmojis(b.DGsID)
updateGuildEmojis.Reset(tickerIntervalUpdateGuildEmojis * time.Minute)
}
}
}
func (b *Type) userConnected(u altvr.User) {
s := fmt.Sprintf("%s**%s is now online in %s!**", b.getUserEmojiByAltVRUser(u), u.GetDisplayName(), u.CurrentSpace.Name)
if !b.isQuiet {
b.dg.Session.ChannelMessageSend(b.DGcID, s)
}
}
func (b *Type) userDisconnected(u altvr.User) {
s := fmt.Sprintf("%s_%s is now offline!_", b.getUserEmojiByAltVRUser(u), u.GetDisplayName())
if !b.isQuiet {
b.dg.Session.ChannelMessageSend(b.DGcID, s)
}
}
func (b *Type) loadUserFile() error {
var err error
file, err := os.Open("users.json")
if err != nil {
return err
}
defer file.Close()
decoder := json.NewDecoder(file)
err = decoder.Decode(&b.users)
if err != nil {
return err
}
return nil
}
func (b *Type) saveUserFile() error {
var err error
file, err := os.OpenFile("users.json", os.O_RDWR|os.O_CREATE, 0644)
if err != nil {
return err
}
defer file.Close()
buffer := new(bytes.Buffer)
encoder := json.NewEncoder(file)
err = encoder.Encode(b.users)
if err != nil {
return err
}
_, err = file.Write(buffer.Bytes())
if err != nil {
return err
}
return nil
}
func (b *Type) handleNewFriendshipRequests(fr []altvr.Friendship) {
for _, rr := range fr {
found := false
for _, rrr := range b.frPending {
if rr.FriendshipID == rrr.Friendship.FriendshipID {
found = true
break
}
}
if !found {
u := b.avr.GetFriend(rr.UserID)
s := b.getMessageString("new_friendship_requested", u.GetDisplayName())
if msg, err := b.dg.Session.ChannelMessageSend(b.DGcID, s); err == nil {
b.frPending = append(b.frPending, FriendshipRequestPending{
Friendship: rr,
MessageID: msg.ID,
})
}
}
}
// XXX: Remove withdrawn requests
}
func (b *Type) handleNewPendingConversation(pm []altvr.Conversation) {
for _, mm := range pm {
au := b.avr.GetFriend(mm.SenderID)
uu, _ := b.getUserByAltVRUserID(mm.SenderID)
at := au.GetDisplayName()
if uu.DiscordID != "" {
at = b.buildMention(uu)
}
s := at + ": " + mm.Subject
if msg, err := b.dg.Session.ChannelMessageSend(b.DGcID, s); err == nil {
b.convos[msg.ID] = mm.ID
if err := b.avr.AcknowkledgeConversation(mm.ID); err != nil {
log.Printf("Error acknownledging conversation: %+v\n", err)
}
}
}
}
func (b *Type) getUserEmojiByAltVRUser(au altvr.User) string {
for _, u := range b.users {
if u.AltVRUserID == au.UserID {
for k, e := range b.dg.Emojis {
if e.Name == u.DiscordEmoji.Name {
return fmt.Sprintf("<:%s:%s> ", b.dg.Emojis[k].Name, b.dg.Emojis[k].ID)
}
}
}
}
return ""
}
func (b *Type) getUserByAltVRUserID(avrID string) (User, error) {
for _, u := range b.users {
if u.AltVRUserID == avrID {
return u, nil
}
}
return User{}, errors.New("User not found")
}
func (b *Type) getUserByDiscordID(discordID string) (User, error) {
for _, u := range b.users {
if u.DiscordID == discordID {
return u, nil
}
}
return User{}, errors.New("User not found")
}
func (b *Type) getUserByDiscordName(discordName string) (User, error) {
for _, u := range b.users {
if u.DiscordName == discordName {
return u, nil
}
}
return User{}, errors.New("User not found")
}
func (b *Type) buildMention(uu User) string {
var msg string
if uu.DiscordEmoji.Name != "" {
msg = fmt.Sprintf("<:%s:%s> ", uu.DiscordEmoji.Name, uu.DiscordEmoji.ID)
}
du, err := b.dg.Session.User(uu.DiscordID)
if err == nil {
msg = msg + du.Mention()
}
return msg
}