Compare commits

...

5 Commits

Author SHA1 Message Date
daru
89c65c29e8 API DOC 🎉 2022-05-18 23:55:35 +02:00
daru
95294eb344 Delete on completed and fetch on hold 2022-05-18 21:29:46 +02:00
daru
be1419bfa7 Delete user from anime on finish 2022-05-18 21:05:37 +02:00
daru
1ae85f9110 watchext use season 2022-05-18 20:29:00 +02:00
daru
96edfbde61 Fix recache issue 2022-05-18 20:17:54 +02:00
8 changed files with 365 additions and 76 deletions

249
README.md
View File

@@ -1,6 +1,253 @@
# huso
Hanami's universeller Serien Organizer
**This is the backend code**
**Backend code**
## API
### General
| Method | Route | Response |
| - | - | - |
| GET | / | Startseite als HTML |
| GET | /api/log | Log als plaintext |
### Auth
| Method | Route | Description | Response | Parameter | Header | POST-Body |
| - | - | - | - | - | - | - |
| GET | /api/auth/{user} | Test für Authentifizierung | (status code) | {user} = MAL username | X-HUSO-AUTH | |
| POST | /api/register | Registrieren | RegisterData JSON | | | RegisterData JSON |
| DELETE | /api/register | User löschen | RegisterData JSON | | | RegisterData JSON |
_RegisterData_
```json
{
"username": "TESTUSERNAME",
"malId": 42069,
"secret": "1ef539ed34435873fc964c2c20da84f8793ae3731a154cffb41039f2f061cabe97dea18cfffa51c58f3f564f8e7f0cd0a98d7ba3dddb7301c0e7549626ea43af",
"sauce": "31f3637d8d05ddca7deee89453e3b68a9b74860facd8d5a6768e5ca842571595c8d31e43ce0e996f893578d0bd2b61f3f3820ec03fbb30407e31a2603c887b1"
}
```
_sauce_
```
SHA512(綾波レイ + malId + username)
```
_secret_
```
SHA512(SAUCE + PASSWORD)
```
_X-HUSO-AUTH_ -> _secret_ des users
### Anime
| Method | Route | Description | Response | Parameter | Header | POST-Body |
| - | - | - | - | - | - | - |
| GET | /api/season | Aktuelle Season holen | []Anime JSON | | | |
| GET | /api/anime/{id} | Einzelnen Anime holen | Anime JSON | {id} = MAL Anime id | | |
| GET | /api/animesearch | Anime auf MAL suchen | []Anime JSON | Query parameter {q} = Suchtext | | |
_[]Anime_
```json
[
{
"anime": 50265,
"title": "Spy x Family",
"titleEn": "",
"titleJp": "SPY×FAMILY",
"imageMedium": "https://cdn.myanimelist.net/images/anime/1441/122795.jpg",
"imageLarge": "https://cdn.myanimelist.net/images/anime/1441/122795l.jpg",
"imageThumb": "https://cdn.myanimelist.net/images/anime/1441/122795t.jpg",
"url": "https://myanimelist.net/anime/50265",
"type": "TV",
"status": "Currently Airing",
"episodes": 12,
"synopsis": "ANYA~ [Written by MAL Rewrite]",
"genres": [
{
"id": 1,
"name": "Action"
},
{
"id": 4,
"name": "Comedy"
}
],
"startDate": "2022-04-09T00:00:00Z",
"endDate": "0001-01-01T00:00:00Z",
"year": 2022,
"season": "spring",
"score": 9.07,
"scoredBy": 191073,
"rank": 5,
"popularity": 239,
"members": 661291,
"source": "Manga",
"weekday": "Saturdays",
"studios": [
{
"id": 858,
"name": "Wit Studio"
},
{
"id": 1835,
"name": "CloverWorks"
}
],
"trailerUrl": "https://www.youtube.com/watch?v=ofXigq9aIpo",
"trailerEmbedUrl": "https://www.youtube.com/embed/ofXigq9aIpo?enablejsapi=1&wmode=opaque&autoplay=1"
},
{
"anime": 43608,
"title": "Kaguya-sama wa Kokurasetai: Ultra Romantic",
"titleEn": "Kaguya-sama: Love is War - Ultra Romantic",
"titleJp": "かぐや様は告らせたい-ウルトラロマンティック-",
"imageMedium": "https://cdn.myanimelist.net/images/anime/1160/122627.jpg",
"imageLarge": "https://cdn.myanimelist.net/images/anime/1160/122627l.jpg",
"imageThumb": "https://cdn.myanimelist.net/images/anime/1160/122627t.jpg",
"url": "https://myanimelist.net/anime/43608",
"type": "TV",
"status": "Currently Airing",
"episodes": 12,
"synopsis": "Tsun [Written by MAL Rewrite]",
"genres": [
{
"id": 4,
"name": "Comedy"
},
{
"id": 41,
"name": "Suspense"
}
],
"startDate": "2022-04-09T00:00:00Z",
"endDate": "0001-01-01T00:00:00Z",
"year": 2022,
"season": "spring",
"score": 8.99,
"scoredBy": 74937,
"rank": 13,
"popularity": 396,
"members": 454326,
"source": "Manga",
"weekday": "Saturdays",
"studios": [
{
"id": 56,
"name": "A-1 Pictures"
}
],
"trailerUrl": "https://www.youtube.com/watch?v=vFN5K-iAyV0",
"trailerEmbedUrl": "https://www.youtube.com/embed/vFN5K-iAyV0?enablejsapi=1&wmode=opaque&autoplay=1"
}
]
```
### User
| Method | Route | Description | Response | Parameter | Header | POST-Body |
| - | - | - | - | - | - | - |
| GET | /api/user/{user?} | MAL user suchen oder alle registrierten user holen | []User JSON | {user?} = optional: MAL username | | |
_[]User_
```json
[
{
"id": 42069,
"username": "TESTUSERNAME",
"url": "https://myanimelist.net/profile/TESTUSERNAME",
"imageUrl": "https://cdn.myanimelist.net/images/userimages/42069.jpg?t=1652707800",
"lastOnline": "2022-01-01T06:26:42Z",
"gender": "",
"birthday": "0001-01-01T00:00:00Z",
"location": "TEST",
"joined": "2010-01-01T00:00:00Z"
}
]
```
### Watch
| Method | Route | Description | Response | Parameter | Header | POST-Body |
| - | - | - | - | - | - | - |
| GET | /api/watch/{user?} | Watches von user oder allen | []AnimeUser JSON | {user?} = optional: MAL username | | |
| GET | /api/watchext/{user?} | Erweiterte Watches von user oder allen | []AnimeUserExtended JSON | {user?} = optional: MAL username | | |
| POST | /api/watch/{user} | Neuen watch schicken | []AnimeUser JSON | {user} = MAL username | X-HUSO-AUTH | []AnimeUser JSON |
| DELETE | /api/watch/{user} | Watch löschen | []AnimeUser JSON | {user} = MAL username | X-HUSO-AUTH | []AnimeUser JSON |
**Verwendung des JSON von GET und POST/DELETE unterscheiden sich. Bei POST/DELETE muss nur der zu verändernde Anime angegeben werden und sonst nichts.**
_[]AnimeUser_ (GET)
```json
[
{
"anime": 50265,
"users": [
{
"username": "TESTUSERNAME",
"malId": 42069,
"progress": 6,
"updated": "2022-05-15T20:44:35Z"
}
]
},
{
"anime": 40356,
"users": [
{
"username": "TESTUSERNAME",
"malId": 42069,
"progress": 3,
"updated": "2022-04-25T18:11:27Z"
}
]
}
]
```
_[]AnimeUserExtended_ (GET)
```json
[
{
"anime": 50265,
"users": [
{
"username": "TESTUSERNAME",
"malId": 42069,
"progress": 6,
"updated": "2022-05-15T20:44:35Z"
}
],
"data": { --SEE ANIME GET (Anime object)-- }
},
{
"anime": 40356,
"users": [
{
"username": "TESTUSERNAME",
"malId": 42069,
"progress": 3,
"updated": "2022-04-25T18:11:27Z"
}
],
"data": { --SEE ANIME GET (Anime object)-- }
}
]
```
_[]AnimeUser_ (POST/DELETE)
```json
[
{
"anime": 40356,
"users": []
}
]
```
### Chat
| Method | Route | Description | Response | Parameter | Header | POST-Body |
| - | - | - | - | - | - | - |
| GET | /api/chat/{id} | Chat für Anime holen | Plaintext chat | {id} = MAL Anime id | | |
| POST | /api/chat/{id}/{user} | Chat für Anime senden | Plaintext chat mit neuer Nachricht | {id} = MAL Anime id; {user} = MAL username | X-HUSO-AUTH | Neue Nachricht in plaintext |
### Appointment [WIP]
| Method | Route | Description | Response | Parameter | Header | POST-Body |
| - | - | - | - | - | - | - |
| GET | /api/appointment | Appointments holen | []Appointment | | | |

2
go.mod
View File

@@ -21,5 +21,5 @@ require (
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
github.com/xujiajun/mmap-go v1.0.1 // indirect
github.com/xujiajun/utils v0.0.0-20190123093513-8bf096c4f53b // indirect
golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a // indirect
golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e // indirect
)

4
go.sum
View File

@@ -59,8 +59,8 @@ golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a h1:N2T1jUrTQE9Re6TFF5PhvEHXHCguynGhKjWVsIUt5cY=
golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e h1:w36l2Uw3dRan1K3TyXriXvY+6T56GNmlKGcqiQUJDfM=
golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=

View File

@@ -76,12 +76,12 @@ func main() {
log.Fatal(err)
}
defer animeCache.Close()
seasoncache, err = bigcache.NewBigCache(bigcache.DefaultConfig(2 * time.Hour))
seasoncache, err = bigcache.NewBigCache(bigcache.DefaultConfig(7 * time.Hour))
if err != nil {
log.Fatal(err)
}
defer seasoncache.Close()
userCache, err = bigcache.NewBigCache(bigcache.DefaultConfig(7 * time.Hour))
userCache, err = bigcache.NewBigCache(bigcache.DefaultConfig(5 * time.Hour))
if err != nil {
log.Fatal(err)
}
@@ -109,6 +109,7 @@ func main() {
LangeArbeit()
go Arbeiten()
go BissleArbeiten()
go LangeArbeiten()
go RunWebserv()

View File

@@ -13,7 +13,7 @@ import (
"github.com/valyala/fasthttp"
)
func SearchAnime(query string) ([]Anime, []byte, error) {
func SearchAnimeData(query string) ([]Anime, []byte, error) {
var animes []Anime
data, err := searchCache.Get(query)
if err != nil {

52
ober.go
View File

@@ -91,21 +91,16 @@ func AnimeGet(ctx *fasthttp.RequestCtx) {
ctx.SetStatusCode(fasthttp.StatusBadRequest)
return
}
// search season first
anime, err := SearchSeason(malId)
var bytes []byte
// search anime
anime, err := SearchAnime(malId)
if err != nil {
_, bytes, err = GetAnimeDetailData(malId)
if err != nil {
addErrorToCtx(ctx, err)
return
}
} else {
bytes, err = json.Marshal(&anime)
if err != nil {
addErrorToCtx(ctx, err)
return
}
addErrorToCtx(ctx, err)
return
}
bytes, err := json.Marshal(&anime)
if err != nil {
addErrorToCtx(ctx, err)
return
}
_, err = ctx.Write(bytes)
if err != nil {
@@ -123,8 +118,8 @@ func AnimeSearchGet(ctx *fasthttp.RequestCtx) {
return
}
query := string(ctx.QueryArgs().Peek("q"))
// Search with query
_, bytes, err := SearchAnime(query)
// search with query
_, bytes, err := SearchAnimeData(query)
if err != nil {
addErrorToCtx(ctx, err)
return
@@ -271,7 +266,8 @@ func WatchExtendedGet(ctx *fasthttp.RequestCtx) {
animeUsersExtended := make([]AnimeUserExtended, 0)
// make list advanced
for _, a := range animeUsers {
data, _, err := GetAnimeDetailData(a.Anime)
// search anime
data, err := SearchAnime(a.Anime)
if err != nil {
addErrorToCtx(ctx, err)
return
@@ -540,16 +536,12 @@ func processUpdateReq(ctx *fasthttp.RequestCtx, update bool) {
// iterate sent animes
for i, anime := range animes {
// check if anime is in season
_, err = SearchSeason(anime.Anime)
// anime holen sie diese
_, err = SearchAnime(anime.Anime)
if err != nil {
// anime not in season => holen sie diese
_, _, err := GetAnimeDetailData(anime.Anime)
if err != nil {
ctx.WriteString(err.Error())
ctx.SetStatusCode(fasthttp.StatusNotFound)
return
}
ctx.WriteString(err.Error())
ctx.SetStatusCode(fasthttp.StatusNotFound)
return
}
var animeData *AnimeUser
@@ -557,7 +549,13 @@ func processUpdateReq(ctx *fasthttp.RequestCtx, update bool) {
if update {
// anime exitsts => save
// get watch progress
progress, updated, _ := FetchProgress(anime.Anime, userId, username, 0)
progress, updated, listState, _ := FetchProgress(anime.Anime, userId, username, 0)
// anime is already completed big baka user
if listState == malApiStatusC {
ctx.WriteString("Du hast schon fertig geschaut bro")
ctx.SetStatusCode(fasthttp.StatusConflict)
return
}
animeData, err = AddUserToAnime(username, userId, anime.Anime, progress, updated)
} else {
// anime exitsts => delete

View File

@@ -17,18 +17,6 @@ func Arbeiten() {
}
func Arbeit() {
// season data
_, bytes, err := GetSeasonDataAll()
if err != nil {
color.Errorln(err.Error())
logOut.WriteError(err)
} else {
err = seasoncache.Set(seasonApiJikan, bytes)
if err != nil {
color.Errorln(err.Error())
logOut.WriteError(err)
}
}
// refresh animelist of users
animesUsers, err := ReadAnimeUsers()
if err != nil {
@@ -42,7 +30,7 @@ func Arbeit() {
for _, a := range animesUsers {
// iterate users
for _, u := range a.Users {
newProgress, updated, err := FetchProgress(a.Anime, u.MalID, u.Username, u.Progress)
newProgress, updated, listState, err := FetchProgress(a.Anime, u.MalID, u.Username, u.Progress)
if err != nil {
color.Errorln(err.Error())
logOut.WriteError(err)
@@ -51,9 +39,21 @@ func Arbeit() {
if newProgress == u.Progress {
continue
}
// update db
color.Infof("%s progress von %d: %d -> %d\n", u.Username, a.Anime, u.Progress, newProgress)
logOut.WriteLine(fmt.Sprintf("📜 %s progress von %d: %d -> %d", u.Username, a.Anime, u.Progress, newProgress))
// check if user set anime as completed
if listState == malApiStatusC {
color.Infof("%s finished %d\n", u.Username, a.Anime)
logOut.WriteLine(fmt.Sprintf("📜 %s finished %d !", u.Username, a.Anime))
// delete user from anime
_, err = DeleteUserFromAnime(u.Username, u.MalID, a.Anime)
if err != nil {
color.Errorln(err.Error())
logOut.WriteError(err)
}
continue
}
// update db
err = UpdateUserAnimeProgress(a.Anime, u.MalID, newProgress, updated)
if err != nil {
color.Errorln(err.Error())
@@ -63,16 +63,15 @@ func Arbeit() {
}
}
func LangeArbeiten() {
for range time.Tick(6 * time.Hour) {
LangeArbeit()
func BissleArbeiten() {
for range time.Tick(4 * time.Hour) {
BissleArbeit()
}
}
func LangeArbeit() {
count := 0
func BissleArbeit() {
// refresh user cache
count := 0
regUsers, err := ReadRegisteredUsers()
if err != nil {
// check if no users registered
@@ -93,9 +92,30 @@ func LangeArbeit() {
}
color.Infof("%d User aktualisiert\n", count)
logOut.WriteLine(fmt.Sprintf("🔃 %d User aktualisiert", count))
}
func LangeArbeiten() {
for range time.Tick(6 * time.Hour) {
LangeArbeit()
}
}
func LangeArbeit() {
// season data
_, bytes, err := GetSeasonDataAll()
if err != nil {
color.Errorln(err.Error())
logOut.WriteError(err)
} else {
err = seasoncache.Set(seasonApiJikan, bytes)
if err != nil {
color.Errorln(err.Error())
logOut.WriteError(err)
}
}
count = 0
// refresh anime cache with watched
count := 0
animesUsers, err := ReadAnimeUsers()
if err != nil {
if err != nutsdb.ErrBucketEmpty {
@@ -106,7 +126,7 @@ func LangeArbeit() {
for _, a := range animesUsers {
// search season first
_, err = SearchSeason(a.Anime)
if err != nil {
if err == nil {
continue
}
err = refreshAnime(a.Anime)

View File

@@ -132,24 +132,50 @@ func SearchSeason(animeId int64) (*Anime, error) {
return nil, errors.New("anime not found")
}
func FetchProgress(animeId, userId int64, username string, progress int) (int, time.Time, error) {
// check watching first
list, _, err := GetUserAnimeListData(username, malApiStatusW)
func SearchAnime(animeId int64) (*Anime, error) {
// search season first
anime, err := SearchSeason(animeId)
if err != nil {
return 0, time.Time{}, err
}
for _, a := range list.Data {
// check if found
if a.Node.ID == animeId {
// check if progress changed
if progress != a.ListStatus.NumEpisodesWatched {
return a.ListStatus.NumEpisodesWatched, a.ListStatus.UpdatedAt, nil
}
return progress, a.ListStatus.UpdatedAt, nil
// get from MAL
anime, _, err = GetAnimeDetailData(animeId)
if err != nil {
return nil, err
}
}
return anime, err
}
func FetchProgress(animeId, userId int64, username string, progress int) (int, time.Time, string, error) {
// check watching first
newProgress, updated, err := fetchProgressOnState(animeId, userId, progress, username, malApiStatusW)
if err != nil {
return newProgress, updated, "", err
}
if newProgress != -1 {
return newProgress, updated, malApiStatusW, err
}
// check on hold
newProgress, updated, err = fetchProgressOnState(animeId, userId, progress, username, malApiStatusH)
if err != nil {
return newProgress, updated, "", err
}
if newProgress != -1 {
return newProgress, updated, malApiStatusH, err
}
// check completed
list, _, err = GetUserAnimeListData(username, malApiStatusC)
newProgress, updated, err = fetchProgressOnState(animeId, userId, progress, username, malApiStatusC)
if err != nil {
return newProgress, updated, "", err
}
if newProgress != -1 {
return newProgress, updated, malApiStatusC, err
}
// has no progress or dropped
return 0, updated, "", nil
}
func fetchProgressOnState(animeId, userId int64, progress int, username, malStatus string) (int, time.Time, error) {
list, _, err := GetUserAnimeListData(username, malStatus)
if err != nil {
return 0, time.Time{}, err
}
@@ -157,14 +183,11 @@ func FetchProgress(animeId, userId int64, username string, progress int) (int, t
// check if found
if a.Node.ID == animeId {
// check if progress changed
if progress != a.ListStatus.NumEpisodesWatched {
return a.ListStatus.NumEpisodesWatched, a.ListStatus.UpdatedAt, nil
}
return progress, a.ListStatus.UpdatedAt, nil
return a.ListStatus.NumEpisodesWatched, a.ListStatus.UpdatedAt, nil
}
}
// has no progress or dropped/hold
return 0, time.Now(), nil
// no progess found
return -1, time.Now(), nil
}
func AddToChat(old, new, user string) string {