add predb command.

predb commmand either fetches the latest or fetches specific information about releases
This commit is contained in:
lyx0 2023-12-13 22:40:48 +01:00
parent d760c0bcb7
commit d0cb43d1f3
6 changed files with 276 additions and 20 deletions

View file

@ -146,6 +146,18 @@ func (app *application) handleCommand(message twitch.PrivateMessage) {
case "nourybot": case "nourybot":
reply = "Lidl Twitch bot made by @nouryxd. Prefix: ()" reply = "Lidl Twitch bot made by @nouryxd. Prefix: ()"
case "predb":
switch cmdParams[1] {
case "latest":
if userLevel >= 100 {
reply = app.PreDBLatest()
}
case "search":
if userLevel >= 100 && len(message.Message) > 16 {
reply = app.PreDBSearch(message.Message[15:len(message.Message)])
}
}
case "phonetic": case "phonetic":
if msgLen == 1 { if msgLen == 1 {
reply = "Not enough arguments provided. Usage: ()phonetic <text to translate>" reply = "Not enough arguments provided. Usage: ()phonetic <text to translate>"

View file

@ -9,5 +9,6 @@ var (
ErrCommandLevelNotInteger = errors.New("command level must be a number") ErrCommandLevelNotInteger = errors.New("command level must be a number")
ErrRecordNotFound = errors.New("user not found in the database") ErrRecordNotFound = errors.New("user not found in the database")
ErrUserInsufficientLevel = errors.New("user has insufficient level") ErrUserInsufficientLevel = errors.New("user has insufficient level")
ErrInternalServerError = errors.New("internal server error")
ErrDuringPasteUpload = errors.New("could not upload paste") ErrDuringPasteUpload = errors.New("could not upload paste")
) )

166
cmd/nourybot/predb.go Normal file
View file

@ -0,0 +1,166 @@
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
)
type latestFullResult struct {
Status string `json:"status"`
Results int `json:"results"`
Data []latestSingleResult `json:"data"`
}
type latestSingleResult struct {
ID string `json:"id"`
PreTime string `json:"pretime"`
Release string `json:"release"`
Section string `json:"section"`
Files string `json:"files"`
Size float64 `json:"size"`
Status string `json:"status"`
Reason string `json:"reason"`
Group string `json:"group"`
Genre string `json:"genre"`
URL string `json:"url"`
NFO string `json:"nfo"`
NFOImage string `json:"nfo_image"`
}
func (app *application) PreDBLatest() string {
baseUrl := "https://api.predb.net/?limit=100"
resp, err := http.Get(baseUrl)
if err != nil {
app.Log.Error(err)
}
app.Log.Info(resp)
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
app.Log.Error(err)
}
app.Log.Info(body)
var release latestFullResult
_ = json.Unmarshal([]byte(body), &release)
var ts []string
app.Log.Info(release)
for i := 0; i < release.Results; i++ {
t := fmt.Sprintf("ID: %v\nRelease timestamp: %v\nRelease: %v\nSection: %v\nFiles: %v\nSize: %v\nStatus: %v\nReason: %v\nRelease group: %v\nRelease genre: %v\npredb.net: %v\nNFO URL: %v\nNFO Image URL: %v\n\n",
release.Data[i].ID,
release.Data[i].PreTime,
release.Data[i].Release,
release.Data[i].Section,
release.Data[i].Files,
release.Data[i].Size,
release.Data[i].Status,
release.Data[i].Reason,
release.Data[i].Group,
release.Data[i].Genre,
fmt.Sprint("https://predb.net"+release.Data[i].URL),
release.Data[i].NFO,
release.Data[i].NFOImage,
)
ts = append(ts, t)
}
// reply, err := app.uploadPaste(strings.Join(ts, ""))
// if err != nil {
// app.Log.Errorw("Error trying to retrieve all timers from database", err)
// return ""
// }
reply := app.YafUploadString(strings.Join(ts, ""))
if err != nil {
app.Log.Errorw("Error trying to retrieve all timers from database", err)
return ""
}
return reply
}
type searchFullResult struct {
Status string `json:"status"`
Results int `json:"results"`
Data []searchSingleResult `json:"data"`
}
type searchSingleResult struct {
ID int `json:"id"`
PreTime int `json:"pretime"`
Release string `json:"release"`
Section string `json:"section"`
Files int `json:"files"`
Size float64 `json:"size"`
Status int `json:"status"`
Reason string `json:"reason"`
Group string `json:"group"`
Genre string `json:"genre"`
URL string `json:"url"`
NFO string `json:"nfo"`
NFOImage string `json:"nfo_image"`
}
func (app *application) PreDBSearch(title string) string {
baseUrl := fmt.Sprintf("https://api.predb.net/?q=%s&order_by=release&sort=asc&limit=100", title)
app.Log.Info(title)
resp, err := http.Get(baseUrl)
if err != nil {
app.Log.Error(err)
}
app.Log.Info(resp)
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
app.Log.Error(err)
}
app.Log.Info(body)
var release searchFullResult
_ = json.Unmarshal([]byte(body), &release)
var ts []string
app.Log.Info(release)
for i := 0; i < release.Results; i++ {
t := fmt.Sprintf("ID: %v\nRelease timestamp: %v\nRelease: %v\nSection: %v\nFiles: %v\nSize: %v\nStatus: %v\nReason: %v\nRelease group: %v\nRelease genre: %v\npredb.net: %v\nNFO URL: %v\nNFO Image URL: %v\n\n",
release.Data[i].ID,
release.Data[i].PreTime,
release.Data[i].Release,
release.Data[i].Section,
release.Data[i].Files,
release.Data[i].Size,
release.Data[i].Status,
release.Data[i].Reason,
release.Data[i].Group,
release.Data[i].Genre,
fmt.Sprint("https://predb.net"+release.Data[i].URL),
release.Data[i].NFO,
release.Data[i].NFOImage,
)
ts = append(ts, t)
}
// reply, err := app.uploadPaste(strings.Join(ts, ""))
// if err != nil {
// app.Log.Errorw("Error trying to retrieve all timers from database", err)
// return ""
// }
reply := app.YafUploadString(strings.Join(ts, ""))
if err != nil {
app.Log.Errorw("Error trying to retrieve all timers from database", err)
return ""
}
return reply
return reply
}

View file

@ -9,10 +9,10 @@
package main package main
import ( import (
"bufio"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"mime/multipart" "mime/multipart"
"net/http" "net/http"
"os" "os"
@ -40,7 +40,6 @@ func (app *application) NewUpload(destination, fileName, target, identifier stri
go app.KappaUpload(target, fileName, identifier, msg) go app.KappaUpload(target, fileName, identifier, msg)
case "gofile": case "gofile":
go app.GofileUpload(target, fileName, identifier, msg) go app.GofileUpload(target, fileName, identifier, msg)
} }
} }
@ -100,7 +99,7 @@ func (app *application) CatboxUpload(target, fileName, identifier string, msg tw
} }
defer resp.Body.Close() defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
app.Send(target, fmt.Sprintf("Something went wrong FeelsBadMan: %q", err), msg) app.Send(target, fmt.Sprintf("Something went wrong FeelsBadMan: %q", err), msg)
return return
@ -366,3 +365,97 @@ func (app *application) YafUpload(target, path, identifier string, msg twitch.Pr
//app.Send(target, fmt.Sprintf("Removing file: %s", path), msg) //app.Send(target, fmt.Sprintf("Removing file: %s", path), msg)
app.Send(target, reply, msg) app.Send(target, reply, msg)
} }
func (app *application) YafUploadString(text string) string {
path := "output.txt"
file, err := os.Create(path)
if err != nil {
app.Log.Error("Error creating file:", err)
os.Remove(path)
return ""
}
defer file.Close()
defer os.Remove(path)
// Create a buffered writer to efficiently write to the file
writer := bufio.NewWriter(file)
// Write the content (string) to the file
_, err = writer.WriteString(text)
if err != nil {
app.Log.Error("Error writing to file:", err)
os.Remove(path)
return ""
}
// Flush the writer to ensure all buffered operations have been applied to the file
err = writer.Flush()
if err != nil {
app.Log.Error("Error flushing writer:", err)
os.Remove(path)
return ""
}
pr, pw := io.Pipe()
form := multipart.NewWriter(pw)
go func() {
defer pw.Close()
err := form.WriteField("name", "xd")
if err != nil {
os.Remove(path)
return
}
file, err := os.Open(path) // path to image file
if err != nil {
os.Remove(path)
return
}
w, err := form.CreateFormFile("file", path)
if err != nil {
os.Remove(path)
return
}
_, err = io.Copy(w, file)
if err != nil {
os.Remove(path)
return
}
form.Close()
}()
req, err := http.NewRequest(http.MethodPost, YAF_ENDPOINT, pr)
if err != nil {
os.Remove(path)
return ""
}
req.Header.Set("Content-Type", form.FormDataContentType())
httpClient := http.Client{
Timeout: 300 * time.Second,
}
resp, err := httpClient.Do(req)
if err != nil {
os.Remove(path)
app.Log.Errorln("Error while sending HTTP request:", err)
return ""
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
os.Remove(path)
app.Log.Errorln("Error while reading response:", err)
return ""
}
var reply = string(body[:])
//app.Send(target, fmt.Sprintf("Removing file: %s", path), msg)
return reply
}

5
go.mod
View file

@ -8,7 +8,6 @@ require (
github.com/jakecoffman/cron v0.0.0-20190106200828-7e2009c226a5 github.com/jakecoffman/cron v0.0.0-20190106200828-7e2009c226a5
github.com/lib/pq v1.10.9 github.com/lib/pq v1.10.9
github.com/nicklaw5/helix/v2 v2.25.1 github.com/nicklaw5/helix/v2 v2.25.1
github.com/rs/zerolog v1.29.1
github.com/shkh/lastfm-go v0.0.0-20191215035245-89a801c244e0 github.com/shkh/lastfm-go v0.0.0-20191215035245-89a801c244e0
github.com/wader/goutubedl v0.0.0-20230924165737-427b7fa536e6 github.com/wader/goutubedl v0.0.0-20230924165737-427b7fa536e6
go.uber.org/zap v1.24.0 go.uber.org/zap v1.24.0
@ -17,6 +16,7 @@ require (
require ( require (
github.com/golang-jwt/jwt/v4 v4.0.0 // indirect github.com/golang-jwt/jwt/v4 v4.0.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
go.uber.org/atomic v1.7.0 // indirect go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect go.uber.org/multierr v1.6.0 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
@ -26,7 +26,4 @@ require (
github.com/briandowns/openweathermap v0.19.0 github.com/briandowns/openweathermap v0.19.0
github.com/dustin/go-humanize v1.0.1 github.com/dustin/go-humanize v1.0.1
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
golang.org/x/sys v0.11.0 // indirect
) )

13
go.sum
View file

@ -1,7 +1,6 @@
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/briandowns/openweathermap v0.19.0 h1:nkopLMEtZLxbZI1th6dOG6xkajpszofqf53r5K8mT9k= github.com/briandowns/openweathermap v0.19.0 h1:nkopLMEtZLxbZI1th6dOG6xkajpszofqf53r5K8mT9k=
github.com/briandowns/openweathermap v0.19.0/go.mod h1:0GLnknqicWxXnGi1IqoOaZIw+kIe5hkt+YM5WY3j8+0= github.com/briandowns/openweathermap v0.19.0/go.mod h1:0GLnknqicWxXnGi1IqoOaZIw+kIe5hkt+YM5WY3j8+0=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -11,7 +10,6 @@ github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/gempir/go-twitch-irc/v4 v4.0.0 h1:sHVIvbWOv9nHXGEErilclxASv0AaQEr/r/f9C0B9aO8= github.com/gempir/go-twitch-irc/v4 v4.0.0 h1:sHVIvbWOv9nHXGEErilclxASv0AaQEr/r/f9C0B9aO8=
github.com/gempir/go-twitch-irc/v4 v4.0.0/go.mod h1:QsOMMAk470uxQ7EYD9GJBGAVqM/jDrXBNbuePfTauzg= github.com/gempir/go-twitch-irc/v4 v4.0.0/go.mod h1:QsOMMAk470uxQ7EYD9GJBGAVqM/jDrXBNbuePfTauzg=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang-jwt/jwt/v4 v4.0.0 h1:RAqyYixv1p7uEnocuy8P1nru5wprCh/MH2BIlW5z5/o= github.com/golang-jwt/jwt/v4 v4.0.0 h1:RAqyYixv1p7uEnocuy8P1nru5wprCh/MH2BIlW5z5/o=
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
@ -22,19 +20,12 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/nicklaw5/helix/v2 v2.25.1 h1:hccFfWf1kdPKeC/Zp8jNbOvqV0f6ya12hdeNHuQa5wg= github.com/nicklaw5/helix/v2 v2.25.1 h1:hccFfWf1kdPKeC/Zp8jNbOvqV0f6ya12hdeNHuQa5wg=
github.com/nicklaw5/helix/v2 v2.25.1/go.mod h1:zZcKsyyBWDli34x3QleYsVMiiNGMXPAEU5NjsiZDtvY= github.com/nicklaw5/helix/v2 v2.25.1/go.mod h1:zZcKsyyBWDli34x3QleYsVMiiNGMXPAEU5NjsiZDtvY=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc=
github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU=
github.com/shkh/lastfm-go v0.0.0-20191215035245-89a801c244e0 h1:cgqwZtnR+IQfUYDLJ3Kiy4aE+O/wExTzEIg8xwC4Qfs= github.com/shkh/lastfm-go v0.0.0-20191215035245-89a801c244e0 h1:cgqwZtnR+IQfUYDLJ3Kiy4aE+O/wExTzEIg8xwC4Qfs=
github.com/shkh/lastfm-go v0.0.0-20191215035245-89a801c244e0/go.mod h1:n3nudMl178cEvD44PaopxH9jhJaQzthSxUzLO5iKMy4= github.com/shkh/lastfm-go v0.0.0-20191215035245-89a801c244e0/go.mod h1:n3nudMl178cEvD44PaopxH9jhJaQzthSxUzLO5iKMy4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@ -51,10 +42,6 @@ go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=