Initial Commit
This commit is contained in:
commit
8a380f4377
173
aocbot.go
Normal file
173
aocbot.go
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gogs.bullercodeworks.com/brian/boltease"
|
||||||
|
)
|
||||||
|
|
||||||
|
const programName = "aocbot"
|
||||||
|
|
||||||
|
// Message Processors are interfaces for interacting.
|
||||||
|
type messageProcessor interface {
|
||||||
|
GetName() string
|
||||||
|
GetHelp() string
|
||||||
|
ProcessMessage(s *Slack, m *Message)
|
||||||
|
ProcessAdminMessage(s *Slack, m *Message)
|
||||||
|
ProcessBotMessage(s *Slack, m *Message)
|
||||||
|
ProcessUserMessage(s *Slack, m *Message)
|
||||||
|
ProcessAdminUserMessage(s *Slack, m *Message)
|
||||||
|
ProcessBotUserMessage(s *Slack, m *Message)
|
||||||
|
ProcessChannelMessage(s *Slack, m *Message)
|
||||||
|
ProcessAdminChannelMessage(s *Slack, m *Message)
|
||||||
|
ProcessBotChannelMessage(s *Slack, m *Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
var messageProcessors []messageProcessor
|
||||||
|
|
||||||
|
// Message Processors are interfaces for accumulating statistics.
|
||||||
|
type statProcessor interface {
|
||||||
|
GetName() string
|
||||||
|
GetStatKeys() []string
|
||||||
|
Initialize()
|
||||||
|
ProcessMessage(m *Message)
|
||||||
|
ProcessBotMessage(m *Message)
|
||||||
|
ProcessUserMessage(m *Message)
|
||||||
|
ProcessBotUserMessage(m *Message)
|
||||||
|
ProcessChannelMessage(m *Message)
|
||||||
|
ProcessBotChannelMessage(m *Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
var statProcessors []statProcessor
|
||||||
|
var db *boltease.DB
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) != 2 {
|
||||||
|
fmt.Fprintf(os.Stderr, "usage: "+programName+" <slack-bot-token>\n")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var slack *Slack
|
||||||
|
|
||||||
|
if db, err = getDatabase(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if slack, err = CreateSlack(os.Args[1]); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
statBotMain(slack)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the main function for the statbot
|
||||||
|
func statBotMain(slack *Slack) {
|
||||||
|
// start a websocket-based Real Time API session
|
||||||
|
registerMessageProcessor(new(generalProcessor))
|
||||||
|
|
||||||
|
fmt.Println("aoc-bot ready, ^C exits")
|
||||||
|
|
||||||
|
writeToLog("== " + time.Now().Format(time.RFC3339) + " - Bot Started ==\n")
|
||||||
|
for {
|
||||||
|
// read each incoming message
|
||||||
|
m, err := slack.getMessage()
|
||||||
|
if err == nil {
|
||||||
|
writeToLog(" " + time.Now().Format(time.RFC3339) + " - Received Message\n")
|
||||||
|
processMessage(slack, &m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeToLog("== " + time.Now().Format(time.RFC3339) + " - Bot Stopped ==\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func processMessage(slack *Slack, m *Message) {
|
||||||
|
if mb, me := json.Marshal(m); me == nil {
|
||||||
|
// Write the JSON representation to the log
|
||||||
|
writeToLog(string(mb) + "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.Type == "message" || m.Type == "reaction_added" {
|
||||||
|
var err error
|
||||||
|
var usr *User
|
||||||
|
|
||||||
|
// Check if we know who the user is
|
||||||
|
usr, err = getUserInfo(m.User)
|
||||||
|
// If the user information hasn't been updated in the last day, update it.
|
||||||
|
if err != nil || usr.LastUpdated.IsZero() || time.Since(usr.LastUpdated) > (time.Hour*24) {
|
||||||
|
if u, ue := slack.getUserInfo(m.User); ue == nil {
|
||||||
|
saveUserInfo(u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, stats := range statProcessors {
|
||||||
|
if usr.IsBot {
|
||||||
|
stats.ProcessBotMessage(m)
|
||||||
|
} else {
|
||||||
|
stats.ProcessMessage(m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, proc := range messageProcessors {
|
||||||
|
if isAdmin(m.User) {
|
||||||
|
proc.ProcessAdminMessage(slack, m)
|
||||||
|
}
|
||||||
|
if usr.IsBot {
|
||||||
|
proc.ProcessBotMessage(slack, m)
|
||||||
|
} else {
|
||||||
|
proc.ProcessMessage(slack, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.Channel != "" {
|
||||||
|
// Check if we know what the channel is
|
||||||
|
chnl, err := getChannelInfo(m.Channel)
|
||||||
|
var isDirectMessage bool
|
||||||
|
|
||||||
|
// If the channel information hasn't been updated in the last day, update it.
|
||||||
|
if err != nil || chnl.LastUpdated.IsZero() || time.Since(chnl.LastUpdated) > (time.Hour*24) {
|
||||||
|
// Either we don't have this channel, or it's a direct message
|
||||||
|
if c, ce := slack.getChannelInfo(m.Channel); ce == nil {
|
||||||
|
// Save channel info
|
||||||
|
saveChannelInfo(c)
|
||||||
|
} else {
|
||||||
|
isDirectMessage = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isDirectMessage {
|
||||||
|
for _, proc := range messageProcessors {
|
||||||
|
if isAdmin(m.User) {
|
||||||
|
proc.ProcessAdminUserMessage(slack, m)
|
||||||
|
}
|
||||||
|
proc.ProcessUserMessage(slack, m)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, proc := range messageProcessors {
|
||||||
|
proc.ProcessChannelMessage(slack, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerMessageProcessor(b messageProcessor) {
|
||||||
|
// Register a Message Processor
|
||||||
|
// Make sure that we haven't already registered it
|
||||||
|
for _, proc := range messageProcessors {
|
||||||
|
if proc.GetName() == b.GetName() {
|
||||||
|
panic(fmt.Errorf("Attempted to Re-register Message Processor %s", b.GetName()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
messageProcessors = append(messageProcessors, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeToLog(d string) {
|
||||||
|
f, err := os.OpenFile("statbot.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0664)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
f.WriteString(d)
|
||||||
|
f.Close()
|
||||||
|
}
|
106
model.go
Normal file
106
model.go
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gogs.bullercodeworks.com/brian/boltease"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getDatabase() (*boltease.DB, error) {
|
||||||
|
db, err := boltease.Create("aocbot.db", 0644, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Make sure that we have a 'users' bucket
|
||||||
|
db.MkBucketPath([]string{"users"})
|
||||||
|
// Make sure that we have a 'channels' bucket
|
||||||
|
db.MkBucketPath([]string{"channels"})
|
||||||
|
|
||||||
|
return db, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *boltease.DB) saveUser(usr *User) error {
|
||||||
|
var err error
|
||||||
|
if err = db.OpenDB(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer db.CloseDB()
|
||||||
|
|
||||||
|
usr := []string{"users", usr.ID}
|
||||||
|
db.SetValue(append(usr, "Name"), usr.Name)
|
||||||
|
db.SetBool(append(usr, "Deleted"), usr.Deleted)
|
||||||
|
db.SetValue(append(usr, "Status"), usr.Status)
|
||||||
|
db.SetValue(append(usr, "Color"), usr.Color)
|
||||||
|
db.SetValue(append(usr, "RealName"), usr.RealName)
|
||||||
|
db.SetValue(append(usr, "TZ"), usr.TZ)
|
||||||
|
db.SetValue(append(usr, "TZLabel"), usr.TZLabel)
|
||||||
|
db.SetInt(append(usr, "TZOffset"), usr.TZOffset)
|
||||||
|
db.SetBool(append(usr, "IsAdmin"), usr.IsAdmin)
|
||||||
|
db.SetBool(append(usr, "IsOwner"), usr.IsOwner)
|
||||||
|
db.SetBool(append(usr, "IsPrimaryOwner"), usr.IsPrimaryOwner)
|
||||||
|
db.SetBool(append(usr, "IsRestricted"), usr.IsRestricted)
|
||||||
|
db.SetBool(append(usr, "IsUltraRestricted"), usr.IsUltraRestricted)
|
||||||
|
db.SetBool(append(usr, "IsBot"), usr.IsBot)
|
||||||
|
db.SetBool(append(usr, "HasFiles"), usr.HasFiles)
|
||||||
|
db.SetTimestamp(append(usr, "LastUpdated"), time.Now())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *boltease.DB) getUser(usrId string) (*User, error) {
|
||||||
|
usr := new(User)
|
||||||
|
var err error
|
||||||
|
if err = db.OpenDB(); err != nil {
|
||||||
|
return usr, err
|
||||||
|
}
|
||||||
|
defer db.CloseDB()
|
||||||
|
|
||||||
|
upath := []string{"users", usrID}
|
||||||
|
if usr.Name, err = db.GetValue(append(upath, "Name")); err != nil {
|
||||||
|
return usr, err
|
||||||
|
}
|
||||||
|
if usr.Deleted, err = db.GetBool(append(upath, "Deleted")); err != nil {
|
||||||
|
return usr, err
|
||||||
|
}
|
||||||
|
if usr.Status, err = db.GetValue(append(upath, "Status")); err != nil {
|
||||||
|
return usr, err
|
||||||
|
}
|
||||||
|
if usr.Color, err = db.GetValue(append(upath, "Color")); err != nil {
|
||||||
|
return usr, err
|
||||||
|
}
|
||||||
|
if usr.RealName, err = db.GetValue(append(upath, "RealName")); err != nil {
|
||||||
|
return usr, err
|
||||||
|
}
|
||||||
|
if usr.TZ, err = db.GetValue(append(upath, "TZ")); err != nil {
|
||||||
|
return usr, err
|
||||||
|
}
|
||||||
|
if usr.TZLabel, err = db.GetValue(append(upath, "TZLabel")); err != nil {
|
||||||
|
return usr, err
|
||||||
|
}
|
||||||
|
if usr.TZOffset, err = db.GetInt(append(upath, "TZOffset")); err != nil {
|
||||||
|
return usr, err
|
||||||
|
}
|
||||||
|
if usr.IsAdmin, err = db.GetBool(append(upath, "IsAdmin")); err != nil {
|
||||||
|
return usr, err
|
||||||
|
}
|
||||||
|
if usr.IsOwner, err = db.GetBool(append(upath, "IsOwner")); err != nil {
|
||||||
|
return usr, err
|
||||||
|
}
|
||||||
|
if usr.IsPrimaryOwner, err = db.GetBool(append(upath, "IsPrimaryOwner")); err != nil {
|
||||||
|
return usr, err
|
||||||
|
}
|
||||||
|
if usr.IsRestricted, err = db.GetBool(append(upath, "IsRestricted")); err != nil {
|
||||||
|
return usr, err
|
||||||
|
}
|
||||||
|
if usr.IsUltraRestricted, err = db.GetBool(append(upath, "IsUltraRestricted")); err != nil {
|
||||||
|
return usr, err
|
||||||
|
}
|
||||||
|
if usr.IsBot, err = db.GetBool(append(upath, "IsBot")); err != nil {
|
||||||
|
return usr, err
|
||||||
|
}
|
||||||
|
if usr.HasFiles, err = db.GetBool(append(upath, "HasFiles")); err != nil {
|
||||||
|
return usr, err
|
||||||
|
}
|
||||||
|
usr.LastUpdated, err = db.GetTimestamp(append(upath, "LastUpdated"))
|
||||||
|
|
||||||
|
return usr, err
|
||||||
|
}
|
207
slack.go
Normal file
207
slack.go
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Slack manages the slack connection and implements API calls
|
||||||
|
type Slack struct {
|
||||||
|
apiToken string
|
||||||
|
id string
|
||||||
|
socket *websocket.Conn
|
||||||
|
alive bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateSlack creates the slack object, opening a websocket
|
||||||
|
func CreateSlack(token string) (*Slack, error) {
|
||||||
|
s := Slack{apiToken: token}
|
||||||
|
url := fmt.Sprintf("https://slack.com/api/rtm.start?token=%s", token)
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
err = fmt.Errorf("API request failed with code %d", resp.StatusCode)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
resp.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var respObj responseRtmStart
|
||||||
|
err = json.Unmarshal(body, &respObj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !respObj.Ok {
|
||||||
|
err = fmt.Errorf("Slack error: %s", respObj.Error)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
wsurl := respObj.URL
|
||||||
|
s.id = respObj.Self.ID
|
||||||
|
|
||||||
|
ws, err := websocket.Dial(wsurl, "", "https://api.slack.com/")
|
||||||
|
s.socket = ws
|
||||||
|
s.keepAlive()
|
||||||
|
|
||||||
|
return &s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Slack) keepAlive() {
|
||||||
|
s.alive = true
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
if s.alive {
|
||||||
|
s.ping()
|
||||||
|
}
|
||||||
|
time.Sleep(time.Second * 30)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
var counter uint64
|
||||||
|
|
||||||
|
func (s *Slack) ping() error {
|
||||||
|
return s.postMessage(Message{Type: "ping"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Slack) postMessage(m Message) error {
|
||||||
|
m.ID = atomic.AddUint64(&counter, 1)
|
||||||
|
return websocket.JSON.Send(s.socket, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Slack) getMessage() (Message, error) {
|
||||||
|
var m Message
|
||||||
|
err := websocket.JSON.Receive(s.socket, &m)
|
||||||
|
|
||||||
|
m.Time = convertSlackTimestamp(m.Ts)
|
||||||
|
|
||||||
|
return m, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Slack) sendMessageToUser(uid string, msg string) error {
|
||||||
|
// TODO: Actually send a direct message to uid
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Slack) getUserInfo(uid string) (*User, error) {
|
||||||
|
url := fmt.Sprintf("https://slack.com/api/users.info?token=%s&user=%s", s.apiToken, uid)
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
err = fmt.Errorf("API request failed with code %d", resp.StatusCode)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
resp.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var respObj responseUserLookup
|
||||||
|
err = json.Unmarshal(body, &respObj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !respObj.Ok {
|
||||||
|
err = fmt.Errorf("Slack error: %s", respObj.Error)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return respObj.User, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Slack) getChannelInfo(cid string) (*Channel, error) {
|
||||||
|
url := fmt.Sprintf("https://slack.com/api/channels.info?token=%s&channel=%s", s.apiToken, cid)
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
err = fmt.Errorf("API request failed with code %d", resp.StatusCode)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
resp.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var respObj responseChannelLookup
|
||||||
|
err = json.Unmarshal(body, &respObj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !respObj.Ok {
|
||||||
|
err = fmt.Errorf("Slack error: %s", respObj.Error)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
respObj.Channel.LastRead = convertSlackTimestamp(respObj.Channel.LastReadRaw)
|
||||||
|
|
||||||
|
return respObj.Channel, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Slack) joinChannel(c *Channel) error {
|
||||||
|
url := fmt.Sprintf("https://slack.com/api/channels.join?token=%s&name=%s", s.apiToken, c.Name)
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
err = fmt.Errorf("API request failed with code %d", resp.StatusCode)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
resp.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var respObj responseChannelLookup
|
||||||
|
err = json.Unmarshal(body, &respObj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !respObj.Ok {
|
||||||
|
err = fmt.Errorf("Slack error: %s", respObj.Error)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// These structures represent the response of the Slack API events
|
||||||
|
// Only some fields are included. The rest are ignored by json.Unmarshal.
|
||||||
|
type responseRtmStart struct {
|
||||||
|
Ok bool `json:"ok"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
Self responseSelf `json:"self"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type responseSelf struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type responseUserLookup struct {
|
||||||
|
Ok bool `json:"ok"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
User *User `json:"user"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type responseChannelLookup struct {
|
||||||
|
Ok bool `json:"ok"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
Channel *Channel `json:"channel"`
|
||||||
|
}
|
63
structs.go
Normal file
63
structs.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// Message We save the message struct into the db,
|
||||||
|
// it's also what we send/receive from slack
|
||||||
|
type Message struct {
|
||||||
|
ID uint64 `json:"id"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Channel string `json:"channel"`
|
||||||
|
User string `json:"user"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
Ts string `json:"ts"`
|
||||||
|
Time time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// Channel object
|
||||||
|
type Channel struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
IsChannel bool `json:"is_channel"`
|
||||||
|
CreatedRaw int `json:"created"`
|
||||||
|
Created time.Time
|
||||||
|
Creator string `json:"creator"`
|
||||||
|
IsArchived bool `json:"is_archived"`
|
||||||
|
IsGeneral bool `json:"is_general"`
|
||||||
|
IsMember bool `json:"is_member"`
|
||||||
|
LastReadRaw string `json:"last_read"`
|
||||||
|
LastRead time.Time
|
||||||
|
Latest *Message `json:"latest"`
|
||||||
|
UnreadCount int `json:"unread_count"`
|
||||||
|
UnreadCountDisplay int `json:"unread_count_display"`
|
||||||
|
Members []string `json:"members"`
|
||||||
|
Topic *ChannelTopic `json:"topic"`
|
||||||
|
Purpose *ChannelTopic `json:"purpose"`
|
||||||
|
|
||||||
|
// LastUpdated is the last time we updated the info in the DB
|
||||||
|
LastUpdated time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// User object
|
||||||
|
type User struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Deleted bool `json:"deleted"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Color string `json:"color"`
|
||||||
|
RealName string `json:"real_name"`
|
||||||
|
TZ string `json:"tz"`
|
||||||
|
TZLabel string `json:"tz_label"`
|
||||||
|
TZOffset int `json:"tz_offset"`
|
||||||
|
IsAdmin bool `json:"is_admin"`
|
||||||
|
IsOwner bool `json:"is_owner"`
|
||||||
|
IsPrimaryOwner bool `json:"is_primary_owner"`
|
||||||
|
IsRestricted bool `json:"is_restricted"`
|
||||||
|
IsUltraRestricted bool `json:"is_ultra_restricted"`
|
||||||
|
IsBot bool `json:"is_bot"`
|
||||||
|
HasFiles bool `json:"has_files"`
|
||||||
|
|
||||||
|
// LastUpdated is the last time we updated the info in the DB
|
||||||
|
LastUpdated time.Time
|
||||||
|
}
|
Reference in New Issue
Block a user