Merge pull request #39 from gempir/api-rework

Api rework
This commit is contained in:
Daniel Pasch 2020-03-02 20:37:51 +01:00 committed by GitHub
commit 2ef9a27726
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 486 additions and 3103 deletions

View file

@ -1,26 +1,23 @@
full: web build
build:
go build
full: build_web build_swagger build
full_run: full run
run: build
./justlog
run_web:
cd web && yarn start
build_swagger:
swag init
build_web: get_web
web: init_web
cd web && yarn build
go run api/assets.go
get_web:
init_web:
cd web && yarn install
init_assets:
go run api/assets.go
# Docker stuff
container:
docker build -t gempir/justlog .

File diff suppressed because one or more lines are too long

View file

@ -1,116 +1,38 @@
package api
import (
"errors"
"fmt"
"net/http"
"strconv"
"strings"
"time"
"github.com/gempir/go-twitch-irc/v2"
"github.com/labstack/echo/v4"
log "github.com/sirupsen/logrus"
)
func (s *Server) getCurrentChannelLogs(c echo.Context) error {
channelID := c.Param("channelid")
year := time.Now().Year()
month := int(time.Now().Month())
day := time.Now().Day()
redirectURL := fmt.Sprintf("/channelid/%s/%d/%d/%d", channelID, year, month, day)
if len(c.QueryString()) > 0 {
redirectURL += "?" + c.QueryString()
}
return c.Redirect(http.StatusSeeOther, redirectURL)
}
func (s *Server) getCurrentChannelLogsByName(c echo.Context) error {
channel := c.Param("channel")
year := time.Now().Year()
month := int(time.Now().Month())
day := time.Now().Day()
redirectURL := fmt.Sprintf("/channel/%s/%d/%d/%d", channel, year, month, day)
if len(c.QueryString()) > 0 {
redirectURL += "?" + c.QueryString()
}
return c.Redirect(http.StatusSeeOther, redirectURL)
}
func (s *Server) getChannelLogsByName(c echo.Context) error {
channel := strings.ToLower(c.Param("channel"))
userMap, err := s.helixClient.GetUsersByUsernames([]string{channel})
if err != nil {
log.Error(err)
return c.JSON(http.StatusInternalServerError, "Failure fetching data from twitch")
}
names := c.ParamNames()
names = append(names, "channelid")
values := c.ParamValues()
values = append(values, userMap[channel].ID)
c.SetParamNames(names...)
c.SetParamValues(values...)
return s.getChannelLogs(c)
}
func (s *Server) getChannelLogsRangeByName(c echo.Context) error {
channel := strings.ToLower(c.Param("channel"))
userMap, err := s.helixClient.GetUsersByUsernames([]string{channel})
if err != nil {
log.Error(err)
return c.JSON(http.StatusInternalServerError, "Failure fetching data from twitch")
}
names := c.ParamNames()
names = append(names, "channelid")
values := c.ParamValues()
values = append(values, userMap[channel].ID)
c.SetParamNames(names...)
c.SetParamValues(values...)
return s.getChannelLogsRange(c)
}
func (s *Server) getChannelLogs(c echo.Context) error {
channelID := c.Param("channelid")
yearStr := c.Param("year")
monthStr := c.Param("month")
dayStr := c.Param("day")
func (s *Server) getChannelLogs(request logRequest) (*chatLog, error) {
yearStr := request.time.year
monthStr := request.time.month
dayStr := request.time.day
year, err := strconv.Atoi(yearStr)
if err != nil {
log.Error(err)
return c.JSON(http.StatusInternalServerError, "Invalid year")
return &chatLog{}, errors.New("invalid year")
}
month, err := strconv.Atoi(monthStr)
if err != nil {
log.Error(err)
return c.JSON(http.StatusInternalServerError, "Invalid month")
return &chatLog{}, errors.New("invalid month")
}
day, err := strconv.Atoi(dayStr)
if err != nil {
log.Error(err)
return c.JSON(http.StatusInternalServerError, "Invalid day")
return &chatLog{}, errors.New("invalid day")
}
logMessages, err := s.fileLogger.ReadLogForChannel(channelID, year, month, day)
logMessages, err := s.fileLogger.ReadLogForChannel(request.channelid, year, month, day)
if err != nil {
log.Error(err)
return c.JSON(http.StatusNotFound, "Failure reading log")
return &chatLog{}, err
}
if shouldReverse(c) {
reverse(logMessages)
if request.reverse {
reverseSlice(logMessages)
}
var logResult chatLog
@ -169,41 +91,27 @@ func (s *Server) getChannelLogs(c echo.Context) error {
logResult.Messages = append(logResult.Messages, chatMsg)
}
if shouldRespondWithJson(c) {
return writeJSONResponse(c, &logResult)
}
if shouldRespondWithRaw(c) {
return writeRawResponse(c, &logResult)
}
return writeTextResponse(c, &logResult)
return &logResult, nil
}
func (s *Server) getChannelLogsRange(c echo.Context) error {
channelID := c.Param("channelid")
fromTime, toTime, err := parseFromTo(c.QueryParam("from"), c.QueryParam("to"), channelHourLimit)
func (s *Server) getChannelLogsRange(request logRequest) (*chatLog, error) {
fromTime, toTime, err := parseFromTo(request.time.from, request.time.to, userHourLimit)
if err != nil {
return c.JSON(http.StatusInternalServerError, err.Error())
return &chatLog{}, err
}
var logMessages []string
logMessages, _ = s.fileLogger.ReadLogForChannel(channelID, fromTime.Year(), int(fromTime.Month()), fromTime.Day())
logMessages, _ = s.fileLogger.ReadLogForChannel(request.channelid, fromTime.Year(), int(fromTime.Month()), fromTime.Day())
if fromTime.Month() != toTime.Month() {
additionalMessages, _ := s.fileLogger.ReadLogForChannel(channelID, toTime.Year(), int(toTime.Month()), toTime.Day())
additionalMessages, _ := s.fileLogger.ReadLogForChannel(request.channelid, toTime.Year(), int(toTime.Month()), toTime.Day())
logMessages = append(logMessages, additionalMessages...)
}
if len(logMessages) == 0 {
return c.JSON(http.StatusNotFound, "No logs found")
}
if shouldReverse(c) {
reverse(logMessages)
if request.reverse {
reverseSlice(logMessages)
}
var logResult chatLog
@ -270,13 +178,5 @@ func (s *Server) getChannelLogsRange(c echo.Context) error {
logResult.Messages = append(logResult.Messages, chatMsg)
}
if shouldRespondWithJson(c) {
return writeJSONResponse(c, &logResult)
}
if shouldRespondWithRaw(c) {
return writeRawResponse(c, &logResult)
}
return writeTextResponse(c, &logResult)
return &logResult, nil
}

163
api/logrequest.go Normal file
View file

@ -0,0 +1,163 @@
package api
import (
"errors"
"fmt"
"net/http"
"regexp"
"strings"
"time"
log "github.com/sirupsen/logrus"
)
type logRequest struct {
channel string
user string
channelid string
userid string
time logTime
reverse bool
responseType string
redirectPath string
isUserRequest bool
isChannelRequest bool
}
// userRandomMessageRequest /channel/pajlada/user/gempir/random
type logTime struct {
from string
to string
year string
month string
day string
random bool
}
var (
pathRegex = regexp.MustCompile(`\/(channel|channelid)\/([a-zA-Z0-9]+)(?:\/(user|userid)\/([a-zA-Z0-9]+))?(?:(?:\/(\d{4})\/(\d{1,2})(?:\/(\d{1,2}))?)|(?:\/(range|random)))?`)
)
func (s *Server) newLogRequestFromURL(r *http.Request) (logRequest, error) {
path := r.URL.EscapedPath()
if !strings.HasPrefix(path, "/channel") && !strings.HasPrefix(path, "/channelid") {
return logRequest{}, errors.New("route not found")
}
url := strings.TrimRight(path, "/")
matches := pathRegex.FindAllStringSubmatch(url, -1)
if len(matches) == 0 || len(matches[0]) < 5 {
return logRequest{}, errors.New("route not found")
}
request := logRequest{
time: logTime{},
}
params := []string{}
for _, match := range matches[0] {
if match != "" {
params = append(params, match)
}
}
request.isUserRequest = len(params) > 4 && (params[3] == "user" || params[3] == "userid")
request.isChannelRequest = len(params) < 4 || (len(params) >= 4 && params[3] != "user" && params[3] != "userid")
if params[1] == "channel" {
request.channel = params[2]
}
if params[1] == "channelid" {
request.channelid = params[2]
}
if request.isUserRequest && params[3] == "user" {
request.user = params[4]
}
if request.isUserRequest && params[3] == "userid" {
request.userid = params[4]
}
if request.isUserRequest && len(params) == 7 {
request.time.year = params[5]
request.time.month = params[6]
} else if request.isChannelRequest && len(params) == 6 {
request.time.year = params[3]
request.time.month = params[4]
request.time.day = params[5]
} else if request.isUserRequest && len(params) == 6 && params[5] == "random" {
request.time.random = true
} else if (request.isUserRequest && len(params) == 6 && params[5] == "range") || (request.isChannelRequest && len(params) == 4 && params[3] == "range") {
request.time.from = r.URL.Query().Get("from")
request.time.to = r.URL.Query().Get("to")
} else {
request.time.year = fmt.Sprintf("%d", time.Now().Year())
request.time.month = fmt.Sprintf("%d", time.Now().Month())
timePath := request.time.year + "/" + request.time.month
if request.isChannelRequest {
request.time.day = fmt.Sprintf("%d", time.Now().Day())
timePath += "/" + request.time.day
}
query := r.URL.Query()
query.Del("from")
query.Del("to")
encodedQuery := ""
if query.Encode() != "" {
encodedQuery = "?" + query.Encode()
}
return logRequest{redirectPath: fmt.Sprintf("%s/%s%s", params[0], timePath, encodedQuery)}, nil
}
if _, ok := r.URL.Query()["reverse"]; ok {
request.reverse = true
} else {
request.reverse = false
}
if _, ok := r.URL.Query()["json"]; ok || r.URL.Query().Get("type") == "json" || r.Header.Get("Content-Type") == "application/json" {
request.responseType = responseTypeJSON
} else if _, ok := r.URL.Query()["raw"]; ok || r.URL.Query().Get("type") == "raw" {
request.responseType = responseTypeRaw
} else {
request.responseType = responseTypeText
}
var err error
request, err = s.fillIds(request)
if err != nil {
log.Error(err)
return logRequest{}, nil
}
return request, nil
}
func (s *Server) fillIds(request logRequest) (logRequest, error) {
usernames := []string{}
if request.channelid == "" && request.channel != "" {
usernames = append(usernames, request.channel)
}
if request.userid == "" && request.user != "" {
usernames = append(usernames, request.user)
}
ids, err := s.helixClient.GetUsersByUsernames(usernames)
if err != nil {
return request, err
}
if request.channelid == "" {
request.channelid = ids[request.channel].ID
}
if request.userid == "" {
request.userid = ids[request.user].ID
}
return request, nil
}

View file

@ -14,22 +14,20 @@ import (
"github.com/gempir/go-twitch-irc/v2"
"github.com/gempir/justlog/filelog"
jsoniter "github.com/json-iterator/go"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
_ "github.com/gempir/justlog/docs"
echoSwagger "github.com/swaggo/echo-swagger"
)
// Server api server
type Server struct {
listenAddress string
logPath string
fileLogger *filelog.Logger
helixClient *helix.Client
channels []string
assets []string
assetHandler http.Handler
}
// NewServer create api Server
func NewServer(logPath string, listenAddress string, fileLogger *filelog.Logger, helixClient *helix.Client, channels []string) Server {
return Server{
listenAddress: listenAddress,
@ -37,67 +35,21 @@ func NewServer(logPath string, listenAddress string, fileLogger *filelog.Logger,
fileLogger: fileLogger,
helixClient: helixClient,
channels: channels,
assets: []string{"/", "/bundle.js", "/favicon.ico"},
assetHandler: http.FileServer(assets),
}
}
// AddChannel adds a channel to the collection to output on the channels endpoint
func (s *Server) AddChannel(channel string) {
s.channels = append(s.channels, channel)
}
func (s *Server) Init() {
e := echo.New()
e.HideBanner = true
DefaultCORSConfig := middleware.CORSConfig{
Skipper: middleware.DefaultSkipper,
AllowOrigins: []string{"*"},
AllowMethods: []string{echo.GET, echo.HEAD, echo.PUT, echo.PATCH, echo.POST, echo.DELETE},
}
e.Use(middleware.RemoveTrailingSlashWithConfig(middleware.TrailingSlashConfig{
RedirectCode: http.StatusMovedPermanently,
}))
e.Use(middleware.SecureWithConfig(middleware.SecureConfig{
XSSProtection: "", // disabled
ContentTypeNosniff: "nosniff",
XFrameOptions: "", // disabled
HSTSMaxAge: 0, // disabled
ContentSecurityPolicy: "", // disabled
}))
e.Use(middleware.CORSWithConfig(DefaultCORSConfig))
assetHandler := http.FileServer(assets)
e.GET("/", echo.WrapHandler(assetHandler))
e.GET("/bundle.js", echo.WrapHandler(assetHandler))
e.GET("/docs", func(c echo.Context) error {
return c.Redirect(http.StatusMovedPermanently, "/swagger/index.html")
})
e.GET("/swagger/*", echoSwagger.WrapHandler)
e.GET("/channels", s.getAllChannels)
e.GET("/channel/:channel/user/:username/range", s.getUserLogsRangeByName)
e.GET("/channelid/:channelid/userid/:userid/range", s.getUserLogsRange)
e.GET("/channel/:channel/user/:username", s.getLastUserLogsByName)
e.GET("/channel/:channel/user/:username/:year/:month", s.getUserLogsByName)
e.GET("/channel/:channel/user/:username/random", s.getRandomQuoteByName)
e.GET("/channelid/:channelid/userid/:userid", s.getLastUserLogs)
e.GET("/channelid/:channelid/userid/:userid/:year/:month", s.getUserLogs)
e.GET("/channelid/:channelid/userid/:userid/random", s.getRandomQuote)
e.GET("/v2/:channelType/:channel/:userType/:user/:year/:month", s.getUserLogsExact)
e.GET("/channelid/:channelid/range", s.getChannelLogsRange)
e.GET("/channel/:channel/range", s.getChannelLogsRangeByName)
e.GET("/channel/:channel", s.getCurrentChannelLogsByName)
e.GET("/channel/:channel/:year/:month/:day", s.getChannelLogsByName)
e.GET("/channelid/:channelid", s.getCurrentChannelLogs)
e.GET("/channelid/:channelid/:year/:month/:day", s.getChannelLogs)
e.Logger.Fatal(e.Start(s.listenAddress))
}
const (
responseTypeJSON = "json"
responseTypeText = "text"
responseTypeRaw = "raw"
)
var (
userHourLimit = 744.0
@ -109,6 +61,7 @@ type channel struct {
Name string `json:"name"`
}
// AllChannelsJSON inlcudes all channels
type AllChannelsJSON struct {
Channels []channel `json:"channels"`
}
@ -127,6 +80,7 @@ type chatMessage struct {
Raw string `json:"raw"`
}
// ErrorResponse a simple error response
type ErrorResponse struct {
Message string `json:"message"`
}
@ -135,35 +89,165 @@ type timestamp struct {
time.Time
}
func reverse(input []string) []string {
// Init start the server
func (s *Server) Init() {
http.Handle("/", corsHandler(http.HandlerFunc(s.route)))
log.Fatal(http.ListenAndServe(s.listenAddress, nil))
}
func (s *Server) route(w http.ResponseWriter, r *http.Request) {
url := r.URL.EscapedPath()
if contains(s.assets, url) {
s.assetHandler.ServeHTTP(w, r)
return
}
if url == "/channels" {
s.getAllChannels(w, r)
return
}
s.routeLogs(w, r)
}
func (s *Server) routeLogs(w http.ResponseWriter, r *http.Request) {
request, err := s.newLogRequestFromURL(r)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if request.redirectPath != "" {
http.Redirect(w, r, request.redirectPath, http.StatusFound)
return
}
var logs *chatLog
if request.time.random {
logs, err = s.getRandomQuote(request)
} else if request.time.from != "" && request.time.to != "" {
if request.isUserRequest {
logs, err = s.getUserLogsRange(request)
} else {
logs, err = s.getChannelLogsRange(request)
}
} else {
if request.isUserRequest {
logs, err = s.getUserLogs(request)
} else {
logs, err = s.getChannelLogs(request)
}
}
if err != nil {
log.Error(err)
http.Error(w, "could not load logs", http.StatusInternalServerError)
return
}
if request.responseType == responseTypeJSON {
writeJSON(logs, http.StatusOK, w, r)
return
}
if request.responseType == responseTypeRaw {
writeRaw(logs, http.StatusOK, w, r)
return
}
if request.responseType == responseTypeText {
writeText(logs, http.StatusOK, w, r)
return
}
http.Error(w, "unkown response type", http.StatusBadRequest)
}
func corsHandler(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == "OPTIONS" {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET")
w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
} else {
w.Header().Set("Access-Control-Allow-Origin", "*")
h.ServeHTTP(w, r)
}
})
}
func contains(s []string, e string) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}
func reverseSlice(input []string) []string {
for i, j := 0, len(input)-1; i < j; i, j = i+1, j-1 {
input[i], input[j] = input[j], input[i]
}
return input
}
// getAllChannels godoc
// @Summary Get all joined channels
// @tags bot
// @Produce json
// @Success 200 {object} api.RandomQuoteJSON json
// @Failure 500 {object} api.ErrorResponse json
// @Router /channels [get]
func (s *Server) getAllChannels(c echo.Context) error {
func (s *Server) getAllChannels(w http.ResponseWriter, r *http.Request) {
response := new(AllChannelsJSON)
response.Channels = []channel{}
users, err := s.helixClient.GetUsersByUserIds(s.channels)
if err != nil {
log.Error(err)
return c.JSON(http.StatusInternalServerError, ErrorResponse{"Failure fetching data from twitch"})
http.Error(w, "Failure fetching data from twitch", http.StatusInternalServerError)
return
}
for _, user := range users {
response.Channels = append(response.Channels, channel{UserID: user.ID, Name: user.Login})
}
return c.JSON(http.StatusOK, response)
writeJSON(response, http.StatusOK, w, r)
}
func writeJSON(data interface{}, code int, w http.ResponseWriter, r *http.Request) {
js, err := json.Marshal(data)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
w.Write(js)
}
func writeRaw(cLog *chatLog, code int, w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(code)
for _, cMessage := range cLog.Messages {
w.Write([]byte(cMessage.Raw + "\n"))
}
}
func writeText(cLog *chatLog, code int, w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(code)
for _, cMessage := range cLog.Messages {
switch cMessage.Type {
case twitch.PRIVMSG:
w.Write([]byte(fmt.Sprintf("[%s] #%s %s: %s\n", cMessage.Timestamp.Format("2006-01-2 15:04:05"), cMessage.Channel, cMessage.Username, cMessage.Text)))
case twitch.CLEARCHAT:
w.Write([]byte(fmt.Sprintf("[%s] #%s %s\n", cMessage.Timestamp.Format("2006-01-2 15:04:05"), cMessage.Channel, cMessage.Text)))
case twitch.USERNOTICE:
w.Write([]byte(fmt.Sprintf("[%s] #%s %s\n", cMessage.Timestamp.Format("2006-01-2 15:04:05"), cMessage.Channel, cMessage.Text)))
}
}
}
func (t timestamp) MarshalJSON() ([]byte, error) {
@ -222,53 +306,6 @@ func parseFromTo(from, to string, limit float64) (time.Time, time.Time, error) {
return fromTime, toTime, nil
}
func writeTextResponse(c echo.Context, cLog *chatLog) error {
c.Response().Header().Set(echo.HeaderContentType, echo.MIMETextPlainCharsetUTF8)
c.Response().WriteHeader(http.StatusOK)
for _, cMessage := range cLog.Messages {
switch cMessage.Type {
case twitch.PRIVMSG:
c.Response().Write([]byte(fmt.Sprintf("[%s] #%s %s: %s\n", cMessage.Timestamp.Format("2006-01-2 15:04:05"), cMessage.Channel, cMessage.Username, cMessage.Text)))
case twitch.CLEARCHAT:
c.Response().Write([]byte(fmt.Sprintf("[%s] #%s %s\n", cMessage.Timestamp.Format("2006-01-2 15:04:05"), cMessage.Channel, cMessage.Text)))
case twitch.USERNOTICE:
c.Response().Write([]byte(fmt.Sprintf("[%s] #%s %s\n", cMessage.Timestamp.Format("2006-01-2 15:04:05"), cMessage.Channel, cMessage.Text)))
}
}
return nil
}
func writeRawResponse(c echo.Context, cLog *chatLog) error {
c.Response().Header().Set(echo.HeaderContentType, echo.MIMETextPlainCharsetUTF8)
c.Response().WriteHeader(http.StatusOK)
for _, cMessage := range cLog.Messages {
c.Response().Write([]byte(cMessage.Raw + "\n"))
}
return nil
}
func writeJSONResponse(c echo.Context, logResult *chatLog) error {
_, stream := c.QueryParams()["stream"]
if stream {
c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8)
c.Response().WriteHeader(http.StatusOK)
return json.NewEncoder(c.Response()).Encode(logResult)
}
json := jsoniter.ConfigCompatibleWithStandardLibrary
data, err := json.Marshal(logResult)
if err != nil {
return c.JSON(http.StatusInternalServerError, err.Error())
}
return c.Blob(http.StatusOK, echo.MIMEApplicationJSONCharsetUTF8, data)
}
func parseTimestamp(timestamp string) (time.Time, error) {
i, err := strconv.ParseInt(timestamp, 10, 64)
@ -278,20 +315,10 @@ func parseTimestamp(timestamp string) (time.Time, error) {
return time.Unix(i, 0), nil
}
func shouldReverse(c echo.Context) bool {
_, ok := c.QueryParams()["reverse"]
func buildClearChatMessageText(message twitch.ClearChatMessage) string {
if message.BanDuration == 0 {
return fmt.Sprintf("%s has been banned", message.TargetUsername)
}
return c.QueryParam("order") == "reverse" || ok
}
func shouldRespondWithJson(c echo.Context) bool {
_, ok := c.QueryParams()["json"]
return c.Request().Header.Get("Content-Type") == "application/json" || c.Request().Header.Get("accept") == "application/json" || c.QueryParam("type") == "json" || ok
}
func shouldRespondWithRaw(c echo.Context) bool {
_, ok := c.QueryParams()["raw"]
return c.QueryParam("type") == "raw" || ok
return fmt.Sprintf("%s has been timed out for %d seconds", message.TargetUsername, message.BanDuration)
}

View file

@ -2,16 +2,11 @@ package api
import (
"fmt"
"net/http"
"strconv"
"strings"
"github.com/gempir/go-twitch-irc/v2"
"github.com/labstack/echo/v4"
helix "github.com/gempir/justlog/helix"
log "github.com/sirupsen/logrus"
)
// RandomQuoteJSON response when request a random message
type RandomQuoteJSON struct {
Channel string `json:"channel"`
Username string `json:"username"`
@ -20,328 +15,64 @@ type RandomQuoteJSON struct {
Timestamp timestamp `json:"timestamp"`
}
// @Summary Redirect to last logs of user
// @tags user
// @Produce json
// @Produce plain
// @Param channelid path string true "twitch userid"
// @Param userid path string true "twitch userid"
// @Param from query int false "unix timestamp, limit logs by timestamps from this point"
// @Param to query int false "unix timestamp, limit logs by timestamps to this point"
// @Param json query int false "response as json"
// @Param type query string false "define response type only json supported currently, rest defaults to plain"
// @Success 303
// @Success 200
// @Failure 404
// @Router /channelid/{channelid}/userid/{userid} [get]
func (s *Server) getLastUserLogs(c echo.Context) error {
channelID := c.Param("channelid")
userID := c.Param("userid")
year, month, err := s.fileLogger.GetLastLogYearAndMonthForUser(channelID, userID)
func (s *Server) getRandomQuote(request logRequest) (*chatLog, error) {
rawMessage, err := s.fileLogger.ReadRandomMessageForUser(request.channelid, request.userid)
if err != nil {
return c.JSON(http.StatusNotFound, ErrorResponse{"No logs found"})
}
redirectURL := fmt.Sprintf("/channelid/%s/userid/%s/%d/%d", channelID, userID, year, month)
if len(c.QueryString()) > 0 {
redirectURL += "?" + c.QueryString()
}
return c.Redirect(303, redirectURL)
}
// @Summary Redirect to last logs of user
// @tags user
// @Produce json
// @Produce plain
// @Param channel path string true "channelname"
// @Param username path string true "username"
// @Param from query int false "unix timestamp, limit logs by timestamps from this point"
// @Param to query int false "unix timestamp, limit logs by timestamps to this point"
// @Param json query int false "response as json"
// @Param type query string false "define response type only json supported currently, rest defaults to plain"
// @Success 303
// @Success 200
// @Failure 500
// @Failure 404
// @Router /channel/{channel}/user/{username} [get]
func (s *Server) getLastUserLogsByName(c echo.Context) error {
channel := strings.ToLower(c.Param("channel"))
username := strings.ToLower(c.Param("username"))
userMap, err := s.helixClient.GetUsersByUsernames([]string{channel, username})
if err != nil {
log.Error(err)
return c.JSON(http.StatusInternalServerError, ErrorResponse{"Failure fetching data from twitch"})
}
var year int
var month int
year, month, err = s.fileLogger.GetLastLogYearAndMonthForUser(userMap[channel].ID, userMap[username].ID)
if err != nil {
return c.JSON(http.StatusNotFound, ErrorResponse{"No logs found"})
}
redirectURL := fmt.Sprintf("/channel/%s/user/%s/%d/%d", channel, username, year, month)
if len(c.QueryString()) > 0 {
redirectURL += "?" + c.QueryString()
}
return c.Redirect(303, redirectURL)
}
// @Summary UNSTABLE DO NOT USE
// @tags user
// @Deprecated
// @Produce json
// @Produce plain
// @Param channelType path string true "id or name"
// @Param userIdType path string true "id or name"
// @Param channel path string true "channelid or channelname"
// @Param user path string true "userid or username"
// @Param year path string true "year of logs"
// @Param month path string true "month of logs"
// @Param from query int false "unix timestamp, limit logs by timestamps from this point"
// @Param to query int false "unix timestamp, limit logs by timestamps to this point"
// @Param json query int false "response as json"
// @Param type query string false "define response type only json supported currently, rest defaults to plain"
// @Success 200
// @Failure 500
// @Router /v2/{channelType}/{channel}/{userType}/{user}/{year}/{month} [get]
func (s *Server) getUserLogsExact(c echo.Context) error {
channel := strings.ToLower(c.Param("channel"))
user := strings.ToLower(c.Param("user"))
userMap := map[string]helix.UserData{}
if (c.Param("channelType") == "name" || c.Param("userType") == "name") {
var err error
userMap, err = s.helixClient.GetUsersByUsernames([]string{channel, user})
if err != nil {
log.Error(err)
return c.JSON(http.StatusInternalServerError, ErrorResponse{"Failure fetching data from twitch"})
}
}
names := c.ParamNames()
values := c.ParamValues()
names = append(names, "channelid")
if (c.Param("channelType") == "name") {
values = append(values, userMap[channel].ID)
} else {
values = append(values, c.Param("channel"))
}
names = append(names, "userid")
if (c.Param("userType") == "name") {
values = append(values, userMap[user].ID)
} else {
values = append(values, c.Param("user"))
}
c.SetParamNames(names...)
c.SetParamValues(values...)
return s.getUserLogs(c)
}
func (s *Server) getUserLogsRangeByName(c echo.Context) error {
channel := strings.ToLower(c.Param("channel"))
username := strings.ToLower(c.Param("username"))
userMap, err := s.helixClient.GetUsersByUsernames([]string{channel, username})
if err != nil {
log.Error(err)
return c.JSON(http.StatusInternalServerError, ErrorResponse{"Failure fetching data from twitch"})
}
names := c.ParamNames()
names = append(names, "channelid")
names = append(names, "userid")
values := c.ParamValues()
values = append(values, userMap[channel].ID)
values = append(values, userMap[username].ID)
c.SetParamNames(names...)
c.SetParamValues(values...)
return s.getUserLogsRange(c)
}
// @Summary Get logs for user by year and month
// @tags user
// @Produce json
// @Produce plain
// @Param channel path string true "channelname"
// @Param username path string true "username"
// @Param year path string true "year of logs"
// @Param month path string true "month of logs"
// @Param from query int false "unix timestamp, limit logs by timestamps from this point"
// @Param to query int false "unix timestamp, limit logs by timestamps to this point"
// @Param json query int false "response as json"
// @Param type query string false "define response type only json supported currently, rest defaults to plain"
// @Success 200
// @Failure 500
// @Router /channel/{channel}/user/{username}/{year}/{month} [get]
func (s *Server) getUserLogsByName(c echo.Context) error {
channel := strings.ToLower(c.Param("channel"))
username := strings.ToLower(c.Param("username"))
userMap, err := s.helixClient.GetUsersByUsernames([]string{channel, username})
if err != nil {
log.Error(err)
return c.JSON(http.StatusInternalServerError, ErrorResponse{"Failure fetching data from twitch"})
}
names := c.ParamNames()
names = append(names, "channelid")
names = append(names, "userid")
values := c.ParamValues()
values = append(values, userMap[channel].ID)
values = append(values, userMap[username].ID)
c.SetParamNames(names...)
c.SetParamValues(values...)
return s.getUserLogs(c)
}
// @Summary Get a random chat message from a user
// @tags user
// @Produce json
// @Produce plain
// @Param channel path string true "channelname"
// @Param username path string true "username"
// @Param json query int false "response as json"
// @Param type query string false "define response type only json supported currently, rest defaults to plain"
// @Success 200 {object} api.RandomQuoteJSON json
// @Router /channel/{channel}/user/{username}/random [get]
func (s *Server) getRandomQuoteByName(c echo.Context) error {
channel := strings.ToLower(c.Param("channel"))
username := strings.ToLower(c.Param("username"))
userMap, err := s.helixClient.GetUsersByUsernames([]string{channel, username})
if err != nil {
log.Error(err)
return c.JSON(http.StatusInternalServerError, ErrorResponse{"Failure fetching data from twitch"})
}
names := c.ParamNames()
names = append(names, "channelid")
names = append(names, "userid")
values := c.ParamValues()
values = append(values, userMap[channel].ID)
values = append(values, userMap[username].ID)
c.SetParamNames(names...)
c.SetParamValues(values...)
return s.getRandomQuote(c)
}
// @Summary Get a random chat message from a user
// @tags user
// @Produce json
// @Produce plain
// @Param channelid path string true "twitch userid"
// @Param userid path string true "twitch userid"
// @Param json query int false "response as json"
// @Param type query string false "define response type only json supported currently, rest defaults to plain"
// @Success 200 {object} api.RandomQuoteJSON json
// @Router /channelid/{channelid}/userid/{userid}/random [get]
func (s *Server) getRandomQuote(c echo.Context) error {
channelID := c.Param("channelid")
userID := c.Param("userid")
rawMessage, err := s.fileLogger.ReadRandomMessageForUser(channelID, userID)
if err != nil {
return c.JSON(http.StatusInternalServerError, err.Error())
return &chatLog{}, err
}
parsedMessage := twitch.ParseMessage(rawMessage)
var chatMsg chatMessage
switch parsedMessage.(type) {
case *twitch.PrivateMessage:
message := *parsedMessage.(*twitch.PrivateMessage)
if shouldRespondWithJson(c) {
randomQ := RandomQuoteJSON{
Channel: message.Channel,
Username: message.User.Name,
DisplayName: message.User.DisplayName,
Message: message.Message,
Timestamp: timestamp{message.Time},
}
return c.JSON(http.StatusOK, randomQ)
chatMsg = chatMessage{
Timestamp: timestamp{message.Time},
Username: message.User.Name,
DisplayName: message.User.DisplayName,
Text: message.Message,
Type: message.Type,
Channel: message.Channel,
Raw: message.Raw,
}
return c.String(http.StatusOK, message.Message)
case *twitch.ClearChatMessage:
message := *parsedMessage.(*twitch.ClearChatMessage)
if shouldRespondWithJson(c) {
randomQ := RandomQuoteJSON{
Channel: message.Channel,
Username: message.TargetUsername,
DisplayName: message.TargetUsername,
Message: buildClearChatMessageText(message),
Timestamp: timestamp{message.Time},
}
return c.JSON(http.StatusOK, randomQ)
chatMsg = chatMessage{
Timestamp: timestamp{message.Time},
Username: message.TargetUsername,
DisplayName: message.TargetUsername,
Text: buildClearChatMessageText(message),
Type: message.Type,
Channel: message.Channel,
Raw: message.Raw,
}
case *twitch.UserNoticeMessage:
message := *parsedMessage.(*twitch.UserNoticeMessage)
return c.String(http.StatusOK, buildClearChatMessageText(message))
chatMsg = chatMessage{
Timestamp: timestamp{message.Time},
Username: message.User.Name,
DisplayName: message.User.DisplayName,
Text: message.SystemMsg + " " + message.Message,
Type: message.Type,
Channel: message.Channel,
Raw: message.Raw,
}
}
return c.String(http.StatusNotFound, "No quote found")
return &chatLog{Messages: []chatMessage{chatMsg}}, nil
}
// @Summary Get logs for user by year and month
// @tags user
// @Produce json
// @Produce plain
// @Param channelid path string true "twitch userid"
// @Param userid path string true "twitch userid"
// @Param year path string true "year of logs"
// @Param month path string true "month of logs"
// @Param from query int false "unix timestamp, limit logs by timestamps from this point"
// @Param to query int false "unix timestamp, limit logs by timestamps to this point"
// @Param json query int false "response as json"
// @Param type query string false "define response type only json supported currently, rest defaults to plain"
// @Success 200
// @Failure 500
// @Router /channelid/{channelid}/userid/{userid}/{year}/{month} [get]
func (s *Server) getUserLogs(c echo.Context) error {
channelID := c.Param("channelid")
userID := c.Param("userid")
yearStr := c.Param("year")
monthStr := c.Param("month")
year, err := strconv.Atoi(yearStr)
func (s *Server) getUserLogs(request logRequest) (*chatLog, error) {
logMessages, err := s.fileLogger.ReadLogForUser(request.channelid, request.userid, request.time.year, request.time.month)
if err != nil {
log.Error(err)
return c.JSON(http.StatusInternalServerError, ErrorResponse{"Invalid year"})
}
month, err := strconv.Atoi(monthStr)
if err != nil {
log.Error(err)
return c.JSON(http.StatusInternalServerError, ErrorResponse{"Invalid month"})
return &chatLog{}, err
}
logMessages, err := s.fileLogger.ReadLogForUser(channelID, userID, year, month)
if err != nil {
log.Error(err)
return c.JSON(http.StatusNotFound, ErrorResponse{"Failure reading log"})
}
if shouldReverse(c) {
reverse(logMessages)
if request.reverse {
reverseSlice(logMessages)
}
var logResult chatLog
@ -393,42 +124,28 @@ func (s *Server) getUserLogs(c echo.Context) error {
logResult.Messages = append(logResult.Messages, chatMsg)
}
if shouldRespondWithJson(c) {
return writeJSONResponse(c, &logResult)
}
if shouldRespondWithRaw(c) {
return writeRawResponse(c, &logResult)
}
return writeTextResponse(c, &logResult)
return &logResult, nil
}
func (s *Server) getUserLogsRange(c echo.Context) error {
channelID := c.Param("channelid")
userID := c.Param("userid")
func (s *Server) getUserLogsRange(request logRequest) (*chatLog, error) {
fromTime, toTime, err := parseFromTo(c.QueryParam("from"), c.QueryParam("to"), userHourLimit)
fromTime, toTime, err := parseFromTo(request.time.from, request.time.to, userHourLimit)
if err != nil {
return c.JSON(http.StatusInternalServerError, ErrorResponse{err.Error()})
return &chatLog{}, err
}
var logMessages []string
logMessages, _ = s.fileLogger.ReadLogForUser(channelID, userID, fromTime.Year(), int(fromTime.Month()))
logMessages, _ = s.fileLogger.ReadLogForUser(request.channelid, request.userid, fmt.Sprintf("%d", fromTime.Year()), fmt.Sprintf("%d", int(fromTime.Month())))
if fromTime.Month() != toTime.Month() {
additionalMessages, _ := s.fileLogger.ReadLogForUser(channelID, userID, toTime.Year(), int(toTime.Month()))
additionalMessages, _ := s.fileLogger.ReadLogForUser(request.channelid, request.userid, string(toTime.Year()), string(toTime.Month()))
logMessages = append(logMessages, additionalMessages...)
}
if len(logMessages) == 0 {
return c.JSON(http.StatusNotFound, ErrorResponse{"No logs found"})
}
if shouldReverse(c) {
reverse(logMessages)
if request.reverse {
reverseSlice(logMessages)
}
var logResult chatLog
@ -488,21 +205,5 @@ func (s *Server) getUserLogsRange(c echo.Context) error {
logResult.Messages = append(logResult.Messages, chatMsg)
}
if shouldRespondWithJson(c) {
return writeJSONResponse(c, &logResult)
}
if shouldRespondWithRaw(c) {
return writeRawResponse(c, &logResult)
}
return writeTextResponse(c, &logResult)
}
func buildClearChatMessageText(message twitch.ClearChatMessage) string {
if message.BanDuration == 0 {
return fmt.Sprintf("%s has been banned", message.TargetUsername)
} else {
return fmt.Sprintf("%s has been timed out for %d seconds", message.TargetUsername, message.BanDuration)
}
return &logResult, nil
}

View file

@ -1,579 +0,0 @@
// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
// This file was generated by swaggo/swag at
// 2020-02-11 18:50:01.55539 +0100 CET m=+0.121127201
package docs
import (
"bytes"
"encoding/json"
"strings"
"github.com/alecthomas/template"
"github.com/swaggo/swag"
)
var doc = `{
"schemes": {{ marshal .Schemes }},
"swagger": "2.0",
"info": {
"description": "{{.Description}}",
"title": "{{.Title}}",
"contact": {
"name": "gempir",
"url": "https://gempir.com",
"email": "gempir.dev@gmail.com"
},
"license": {
"name": "MIT",
"url": "https://github.com/gempir/justlog/blob/master/LICENSE"
},
"version": "{{.Version}}"
},
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/channel/{channel}/user/{username}": {
"get": {
"produces": [
"application/json",
"text/plain"
],
"tags": [
"user"
],
"summary": "Redirect to last logs of user",
"parameters": [
{
"type": "string",
"description": "channelname",
"name": "channel",
"in": "path",
"required": true
},
{
"type": "string",
"description": "username",
"name": "username",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "unix timestamp, limit logs by timestamps from this point",
"name": "from",
"in": "query"
},
{
"type": "integer",
"description": "unix timestamp, limit logs by timestamps to this point",
"name": "to",
"in": "query"
},
{
"type": "integer",
"description": "response as json",
"name": "json",
"in": "query"
},
{
"type": "string",
"description": "define response type only json supported currently, rest defaults to plain",
"name": "type",
"in": "query"
}
],
"responses": {
"200": {},
"303": {},
"404": {},
"500": {}
}
}
},
"/channel/{channel}/user/{username}/random": {
"get": {
"produces": [
"application/json",
"text/plain"
],
"tags": [
"user"
],
"summary": "Get a random chat message from a user",
"parameters": [
{
"type": "string",
"description": "channelname",
"name": "channel",
"in": "path",
"required": true
},
{
"type": "string",
"description": "username",
"name": "username",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "response as json",
"name": "json",
"in": "query"
},
{
"type": "string",
"description": "define response type only json supported currently, rest defaults to plain",
"name": "type",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.RandomQuoteJSON"
}
}
}
}
},
"/channel/{channel}/user/{username}/{year}/{month}": {
"get": {
"produces": [
"application/json",
"text/plain"
],
"tags": [
"user"
],
"summary": "Get logs for user by year and month",
"parameters": [
{
"type": "string",
"description": "channelname",
"name": "channel",
"in": "path",
"required": true
},
{
"type": "string",
"description": "username",
"name": "username",
"in": "path",
"required": true
},
{
"type": "string",
"description": "year of logs",
"name": "year",
"in": "path",
"required": true
},
{
"type": "string",
"description": "month of logs",
"name": "month",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "unix timestamp, limit logs by timestamps from this point",
"name": "from",
"in": "query"
},
{
"type": "integer",
"description": "unix timestamp, limit logs by timestamps to this point",
"name": "to",
"in": "query"
},
{
"type": "integer",
"description": "response as json",
"name": "json",
"in": "query"
},
{
"type": "string",
"description": "define response type only json supported currently, rest defaults to plain",
"name": "type",
"in": "query"
}
],
"responses": {
"200": {},
"500": {}
}
}
},
"/channelid/{channelid}/userid/{userid}": {
"get": {
"produces": [
"application/json",
"text/plain"
],
"tags": [
"user"
],
"summary": "Redirect to last logs of user",
"parameters": [
{
"type": "string",
"description": "twitch userid",
"name": "channelid",
"in": "path",
"required": true
},
{
"type": "string",
"description": "twitch userid",
"name": "userid",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "unix timestamp, limit logs by timestamps from this point",
"name": "from",
"in": "query"
},
{
"type": "integer",
"description": "unix timestamp, limit logs by timestamps to this point",
"name": "to",
"in": "query"
},
{
"type": "integer",
"description": "response as json",
"name": "json",
"in": "query"
},
{
"type": "string",
"description": "define response type only json supported currently, rest defaults to plain",
"name": "type",
"in": "query"
}
],
"responses": {
"200": {},
"303": {},
"404": {}
}
}
},
"/channelid/{channelid}/userid/{userid}/random": {
"get": {
"produces": [
"application/json",
"text/plain"
],
"tags": [
"user"
],
"summary": "Get a random chat message from a user",
"parameters": [
{
"type": "string",
"description": "twitch userid",
"name": "channelid",
"in": "path",
"required": true
},
{
"type": "string",
"description": "twitch userid",
"name": "userid",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "response as json",
"name": "json",
"in": "query"
},
{
"type": "string",
"description": "define response type only json supported currently, rest defaults to plain",
"name": "type",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.RandomQuoteJSON"
}
}
}
}
},
"/channelid/{channelid}/userid/{userid}/{year}/{month}": {
"get": {
"produces": [
"application/json",
"text/plain"
],
"tags": [
"user"
],
"summary": "Get logs for user by year and month",
"parameters": [
{
"type": "string",
"description": "twitch userid",
"name": "channelid",
"in": "path",
"required": true
},
{
"type": "string",
"description": "twitch userid",
"name": "userid",
"in": "path",
"required": true
},
{
"type": "string",
"description": "year of logs",
"name": "year",
"in": "path",
"required": true
},
{
"type": "string",
"description": "month of logs",
"name": "month",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "unix timestamp, limit logs by timestamps from this point",
"name": "from",
"in": "query"
},
{
"type": "integer",
"description": "unix timestamp, limit logs by timestamps to this point",
"name": "to",
"in": "query"
},
{
"type": "integer",
"description": "response as json",
"name": "json",
"in": "query"
},
{
"type": "string",
"description": "define response type only json supported currently, rest defaults to plain",
"name": "type",
"in": "query"
}
],
"responses": {
"200": {},
"500": {}
}
}
},
"/channels": {
"get": {
"produces": [
"application/json"
],
"tags": [
"bot"
],
"summary": "Get all joined channels",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.RandomQuoteJSON"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/api.ErrorResponse"
}
}
}
}
},
"/v2/{channelType}/{channel}/{userType}/{user}/{year}/{month}": {
"get": {
"produces": [
"application/json",
"text/plain"
],
"tags": [
"user"
],
"summary": "UNSTABLE DO NOT USE",
"deprecated": true,
"parameters": [
{
"type": "string",
"description": "id or name",
"name": "channelType",
"in": "path",
"required": true
},
{
"type": "string",
"description": "id or name",
"name": "userIdType",
"in": "path",
"required": true
},
{
"type": "string",
"description": "channelid or channelname",
"name": "channel",
"in": "path",
"required": true
},
{
"type": "string",
"description": "userid or username",
"name": "user",
"in": "path",
"required": true
},
{
"type": "string",
"description": "year of logs",
"name": "year",
"in": "path",
"required": true
},
{
"type": "string",
"description": "month of logs",
"name": "month",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "unix timestamp, limit logs by timestamps from this point",
"name": "from",
"in": "query"
},
{
"type": "integer",
"description": "unix timestamp, limit logs by timestamps to this point",
"name": "to",
"in": "query"
},
{
"type": "integer",
"description": "response as json",
"name": "json",
"in": "query"
},
{
"type": "string",
"description": "define response type only json supported currently, rest defaults to plain",
"name": "type",
"in": "query"
}
],
"responses": {
"200": {},
"500": {}
}
}
}
},
"definitions": {
"api.ErrorResponse": {
"type": "object",
"properties": {
"message": {
"type": "string"
}
}
},
"api.RandomQuoteJSON": {
"type": "object",
"properties": {
"channel": {
"type": "string"
},
"displayName": {
"type": "string"
},
"message": {
"type": "string"
},
"timestamp": {
"type": "object",
"$ref": "#/definitions/api.timestamp"
},
"username": {
"type": "string"
}
}
},
"api.timestamp": {
"type": "object"
}
}
}`
type swaggerInfo struct {
Version string
Host string
BasePath string
Schemes []string
Title string
Description string
}
// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = swaggerInfo{
Version: "1.0",
Host: "logs.ivr.fi",
BasePath: "/",
Schemes: []string{},
Title: "justlog API",
Description: "API for twitch logs",
}
type s struct{}
func (s *s) ReadDoc() string {
sInfo := SwaggerInfo
sInfo.Description = strings.Replace(sInfo.Description, "\n", "\\n", -1)
t, err := template.New("swagger_info").Funcs(template.FuncMap{
"marshal": func(v interface{}) string {
a, _ := json.Marshal(v)
return string(a)
},
}).Parse(doc)
if err != nil {
return doc
}
var tpl bytes.Buffer
if err := t.Execute(&tpl, sInfo); err != nil {
return doc
}
return tpl.String()
}
func init() {
swag.Register(swag.Name, &s{})
}

View file

@ -1,516 +0,0 @@
{
"swagger": "2.0",
"info": {
"description": "API for twitch logs",
"title": "justlog API",
"contact": {
"name": "gempir",
"url": "https://gempir.com",
"email": "gempir.dev@gmail.com"
},
"license": {
"name": "MIT",
"url": "https://github.com/gempir/justlog/blob/master/LICENSE"
},
"version": "1.0"
},
"host": "logs.ivr.fi",
"basePath": "/",
"paths": {
"/channel/{channel}/user/{username}": {
"get": {
"produces": [
"application/json",
"text/plain"
],
"tags": [
"user"
],
"summary": "Redirect to last logs of user",
"parameters": [
{
"type": "string",
"description": "channelname",
"name": "channel",
"in": "path",
"required": true
},
{
"type": "string",
"description": "username",
"name": "username",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "unix timestamp, limit logs by timestamps from this point",
"name": "from",
"in": "query"
},
{
"type": "integer",
"description": "unix timestamp, limit logs by timestamps to this point",
"name": "to",
"in": "query"
},
{
"type": "integer",
"description": "response as json",
"name": "json",
"in": "query"
},
{
"type": "string",
"description": "define response type only json supported currently, rest defaults to plain",
"name": "type",
"in": "query"
}
],
"responses": {
"200": {},
"303": {},
"404": {},
"500": {}
}
}
},
"/channel/{channel}/user/{username}/random": {
"get": {
"produces": [
"application/json",
"text/plain"
],
"tags": [
"user"
],
"summary": "Get a random chat message from a user",
"parameters": [
{
"type": "string",
"description": "channelname",
"name": "channel",
"in": "path",
"required": true
},
{
"type": "string",
"description": "username",
"name": "username",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "response as json",
"name": "json",
"in": "query"
},
{
"type": "string",
"description": "define response type only json supported currently, rest defaults to plain",
"name": "type",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.RandomQuoteJSON"
}
}
}
}
},
"/channel/{channel}/user/{username}/{year}/{month}": {
"get": {
"produces": [
"application/json",
"text/plain"
],
"tags": [
"user"
],
"summary": "Get logs for user by year and month",
"parameters": [
{
"type": "string",
"description": "channelname",
"name": "channel",
"in": "path",
"required": true
},
{
"type": "string",
"description": "username",
"name": "username",
"in": "path",
"required": true
},
{
"type": "string",
"description": "year of logs",
"name": "year",
"in": "path",
"required": true
},
{
"type": "string",
"description": "month of logs",
"name": "month",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "unix timestamp, limit logs by timestamps from this point",
"name": "from",
"in": "query"
},
{
"type": "integer",
"description": "unix timestamp, limit logs by timestamps to this point",
"name": "to",
"in": "query"
},
{
"type": "integer",
"description": "response as json",
"name": "json",
"in": "query"
},
{
"type": "string",
"description": "define response type only json supported currently, rest defaults to plain",
"name": "type",
"in": "query"
}
],
"responses": {
"200": {},
"500": {}
}
}
},
"/channelid/{channelid}/userid/{userid}": {
"get": {
"produces": [
"application/json",
"text/plain"
],
"tags": [
"user"
],
"summary": "Redirect to last logs of user",
"parameters": [
{
"type": "string",
"description": "twitch userid",
"name": "channelid",
"in": "path",
"required": true
},
{
"type": "string",
"description": "twitch userid",
"name": "userid",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "unix timestamp, limit logs by timestamps from this point",
"name": "from",
"in": "query"
},
{
"type": "integer",
"description": "unix timestamp, limit logs by timestamps to this point",
"name": "to",
"in": "query"
},
{
"type": "integer",
"description": "response as json",
"name": "json",
"in": "query"
},
{
"type": "string",
"description": "define response type only json supported currently, rest defaults to plain",
"name": "type",
"in": "query"
}
],
"responses": {
"200": {},
"303": {},
"404": {}
}
}
},
"/channelid/{channelid}/userid/{userid}/random": {
"get": {
"produces": [
"application/json",
"text/plain"
],
"tags": [
"user"
],
"summary": "Get a random chat message from a user",
"parameters": [
{
"type": "string",
"description": "twitch userid",
"name": "channelid",
"in": "path",
"required": true
},
{
"type": "string",
"description": "twitch userid",
"name": "userid",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "response as json",
"name": "json",
"in": "query"
},
{
"type": "string",
"description": "define response type only json supported currently, rest defaults to plain",
"name": "type",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.RandomQuoteJSON"
}
}
}
}
},
"/channelid/{channelid}/userid/{userid}/{year}/{month}": {
"get": {
"produces": [
"application/json",
"text/plain"
],
"tags": [
"user"
],
"summary": "Get logs for user by year and month",
"parameters": [
{
"type": "string",
"description": "twitch userid",
"name": "channelid",
"in": "path",
"required": true
},
{
"type": "string",
"description": "twitch userid",
"name": "userid",
"in": "path",
"required": true
},
{
"type": "string",
"description": "year of logs",
"name": "year",
"in": "path",
"required": true
},
{
"type": "string",
"description": "month of logs",
"name": "month",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "unix timestamp, limit logs by timestamps from this point",
"name": "from",
"in": "query"
},
{
"type": "integer",
"description": "unix timestamp, limit logs by timestamps to this point",
"name": "to",
"in": "query"
},
{
"type": "integer",
"description": "response as json",
"name": "json",
"in": "query"
},
{
"type": "string",
"description": "define response type only json supported currently, rest defaults to plain",
"name": "type",
"in": "query"
}
],
"responses": {
"200": {},
"500": {}
}
}
},
"/channels": {
"get": {
"produces": [
"application/json"
],
"tags": [
"bot"
],
"summary": "Get all joined channels",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.RandomQuoteJSON"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/api.ErrorResponse"
}
}
}
}
},
"/v2/{channelType}/{channel}/{userType}/{user}/{year}/{month}": {
"get": {
"produces": [
"application/json",
"text/plain"
],
"tags": [
"user"
],
"summary": "UNSTABLE DO NOT USE",
"deprecated": true,
"parameters": [
{
"type": "string",
"description": "id or name",
"name": "channelType",
"in": "path",
"required": true
},
{
"type": "string",
"description": "id or name",
"name": "userIdType",
"in": "path",
"required": true
},
{
"type": "string",
"description": "channelid or channelname",
"name": "channel",
"in": "path",
"required": true
},
{
"type": "string",
"description": "userid or username",
"name": "user",
"in": "path",
"required": true
},
{
"type": "string",
"description": "year of logs",
"name": "year",
"in": "path",
"required": true
},
{
"type": "string",
"description": "month of logs",
"name": "month",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "unix timestamp, limit logs by timestamps from this point",
"name": "from",
"in": "query"
},
{
"type": "integer",
"description": "unix timestamp, limit logs by timestamps to this point",
"name": "to",
"in": "query"
},
{
"type": "integer",
"description": "response as json",
"name": "json",
"in": "query"
},
{
"type": "string",
"description": "define response type only json supported currently, rest defaults to plain",
"name": "type",
"in": "query"
}
],
"responses": {
"200": {},
"500": {}
}
}
}
},
"definitions": {
"api.ErrorResponse": {
"type": "object",
"properties": {
"message": {
"type": "string"
}
}
},
"api.RandomQuoteJSON": {
"type": "object",
"properties": {
"channel": {
"type": "string"
},
"displayName": {
"type": "string"
},
"message": {
"type": "string"
},
"timestamp": {
"type": "object",
"$ref": "#/definitions/api.timestamp"
},
"username": {
"type": "string"
}
}
},
"api.timestamp": {
"type": "object"
}
}
}

View file

@ -1,358 +0,0 @@
basePath: /
definitions:
api.ErrorResponse:
properties:
message:
type: string
type: object
api.RandomQuoteJSON:
properties:
channel:
type: string
displayName:
type: string
message:
type: string
timestamp:
$ref: '#/definitions/api.timestamp'
type: object
username:
type: string
type: object
api.timestamp:
type: object
host: logs.ivr.fi
info:
contact:
email: gempir.dev@gmail.com
name: gempir
url: https://gempir.com
description: API for twitch logs
license:
name: MIT
url: https://github.com/gempir/justlog/blob/master/LICENSE
title: justlog API
version: "1.0"
paths:
/channel/{channel}/user/{username}:
get:
parameters:
- description: channelname
in: path
name: channel
required: true
type: string
- description: username
in: path
name: username
required: true
type: string
- description: unix timestamp, limit logs by timestamps from this point
in: query
name: from
type: integer
- description: unix timestamp, limit logs by timestamps to this point
in: query
name: to
type: integer
- description: response as json
in: query
name: json
type: integer
- description: define response type only json supported currently, rest defaults
to plain
in: query
name: type
type: string
produces:
- application/json
- text/plain
responses:
"200": {}
"303": {}
"404": {}
"500": {}
summary: Redirect to last logs of user
tags:
- user
/channel/{channel}/user/{username}/{year}/{month}:
get:
parameters:
- description: channelname
in: path
name: channel
required: true
type: string
- description: username
in: path
name: username
required: true
type: string
- description: year of logs
in: path
name: year
required: true
type: string
- description: month of logs
in: path
name: month
required: true
type: string
- description: unix timestamp, limit logs by timestamps from this point
in: query
name: from
type: integer
- description: unix timestamp, limit logs by timestamps to this point
in: query
name: to
type: integer
- description: response as json
in: query
name: json
type: integer
- description: define response type only json supported currently, rest defaults
to plain
in: query
name: type
type: string
produces:
- application/json
- text/plain
responses:
"200": {}
"500": {}
summary: Get logs for user by year and month
tags:
- user
/channel/{channel}/user/{username}/random:
get:
parameters:
- description: channelname
in: path
name: channel
required: true
type: string
- description: username
in: path
name: username
required: true
type: string
- description: response as json
in: query
name: json
type: integer
- description: define response type only json supported currently, rest defaults
to plain
in: query
name: type
type: string
produces:
- application/json
- text/plain
responses:
"200":
description: OK
schema:
$ref: '#/definitions/api.RandomQuoteJSON'
summary: Get a random chat message from a user
tags:
- user
/channelid/{channelid}/userid/{userid}:
get:
parameters:
- description: twitch userid
in: path
name: channelid
required: true
type: string
- description: twitch userid
in: path
name: userid
required: true
type: string
- description: unix timestamp, limit logs by timestamps from this point
in: query
name: from
type: integer
- description: unix timestamp, limit logs by timestamps to this point
in: query
name: to
type: integer
- description: response as json
in: query
name: json
type: integer
- description: define response type only json supported currently, rest defaults
to plain
in: query
name: type
type: string
produces:
- application/json
- text/plain
responses:
"200": {}
"303": {}
"404": {}
summary: Redirect to last logs of user
tags:
- user
/channelid/{channelid}/userid/{userid}/{year}/{month}:
get:
parameters:
- description: twitch userid
in: path
name: channelid
required: true
type: string
- description: twitch userid
in: path
name: userid
required: true
type: string
- description: year of logs
in: path
name: year
required: true
type: string
- description: month of logs
in: path
name: month
required: true
type: string
- description: unix timestamp, limit logs by timestamps from this point
in: query
name: from
type: integer
- description: unix timestamp, limit logs by timestamps to this point
in: query
name: to
type: integer
- description: response as json
in: query
name: json
type: integer
- description: define response type only json supported currently, rest defaults
to plain
in: query
name: type
type: string
produces:
- application/json
- text/plain
responses:
"200": {}
"500": {}
summary: Get logs for user by year and month
tags:
- user
/channelid/{channelid}/userid/{userid}/random:
get:
parameters:
- description: twitch userid
in: path
name: channelid
required: true
type: string
- description: twitch userid
in: path
name: userid
required: true
type: string
- description: response as json
in: query
name: json
type: integer
- description: define response type only json supported currently, rest defaults
to plain
in: query
name: type
type: string
produces:
- application/json
- text/plain
responses:
"200":
description: OK
schema:
$ref: '#/definitions/api.RandomQuoteJSON'
summary: Get a random chat message from a user
tags:
- user
/channels:
get:
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/api.RandomQuoteJSON'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/api.ErrorResponse'
summary: Get all joined channels
tags:
- bot
/v2/{channelType}/{channel}/{userType}/{user}/{year}/{month}:
get:
deprecated: true
parameters:
- description: id or name
in: path
name: channelType
required: true
type: string
- description: id or name
in: path
name: userIdType
required: true
type: string
- description: channelid or channelname
in: path
name: channel
required: true
type: string
- description: userid or username
in: path
name: user
required: true
type: string
- description: year of logs
in: path
name: year
required: true
type: string
- description: month of logs
in: path
name: month
required: true
type: string
- description: unix timestamp, limit logs by timestamps from this point
in: query
name: from
type: integer
- description: unix timestamp, limit logs by timestamps to this point
in: query
name: to
type: integer
- description: response as json
in: query
name: json
type: integer
- description: define response type only json supported currently, rest defaults
to plain
in: query
name: type
type: string
produces:
- application/json
- text/plain
responses:
"200": {}
"500": {}
summary: UNSTABLE DO NOT USE
tags:
- user
swagger: "2.0"

View file

@ -1,435 +0,0 @@
{
"swagger": "2.0",
"info": {
"description": "API for twitch logs",
"title": "justlog API",
"contact": {
"name": "gempir",
"url": "https://gempir.com",
"email": "gempir.dev@gmail.com"
},
"license": {
"name": "MIT",
"url": "https://github.com/gempir/justlog/blob/master/LICENSE"
},
"version": "1.0"
},
"host": "api.gempir.com",
"basePath": "/",
"paths": {
"/channel/{channel}/user/{username}": {
"get": {
"produces": [
"application/json",
"text/plain"
],
"tags": [
"user"
],
"summary": "Redirect to last logs of user",
"parameters": [
{
"type": "string",
"description": "channelname",
"name": "channel",
"in": "path",
"required": true
},
{
"type": "string",
"description": "username",
"name": "username",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "unix timestamp, limit logs by timestamps from this point",
"name": "from",
"in": "query"
},
{
"type": "integer",
"description": "unix timestamp, limit logs by timestamps to this point",
"name": "to",
"in": "query"
},
{
"type": "any",
"description": "response as json",
"name": "json",
"in": "query"
},
{
"type": "string",
"description": "define response type only json supported currently, rest defaults to plain",
"name": "type",
"in": "query"
}
],
"responses": {
"200": {},
"303": {},
"404": {},
"500": {}
}
}
},
"/channel/{channel}/user/{username}/random": {
"get": {
"produces": [
"application/json",
"text/plain"
],
"tags": [
"user"
],
"summary": "Get a random chat message from a user",
"parameters": [
{
"type": "string",
"description": "channelname",
"name": "channel",
"in": "path",
"required": true
},
{
"type": "string",
"description": "username",
"name": "username",
"in": "path",
"required": true
},
{
"type": "any",
"description": "response as json",
"name": "json",
"in": "query"
},
{
"type": "string",
"description": "define response type only json supported currently, rest defaults to plain",
"name": "type",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object",
"$ref": "#/definitions/api.RandomQuoteJSON"
}
}
}
}
},
"/channel/{channel}/user/{username}/{year}/{month}": {
"get": {
"produces": [
"application/json",
"text/plain"
],
"tags": [
"user"
],
"summary": "Get logs for user by year and month",
"parameters": [
{
"type": "string",
"description": "channelname",
"name": "channel",
"in": "path",
"required": true
},
{
"type": "string",
"description": "username",
"name": "username",
"in": "path",
"required": true
},
{
"type": "string",
"description": "year of logs",
"name": "year",
"in": "path",
"required": true
},
{
"type": "string",
"description": "month of logs",
"name": "month",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "unix timestamp, limit logs by timestamps from this point",
"name": "from",
"in": "query"
},
{
"type": "integer",
"description": "unix timestamp, limit logs by timestamps to this point",
"name": "to",
"in": "query"
},
{
"type": "any",
"description": "response as json",
"name": "json",
"in": "query"
},
{
"type": "string",
"description": "define response type only json supported currently, rest defaults to plain",
"name": "type",
"in": "query"
}
],
"responses": {
"200": {},
"500": {}
}
}
},
"/channelid/{channelid}/user/{userid}": {
"get": {
"produces": [
"application/json",
"text/plain"
],
"tags": [
"user"
],
"summary": "Redirect to last logs of user",
"parameters": [
{
"type": "string",
"description": "twitch userid",
"name": "channelid",
"in": "path",
"required": true
},
{
"type": "string",
"description": "twitch userid",
"name": "userid",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "unix timestamp, limit logs by timestamps from this point",
"name": "from",
"in": "query"
},
{
"type": "integer",
"description": "unix timestamp, limit logs by timestamps to this point",
"name": "to",
"in": "query"
},
{
"type": "any",
"description": "response as json",
"name": "json",
"in": "query"
},
{
"type": "string",
"description": "define response type only json supported currently, rest defaults to plain",
"name": "type",
"in": "query"
}
],
"responses": {
"200": {},
"303": {},
"404": {}
}
}
},
"/channelid/{channelid}/userid/{userid}/random": {
"get": {
"produces": [
"application/json",
"text/plain"
],
"tags": [
"user"
],
"summary": "Get a random chat message from a user",
"parameters": [
{
"type": "string",
"description": "twitch userid",
"name": "channelid",
"in": "path",
"required": true
},
{
"type": "string",
"description": "twitch userid",
"name": "userid",
"in": "path",
"required": true
},
{
"type": "any",
"description": "response as json",
"name": "json",
"in": "query"
},
{
"type": "string",
"description": "define response type only json supported currently, rest defaults to plain",
"name": "type",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object",
"$ref": "#/definitions/api.RandomQuoteJSON"
}
}
}
}
},
"/channelid/{channelid}/userid/{userid}/{year}/{month}": {
"get": {
"produces": [
"application/json",
"text/plain"
],
"tags": [
"user"
],
"summary": "Get logs for user by year and month",
"parameters": [
{
"type": "string",
"description": "twitch userid",
"name": "channelid",
"in": "path",
"required": true
},
{
"type": "string",
"description": "twitch userid",
"name": "userid",
"in": "path",
"required": true
},
{
"type": "string",
"description": "year of logs",
"name": "year",
"in": "path",
"required": true
},
{
"type": "string",
"description": "month of logs",
"name": "month",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "unix timestamp, limit logs by timestamps from this point",
"name": "from",
"in": "query"
},
{
"type": "integer",
"description": "unix timestamp, limit logs by timestamps to this point",
"name": "to",
"in": "query"
},
{
"type": "any",
"description": "response as json",
"name": "json",
"in": "query"
},
{
"type": "string",
"description": "define response type only json supported currently, rest defaults to plain",
"name": "type",
"in": "query"
}
],
"responses": {
"200": {},
"500": {}
}
}
},
"/channels": {
"get": {
"produces": [
"application/json"
],
"tags": [
"bot"
],
"summary": "Get all joined channels",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object",
"$ref": "#/definitions/api.RandomQuoteJSON"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "object",
"$ref": "#/definitions/api.ErrorResponse"
}
}
}
}
}
},
"definitions": {
"api.ErrorResponse": {
"type": "object",
"properties": {
"message": {
"type": "string"
}
}
},
"api.RandomQuoteJSON": {
"type": "object",
"properties": {
"channel": {
"type": "string"
},
"displayName": {
"type": "string"
},
"message": {
"type": "string"
},
"timestamp": {
"type": "object",
"$ref": "#/definitions/api.timestamp"
},
"username": {
"type": "string"
}
}
},
"api.timestamp": {
"type": "object"
}
}
}

View file

@ -1,302 +0,0 @@
basePath: /
definitions:
api.ErrorResponse:
properties:
message:
type: string
type: object
api.RandomQuoteJSON:
properties:
channel:
type: string
displayName:
type: string
message:
type: string
timestamp:
$ref: '#/definitions/api.timestamp'
type: object
username:
type: string
type: object
api.timestamp:
type: object
host: api.gempir.com
info:
contact:
email: gempir.dev@gmail.com
name: gempir
url: https://gempir.com
description: API for twitch logs
license:
name: MIT
url: https://github.com/gempir/justlog/blob/master/LICENSE
title: justlog API
version: "1.0"
paths:
/channel/{channel}/user/{username}:
get:
parameters:
- description: channelname
in: path
name: channel
required: true
type: string
- description: username
in: path
name: username
required: true
type: string
- description: unix timestamp, limit logs by timestamps from this point
in: query
name: from
type: integer
- description: unix timestamp, limit logs by timestamps to this point
in: query
name: to
type: integer
- description: response as json
in: query
name: json
type: any
- description: define response type only json supported currently, rest defaults
to plain
in: query
name: type
type: string
produces:
- application/json
- text/plain
responses:
"200": {}
"303": {}
"404": {}
"500": {}
summary: Redirect to last logs of user
tags:
- user
/channel/{channel}/user/{username}/{year}/{month}:
get:
parameters:
- description: channelname
in: path
name: channel
required: true
type: string
- description: username
in: path
name: username
required: true
type: string
- description: year of logs
in: path
name: year
required: true
type: string
- description: month of logs
in: path
name: month
required: true
type: string
- description: unix timestamp, limit logs by timestamps from this point
in: query
name: from
type: integer
- description: unix timestamp, limit logs by timestamps to this point
in: query
name: to
type: integer
- description: response as json
in: query
name: json
type: any
- description: define response type only json supported currently, rest defaults
to plain
in: query
name: type
type: string
produces:
- application/json
- text/plain
responses:
"200": {}
"500": {}
summary: Get logs for user by year and month
tags:
- user
/channel/{channel}/user/{username}/random:
get:
parameters:
- description: channelname
in: path
name: channel
required: true
type: string
- description: username
in: path
name: username
required: true
type: string
- description: response as json
in: query
name: json
type: any
- description: define response type only json supported currently, rest defaults
to plain
in: query
name: type
type: string
produces:
- application/json
- text/plain
responses:
"200":
description: OK
schema:
$ref: '#/definitions/api.RandomQuoteJSON'
type: object
summary: Get a random chat message from a user
tags:
- user
/channelid/{channelid}/user/{userid}:
get:
parameters:
- description: twitch userid
in: path
name: channelid
required: true
type: string
- description: twitch userid
in: path
name: userid
required: true
type: string
- description: unix timestamp, limit logs by timestamps from this point
in: query
name: from
type: integer
- description: unix timestamp, limit logs by timestamps to this point
in: query
name: to
type: integer
- description: response as json
in: query
name: json
type: any
- description: define response type only json supported currently, rest defaults
to plain
in: query
name: type
type: string
produces:
- application/json
- text/plain
responses:
"200": {}
"303": {}
"404": {}
summary: Redirect to last logs of user
tags:
- user
/channelid/{channelid}/userid/{userid}/{year}/{month}:
get:
parameters:
- description: twitch userid
in: path
name: channelid
required: true
type: string
- description: twitch userid
in: path
name: userid
required: true
type: string
- description: year of logs
in: path
name: year
required: true
type: string
- description: month of logs
in: path
name: month
required: true
type: string
- description: unix timestamp, limit logs by timestamps from this point
in: query
name: from
type: integer
- description: unix timestamp, limit logs by timestamps to this point
in: query
name: to
type: integer
- description: response as json
in: query
name: json
type: any
- description: define response type only json supported currently, rest defaults
to plain
in: query
name: type
type: string
produces:
- application/json
- text/plain
responses:
"200": {}
"500": {}
summary: Get logs for user by year and month
tags:
- user
/channelid/{channelid}/userid/{userid}/random:
get:
parameters:
- description: twitch userid
in: path
name: channelid
required: true
type: string
- description: twitch userid
in: path
name: userid
required: true
type: string
- description: response as json
in: query
name: json
type: any
- description: define response type only json supported currently, rest defaults
to plain
in: query
name: type
type: string
produces:
- application/json
- text/plain
responses:
"200":
description: OK
schema:
$ref: '#/definitions/api.RandomQuoteJSON'
type: object
summary: Get a random chat message from a user
tags:
- user
/channels:
get:
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/api.RandomQuoteJSON'
type: object
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/api.ErrorResponse'
type: object
summary: Get all joined channels
tags:
- bot
swagger: "2.0"

View file

@ -148,12 +148,13 @@ func (l *Logger) GetLastLogYearAndMonthForUser(channelID, userID string) (int, i
return 0, 0, errors.New("No logs file")
}
func (l *Logger) ReadLogForUser(channelID, userID string, year int, month int) ([]string, error) {
// ReadLogForUser fetch logs
func (l *Logger) ReadLogForUser(channelID, userID string, year string, month string) ([]string, error) {
if channelID == "" || userID == "" {
return []string{}, fmt.Errorf("Invalid channelID: %s or userID: %s", channelID, userID)
}
filename := fmt.Sprintf(l.logPath+"/%s/%d/%d/%s.txt", channelID, year, month, userID)
filename := fmt.Sprintf(l.logPath+"/%s/%s/%s/%s.txt", channelID, year, month, userID)
if _, err := os.Stat(filename); err != nil {
filename = filename + ".gz"

18
go.mod
View file

@ -3,20 +3,12 @@ module github.com/gempir/justlog
go 1.12
require (
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/gempir/go-twitch-irc/v2 v2.2.0
github.com/go-openapi/spec v0.19.6 // indirect
github.com/go-openapi/swag v0.19.7 // indirect
github.com/json-iterator/go v1.1.7
github.com/labstack/echo/v4 v4.0.0
github.com/mailru/easyjson v0.7.0 // indirect
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect
github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd
github.com/kr/pretty v0.1.0 // indirect
github.com/nicklaw5/helix v0.5.7
github.com/sirupsen/logrus v1.4.2
github.com/swaggo/echo-swagger v0.0.0-20190329130007-1219b460a043
github.com/swaggo/swag v1.6.5
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 // indirect
golang.org/x/tools v0.0.0-20200211045251-2de505fc5306 // indirect
github.com/stretchr/testify v1.4.0 // indirect
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/yaml.v2 v2.2.8 // indirect
)

189
go.sum
View file

@ -1,217 +1,34 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/PuerkitoBio/purell v1.1.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVkjK4=
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/gempir/go-twitch-irc/v2 v2.2.0 h1:9iYRr/PkT5tqnD9J0awBXtwS4R4DatA5cMQbsua6OvM=
github.com/gempir/go-twitch-irc/v2 v2.2.0/go.mod h1:0HXoEr9l7gNjwajosptV0w0xGpHeU6gsD7JDlfvjTYI=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w=
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y=
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonpointer v0.18.0 h1:KVRzjXpMzgdM4GEMDmDTnGcY5yBwGWreJwmmk4k35yU=
github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/jsonreference v0.19.0 h1:BqWKpV1dFd+AuiKlgtddwVIFQsuMpxfBDBHGfM2yNpk=
github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o=
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/spec v0.19.0 h1:A4SZ6IWh3lnjH0rG0Z5lkxazMGBECtrZcbyYQi+64k4=
github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo=
github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
github.com/go-openapi/spec v0.19.5 h1:Xm0Ao53uqnk9QE/LlYV5DEU09UAgpliA85QoT9LzqPw=
github.com/go-openapi/spec v0.19.5/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk=
github.com/go-openapi/spec v0.19.6 h1:rMMMj8cV38KVXK7SFc+I2MWClbEfbK705+j+dyqun5g=
github.com/go-openapi/spec v0.19.6/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk=
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/swag v0.18.0 h1:1DU8Km1MRGv9Pj7BNLmkA+umwTStwDHttXvx3NhJA70=
github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.6 h1:JSUbVWlaTLMhXeOMyArSUNCdroxZu2j1TcrsOV8Mj7Q=
github.com/go-openapi/swag v0.19.6/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY=
github.com/go-openapi/swag v0.19.7 h1:VRuXN2EnMSsZdauzdss6JBC29YotDqG59BZ+tdlIL1s=
github.com/go-openapi/swag v0.19.7/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/labstack/echo/v4 v4.0.0 h1:q1GH+caIXPP7H2StPIdzy/ez9CO0EepqYeUg6vi9SWM=
github.com/labstack/echo/v4 v4.0.0/go.mod h1:tZv7nai5buKSg5h/8E6zz4LsD/Dqh9/91Mvs7Z5Zyno=
github.com/labstack/gommon v0.2.8 h1:JvRqmeZcfrHC5u6uVleB4NxxNbzx6gpbJiQknDbKQu0=
github.com/labstack/gommon v0.2.8/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM=
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.0 h1:v2XXALHHh6zHfYTJ+cSkwtyffnaOyR1MXaA91mTrb8o=
github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/nicklaw5/helix v0.5.7 h1:DvNyoKkuLYrqZv5/yugL18Ud99UeQoXzzAsg4OwU8uY=
github.com/nicklaw5/helix v0.5.7/go.mod h1:nRcok4VLg8ONQYW/iXBZ24wcfiJjTlDbhgk0ZatOrUY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 h1:bUGsEnyNbVPw06Bs80sCeARAlK8lhwqGyi6UT8ymuGk=
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd h1:ug7PpSOB5RBPK1Kg6qskGBoP3Vnj/aNYFTznWvlkGo0=
github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/swaggo/echo-swagger v0.0.0-20190329130007-1219b460a043 h1:OAfyh6btUpExwOwroWsVem3XMlQrxONtQUMtglckrPo=
github.com/swaggo/echo-swagger v0.0.0-20190329130007-1219b460a043/go.mod h1:XxhCMHL5pDVR8YSHWhc4duJmH4T1eV5CYD5IaYPxFzg=
github.com/swaggo/files v0.0.0-20190110041405-30649e0721f8 h1:ENF9W2s6+pqe/CmdQTQFPuzSdCB91LQ3WWzdMWucs7c=
github.com/swaggo/files v0.0.0-20190110041405-30649e0721f8/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E=
github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14 h1:PyYN9JH5jY9j6av01SpfRMb+1DWg/i3MbGOKPxJ2wjM=
github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E=
github.com/swaggo/gin-swagger v1.0.0 h1:k6Nn1jV49u+SNIWt7kejQS/iENZKZVMCNQrKOYatNF8=
github.com/swaggo/gin-swagger v1.0.0/go.mod h1:Mt37wE46iUaTAOv+HSnHbJYssKGqbS25X19lNF4YpBo=
github.com/swaggo/gin-swagger v1.2.0/go.mod h1:qlH2+W7zXGZkczuL+r2nEBR2JTT+/lX05Nn6vPhc7OI=
github.com/swaggo/swag v1.4.0/go.mod h1:hog2WgeMOrQ/LvQ+o1YGTeT+vWVrbi0SiIslBtxKTyM=
github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+tv8Y=
github.com/swaggo/swag v1.6.2 h1:WQMAtT/FmMBb7g0rAuHDhG3vvdtHKJ3WZ+Ssb0p4Y6E=
github.com/swaggo/swag v1.6.2/go.mod h1:YyZstMc22WYm6GEDx/CYWxq+faBbjQ5EqwQcrjREDBo=
github.com/swaggo/swag v1.6.5 h1:2C+t+xyK6p1sujqncYO/VnMvPZcBJjNdKKyxbOdAW8o=
github.com/swaggo/swag v1.6.5/go.mod h1:Y7ZLSS0d0DdxhWGVhQdu+Bu1QhaF5k0RD7FKdiAykeY=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go v1.1.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/oBQZ/0=
github.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ugorji/go/codec v1.1.5-pre/go.mod h1:tULtS6Gy1AE1yCENaw4Vb//HLH5njI2tfCQDUqRd8fI=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4 h1:gKMu1Bf6QINDnvyZuTaACm9ofY+PRh+5vFz4oxBZeF8=
github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4/go.mod h1:50wTf68f99/Zt14pr046Tgt3Lp2vLyFZKzbFXTOabXw=
golang.org/x/crypto v0.0.0-20190130090550-b01c7a725664/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 h1:1wopBVtVdWnn03fZelqdXTqk7U7zPQCb+T4rbU9ZEoU=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa h1:F+8P+gmewFQYRk6JoLQLwjBCTu3mcIURZfNkVweuRKA=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f h1:25KHgbfyiSm6vwQLbM3zZIe1v9p/3ea4Rz+nnM5K/i4=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190205050122-7f7074d5bcfd/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468 h1:fTfk6GjmihJbK0mSUFgPPgYpsdmApQ86Mcd4GuKax9U=
golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59 h1:QjA/9ArTfVTLfEhClDCG7SGrZkZixxWpwNCDiwJfh88=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20200125223703-d33eef8e6825 h1:aNQeSIHKi0RWpKA5NO0CqyLjx6Beh5l0LLUEnndEjz0=
golang.org/x/tools v0.0.0-20200125223703-d33eef8e6825/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200211045251-2de505fc5306 h1:5gd/+xxg4X7hx+44aG6Sdh17vBwwRFacMaSfqF4wkWk=
golang.org/x/tools v0.0.0-20200211045251-2de505fc5306/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View file

@ -1,32 +1,41 @@
package helix
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
helixClient "github.com/nicklaw5/helix"
log "github.com/sirupsen/logrus"
)
// Client wrapper for helix
type Client struct {
clientID string
client *helixClient.Client
httpClient *http.Client
}
var (
userCacheByID map[string]UserData
userCacheByUsername map[string]UserData
userCacheByID map[string]*UserData
userCacheByUsername map[string]*UserData
)
func init() {
userCacheByID = map[string]UserData{}
userCacheByUsername = map[string]UserData{}
userCacheByID = map[string]*UserData{}
userCacheByUsername = map[string]*UserData{}
}
// NewClient Create helix client
func NewClient(clientID string) Client {
client, err := helixClient.NewClient(&helixClient.Options{
ClientID: clientID,
})
if err != nil {
panic(err)
}
return Client{
clientID: clientID,
client: client,
httpClient: &http.Client{},
}
}
@ -35,6 +44,7 @@ type userResponse struct {
Data []UserData `json:"data"`
}
// UserData exported data from twitch
type UserData struct {
ID string `json:"id"`
Login string `json:"login"`
@ -48,6 +58,7 @@ type UserData struct {
Email string `json:"email"`
}
// GetUsersByUserIds receive userData for given ids
func (c *Client) GetUsersByUserIds(userIDs []string) (map[string]UserData, error) {
var filteredUserIDs []string
@ -57,40 +68,44 @@ func (c *Client) GetUsersByUserIds(userIDs []string) (map[string]UserData, error
}
}
if len(filteredUserIDs) == 1 {
params := "?id=" + filteredUserIDs[0]
err := c.makeRequest(params)
if len(filteredUserIDs) > 0 {
resp, err := c.client.GetUsers(&helixClient.UsersParams{
IDs: filteredUserIDs,
})
if err != nil {
return nil, err
return map[string]UserData{}, err
}
} else if len(filteredUserIDs) > 1 {
var params string
log.Infof("%d GetUsersByUserIds %v", resp.StatusCode, filteredUserIDs)
for index, id := range filteredUserIDs {
if index == 0 {
params += "?id=" + id
} else {
params += "&id=" + id
for _, user := range resp.Data.Users {
data := &UserData{
ID: user.ID,
Login: user.Login,
DisplayName: user.Login,
Type: user.Type,
BroadcasterType: user.BroadcasterType,
Description: user.Description,
ProfileImageURL: user.ProfileImageURL,
OfflineImageURL: user.OfflineImageURL,
ViewCount: user.ViewCount,
Email: user.Email,
}
}
err := c.makeRequest(params)
if err != nil {
return nil, err
userCacheByID[user.ID] = data
userCacheByUsername[user.Login] = data
}
}
result := make(map[string]UserData)
for _, id := range userIDs {
result[id] = userCacheByID[id]
result[id] = *userCacheByID[id]
}
return result, nil
}
// GetUsersByUsernames fetches userdata from helix
func (c *Client) GetUsersByUsernames(usernames []string) (map[string]UserData, error) {
var filteredUsernames []string
@ -100,72 +115,39 @@ func (c *Client) GetUsersByUsernames(usernames []string) (map[string]UserData, e
}
}
if len(filteredUsernames) == 1 {
params := "?login=" + filteredUsernames[0]
err := c.makeRequest(params)
if len(filteredUsernames) > 0 {
resp, err := c.client.GetUsers(&helixClient.UsersParams{
Logins: filteredUsernames,
})
if err != nil {
return nil, err
return map[string]UserData{}, err
}
} else if len(filteredUsernames) > 1 {
var params string
log.Infof("%d GetUsersByUsernames %v", resp.StatusCode, filteredUsernames)
for index, id := range filteredUsernames {
if index == 0 {
params += "?login=" + id
} else {
params += "&login=" + id
for _, user := range resp.Data.Users {
data := &UserData{
ID: user.ID,
Login: user.Login,
DisplayName: user.Login,
Type: user.Type,
BroadcasterType: user.BroadcasterType,
Description: user.Description,
ProfileImageURL: user.ProfileImageURL,
OfflineImageURL: user.OfflineImageURL,
ViewCount: user.ViewCount,
Email: user.Email,
}
}
err := c.makeRequest(params)
if err != nil {
return nil, err
userCacheByID[user.ID] = data
userCacheByUsername[user.Login] = data
}
}
result := make(map[string]UserData)
for _, username := range usernames {
result[username] = userCacheByUsername[username]
result[username] = *userCacheByUsername[username]
}
return result, nil
}
func (c *Client) makeRequest(parameters string) error {
request, err := http.NewRequest("GET", "https://api.twitch.tv/helix/users"+parameters, nil)
if err != nil {
return err
}
request.Header.Set("Client-ID", c.clientID)
response, err := c.httpClient.Do(request)
if err != nil {
return err
}
if response.StatusCode >= 400 {
return fmt.Errorf("%d GET https://api.twitch.tv/helix/users%s", response.StatusCode, parameters)
}
log.Infof("%d GET https://api.twitch.tv/helix/users%s", response.StatusCode, parameters)
defer response.Body.Close()
contents, err := ioutil.ReadAll(response.Body)
if err != nil {
return err
}
var userResp userResponse
err = json.Unmarshal(contents, &userResp)
if err != nil {
return err
}
for _, user := range userResp.Data {
userCacheByID[user.ID] = user
userCacheByUsername[user.Login] = user
}
return nil
}

14
main.go
View file

@ -28,20 +28,6 @@ type config struct {
LogLevel string `json:"logLevel"`
}
// @title justlog API
// @version 1.0
// @description API for twitch logs
// @contact.name gempir
// @contact.url https://gempir.com
// @contact.email gempir.dev@gmail.com
// @license.name MIT
// @license.url https://github.com/gempir/justlog/blob/master/LICENSE
// @host logs.ivr.fi
// @BasePath /
func main() {
startTime := time.Now()

File diff suppressed because one or more lines are too long

BIN
web/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -6,7 +6,6 @@ import reducer from "./store/reducer";
import createInitialState from "./store/createInitialState";
import LogSearch from './components/LogSearch';
export default class App extends Component {
constructor(props) {

View file

@ -13,18 +13,18 @@ export default function (channel, username, year, month) {
dispatch(setLoading(true));
let channelPath = "name";
let channelPath = "channel";
if (channel.toLowerCase().startsWith("id:")) {
channelPath = "id";
}
let usernamePath = "name";
let usernamePath = "user";
if (username.toLowerCase().startsWith("id:")) {
usernamePath = "id";
}
channel = channel.replace("id:", "")
username = username.replace("id:", "")
const url = `${getState().apiBaseUrl}/v2/${channelPath}/${channel}/${usernamePath}/${username}/${year}/${month}`;
const url = `${getState().apiBaseUrl}/${channelPath}/${channel}/${usernamePath}/${username}/${year}/${month}`;
fetch(url, { headers: { "Content-Type": "application/json" } }).then((response) => {
if (response.status >= 200 && response.status < 300) {