diff --git a/.gitignore b/.gitignore index 092a929..4006f99 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ huso nuts/* +mm.cred diff --git a/build.sh b/build.sh index a250805..8fc9e19 100755 --- a/build.sh +++ b/build.sh @@ -1,2 +1,3 @@ +#!/bin/bash date=$(date '+%Y-%m-%dT%H:%M:%S') -go build -ldflags "-X main.buildTime=$date" \ No newline at end of file +go build -ldflags "-X main.buildTime=$date" diff --git a/go.mod b/go.mod index 633ec92..e0a9048 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.18 require ( github.com/allegro/bigcache/v3 v3.0.2 github.com/fasthttp/router v1.4.10 + github.com/go-sql-driver/mysql v1.6.0 github.com/gookit/color v1.5.1 github.com/valyala/fasthttp v1.38.0 github.com/valyala/quicktemplate v1.7.0 diff --git a/go.sum b/go.sum index 66ddff6..4f14043 100644 --- a/go.sum +++ b/go.sum @@ -11,6 +11,8 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fasthttp/router v1.4.10 h1:C8z6K1pTqhLjSv97/qCY9tZiiPT8JuFwDoO9E2HJFWQ= github.com/fasthttp/router v1.4.10/go.mod h1:FGSUOg9SQ/tU864SfD23kG/HwfD0akXqOqhTQ27gTFQ= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gookit/color v1.5.1 h1:Vjg2VEcdHpwq+oY63s/ksHrgJYCTo0bwWvmmYWdE9fQ= diff --git a/huso.go b/huso.go index 4b511e6..f7ed983 100644 --- a/huso.go +++ b/huso.go @@ -1,6 +1,7 @@ package main import ( + "database/sql" "flag" "fmt" "log" @@ -11,6 +12,7 @@ import ( "time" "github.com/allegro/bigcache/v3" + _ "github.com/go-sql-driver/mysql" "github.com/gookit/color" "github.com/xujiajun/nutsdb" "golang.org/x/time/rate" @@ -45,12 +47,16 @@ var ( jikanApiBaseUri = flag.String("jikanApiBaseUri", "https://api.jikan.moe/v4/", "Jikan API base URL") malApiId = flag.String("malApiId", "cc17dcf40581b9dfc8a5a12dba458153", "MyAnimeList API Client ID") localServer = flag.Bool("localServer", false, "Set varius headers for running locally") + mmDbServer = flag.String("mmDbServer", "hanami.family:3306", "MovieManager db server") + mmDbUser = flag.String("mmDbUser", "yui", "MovieManager db user") animeCache *bigcache.BigCache seasoncache *bigcache.BigCache userCache *bigcache.BigCache searchCache *bigcache.BigCache animeListCache *bigcache.BigCache + mmCache *bigcache.BigCache db *nutsdb.DB + mmDb *sql.DB jikanLimiter *rate.Limiter logOut *RingBuf buildTime string @@ -66,7 +72,7 @@ func main() { logOut = NewRingBuf(10101) color.Notice.Printf("huso %s built on %s with %s\n", husoVersion, buildTime, runtime.Version()) - logOut.WriteLine(fmt.Sprintf("🎉 huso %s built on %s with %s\n", husoVersion, buildTime, runtime.Version())) + logOut.WriteLine(fmt.Sprintf("🎉 huso %s built on %s with %s", husoVersion, buildTime, runtime.Version())) jikanLimiter = rate.NewLimiter(rate.Every(time.Second), 1) @@ -97,6 +103,11 @@ func main() { log.Fatal(err) } defer animeListCache.Close() + mmCache, err = bigcache.NewBigCache(bigcache.DefaultConfig(time.Minute)) + if err != nil { + log.Fatal(err) + } + defer mmCache.Close() nutsOpt := nutsdb.DefaultOptions nutsOpt.Dir = "nuts" @@ -106,6 +117,32 @@ func main() { } defer db.Close() + color.Infoln("NutsDB connected") + logOut.WriteLine("🗃️ NutsDB connected") + + conns, err := GetMmConnString() + if err != nil { + color.Errorln(err.Error()) + } else { + mmDb, err = sql.Open("mysql", conns) + if err != nil || mmDb == nil { + color.Errorln(err.Error()) + } else { + mmDb.SetConnMaxLifetime(time.Minute * 3) + mmDb.SetMaxOpenConns(10) + mmDb.SetMaxIdleConns(10) + defer mmDb.Close() + + err = mmDb.Ping() + if err != nil { + color.Errorln(err.Error()) + } else { + color.Infoln("MovieManager DB connected") + logOut.WriteLine("🗃️ MovieManager DB connected") + } + } + } + Arbeit() LangeArbeit() diff --git a/klotz.go b/klotz.go index 67bd59b..272dcb6 100644 --- a/klotz.go +++ b/klotz.go @@ -310,3 +310,10 @@ type SeasonJikan struct { } `json:"pagination"` Data []SeasonAnimeJikan `json:"data"` } + +type MmOracle struct { + Id int `json:"id"` + Title string `json:"title"` + Anime int64 `json:"anime"` + AvgScore int `json:"avgScore"` +} diff --git a/ober.go b/ober.go index 5767378..39679d9 100644 --- a/ober.go +++ b/ober.go @@ -44,7 +44,13 @@ func Start(ctx *fasthttp.RequestCtx) { return } - WriteIndex(ctx, season, logOut.String()) + oracles, err := MmReadOracle() + if err != nil { + addErrorToCtx(ctx, err) + return + } + + WriteIndex(ctx, season, oracles, logOut.String()) ctx.SetContentType("text/html; charset=utf-8") ctx.SetStatusCode(fasthttp.StatusOK) diff --git a/rechner.go b/rechner.go index ee2a7bb..e68558f 100644 --- a/rechner.go +++ b/rechner.go @@ -43,3 +43,7 @@ func BytesToInt64AndDate(bytes []byte) (int64, time.Time, error) { func Int64AndDateToBytes(num int64, appoint time.Time) []byte { return []byte(fmt.Sprintf("%d%s%s", num, AppointSplit, appoint.Format(time.RFC3339))) } + +func PrintDate(t time.Time) string { + return t.Format("2006-01-02") +} diff --git a/season.qtpl b/season.qtpl index b1df9a6..e649b8b 100644 --- a/season.qtpl +++ b/season.qtpl @@ -1,5 +1,5 @@ {% package main %} -{% func Index(animes []Anime, log string) %} +{% func Index(animes []Anime, oracles []MmOracle, log string) %} {% collapsespace %} @@ -19,6 +19,8 @@ body { background-color: #1a1a1a; color: #fff; }

HUSO - Hanami universeller Serien Organizer

+

Log

+
{%s log %}

Anime

@@ -30,16 +32,24 @@ body { background-color: #1a1a1a; color: #fff; }
{% for _, anime := range animes %} + - + + + {% endfor %} +
{%dl anime.Anime %} {%s anime.Title %}{%dl anime.Anime %} {%d anime.Episodes %}{%f anime.Score %}
+

MovieManager Oracle

+ + {% for _, oracle := range oracles %} + + + + + {% endfor %}
{%d oracle.Id %}{%d oracle.AvgScore %}{%s oracle.Title %}{%dl oracle.Anime %}
-

Log

-
-        {%s log %}
-        
{% endcollapsespace %} diff --git a/season.qtpl.go b/season.qtpl.go index ae612ca..50600b4 100644 --- a/season.qtpl.go +++ b/season.qtpl.go @@ -18,68 +18,104 @@ var ( ) //line season.qtpl:2 -func StreamIndex(qw422016 *qt422016.Writer, animes []Anime, log string) { +func StreamIndex(qw422016 *qt422016.Writer, animes []Anime, oracles []MmOracle, log string) { //line season.qtpl:2 qw422016.N().S(` `) //line season.qtpl:3 - qw422016.N().S(` HUSO - Hanami universeller Serien Organizer

HUSO - Hanami universeller Serien Organizer

Anime

Airing 📺 `) -//line season.qtpl:26 - qw422016.N().D(len(animes)) -//line season.qtpl:26 - qw422016.N().S(`

`) -//line season.qtpl:31 - for _, anime := range animes { -//line season.qtpl:31 - qw422016.N().S(` `) -//line season.qtpl:37 - } -//line season.qtpl:37 - qw422016.N().S(`
`) -//line season.qtpl:33 - qw422016.E().S(anime.Title) -//line season.qtpl:33 - qw422016.N().S(` `) -//line season.qtpl:34 - qw422016.N().DL(anime.Anime) -//line season.qtpl:34 - qw422016.N().S(` `) -//line season.qtpl:35 - qw422016.N().D(anime.Episodes) -//line season.qtpl:35 - qw422016.N().S(`

Log

 `)
-//line season.qtpl:41
+	qw422016.N().S(`        HUSO - Hanami universeller Serien Organizer   

HUSO - Hanami universeller Serien Organizer

Log

`)
+//line season.qtpl:23
 	qw422016.E().S(log)
-//line season.qtpl:41
-	qw422016.N().S(` 
`) -//line season.qtpl:45 +//line season.qtpl:23 + qw422016.N().S(`

Anime

Airing 📺 `) +//line season.qtpl:28 + qw422016.N().D(len(animes)) +//line season.qtpl:28 + qw422016.N().S(`

`) +//line season.qtpl:33 + for _, anime := range animes { +//line season.qtpl:33 + qw422016.N().S(` `) +//line season.qtpl:40 + } +//line season.qtpl:40 + qw422016.N().S(`
`) +//line season.qtpl:35 + qw422016.N().DL(anime.Anime) +//line season.qtpl:35 + qw422016.N().S(` `) +//line season.qtpl:36 + qw422016.E().S(anime.Title) +//line season.qtpl:36 + qw422016.N().S(` `) +//line season.qtpl:37 + qw422016.N().D(anime.Episodes) +//line season.qtpl:37 + qw422016.N().S(` `) +//line season.qtpl:38 + qw422016.N().F(anime.Score) +//line season.qtpl:38 + qw422016.N().S(`

MovieManager Oracle

`) +//line season.qtpl:44 + for _, oracle := range oracles { +//line season.qtpl:44 + qw422016.N().S(` `) +//line season.qtpl:51 + } +//line season.qtpl:51 + qw422016.N().S(`
`) +//line season.qtpl:46 + qw422016.N().D(oracle.Id) +//line season.qtpl:46 + qw422016.N().S(` `) +//line season.qtpl:47 + qw422016.N().D(oracle.AvgScore) +//line season.qtpl:47 + qw422016.N().S(` `) +//line season.qtpl:48 + qw422016.E().S(oracle.Title) +//line season.qtpl:48 + qw422016.N().S(` `) +//line season.qtpl:49 + qw422016.N().DL(oracle.Anime) +//line season.qtpl:49 + qw422016.N().S(`
`) +//line season.qtpl:55 qw422016.N().S(` `) -//line season.qtpl:46 +//line season.qtpl:56 } -//line season.qtpl:46 -func WriteIndex(qq422016 qtio422016.Writer, animes []Anime, log string) { -//line season.qtpl:46 +//line season.qtpl:56 +func WriteIndex(qq422016 qtio422016.Writer, animes []Anime, oracles []MmOracle, log string) { +//line season.qtpl:56 qw422016 := qt422016.AcquireWriter(qq422016) -//line season.qtpl:46 - StreamIndex(qw422016, animes, log) -//line season.qtpl:46 +//line season.qtpl:56 + StreamIndex(qw422016, animes, oracles, log) +//line season.qtpl:56 qt422016.ReleaseWriter(qw422016) -//line season.qtpl:46 +//line season.qtpl:56 } -//line season.qtpl:46 -func Index(animes []Anime, log string) string { -//line season.qtpl:46 +//line season.qtpl:56 +func Index(animes []Anime, oracles []MmOracle, log string) string { +//line season.qtpl:56 qb422016 := qt422016.AcquireByteBuffer() -//line season.qtpl:46 - WriteIndex(qb422016, animes, log) -//line season.qtpl:46 +//line season.qtpl:56 + WriteIndex(qb422016, animes, oracles, log) +//line season.qtpl:56 qs422016 := string(qb422016.B) -//line season.qtpl:46 +//line season.qtpl:56 qt422016.ReleaseByteBuffer(qb422016) -//line season.qtpl:46 +//line season.qtpl:56 return qs422016 -//line season.qtpl:46 +//line season.qtpl:56 } diff --git a/template.sh b/template.sh new file mode 100755 index 0000000..0cb35fd --- /dev/null +++ b/template.sh @@ -0,0 +1,2 @@ +#!/bin/bash +~/go/bin/qtc diff --git a/zecke.go b/zecke.go new file mode 100644 index 0000000..200de8c --- /dev/null +++ b/zecke.go @@ -0,0 +1,90 @@ +package main + +import ( + "database/sql" + _ "embed" + "encoding/base64" + "encoding/json" + "fmt" + "time" +) + +//go:embed mm.cred +var mmDbCred string + +func GetMmConnString() (string, error) { + pw, err := base64.StdEncoding.DecodeString(mmDbCred) + if err != nil { + return "", err + } + return fmt.Sprintf("%s:%s@tcp(%s)/moviemanager?parseTime=true", *mmDbUser, pw, *mmDbServer), err +} + +func MmReadOracle() ([]MmOracle, error) { + key := "oracle" + var oracles []MmOracle + data, err := mmCache.Get(key) + if err == nil { + err = json.Unmarshal(data, &oracles) + if err == nil { + return oracles, err + } + } + + oracleQuery := `SELECT m.id, m.title, m.mal_id, COALESCE(SUM(pv.score), 0) AS avgScore FROM movie m + INNER JOIN vote pv ON (m.id = pv.movie_id) + INNER JOIN promise pp ON (pp.user_id = pv.user_id) + INNER JOIN evening me ON (pp.evening_id = me.id) + WHERE m.id NOT IN ( + SELECT m2.id FROM movie m2 + INNER JOIN evening_movie em2 ON (m2.id = em2.movie_id) + INNER JOIN evening e2 ON (em2.evening_id = e2.id) + WHERE e2.date < ? + ) AND ( + m.id IN ( + SELECT mm.movie_source FROM movie m3 + INNER JOIN movie_movie mm ON (m3.id = mm.movie_target) + INNER JOIN evening_movie em3 ON (mm.movie_target = em3.movie_id) + INNER JOIN evening e3 ON (em3.evening_id = e3.id) + WHERE e3.date < ? GROUP BY m3.id + ) + OR + m.id NOT IN ( + SELECT mm.movie_source FROM movie m4 + INNER JOIN movie_movie mm ON (m4.id = mm.movie_source) + ) + ) + AND pp.promised = 1 AND m.aired = 1 AND me.date >= ? AND me.date < ? GROUP BY m.id ORDER BY avgScore DESC, m.id ASC;` + today := PrintDate(time.Now()) + nextWeek := PrintDate(time.Now().AddDate(0, 0, 7)) + rows, err := mmDb.Query(oracleQuery, today, today, today, nextWeek) + if err != nil { + return nil, err + } + defer rows.Close() + + oracles = make([]MmOracle, 0) + for rows.Next() { + var oracle MmOracle + var qMalId sql.NullInt64 + err = rows.Scan(&oracle.Id, &oracle.Title, &qMalId, &oracle.AvgScore) + if err != nil { + return oracles, err + } + if qMalId.Valid { + oracle.Anime = qMalId.Int64 + } + oracles = append(oracles, oracle) + } + + err = rows.Err() + + if err == nil { + bytes, err := json.Marshal(oracles) + if err == nil { + mmCache.Set(key, bytes) + } + } + + return oracles, err +}