// NewTwitchLogger ... func NewTwitchLogger(f func(m <-chan *common.Message)) *TwitchLogger { t := &TwitchLogger{ chats: make(map[int]*common.Twitch, 0), admins: make(map[string]struct{}), logHandler: f, } admins := common.GetConfig().Twitch.Admins for _, a := range admins { t.admins[a] = struct{}{} } d, err := ioutil.ReadFile(common.GetConfig().Twitch.ChannelListPath) if err != nil { log.Fatalf("unable to read channels %s", err) } if err := json.Unmarshal(d, &t.channels); err != nil { log.Fatalf("unable to read channels %s", err) } t.commandChannel = common.GetConfig().Twitch.CommandChannel return t }
// UsersHandle channel index func UsersHandle(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) f, err := os.Open(common.GetConfig().LogPath + "/" + vars["channel"] + "/" + vars["month"]) if err != nil { serveError(w, ErrNotFound) return } files, err := f.Readdir(0) if err != nil { serveError(w, err) return } nicks := common.NickList{} for _, file := range files { if NicksExtension.MatchString(file.Name()) { common.ReadNickList(nicks, common.GetConfig().LogPath+"/"+vars["channel"]+"/"+vars["month"]+"/"+file.Name()) } } names := make([]string, 0, len(nicks)) for nick := range nicks { names = append(names, nick+".txt") } sort.Sort(handysort.Strings(names)) serveDirIndex(w, []string{vars["channel"], vars["month"], "userlogs"}, names) }
// UserHandle user log func UserHandle(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) if _, ok := vars["filter"]; ok { serveFilteredLogs(w, common.GetConfig().LogPath+"/"+vars["channel"]+"/"+vars["month"], searchKey(vars["nick"], vars["filter"])) return } serveFilteredLogs(w, common.GetConfig().LogPath+"/"+vars["channel"]+"/"+vars["month"], nickFilter(vars["nick"])) }
func (b *Bot) handleTwitchLogs(m *common.Message, r *bufio.Reader) (string, error) { rs, s, err := b.searchNickFromLine(twitchPath, r) if err != nil { return s, err } if rs != nil { return rs.Month() + " logs. " + b.toURL(common.GetConfig().Twitch.LogHost, "/Destiny/"+rs.Nick()), nil } return b.toURL(common.GetConfig().Twitch.LogHost, "/Destiny"), nil }
// ChangelogHandle changelog page func ChangelogHandle(w http.ResponseWriter, r *http.Request) { tpl, err := ace.Load(common.GetConfig().Server.ViewPath+"/layout", common.GetConfig().Server.ViewPath+"/changelog", nil) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-type", "text/html") if err := tpl.Execute(w, nil); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } }
// NewBot ... func NewBot(c *common.Destiny) *Bot { b := &Bot{ c: c, start: time.Now(), admins: make(map[string]struct{}, len(common.GetConfig().Bot.Admins)), ignoreLog: make(map[string]struct{}), } for _, admin := range common.GetConfig().Bot.Admins { b.admins[admin] = struct{}{} } b.public = map[string]command{ "log": b.handleDestinyLogs, "tlog": b.handleTwitchLogs, "logs": b.handleDestinyLogs, "tlogs": b.handleTwitchLogs, "nuke": b.handleSimpleNuke, "aegis": b.handleAegis, "bans": b.handleBans, "subs": b.handleSubs, } b.private = map[string]command{ "log": b.handleDestinyLogs, "tlog": b.handleTwitchLogs, "logs": b.handleDestinyLogs, "tlogs": b.handleTwitchLogs, "p": b.handlePremiumLog, "uptime": b.handleUptime, "ignore": b.handleIgnore, "unignore": b.handleUnignore, "ignrlog": b.handleIgnoreLog, "unignrlog": b.handleUnignoreLog, } b.ignore = make(map[string]struct{}) if d, err := ioutil.ReadFile(common.GetConfig().Bot.IgnoreListPath); err == nil { ignore := []string{} if err := json.Unmarshal(d, &ignore); err == nil { for _, nick := range ignore { b.addIgnore(nick) } } } if d, err := ioutil.ReadFile(common.GetConfig().Bot.IgnoreLogListPath); err == nil { ignoreLog := []string{} if err := json.Unmarshal(d, &ignoreLog); err == nil { for _, nick := range ignoreLog { b.addIgnoreLog(nick) } } } return b }
// WrapperHandle static html log wrapper func WrapperHandle(w http.ResponseWriter, r *http.Request) { tpl, err := ace.Load(common.GetConfig().Server.ViewPath+"/layout", common.GetConfig().Server.ViewPath+"/wrapper", nil) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-type", "text/html; charset=UTF-8") path := r.URL.Path + ".txt" if r.URL.RawQuery != "" { path += "?" + r.URL.RawQuery } if err := tpl.Execute(w, struct{ Path string }{Path: path}); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } }
// DayHandle channel index func DayHandle(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) data, err := readLogFile(common.GetConfig().LogPath + "/" + vars["channel"] + "/" + vars["month"] + "/" + vars["date"]) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } w.Header().Set("Content-type", "text/plain; charset=UTF-8") w.Header().Set("Cache-control", "max-age=60") if _, ok := vars["filter"]; ok { reader := bufio.NewReaderSize(bytes.NewReader(data), len(data)) var lineCount int for { line, err := reader.ReadSlice('\n') if err != nil { if err != io.EOF { log.Printf("error reading bytes %s", err) } break } if filterKey(line, vars["filter"]) { w.Write(line) lineCount++ } } if lineCount == 0 { http.Error(w, ErrSearchKeyNotFound.Error(), http.StatusNotFound) return } return } w.Write(data) }
// PremiumUserHandle user logs + replies func PremiumUserHandle(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) nick := bytes.ToLower([]byte(vars["nick"])) filter := func(line []byte) bool { return bytes.Contains(bytes.ToLower(line), nick) } serveFilteredLogs(w, common.GetConfig().LogPath+"/"+vars["channel"]+"/"+vars["month"], filter) }
// BroadcasterHandle channel index func BroadcasterHandle(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) nick := vars["channel"][:len(vars["channel"])-8] search, err := common.NewNickSearch(common.GetConfig().LogPath+"/"+vars["channel"], nick) if err != nil { http.Error(w, ErrUserNotFound.Error(), http.StatusNotFound) return } rs, err := search.Next() if err == io.EOF { http.Error(w, ErrUserNotFound.Error(), http.StatusNotFound) return } else if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } serveFilteredLogs(w, common.GetConfig().LogPath+"/"+vars["channel"]+"/"+vars["month"], nickFilter(rs.Nick())) }
// BaseHandle channel index func BaseHandle(w http.ResponseWriter, r *http.Request) { paths, err := readDirIndex(common.GetConfig().LogPath) if err != nil { serveError(w, err) return } serveDirIndex(w, []string{}, paths) }
func (l *Logger) writeLine(timestamp time.Time, channel, nick, message string) { logs, err := l.logs.Get(common.GetConfig().LogPath + "/" + strings.Title(channel) + " chatlog/" + timestamp.Format("January 2006") + "/" + timestamp.Format("2006-01-02") + ".txt") if err != nil { log.Printf("error opening log %s", err) return } logs.Write(timestamp, nick, message) }
// Start server func main() { log.SetFlags(log.LstdFlags | log.Lshortfile) d := NewDebugger() r := mux.NewRouter() r.StrictSlash(true) r.HandleFunc("/", d.WatchHandle("Base", BaseHandle)).Methods("GET") r.HandleFunc("/contact", d.WatchHandle("Contact", ContactHandle)).Methods("GET") r.HandleFunc("/changelog", d.WatchHandle("Changelog", ChangelogHandle)).Methods("GET") r.HandleFunc("/{channel:[a-zA-Z0-9_-]+ chatlog}", d.WatchHandle("Channel", ChannelHandle)).Methods("GET") r.HandleFunc("/{channel:[a-zA-Z0-9_-]+ chatlog}/{month:[a-zA-Z]+ [0-9]{4}}", d.WatchHandle("Month", MonthHandle)).Methods("GET") r.HandleFunc("/{channel:[a-zA-Z0-9_-]+ chatlog}/{month:[a-zA-Z]+ [0-9]{4}}/{date:[0-9]{4}-[0-9]{2}-[0-9]{2}}.txt", d.WatchHandle("Day", DayHandle)).Queries("search", "{filter:.+}").Methods("GET") r.HandleFunc("/{channel:[a-zA-Z0-9_-]+ chatlog}/{month:[a-zA-Z]+ [0-9]{4}}/{date:[0-9]{4}-[0-9]{2}-[0-9]{2}}.txt", d.WatchHandle("Day", DayHandle)).Methods("GET") r.HandleFunc("/{channel:[a-zA-Z0-9_-]+ chatlog}/{month:[a-zA-Z]+ [0-9]{4}}/{date:[0-9]{4}-[0-9]{2}-[0-9]{2}}", d.WatchHandle("Day", WrapperHandle)).Methods("GET") r.HandleFunc("/{channel:[a-zA-Z0-9_-]+ chatlog}/{month:[a-zA-Z]+ [0-9]{4}}/userlogs", d.WatchHandle("Users", UsersHandle)).Methods("GET") r.HandleFunc("/{channel:[a-zA-Z0-9_-]+ chatlog}/{month:[a-zA-Z]+ [0-9]{4}}/userlogs/{nick:[a-zA-Z0-9_-]{1,25}}.txt", d.WatchHandle("User", UserHandle)).Queries("search", "{filter:.+}").Methods("GET") r.HandleFunc("/{channel:[a-zA-Z0-9_-]+ chatlog}/{month:[a-zA-Z]+ [0-9]{4}}/userlogs/{nick:[a-zA-Z0-9_-]{1,25}}.txt", d.WatchHandle("User", UserHandle)).Methods("GET") r.HandleFunc("/{channel:[a-zA-Z0-9_-]+ chatlog}/{month:[a-zA-Z]+ [0-9]{4}}/userlogs/{nick:[a-zA-Z0-9_-]{1,25}}", d.WatchHandle("User", WrapperHandle)).Methods("GET") r.HandleFunc("/{channel:[a-zA-Z0-9_-]+ chatlog}/premium/{nick:[a-zA-Z0-9_-]{1,25}}", d.WatchHandle("Premium", PremiumHandle)).Methods("GET") r.HandleFunc("/{channel:[a-zA-Z0-9_-]+ chatlog}/premium/{nick:[a-zA-Z0-9_-]{1,25}}/{month:[a-zA-Z]+ [0-9]{4}}.txt", d.WatchHandle("PremiumUser", PremiumUserHandle)).Methods("GET") r.HandleFunc("/{channel:[a-zA-Z0-9_-]+ chatlog}/premium/{nick:[a-zA-Z0-9_-]{1,25}}/{month:[a-zA-Z]+ [0-9]{4}}", d.WatchHandle("PremiumUser", WrapperHandle)).Methods("GET") r.HandleFunc("/{channel:[a-zA-Z0-9_-]+ chatlog}/current", d.WatchHandle("CurrentBase", CurrentBaseHandle)).Methods("GET") r.HandleFunc("/{channel:[a-zA-Z0-9_-]+ chatlog}/current/{nick:[a-zA-Z0-9_]+}.txt", d.WatchHandle("NickHandle", NickHandle)).Methods("GET") r.HandleFunc("/{channel:[a-zA-Z0-9_-]+ chatlog}/current/{nick:[a-zA-Z0-9_]+}", d.WatchHandle("NickHandle", WrapperHandle)).Methods("GET") r.HandleFunc("/Destinygg chatlog/{month:[a-zA-Z]+ [0-9]{4}}/broadcaster.txt", d.WatchHandle("DestinyBroadcaster", DestinyBroadcasterHandle)).Methods("GET") r.HandleFunc("/Destinygg chatlog/{month:[a-zA-Z]+ [0-9]{4}}/broadcaster", d.WatchHandle("DestinyBroadcaster", WrapperHandle)).Methods("GET") r.HandleFunc("/Destinygg chatlog/{month:[a-zA-Z]+ [0-9]{4}}/subscribers.txt", d.WatchHandle("DestinySubscriber", DestinySubscriberHandle)).Methods("GET") r.HandleFunc("/Destinygg chatlog/{month:[a-zA-Z]+ [0-9]{4}}/subscribers", d.WatchHandle("DestinySubscriber", WrapperHandle)).Methods("GET") r.HandleFunc("/Destinygg chatlog/{month:[a-zA-Z]+ [0-9]{4}}/bans.txt", d.WatchHandle("DestinyBan", DestinyBanHandle)).Methods("GET") r.HandleFunc("/Destinygg chatlog/{month:[a-zA-Z]+ [0-9]{4}}/bans", d.WatchHandle("DestinyBan", WrapperHandle)).Methods("GET") r.HandleFunc("/{channel:[a-zA-Z0-9_-]+ chatlog}/{month:[a-zA-Z]+ [0-9]{4}}/broadcaster.txt", d.WatchHandle("Broadcaster", BroadcasterHandle)).Methods("GET") r.HandleFunc("/{channel:[a-zA-Z0-9_-]+ chatlog}/{month:[a-zA-Z]+ [0-9]{4}}/broadcaster", d.WatchHandle("Broadcaster", WrapperHandle)).Methods("GET") r.HandleFunc("/{channel:[a-zA-Z0-9_-]+ chatlog}/{month:[a-zA-Z]+ [0-9]{4}}/subscribers.txt", d.WatchHandle("Subscriber", SubscriberHandle)).Methods("GET") r.HandleFunc("/{channel:[a-zA-Z0-9_-]+ chatlog}/{month:[a-zA-Z]+ [0-9]{4}}/subscribers", d.WatchHandle("Subscriber", WrapperHandle)).Methods("GET") r.HandleFunc("/api/v1/stalk/{channel:[a-zA-Z0-9_-]+ chatlog}/{nick:[a-zA-Z0-9_-]+}.json", d.WatchHandle("Stalk", StalkHandle)).Queries("limit", "{limit:[0-9]+}").Methods("GET") r.HandleFunc("/api/v1/stalk/{channel:[a-zA-Z0-9_-]+ chatlog}/{nick:[a-zA-Z0-9_-]+}.json", d.WatchHandle("Stalk", StalkHandle)).Methods("GET") r.HandleFunc("/api/v1/status.json", d.WatchHandle("Debug", d.HTTPHandle)) r.NotFoundHandler = http.HandlerFunc(NotFoundHandle) // r.PathPrefix("/assets/").Handler(http.StripPrefix("/assets/", http.FileServer(http.Dir("assets")))) srv := &http.Server{ Addr: common.GetConfig().Server.Address, Handler: r, ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, } go srv.ListenAndServe() sigint := make(chan os.Signal, 1) signal.Notify(sigint, os.Interrupt, syscall.SIGTERM) select { case <-sigint: log.Println("i love you guys, be careful") os.Exit(0) } }
// ChannelHandle channel index func ChannelHandle(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) paths, err := readDirIndex(common.GetConfig().LogPath + "/" + vars["channel"]) if err != nil { serveError(w, err) return } sort.Sort(dirsByMonth(paths)) serveDirIndex(w, []string{vars["channel"]}, paths) }
// NewChatLogs instantiates chat log collection func NewChatLogs() *ChatLogs { l := &ChatLogs{} cache, err := lru.NewWithEvict(common.GetConfig().MaxOpenLogs/2, l.HandleEvict) if err != nil { log.Fatalf("error creating log cache %s", err) } l.logs = cache go l.housekeeping() return l }
// Stop bot func (b *Bot) Stop() { b.c.Stop() ignore := []string{} for nick := range b.ignore { ignore = append(ignore, nick) } data, _ := json.Marshal(ignore) if err := ioutil.WriteFile(common.GetConfig().Bot.IgnoreListPath, data, 0644); err != nil { log.Printf("unable to write ignore list %s", err) return } ignoreLog := []string{} for nick := range b.ignoreLog { ignoreLog = append(ignoreLog, nick) } data, _ = json.Marshal(ignoreLog) if err := ioutil.WriteFile(common.GetConfig().Bot.IgnoreLogListPath, data, 0644); err != nil { log.Printf("unable to write ignorelog list %s", err) } }
// PremiumHandle premium user log index func PremiumHandle(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) paths, err := readDirIndex(common.GetConfig().LogPath + "/" + vars["channel"]) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } for i := range paths { paths[i] += ".txt" } serveDirIndex(w, []string{vars["channel"], "premium", vars["nick"]}, paths) }
// serveError ... func serveError(w http.ResponseWriter, e error) { tpl, err := ace.Load(common.GetConfig().Server.ViewPath+"/layout", common.GetConfig().Server.ViewPath+"/error", nil) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } data := map[string]interface{}{} if e == ErrNotFound { w.WriteHeader(http.StatusNotFound) data["Message"] = e.Error() } else if e != nil { // w.WriteHeader(http.StatusInternalServerError) data["Message"] = e.Error() } else { // w.WriteHeader(http.StatusInternalServerError) data["Message"] = "Unknown Error" } w.Header().Set("Content-type", "text/html") if err := tpl.Execute(w, data); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } }
// serveDirIndex ... func serveDirIndex(w http.ResponseWriter, base []string, paths []string) { tpl, err := ace.Load(common.GetConfig().Server.ViewPath+"/layout", common.GetConfig().Server.ViewPath+"/directory", nil) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } data := map[string]interface{}{ "Breadcrumbs": []map[string]string{}, "Paths": []map[string]string{}, } basePath := "" for _, path := range base { basePath += "/" + path data["Breadcrumbs"] = append(data["Breadcrumbs"].([]map[string]string), map[string]string{ "Path": basePath, "Name": path, }) } basePath += "/" for _, path := range paths { icon := "file-text" if filepath.Ext(path) == "" { icon = "folder" } data["Paths"] = append(data["Paths"].([]map[string]string), map[string]string{ "Path": basePath + strings.Replace(path, ".txt", "", -1), "Name": path, "Icon": icon, }) } w.Header().Set("Content-type", "text/html") if err := tpl.Execute(w, data); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } }
// channelExists func channelExists(ch string) bool { req, err := http.NewRequest("GET", "https://api.twitch.tv/kraken/users/"+strings.ToLower(ch), nil) if err != nil { return false } req.Header.Add("Client-ID", common.GetConfig().Twitch.ClientID) client := http.Client{ Timeout: 10 * time.Second, } res, err := client.Do(req) if err != nil { log.Println(err) return false } res.Body.Close() return res.StatusCode == http.StatusOK }
// MonthHandle channel index func MonthHandle(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) paths, err := readLogDir(common.GetConfig().LogPath + "/" + vars["channel"] + "/" + vars["month"]) if err != nil { serveError(w, err) return } metaPaths := []string{"userlogs", "broadcaster.txt", "subscribers.txt"} if vars["channel"] == "Destinygg chatlog" { metaPaths = append(metaPaths, "bans.txt") } paths = append(paths, metaPaths...) copy(paths[len(metaPaths):], paths) copy(paths, metaPaths) for i, path := range paths { paths[i] = LogExtension.ReplaceAllString(path, ".txt") } serveDirIndex(w, []string{vars["channel"], vars["month"]}, paths) }
func (b *Bot) searchNickFromLine(path string, r *bufio.Reader) (*common.NickSearchResult, string, error) { nick, err := r.ReadString(' ') nick = strings.TrimSpace(nick) if (err != nil && err != io.EOF) || len(nick) < 1 || b.isLogIgnored(nick) { return nil, "", nil } if !validNick.Match([]byte(nick)) { return nil, "", ErrInvalidNick } s, err := common.NewNickSearch(common.GetConfig().LogPath+"/"+path, string(nick)) if err != nil { return nil, "", err } rs, err := s.Next() if err != nil { return nil, "No logs found for that user.", err } return rs, "", nil }
// NickHandle shows the users most recent available log func NickHandle(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) vars["channel"] = convertChannelCase(vars["channel"]) search, err := common.NewNickSearch(common.GetConfig().LogPath+"/"+vars["channel"], vars["nick"]) if err != nil { http.Error(w, ErrUserNotFound.Error(), http.StatusNotFound) return } rs, err := search.Next() if err != nil { http.Error(w, ErrUserNotFound.Error(), http.StatusNotFound) return } if rs.Nick() != vars["nick"] { http.Redirect(w, r, "./"+rs.Nick()+".txt", 301) return } vars["month"] = rs.Month() UserHandle(w, r) }
func (t *TwitchLogger) saveChannels() error { f, err := os.Create(common.GetConfig().Twitch.ChannelListPath) if err != nil { log.Printf("error saving channel list %s", err) return err } defer f.Close() sort.Strings(t.channels) data, err := json.Marshal(t.channels) if err != nil { log.Printf("error saving channel list %s", err) return err } var buf bytes.Buffer if err := json.Indent(&buf, data, "", "\t"); err != nil { log.Printf("error saving channel list %s", err) return err } f.Write(buf.Bytes()) return nil }
func (b *Bot) handleSubs(m *common.Message, r *bufio.Reader) (string, error) { return b.toURL(common.GetConfig().DestinyGG.LogHost, "/Subscriber"), nil }
// DestinyBanHandle channel ban list func DestinyBanHandle(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) serveFilteredLogs(w, common.GetConfig().LogPath+"/Destinygg chatlog/"+vars["month"], nickFilter("Ban")) }
func (b *Bot) handlePremiumLog(m *common.Message, r *bufio.Reader) (string, error) { return b.toURL(common.GetConfig().LogHost, "/"+destinyPath+"/premium/"+m.Nick+"/"+time.Now().UTC().Format("January 2006")+".txt"), nil }
// StalkHandle return n most recent lines of chat for user func StalkHandle(w http.ResponseWriter, r *http.Request) { type Error struct { Error string `json:"error"` } w.Header().Set("Content-type", "application/json") vars := mux.Vars(r) if _, ok := vars["limit"]; !ok { vars["limit"] = "3" } limit, err := strconv.ParseUint(vars["limit"], 10, 32) if err != nil { d, _ := json.Marshal(Error{err.Error()}) http.Error(w, string(d), http.StatusBadRequest) return } if limit > uint64(common.GetConfig().Server.MaxStalkLines) { limit = uint64(common.GetConfig().Server.MaxStalkLines) } else if limit < 1 { limit = 3 } buf := make([]string, limit) index := limit search, err := common.NewNickSearch(common.GetConfig().LogPath+"/"+vars["channel"], vars["nick"]) if err != nil { d, _ := json.Marshal(Error{err.Error()}) http.Error(w, string(d), http.StatusNotFound) return } ScanLogs: for { rs, err := search.Next() if err == io.EOF { break } else if err != nil { d, _ := json.Marshal(Error{err.Error()}) http.Error(w, string(d), http.StatusInternalServerError) return } data, err := readLogFile(common.GetConfig().LogPath + "/" + vars["channel"] + "/" + rs.Month() + "/" + rs.Day()) if err != nil { d, _ := json.Marshal(Error{err.Error()}) http.Error(w, string(d), http.StatusInternalServerError) return } lines := [][]byte{} r := bufio.NewReaderSize(bytes.NewReader(data), len(data)) filter := nickFilter(rs.Nick()) for { line, err := r.ReadSlice('\n') if err != nil { if err != io.EOF { log.Printf("error reading bytes %s", err) } break } if filter(line) { lines = append(lines, line[0:len(line)-1]) } } for i := len(lines) - 1; i >= 0; i-- { index-- buf[index] = string(lines[i]) if index == 0 { break ScanLogs } } } if index == limit { d, _ := json.Marshal(Error{"User not found"}) http.Error(w, string(d), http.StatusInternalServerError) return } type Line struct { Timestamp int64 `json:"timestamp"` Text string `json:"text"` } data := struct { Nick string `json:"nick"` Lines []Line `json:"lines"` }{ Lines: []Line{}, } for i := int(index); i < len(buf); i++ { t, err := time.Parse("2006-01-02 15:04:05 MST", buf[i][1:24]) if err != nil { continue } ci := strings.Index(buf[i][LogLinePrefixLength:], ":") data.Nick = buf[i][LogLinePrefixLength : LogLinePrefixLength+ci] data.Lines = append(data.Lines, Line{ Timestamp: t.Unix(), Text: buf[i][ci+LogLinePrefixLength+2:], }) } d, _ := json.Marshal(data) w.Write(d) }
// SubscriberHandle channel index func SubscriberHandle(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) serveFilteredLogs(w, common.GetConfig().LogPath+"/"+vars["channel"]+"/"+vars["month"], nickFilter("twitchnotify")) }