Compare commits

...

36 Commits

Author SHA1 Message Date
daru
17a754b918 Anime lists did not age well 😵‍💫 2022-08-25 23:32:31 +02:00
daru
730f91e74e Anime lists didn't age well 😵‍💫 2022-08-25 23:31:18 +02:00
daru
dbd34abea1 Anime list fetching not broken but ugly insted ✔️ 2022-08-25 22:54:21 +02:00
daru
beed95eed9 LET THERE BE ORDER 2022-08-19 18:05:54 +02:00
daru
074a4a88f0 go 1.19 2022-08-03 19:09:33 +02:00
daru
fa765859e4 ☮️ 2022-07-31 19:00:48 +02:00
daru
1f59f13bfc Get your codebase togehter 2022-07-31 14:55:31 +02:00
daru
92660df667 💣🩹 2022-07-31 14:50:56 +02:00
daru
db4ca8e4cf 💣💣💣 2022-07-29 18:57:43 +02:00
daru
13b2335ae8 💣 2022-07-29 18:43:00 +02:00
daru
1bb8536334 Big cleanup 🧹 2022-07-28 00:43:35 +02:00
daru
6a2e573828 search hacks 2022-07-26 22:44:43 +02:00
daru
7185544e03 Prime hotfix 2022-07-26 20:48:17 +02:00
daru
7fe4181c72 prime fun + user stash 2022-07-26 20:33:11 +02:00
daru
3771b1d2c4 Embrace the pointer 2022-07-20 23:47:31 +02:00
daru
d54ed135e3 Build +20 coolness 2022-07-18 20:46:57 +02:00
daru
ec6ed3cc63 So close and yet so far 2022-07-18 20:31:09 +02:00
daru
2e5b45a699 One does not simply: time 2022-07-18 20:20:38 +02:00
daru
10a563bb23 AnnounceAppointmentSoon 2022-07-18 18:01:24 +02:00
daru
ecba4d68e6 Do recycle the future 2022-07-18 01:12:04 +02:00
daru
1a075e79ff nil pointer fun 2022-07-18 00:57:48 +02:00
daru
12c69818ce Implement Spam 2022-07-18 00:47:26 +02:00
daru
3efd003424 Erster Discord Versuch 2022-07-17 19:42:49 +02:00
daru
fd0f8bc8e2 DiscordId + PATCH register + nil fixes 2022-07-17 03:19:14 +02:00
daru
0417ceb1f9 Can provide Discord ID on register 2022-07-16 22:03:58 +02:00
daru
aec582ff8e Keep appointmets longer 2022-07-16 21:37:29 +02:00
daru
5d1efd6bcf RUHE 2022-07-02 02:00:12 +02:00
daru
6f37daedfe Hotter hotfix 2022-07-01 19:08:45 +02:00
daru
8df72d6f18 Hotfix 2022-07-01 19:05:31 +02:00
daru
d87b658837 LastSeason 2022-07-01 18:43:30 +02:00
daru
be04fb92e5 Doc bump 2022-07-01 18:28:17 +02:00
daru
f968c1b98e NextNextSeason 2022-07-01 18:20:17 +02:00
daru
fd84193d9d the big talk 2022-07-01 17:47:08 +02:00
daru
f078c93b9e We fast now 2022-07-01 17:08:10 +02:00
daru
cc525f2460 Charts V2 2022-06-30 00:21:38 +02:00
daru
da474ecede Charts V1 2022-06-29 22:40:46 +02:00
16 changed files with 934 additions and 356 deletions

View File

@@ -16,13 +16,15 @@ Hanami's universeller Serien Organizer
| - | - | - | - | - | - | - | | - | - | - | - | - | - | - |
| GET | /api/auth/{user} | Test für Authentifizierung | (status code) | {user} = MAL username | X-HUSO-AUTH | | | GET | /api/auth/{user} | Test für Authentifizierung | (status code) | {user} = MAL username | X-HUSO-AUTH | |
| POST | /api/register | Registrieren | RegisterData JSON | | | RegisterData JSON | | POST | /api/register | Registrieren | RegisterData JSON | | | RegisterData JSON |
| DELETE | /api/register | User löschen | RegisterData JSON | | | RegisterData JSON | | PATCH | /api/register | Registrierung bearbeiten | RegisterData JSON | | X-HUSO-AUTH | RegisterData JSON |
| DELETE | /api/register | User löschen | RegisterData JSON | | X-HUSO-AUTH | RegisterData JSON |
_RegisterData_ _RegisterData_
```json ```json
{ {
"username": "TESTUSERNAME", "username": "TESTUSERNAME",
"malId": 42069, "malId": 42069,
"discordId": 111111111111111111,
"secret": "1ef539ed34435873fc964c2c20da84f8793ae3731a154cffb41039f2f061cabe97dea18cfffa51c58f3f564f8e7f0cd0a98d7ba3dddb7301c0e7549626ea43af", "secret": "1ef539ed34435873fc964c2c20da84f8793ae3731a154cffb41039f2f061cabe97dea18cfffa51c58f3f564f8e7f0cd0a98d7ba3dddb7301c0e7549626ea43af",
"sauce": "31f3637d8d05ddca7deee89453e3b68a9b74860facd8d5a6768e5ca842571595c8d31e43ce0e996f893578d0bd2b61f3f3820ec03fbb30407e31a2603c887b1" "sauce": "31f3637d8d05ddca7deee89453e3b68a9b74860facd8d5a6768e5ca842571595c8d31e43ce0e996f893578d0bd2b61f3f3820ec03fbb30407e31a2603c887b1"
} }
@@ -43,6 +45,8 @@ _X-HUSO-AUTH_ -> _secret_ des users
| - | - | - | - | - | - | - | | - | - | - | - | - | - | - |
| GET | /api/season | Aktuelle Season holen | []Anime JSON | | | | | GET | /api/season | Aktuelle Season holen | []Anime JSON | | | |
| GET | /api/nextseason | Nächste Season holen | []Anime JSON | | | | | GET | /api/nextseason | Nächste Season holen | []Anime JSON | | | |
| GET | /api/nextnextseason | Übernächste Season holen | []Anime JSON | | | |
| GET | /api/lastseason | Letzte Season holen | []Anime JSON | | | |
| GET | /api/anime/{id} | Einzelnen Anime holen | Anime JSON | {id} = MAL Anime id | | | | 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 | | | | GET | /api/animesearch | Anime auf MAL suchen | []Anime JSON | Query parameter {q} = Suchtext | | |
@@ -291,3 +295,8 @@ _[]Appointment_ (POST/DELETE)
} }
] ]
``` ```
### User data
| Method | Route | Description | Response | Parameter | Header | POST-Body |
| - | - | - | - | - | - | - |
| GET | /api/userdata/{user}/{key} | User data holen | | {user} = MAL username; {key} = Key der data | X-HUSO-AUTH | Plaintext(binary) data |
| PUT | /api/userdata/{user}/{key} | User data setzen | Plaintext(binary) data | {user} = MAL username; {key} = Key der data | X-HUSO-AUTH | |

View File

@@ -1,3 +1,4 @@
#!/bin/bash #!/bin/bash
date=$(date '+%Y-%m-%dT%H:%M:%S') date=$(date '+%Y-%m-%dT%H:%M:%S')
go build -ldflags "-X main.buildTime=$date" commit=$(git log -1 --pretty=format:'%h %B')
go build -ldflags "-X 'main.buildTime=$date' -X 'main.buildCommit=$commit'"

17
go.mod
View File

@@ -1,27 +1,30 @@
module github.com/ultrasn0w/huso module github.com/ultrasn0w/huso
go 1.18 go 1.19
require ( require (
github.com/allegro/bigcache/v3 v3.0.2 github.com/allegro/bigcache/v3 v3.0.2
github.com/fasthttp/router v1.4.10 github.com/bwmarrin/discordgo v0.26.0
github.com/fasthttp/router v1.4.11
github.com/go-sql-driver/mysql v1.6.0 github.com/go-sql-driver/mysql v1.6.0
github.com/gookit/color v1.5.1 github.com/gookit/color v1.5.1
github.com/valyala/fasthttp v1.38.0 github.com/klauspost/compress v1.15.9
github.com/valyala/fasthttp v1.39.0
github.com/valyala/quicktemplate v1.7.0 github.com/valyala/quicktemplate v1.7.0
github.com/xujiajun/nutsdb v0.9.0 github.com/xujiajun/nutsdb v0.10.0
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9
) )
require ( require (
github.com/andybalholm/brotli v1.0.4 // indirect github.com/andybalholm/brotli v1.0.4 // indirect
github.com/bwmarrin/snowflake v0.3.0 // indirect github.com/bwmarrin/snowflake v0.3.0 // indirect
github.com/klauspost/compress v1.15.6 // indirect github.com/gorilla/websocket v1.5.0 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d // indirect github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
github.com/xujiajun/mmap-go v1.0.1 // indirect github.com/xujiajun/mmap-go v1.0.1 // indirect
github.com/xujiajun/utils v0.0.0-20190123093513-8bf096c4f53b // indirect github.com/xujiajun/utils v0.0.0-20190123093513-8bf096c4f53b // indirect
golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b // indirect golang.org/x/crypto v0.0.0-20220824171710-5757bc0c5503 // indirect
golang.org/x/sys v0.0.0-20220823224334-20c2bfdbfe24 // indirect
) )

32
go.sum
View File

@@ -4,24 +4,29 @@ github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu
github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/bwmarrin/discordgo v0.26.0 h1:/AdFmxHXSHInYAZ7K0O3VEIXlVjGpztk/nuCr9o+JCs=
github.com/bwmarrin/discordgo v0.26.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0=
github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 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/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.11 h1:99BvgVxeS2oOZBHnKr/okpdPq1jkn8WvYA2trh/71LY=
github.com/fasthttp/router v1.4.10/go.mod h1:FGSUOg9SQ/tU864SfD23kG/HwfD0akXqOqhTQ27gTFQ= github.com/fasthttp/router v1.4.11/go.mod h1:luEEYkGBSAmYyPaMeIUGNgqY+FdHHYDOK9Kivaw7aNo=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= 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/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/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/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gookit/color v1.5.1 h1:Vjg2VEcdHpwq+oY63s/ksHrgJYCTo0bwWvmmYWdE9fQ= github.com/gookit/color v1.5.1 h1:Vjg2VEcdHpwq+oY63s/ksHrgJYCTo0bwWvmmYWdE9fQ=
github.com/gookit/color v1.5.1/go.mod h1:wZFzea4X8qN6vHOSP2apMb4/+w/orMznEzYsIHPaqKM= github.com/gookit/color v1.5.1/go.mod h1:wZFzea4X8qN6vHOSP2apMb4/+w/orMznEzYsIHPaqKM=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.15.6 h1:6D9PcO8QWu0JyaQ2zUMmu16T1T+zjjEpP91guRsvDfY= github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY=
github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
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=
@@ -35,9 +40,9 @@ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1F
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 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/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.30.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus= github.com/valyala/fasthttp v1.30.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus=
github.com/valyala/fasthttp v1.37.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I=
github.com/valyala/fasthttp v1.38.0 h1:yTjSSNjuDi2PPvXY2836bIwLmiTS2T4T9p1coQshpco=
github.com/valyala/fasthttp v1.38.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= github.com/valyala/fasthttp v1.38.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I=
github.com/valyala/fasthttp v1.39.0 h1:lW8mGeM7yydOqZKmwyMTaz/PH/A+CLgtmmcjv+OORfU=
github.com/valyala/fasthttp v1.39.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I=
github.com/valyala/quicktemplate v1.7.0 h1:LUPTJmlVcb46OOUY3IeD9DojFpAVbsG+5WFTcjMJzCM= github.com/valyala/quicktemplate v1.7.0 h1:LUPTJmlVcb46OOUY3IeD9DojFpAVbsG+5WFTcjMJzCM=
github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/VPSJnLYn+LmLk8= github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/VPSJnLYn+LmLk8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
@@ -46,12 +51,15 @@ github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1z
github.com/xujiajun/gorouter v1.2.0/go.mod h1:yJrIta+bTNpBM/2UT8hLOaEAFckO+m/qmR3luMIQygM= github.com/xujiajun/gorouter v1.2.0/go.mod h1:yJrIta+bTNpBM/2UT8hLOaEAFckO+m/qmR3luMIQygM=
github.com/xujiajun/mmap-go v1.0.1 h1:7Se7ss1fLPPRW+ePgqGpCkfGIZzJV6JPq9Wq9iv/WHc= github.com/xujiajun/mmap-go v1.0.1 h1:7Se7ss1fLPPRW+ePgqGpCkfGIZzJV6JPq9Wq9iv/WHc=
github.com/xujiajun/mmap-go v1.0.1/go.mod h1:CNN6Sw4SL69Sui00p0zEzcZKbt+5HtEnYUsc6BKKRMg= github.com/xujiajun/mmap-go v1.0.1/go.mod h1:CNN6Sw4SL69Sui00p0zEzcZKbt+5HtEnYUsc6BKKRMg=
github.com/xujiajun/nutsdb v0.9.0 h1:vy8rjDp0Sk/SnTAqg61i+G4NIN/3tBKSdZ6rIyKYVIo= github.com/xujiajun/nutsdb v0.10.0 h1:kSxd7MyZiAVQM2I79FK74WneGI+uaHsUdak8dbjzKJc=
github.com/xujiajun/nutsdb v0.9.0/go.mod h1:8ZdTTF0cEQO+wN940htfHYKswFql2iB6Osckx+GmOoU= github.com/xujiajun/nutsdb v0.10.0/go.mod h1:8ZdTTF0cEQO+wN940htfHYKswFql2iB6Osckx+GmOoU=
github.com/xujiajun/utils v0.0.0-20190123093513-8bf096c4f53b h1:jKG9OiL4T4xQN3IUrhUpc1tG+HfDXppkgVcrAiiaI/0= github.com/xujiajun/utils v0.0.0-20190123093513-8bf096c4f53b h1:jKG9OiL4T4xQN3IUrhUpc1tG+HfDXppkgVcrAiiaI/0=
github.com/xujiajun/utils v0.0.0-20190123093513-8bf096c4f53b/go.mod h1:AZd87GYJlUzl82Yab2kTjx1EyXSQCAfZDhpTo1SQC4k= github.com/xujiajun/utils v0.0.0-20190123093513-8bf096c4f53b/go.mod h1:AZd87GYJlUzl82Yab2kTjx1EyXSQCAfZDhpTo1SQC4k=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220824171710-5757bc0c5503 h1:vJ2V3lFLg+bBhgroYuRfyN583UzVveQmIXjc8T/y3to=
golang.org/x/crypto v0.0.0-20220824171710-5757bc0c5503/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@@ -65,15 +73,15 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/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-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220405210540-1e041c57c461/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220405210540-1e041c57c461/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b h1:2n253B2r0pYSmEV+UNCQoPfU/FiaizQEK5Gu4Bq4JE8= golang.org/x/sys v0.0.0-20220823224334-20c2bfdbfe24 h1:TyKJRhyo17yWxOMCTHKWrc5rddHORMlnZ/j57umaUd8=
golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220823224334-20c2bfdbfe24/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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 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= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U= golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ=
golang.org/x/time v0.0.0-20220609170525-579cf78fd858/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

41
huso.go
View File

@@ -12,6 +12,7 @@ import (
"time" "time"
"github.com/allegro/bigcache/v3" "github.com/allegro/bigcache/v3"
"github.com/bwmarrin/discordgo"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
"github.com/gookit/color" "github.com/gookit/color"
"github.com/xujiajun/nutsdb" "github.com/xujiajun/nutsdb"
@@ -19,9 +20,8 @@ import (
) )
const ( const (
husoVersion = "1.2" husoVersion = "1.7.1"
registerSecret = "綾波レイ" registerSecret = "綾波レイ"
seasonApiJikan = "seasons/now"
seasonStrJikan = "seasons/" seasonStrJikan = "seasons/"
userApiJikan = "users/" userApiJikan = "users/"
userApiMal = "users/" userApiMal = "users/"
@@ -37,6 +37,7 @@ const (
bucketMedia = "media" bucketMedia = "media"
bucketAppoint = "appoint" bucketAppoint = "appoint"
bucketChat = "chat" bucketChat = "chat"
bucketStash = "stash"
AppointSplit = "§" AppointSplit = "§"
chatLength = 10101 chatLength = 10101
) )
@@ -49,6 +50,10 @@ var (
localServer = flag.Bool("localServer", false, "Set varius headers for running locally") localServer = flag.Bool("localServer", false, "Set varius headers for running locally")
mmDbServer = flag.String("mmDbServer", "hanami.family:3306", "MovieManager db server") mmDbServer = flag.String("mmDbServer", "hanami.family:3306", "MovieManager db server")
mmDbUser = flag.String("mmDbUser", "yui", "MovieManager db user") mmDbUser = flag.String("mmDbUser", "yui", "MovieManager db user")
discordToken = flag.String("discordToken", "OTk4MTk3MTMzNjE2MzUzMzUw.GKPdLt.zaMvvlikffjBxJO-Qx69A5nQNQxtA-FmkH2yTA", "Discord bot auth token")
discordChannel = flag.String("discordChannel", "998277590609559632", "Discord bot channel id")
husoWebAnimeUri = flag.String("husoWebAnimeUri", "https://anime.hanami.family/anime/", "HusoWeb Anime base URL")
husoWebUserUri = flag.String("husoWebUserUri", "https://anime.hanami.family/user/", "HusoWeb User base URL")
animeCache *bigcache.BigCache animeCache *bigcache.BigCache
seasoncache *bigcache.BigCache seasoncache *bigcache.BigCache
userCache *bigcache.BigCache userCache *bigcache.BigCache
@@ -57,9 +62,12 @@ var (
mmCache *bigcache.BigCache mmCache *bigcache.BigCache
db *nutsdb.DB db *nutsdb.DB
mmDb *sql.DB mmDb *sql.DB
discc *discordgo.Session
jikanLimiter *rate.Limiter jikanLimiter *rate.Limiter
logOut *RingBuf logOut *RingBuf
buildTime string buildTime string
buildCommit string
lastAnnounce *SomewhatThreadSafeTime
) )
func main() { func main() {
@@ -69,12 +77,14 @@ func main() {
sc := make(chan os.Signal, 1) sc := make(chan os.Signal, 1)
signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt) signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
logOut = NewRingBuf(10101) logOut = NewRingBuf(101010)
color.Notice.Printf("huso %s built on %s with %s\n", husoVersion, buildTime, runtime.Version()) color.Notice.Printf("huso %s built on %s with %s\n", husoVersion, buildTime, runtime.Version())
color.Notice.Printf("Last git commit: %s\n", buildCommit)
logOut.WriteLine(fmt.Sprintf("🎉 huso %s built on %s with %s", husoVersion, buildTime, runtime.Version())) logOut.WriteLine(fmt.Sprintf("🎉 huso %s built on %s with %s", husoVersion, buildTime, runtime.Version()))
logOut.WriteLine(fmt.Sprintf("🪄 Last git commit: %s", buildCommit))
jikanLimiter = rate.NewLimiter(rate.Every(time.Second), 1) jikanLimiter = rate.NewLimiter(rate.Every(time.Second+(time.Millisecond*420)), 1)
// cache init // cache init
var err error var err error
@@ -98,7 +108,7 @@ func main() {
log.Fatal(err) log.Fatal(err)
} }
defer searchCache.Close() defer searchCache.Close()
animeListCache, err = bigcache.NewBigCache(bigcache.DefaultConfig(42 * time.Minute)) animeListCache, err = bigcache.NewBigCache(bigcache.DefaultConfig(15 * time.Minute))
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@@ -117,6 +127,10 @@ func main() {
} }
defer db.Close() defer db.Close()
lastAnnounce = &SomewhatThreadSafeTime{
stamp: time.Now().Add(-1 * time.Hour),
}
color.Infoln("NutsDB connected") color.Infoln("NutsDB connected")
logOut.WriteLine("🗃️ NutsDB connected") logOut.WriteLine("🗃️ NutsDB connected")
@@ -125,7 +139,7 @@ func main() {
color.Errorln(err.Error()) color.Errorln(err.Error())
} else { } else {
mmDb, err = sql.Open("mysql", conns) mmDb, err = sql.Open("mysql", conns)
if err != nil || mmDb == nil { if err != nil {
color.Errorln(err.Error()) color.Errorln(err.Error())
} else { } else {
mmDb.SetConnMaxLifetime(time.Minute * 3) mmDb.SetConnMaxLifetime(time.Minute * 3)
@@ -143,7 +157,22 @@ func main() {
} }
} }
discc, err = discordgo.New("Bot " + *discordToken)
if err != nil {
color.Errorln(err.Error())
} else {
err = discc.Open()
if err != nil {
color.Errorln(err.Error())
} else {
defer discc.Close()
color.Infoln("Discord huso online")
logOut.WriteLine("📯 Discord huso online")
}
}
Arbeit() Arbeit()
BissleArbeit()
LangeArbeit() LangeArbeit()
go Arbeiten() go Arbeiten()

View File

@@ -1,6 +1,9 @@
package main package main
import "time" import (
"sync"
"time"
)
type AnimeUser struct { type AnimeUser struct {
Anime int64 `json:"anime"` Anime int64 `json:"anime"`
@@ -55,9 +58,10 @@ type AnimeStudio struct {
} }
type Appointment struct { type Appointment struct {
Anime int64 `json:"anime"` Anime int64 `json:"anime"`
Time time.Time `json:"date"` Time time.Time `json:"date"`
Users []string `json:"users"` Users []string `json:"users"`
Announced bool `json:"announced"`
} }
type WatchUser struct { type WatchUser struct {
@@ -67,12 +71,6 @@ type WatchUser struct {
Updated time.Time `json:"updated"` Updated time.Time `json:"updated"`
} }
type UserData struct {
Username string `json:"username"`
MalID int64 `json:"malId"`
Secret string `json:"secret"`
}
type User struct { type User struct {
MalID int64 `json:"id"` MalID int64 `json:"id"`
Username string `json:"username"` Username string `json:"username"`
@@ -85,11 +83,19 @@ type User struct {
Joined time.Time `json:"joined"` Joined time.Time `json:"joined"`
} }
type UserData struct {
Username string `json:"username"`
MalID int64 `json:"malId"`
DiscordID int64 `json:"discordId"`
Secret string `json:"secret"`
}
type RegisterData struct { type RegisterData struct {
Username string `json:"username"` Username string `json:"username"`
MalID int64 `json:"malId"` MalID int64 `json:"malId"`
Secret string `json:"secret"` DiscordID int64 `json:"discordId"`
Sauce string `json:"sauce"` Secret string `json:"secret"`
Sauce string `json:"sauce"`
} }
type AnimeDetailMal struct { type AnimeDetailMal struct {
@@ -317,3 +323,16 @@ type MmOracle struct {
Anime int64 `json:"anime"` Anime int64 `json:"anime"`
AvgScore int `json:"avgScore"` AvgScore int `json:"avgScore"`
} }
type MovieChart struct {
MmId int `json:"mmId"`
Anime int64 `json:"anime"`
Title string `json:"title"`
AvgScore float64 `json:"avgScore"`
UserCount int `json:"userCount"`
}
type SomewhatThreadSafeTime struct {
sync.Mutex
stamp time.Time
}

163
knecht.go
View File

@@ -8,6 +8,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"time" "time"
"unicode/utf8"
"github.com/gookit/color" "github.com/gookit/color"
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
@@ -18,19 +19,29 @@ func SearchAnimeData(query string) ([]Anime, []byte, error) {
data, err := searchCache.Get(query) data, err := searchCache.Get(query)
if err != nil { if err != nil {
err = nil err = nil
dataMal, err := GetDataMal(searchApiMal + query + "&fields=id,title,main_picture,alternative_titles,start_date,end_date,synopsis,mean,rank,popularity,num_list_users,num_scoring_users,nsfw,media_type,status,genres,my_list_status,num_episodes,start_season,broadcast,source,average_episode_duration,rating,studios&limit=100")
if err != nil {
return nil, nil, err
}
var animeList AnimeListMal
animes = make([]Anime, 0) animes = make([]Anime, 0)
err = json.Unmarshal(dataMal, &animeList) parseId, err := strconv.ParseInt(query, 10, 64)
if err != nil { if err == nil && parseId > 0 {
return nil, nil, err anime, err := SearchAnime(parseId)
if err == nil {
animes = append(animes, *anime)
}
} }
// convert to anime err = nil
for _, a := range animeList.Data { if utf8.RuneCountInString(query) > 2 {
animes = append(animes, MalConvert(&a.Node)) dataMal, err := GetDataMal(searchApiMal + query + "&fields=id,title,main_picture,alternative_titles,start_date,end_date,synopsis,mean,rank,popularity,num_list_users,num_scoring_users,nsfw,media_type,status,genres,my_list_status,num_episodes,start_season,broadcast,source,average_episode_duration,rating,studios&limit=100")
if err != nil {
return nil, nil, err
}
var animeList AnimeListMal
err = json.Unmarshal(dataMal, &animeList)
if err != nil {
return nil, nil, err
}
// convert to anime
for _, a := range animeList.Data {
animes = append(animes, MalConvert(&a.Node))
}
} }
data, err = json.Marshal(animes) data, err = json.Marshal(animes)
if err != nil { if err != nil {
@@ -75,7 +86,9 @@ func GetUserAnimeListData(username, status string) (*AnimeListMal, []byte, error
var list AnimeListMal var list AnimeListMal
data, err := animeListCache.Get(username + status) data, err := animeListCache.Get(username + status)
if err != nil { if err != nil {
data, err = GetDataMal(userApiMal + username + "/animelist?limit=1000&status=" + status + "&fields=list_status") color.Infoln(username + "'s " + status + " abfragen...")
//logOut.WriteLine("📄 " + username + "'s " + status + " abfragen...")
data, err = GetDataMal(userApiMal + username + "/animelist?limit=1000&status=" + status + "&fields=list_status&nsfw=true")
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@@ -85,47 +98,17 @@ func GetUserAnimeListData(username, status string) (*AnimeListMal, []byte, error
return &list, data, err return &list, data, err
} }
func GetUserData(username string) (*User, []byte, error) { func GetUser(username string) (*User, []byte, error) {
var user User var user User
data, err := userCache.Get(username) data, err := userCache.Get(username)
if err != nil { if err != nil {
err = nil err = nil
dataJikan, err := GetDataJikan(userApiJikan + username) userp, data, err := GetUserData(username)
if err != nil { if err != nil {
return nil, nil, err return userp, data, err
}
if strings.Contains(string(dataJikan), "BadResponseException") {
return nil, nil, fmt.Errorf("user not found: %s", username)
}
var userJikan UserJikan
err = json.Unmarshal(dataJikan, &userJikan)
if err != nil {
return nil, nil, err
}
// workaround number #2 bcs jikan kekw
if userJikan.Data.MalID == 0 {
dataJikan, err = GetDataJikan(userApiJikan + username)
if err != nil {
return nil, nil, err
}
if strings.Contains(string(dataJikan), "BadResponseException") {
return nil, nil, fmt.Errorf("user not found: %s", username)
}
err = json.Unmarshal(dataJikan, &userJikan)
if err != nil {
return nil, nil, err
}
}
if userJikan.Data.MalID == 0 {
return nil, nil, fmt.Errorf("user returned empty object: %s", username)
}
// convert to user
user = UserConvert(&userJikan)
data, err = json.Marshal(&user)
if err != nil {
return &user, data, err
} }
userCache.Set(username, data) userCache.Set(username, data)
return userp, data, err
} else { } else {
err = json.Unmarshal(data, &user) err = json.Unmarshal(data, &user)
} }
@@ -133,45 +116,45 @@ func GetUserData(username string) (*User, []byte, error) {
return &user, data, err return &user, data, err
} }
func GetSeasonDataAll() ([]Anime, []byte, error) { func GetUserData(username string) (*User, []byte, error) {
seasonStr := GetCurrentSeasonString() dataJikan, err := GetDataJikan(userApiJikan + username)
color.Infoln("Aktuelle Season (" + seasonStr + ") abfragen...")
logOut.WriteLine("📺 Aktuelle Season (" + seasonStr + ") abfragen...")
data, _, err := GetSeasonData(seasonApiJikan, 1)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
color.Infof("%d Anime auf %d Seiten\n", data.Pagination.Items.Total, data.Pagination.LastVisiblePage) if strings.Contains(string(dataJikan), "BadResponseException") {
logOut.WriteLine(fmt.Sprintf("📺 %d Anime auf %d Seiten", data.Pagination.Items.Total, data.Pagination.LastVisiblePage)) return nil, nil, fmt.Errorf("user not found: %s", username)
animes := make([]Anime, 0)
// convert to anime
for _, a := range data.Data {
animes = append(animes, JikanConvert(&a))
} }
for i := 2; data.Pagination.HasNextPage; i++ { var userJikan UserJikan
color.Infof("Seite %d abfragen...\n", i) err = json.Unmarshal(dataJikan, &userJikan)
logOut.WriteLine(fmt.Sprintf("📺 Seite %d abfragen...", i)) if err != nil {
newData, _, err := GetSeasonData(seasonApiJikan, i) return nil, nil, err
}
// workaround number #2 bcs jikan kekw
if userJikan.Data.MalID == 0 {
dataJikan, err = GetDataJikan(userApiJikan + username)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
data.Pagination.CurrentPage = newData.Pagination.CurrentPage if strings.Contains(string(dataJikan), "BadResponseException") {
data.Pagination.HasNextPage = newData.Pagination.HasNextPage return nil, nil, fmt.Errorf("user not found: %s", username)
data.Pagination.Items.Count += newData.Pagination.Items.Count }
// convert to anime err = json.Unmarshal(dataJikan, &userJikan)
for _, a := range newData.Data { if err != nil {
animes = append(animes, JikanConvert(&a)) return nil, nil, err
} }
} }
color.Infof("%d Anime bekommen\n", len(animes)) if userJikan.Data.MalID == 0 {
logOut.WriteLine(fmt.Sprintf("📺 %d Anime bekommen", len(animes))) return nil, nil, fmt.Errorf("user returned empty object: %s", username)
bytes, err := json.Marshal(animes) }
return animes, bytes, err // convert to user
user := UserConvert(&userJikan)
data, err := json.Marshal(&user)
return &user, data, err
} }
func GetNextSeasonDataAll(season string) ([]Anime, []byte, error) { func GetSeasonDataAll(season string) ([]Anime, []byte, error) {
color.Infoln("Nächste Season (" + season + ") abfragen...") color.Infoln("Season " + season + " abfragen...")
logOut.WriteLine("📺 Nächste Season (" + season + ") abfragen...") logOut.WriteLine("📺 Season " + season + " abfragen...")
data, _, err := GetSeasonData(seasonStrJikan+season, 1) data, _, err := GetSeasonData(seasonStrJikan+season, 1)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@@ -292,3 +275,35 @@ func GetNextSeasonString() string {
return fmt.Sprintf("%04d", now.Year()) return fmt.Sprintf("%04d", now.Year())
} }
} }
func GetNextNextSeasonString() string {
var now = time.Now()
switch now.Month() {
case time.January, time.February, time.March:
return fmt.Sprintf("%04d/summer", now.Year())
case time.April, time.May, time.June:
return fmt.Sprintf("%04d/fall", now.Year())
case time.July, time.August, time.September:
return fmt.Sprintf("%04d/winter", now.Year()+1)
case time.October, time.November, time.December:
return fmt.Sprintf("%04d/spring", now.Year()+1)
default:
return fmt.Sprintf("%04d", now.Year())
}
}
func GetLastSeasonString() string {
var now = time.Now()
switch now.Month() {
case time.January, time.February, time.March:
return fmt.Sprintf("%04d/fall", now.Year()-1)
case time.April, time.May, time.June:
return fmt.Sprintf("%04d/winter", now.Year())
case time.July, time.August, time.September:
return fmt.Sprintf("%04d/spring", now.Year())
case time.October, time.November, time.December:
return fmt.Sprintf("%04d/summer", now.Year())
default:
return fmt.Sprintf("%04d", now.Year())
}
}

114
labersack.go Normal file
View File

@@ -0,0 +1,114 @@
package main
import (
"fmt"
"strings"
"time"
"github.com/bwmarrin/discordgo"
"github.com/gookit/color"
)
func SendAppointBroadcast(creator string, app *Appointment) {
if app != nil && discc != nil {
watchData, err := GetAnimeWatchFromDb(app.Anime)
if err != nil {
color.Errorln(err.Error())
return
}
anime, err := SearchAnime(app.Anime)
if err != nil {
color.Errorln(err.Error())
return
}
var msgStart strings.Builder
for _, u := range watchData.Users {
uData, err := ReadUser(u.Username)
if err != nil || uData.DiscordID == 0 {
continue
}
msgStart.WriteString(fmt.Sprintf("<@%d>", uData.DiscordID))
}
msgs := fmt.Sprintf("🗓️ New appointment for [%s](%s%d) at\n<t:%d:F>\n<t:%d:R>", anime.Title, *husoWebAnimeUri, app.Anime, app.Time.Unix(), app.Time.Unix())
msge := discordgo.MessageEmbed{
Description: msgs,
URL: fmt.Sprintf("%s%d", *husoWebAnimeUri, app.Anime),
Thumbnail: &discordgo.MessageEmbedThumbnail{URL: anime.ImageLargeURL},
Color: 7187428,
Footer: &discordgo.MessageEmbedFooter{
Text: "huso " + husoVersion,
},
}
user, _, err := GetUser(creator)
if err == nil {
msge.Author = &discordgo.MessageEmbedAuthor{
Name: user.Username,
IconURL: user.ImageURL,
URL: fmt.Sprintf("%s%s", *husoWebUserUri, creator),
}
}
msgc := discordgo.MessageSend{
Content: msgStart.String(),
Embeds: []*discordgo.MessageEmbed{&msge},
}
_, err = discc.ChannelMessageSendComplex(*discordChannel, &msgc)
if err != nil {
color.Errorln(err.Error())
}
}
}
func AnnounceBomb(animeId, meetingUnix int64, timer time.Duration) {
time.Sleep(timer)
AnnounceAppointmentSoon(animeId, meetingUnix)
}
func AnnounceAppointmentSoon(animeId, meetingUnix int64) {
if animeId != 0 && discc != nil {
watchData, err := GetAnimeWatchFromDb(animeId)
if err != nil {
color.Errorln(err.Error())
return
}
anime, err := SearchAnime(animeId)
if err != nil {
color.Errorln(err.Error())
return
}
var msgStart strings.Builder
for _, u := range watchData.Users {
uData, err := ReadUser(u.Username)
if err != nil || uData.DiscordID == 0 {
continue
}
msgStart.WriteString(fmt.Sprintf("<@%d>", uData.DiscordID))
}
msgs := fmt.Sprintf("⏰ Appointment starting <t:%d:R>!\n[%s](%s%d) at\n<t:%d:F>", meetingUnix, anime.Title, *husoWebAnimeUri, animeId, meetingUnix)
msge := discordgo.MessageEmbed{
Description: msgs,
URL: fmt.Sprintf("%s%d", *husoWebAnimeUri, animeId),
Thumbnail: &discordgo.MessageEmbedThumbnail{URL: anime.ImageLargeURL},
Color: 7187428,
Footer: &discordgo.MessageEmbedFooter{
Text: "huso " + husoVersion,
},
}
msgc := discordgo.MessageSend{
Content: msgStart.String(),
Embeds: []*discordgo.MessageEmbed{&msge},
}
_, err = discc.ChannelMessageSendComplex(*discordChannel, &msgc)
if err != nil {
color.Errorln(err.Error())
}
}
}

13
nuss.go
View File

@@ -79,14 +79,16 @@ func AddUserToAnime(username string, userId, animeId int64, progress int, update
return &anime, err return &anime, err
} }
func AddUserToAppointment(username string, animeId int64, meeting time.Time) (*Appointment, error) { func AddUserToAppointment(username string, animeId int64, meeting time.Time) (*Appointment, bool, error) {
var appoint Appointment var appoint Appointment
fresh := false
err := db.Update( err := db.Update(
func(tx *nutsdb.Tx) error { func(tx *nutsdb.Tx) error {
keyBytes := Int64AndDateToBytes(animeId, meeting) keyBytes := Int64AndDateToBytes(animeId, meeting)
e, err := tx.Get(bucketAppoint, keyBytes) e, err := tx.Get(bucketAppoint, keyBytes)
var users []string var users []string
if err != nil { if err != nil {
fresh = true
users = make([]string, 0) users = make([]string, 0)
} else { } else {
// parse user list // parse user list
@@ -121,7 +123,7 @@ func AddUserToAppointment(username string, animeId int64, meeting time.Time) (*A
} }
return tx.Put(bucketAppoint, keyBytes, newData, nutsdb.Persistent) return tx.Put(bucketAppoint, keyBytes, newData, nutsdb.Persistent)
}) })
return &appoint, err return &appoint, fresh, err
} }
func DeleteUserFromAnime(username string, userId, animeId int64) (*AnimeUser, error) { func DeleteUserFromAnime(username string, userId, animeId int64) (*AnimeUser, error) {
@@ -430,9 +432,10 @@ func ReadAppointments() ([]Appointment, error) {
return err return err
} }
appointment := Appointment{ appointment := Appointment{
Anime: animeId, Anime: animeId,
Time: date, Time: date,
Users: appointmentUsers, Users: appointmentUsers,
Announced: false,
} }
appoints = append(appoints, appointment) appoints = append(appoints, appointment)
} }

325
ober.go
View File

@@ -4,20 +4,31 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"log" "log"
"sort"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/fasthttp/router" "github.com/fasthttp/router"
"github.com/gookit/color"
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
"github.com/xujiajun/nutsdb" "github.com/xujiajun/nutsdb"
) )
const (
xHusoAuth = "X-HUSO-AUTH"
htmlContentType = "text/html; charset=utf-8"
jsonContentType = "application/json; charset=utf-8"
textContentType = "text/plain; charset=utf-8"
)
func RunWebserv() { func RunWebserv() {
r := router.New() r := router.New()
r.GET("/", Headers(Start)) r.GET("/", Headers(Start))
r.GET("/api/season", Headers(Season)) r.GET("/api/season", Headers(Season))
r.GET("/api/nextseason", Headers(SeasonNext)) r.GET("/api/nextseason", Headers(SeasonNext))
r.GET("/api/nextnextseason", Headers(SeasonNextNext))
r.GET("/api/lastseason", Headers(SeasonLast))
r.GET("/api/auth/{user}", Headers(AuthTest)) r.GET("/api/auth/{user}", Headers(AuthTest))
r.GET("/api/anime/{id}", Headers(AnimeGet)) r.GET("/api/anime/{id}", Headers(AnimeGet))
r.GET("/api/animesearch", Headers(AnimeSearchGet)) r.GET("/api/animesearch", Headers(AnimeSearchGet))
@@ -25,12 +36,15 @@ func RunWebserv() {
r.GET("/api/chat/{id}", Headers(ChatGet)) r.GET("/api/chat/{id}", Headers(ChatGet))
r.GET("/api/log", Headers(LogGet)) r.GET("/api/log", Headers(LogGet))
r.GET("/api/user/{user?}", Headers(UserGet)) r.GET("/api/user/{user?}", Headers(UserGet))
r.GET("/api/userdata/{user}/{data}", Headers(UserStashGet))
r.GET("/api/watch/{user?}", Headers(WatchGet)) r.GET("/api/watch/{user?}", Headers(WatchGet))
r.GET("/api/watchext/{user?}", Headers(WatchExtendedGet)) r.GET("/api/watchext/{user?}", Headers(WatchExtendedGet))
r.POST("/api/appointment/{user}", Headers(AppointmentPost)) r.POST("/api/appointment/{user}", Headers(AppointmentPost))
r.POST("/api/chat/{id}/{user}", Headers(ChatPost)) r.POST("/api/chat/{id}/{user}", Headers(ChatPost))
r.POST("/api/register", Headers(Register)) r.POST("/api/register", Headers(Register))
r.POST("/api/watch/{user}", Headers(WatchPost)) r.POST("/api/watch/{user}", Headers(WatchPost))
r.PATCH("/api/register", Headers(RegisterUpdate))
r.PUT("/api/userdata/{user}/{data}", Headers(UserStashPut))
r.DELETE("/api/appointment/{user}", Headers(AppointmentDelete)) r.DELETE("/api/appointment/{user}", Headers(AppointmentDelete))
r.DELETE("/api/register", Headers(UnRegister)) r.DELETE("/api/register", Headers(UnRegister))
r.DELETE("/api/watch/{user}", Headers(WatchDelete)) r.DELETE("/api/watch/{user}", Headers(WatchDelete))
@@ -38,7 +52,7 @@ func RunWebserv() {
} }
func Start(ctx *fasthttp.RequestCtx) { func Start(ctx *fasthttp.RequestCtx) {
season, err := GetSeasonCache(seasonApiJikan) season, err := GetSeasonCache(GetCurrentSeasonString())
if err != nil { if err != nil {
addErrorToCtx(ctx, err) addErrorToCtx(ctx, err)
return return
@@ -46,18 +60,22 @@ func Start(ctx *fasthttp.RequestCtx) {
oracles, err := MmReadOracle() oracles, err := MmReadOracle()
if err != nil { if err != nil {
addErrorToCtx(ctx, err) color.Errorln(err.Error())
return
} }
WriteIndex(ctx, season, oracles, logOut.String()) charts, err := BuildMovieCharts()
if err != nil {
color.Errorln(err.Error())
}
ctx.SetContentType("text/html; charset=utf-8") WriteIndex(ctx, season, oracles, charts, logOut.String())
ctx.SetContentType(htmlContentType)
ctx.SetStatusCode(fasthttp.StatusOK) ctx.SetStatusCode(fasthttp.StatusOK)
} }
func AuthTest(ctx *fasthttp.RequestCtx) { func AuthTest(ctx *fasthttp.RequestCtx) {
auth := ctx.Request.Header.Peek("X-HUSO-AUTH") auth := ctx.Request.Header.Peek(xHusoAuth)
if ctx.UserValue("user") == nil || auth == nil || string(auth) == "" { if ctx.UserValue("user") == nil || auth == nil || string(auth) == "" {
ctx.SetStatusCode(fasthttp.StatusBadRequest) ctx.SetStatusCode(fasthttp.StatusBadRequest)
return return
@@ -72,20 +90,12 @@ func AuthTest(ctx *fasthttp.RequestCtx) {
} }
func Season(ctx *fasthttp.RequestCtx) { func Season(ctx *fasthttp.RequestCtx) {
data, err := seasoncache.Get(seasonApiJikan) data, err := seasoncache.Get(GetCurrentSeasonString())
if err != nil { if err != nil {
addErrorToCtx(ctx, err) addErrorToCtx(ctx, err)
return return
} }
setResponseJson(ctx, data)
err = writeResponseBody(ctx, data)
if err != nil {
addErrorToCtx(ctx, err)
return
}
ctx.SetContentType("application/json; charset=utf-8")
ctx.SetStatusCode(fasthttp.StatusOK)
} }
func SeasonNext(ctx *fasthttp.RequestCtx) { func SeasonNext(ctx *fasthttp.RequestCtx) {
@@ -94,15 +104,25 @@ func SeasonNext(ctx *fasthttp.RequestCtx) {
addErrorToCtx(ctx, err) addErrorToCtx(ctx, err)
return return
} }
setResponseJson(ctx, data)
}
err = writeResponseBody(ctx, data) func SeasonNextNext(ctx *fasthttp.RequestCtx) {
data, err := seasoncache.Get(GetNextNextSeasonString())
if err != nil { if err != nil {
addErrorToCtx(ctx, err) addErrorToCtx(ctx, err)
return return
} }
setResponseJson(ctx, data)
}
ctx.SetContentType("application/json; charset=utf-8") func SeasonLast(ctx *fasthttp.RequestCtx) {
ctx.SetStatusCode(fasthttp.StatusOK) data, err := seasoncache.Get(GetLastSeasonString())
if err != nil {
addErrorToCtx(ctx, err)
return
}
setResponseJson(ctx, data)
} }
func AnimeGet(ctx *fasthttp.RequestCtx) { func AnimeGet(ctx *fasthttp.RequestCtx) {
@@ -129,14 +149,7 @@ func AnimeGet(ctx *fasthttp.RequestCtx) {
addErrorToCtx(ctx, err) addErrorToCtx(ctx, err)
return return
} }
_, err = ctx.Write(bytes) setResponseJson(ctx, bytes)
if err != nil {
addErrorToCtx(ctx, err)
return
}
ctx.SetContentType("application/json; charset=utf-8")
ctx.SetStatusCode(fasthttp.StatusOK)
} }
func AnimeSearchGet(ctx *fasthttp.RequestCtx) { func AnimeSearchGet(ctx *fasthttp.RequestCtx) {
@@ -151,14 +164,7 @@ func AnimeSearchGet(ctx *fasthttp.RequestCtx) {
addErrorToCtx(ctx, err) addErrorToCtx(ctx, err)
return return
} }
_, err = ctx.Write(bytes) setResponseJson(ctx, bytes)
if err != nil {
addErrorToCtx(ctx, err)
return
}
ctx.SetContentType("application/json; charset=utf-8")
ctx.SetStatusCode(fasthttp.StatusOK)
} }
func AppointmentGet(ctx *fasthttp.RequestCtx) { func AppointmentGet(ctx *fasthttp.RequestCtx) {
@@ -178,14 +184,7 @@ func AppointmentGet(ctx *fasthttp.RequestCtx) {
addErrorToCtx(ctx, err) addErrorToCtx(ctx, err)
return return
} }
_, err = ctx.Write(bytes) setResponseJson(ctx, bytes)
if err != nil {
addErrorToCtx(ctx, err)
return
}
ctx.SetContentType("application/json; charset=utf-8")
ctx.SetStatusCode(fasthttp.StatusOK)
} }
func ChatGet(ctx *fasthttp.RequestCtx) { func ChatGet(ctx *fasthttp.RequestCtx) {
@@ -224,7 +223,7 @@ func ChatGet(ctx *fasthttp.RequestCtx) {
addErrorToCtx(ctx, err) addErrorToCtx(ctx, err)
return return
} }
ctx.SetContentType("text/plain; charset=utf-8") ctx.SetContentType(textContentType)
ctx.SetStatusCode(fasthttp.StatusOK) ctx.SetStatusCode(fasthttp.StatusOK)
} }
@@ -234,7 +233,7 @@ func LogGet(ctx *fasthttp.RequestCtx) {
addErrorToCtx(ctx, err) addErrorToCtx(ctx, err)
return return
} }
ctx.SetContentType("text/plain; charset=utf-8") ctx.SetContentType(textContentType)
ctx.SetStatusCode(fasthttp.StatusOK) ctx.SetStatusCode(fasthttp.StatusOK)
} }
@@ -243,7 +242,7 @@ func UserGet(ctx *fasthttp.RequestCtx) {
users := make([]User, 0) users := make([]User, 0)
if usrVal != nil { if usrVal != nil {
// get specific user // get specific user
user, _, err := GetUserData(fmt.Sprintf("%s", usrVal)) user, _, err := GetUser(fmt.Sprintf("%s", usrVal))
if err != nil { if err != nil {
ctx.WriteString(err.Error()) ctx.WriteString(err.Error())
ctx.SetStatusCode(fasthttp.StatusNotFound) ctx.SetStatusCode(fasthttp.StatusNotFound)
@@ -261,7 +260,7 @@ func UserGet(ctx *fasthttp.RequestCtx) {
regUsers = make([]UserData, 0) regUsers = make([]UserData, 0)
} }
for _, u := range regUsers { for _, u := range regUsers {
user, _, err := GetUserData(u.Username) user, _, err := GetUser(u.Username)
if err != nil { if err != nil {
addErrorToCtx(ctx, err) addErrorToCtx(ctx, err)
return return
@@ -275,14 +274,11 @@ func UserGet(ctx *fasthttp.RequestCtx) {
addErrorToCtx(ctx, err) addErrorToCtx(ctx, err)
return return
} }
_, err = ctx.Write(bytes) setResponseJson(ctx, bytes)
if err != nil { }
addErrorToCtx(ctx, err)
return
}
ctx.SetContentType("application/json; charset=utf-8") func UserStashGet(ctx *fasthttp.RequestCtx) {
ctx.SetStatusCode(fasthttp.StatusOK) processUserStashReq(ctx, false)
} }
func WatchGet(ctx *fasthttp.RequestCtx) { func WatchGet(ctx *fasthttp.RequestCtx) {
@@ -297,14 +293,7 @@ func WatchGet(ctx *fasthttp.RequestCtx) {
addErrorToCtx(ctx, err) addErrorToCtx(ctx, err)
return return
} }
_, err = ctx.Write(bytes) setResponseJson(ctx, bytes)
if err != nil {
addErrorToCtx(ctx, err)
return
}
ctx.SetContentType("application/json; charset=utf-8")
ctx.SetStatusCode(fasthttp.StatusOK)
} }
func WatchExtendedGet(ctx *fasthttp.RequestCtx) { func WatchExtendedGet(ctx *fasthttp.RequestCtx) {
@@ -340,14 +329,7 @@ func WatchExtendedGet(ctx *fasthttp.RequestCtx) {
addErrorToCtx(ctx, err) addErrorToCtx(ctx, err)
return return
} }
_, err = ctx.Write(bytes) setResponseJson(ctx, bytes)
if err != nil {
addErrorToCtx(ctx, err)
return
}
ctx.SetContentType("application/json; charset=utf-8")
ctx.SetStatusCode(fasthttp.StatusOK)
} }
func Register(ctx *fasthttp.RequestCtx) { func Register(ctx *fasthttp.RequestCtx) {
@@ -382,7 +364,7 @@ func Register(ctx *fasthttp.RequestCtx) {
return return
} }
// check user legit // check user legit
userData, _, err := GetUserData(register.Username) userData, _, err := GetUser(register.Username)
if err != nil { if err != nil {
ctx.WriteString(err.Error()) ctx.WriteString(err.Error())
ctx.SetStatusCode(fasthttp.StatusExpectationFailed) ctx.SetStatusCode(fasthttp.StatusExpectationFailed)
@@ -393,11 +375,21 @@ func Register(ctx *fasthttp.RequestCtx) {
ctx.SetStatusCode(fasthttp.StatusExpectationFailed) ctx.SetStatusCode(fasthttp.StatusExpectationFailed)
return return
} }
// check user discord id
if register.DiscordID != 0 && discc != nil {
_, err = discc.User(fmt.Sprint(register.DiscordID != 0))
if err != nil {
ctx.WriteString("Discord user id is kaputt")
ctx.SetStatusCode(fasthttp.StatusExpectationFailed)
return
}
}
// REGISTER // REGISTER
user := UserData{ user := UserData{
Username: register.Username, Username: register.Username,
MalID: register.MalID, MalID: register.MalID,
Secret: register.Secret, Secret: register.Secret,
DiscordID: register.DiscordID,
} }
err = SaveUser(&user) err = SaveUser(&user)
if err != nil { if err != nil {
@@ -405,9 +397,74 @@ func Register(ctx *fasthttp.RequestCtx) {
return return
} }
ctx.SetBody(body) setResponseJson(ctx, body)
ctx.SetContentType("application/json; charset=utf-8") }
ctx.SetStatusCode(fasthttp.StatusOK)
func RegisterUpdate(ctx *fasthttp.RequestCtx) {
auth := ctx.Request.Header.Peek(xHusoAuth)
if auth == nil || string(auth) == "" || !strings.Contains(string(ctx.Request.Header.ContentType()), "application/json") {
ctx.SetStatusCode(fasthttp.StatusBadRequest)
return
}
body := ctx.PostBody()
var regUpdate RegisterData
err := json.Unmarshal(body, &regUpdate)
if err != nil {
ctx.WriteString(err.Error())
ctx.SetStatusCode(fasthttp.StatusBadRequest)
return
}
if regUpdate.MalID == 0 || regUpdate.Username == "" || regUpdate.Sauce == "" {
ctx.WriteString("Sprich JSON du Hurensohn")
ctx.SetStatusCode(fasthttp.StatusBadRequest)
return
}
legit, _ := GheddoAuth(regUpdate.Username, string(auth))
if !legit {
ctx.SetStatusCode(fasthttp.StatusUnauthorized)
return
}
calcSauce := Sauce(regUpdate.MalID, regUpdate.Username)
if calcSauce != strings.ToLower(regUpdate.Sauce) {
ctx.WriteString("Möge die Sauce mit dir sein")
ctx.SetStatusCode(fasthttp.StatusBadRequest)
return
}
// check user exists
user, err := ReadUser(regUpdate.Username)
if err != nil {
ctx.WriteString("Dich gibts hier nicht wtf")
ctx.SetStatusCode(fasthttp.StatusNotFound)
return
}
if regUpdate.MalID != user.MalID {
ctx.WriteString("MAL id ändern is nich")
ctx.SetStatusCode(fasthttp.StatusBadRequest)
return
}
if regUpdate.Secret != "" {
user.Secret = regUpdate.Secret
}
// check user discord id
if regUpdate.DiscordID != 0 && discc != nil {
_, err = discc.User(fmt.Sprint(regUpdate.DiscordID))
if err != nil {
ctx.WriteString("Discord user id is kaputt")
ctx.SetStatusCode(fasthttp.StatusExpectationFailed)
return
}
}
user.DiscordID = regUpdate.DiscordID
err = SaveUser(user)
if err != nil {
addErrorToCtx(ctx, err)
return
}
setResponseJson(ctx, body)
} }
func AppointmentPost(ctx *fasthttp.RequestCtx) { func AppointmentPost(ctx *fasthttp.RequestCtx) {
@@ -415,7 +472,7 @@ func AppointmentPost(ctx *fasthttp.RequestCtx) {
} }
func ChatPost(ctx *fasthttp.RequestCtx) { func ChatPost(ctx *fasthttp.RequestCtx) {
auth := ctx.Request.Header.Peek("X-HUSO-AUTH") auth := ctx.Request.Header.Peek(xHusoAuth)
if ctx.UserValue("id") == nil || ctx.UserValue("user") == nil || auth == nil || string(auth) == "" || !strings.Contains(string(ctx.Request.Header.ContentType()), "text/plain") { if ctx.UserValue("id") == nil || ctx.UserValue("user") == nil || auth == nil || string(auth) == "" || !strings.Contains(string(ctx.Request.Header.ContentType()), "text/plain") {
ctx.SetStatusCode(fasthttp.StatusBadRequest) ctx.SetStatusCode(fasthttp.StatusBadRequest)
return return
@@ -456,7 +513,7 @@ func ChatPost(ctx *fasthttp.RequestCtx) {
addErrorToCtx(ctx, err) addErrorToCtx(ctx, err)
return return
} }
ctx.SetContentType("text/plain; charset=utf-8") ctx.SetContentType(textContentType)
ctx.SetStatusCode(fasthttp.StatusOK) ctx.SetStatusCode(fasthttp.StatusOK)
} }
@@ -464,12 +521,17 @@ func WatchPost(ctx *fasthttp.RequestCtx) {
processUpdateReq(ctx, true) processUpdateReq(ctx, true)
} }
func UserStashPut(ctx *fasthttp.RequestCtx) {
processUserStashReq(ctx, true)
}
func AppointmentDelete(ctx *fasthttp.RequestCtx) { func AppointmentDelete(ctx *fasthttp.RequestCtx) {
processUpdateAppointmentReq(ctx, false) processUpdateAppointmentReq(ctx, false)
} }
func UnRegister(ctx *fasthttp.RequestCtx) { func UnRegister(ctx *fasthttp.RequestCtx) {
if !strings.Contains(string(ctx.Request.Header.ContentType()), "application/json") { auth := ctx.Request.Header.Peek(xHusoAuth)
if auth == nil || string(auth) == "" || !strings.Contains(string(ctx.Request.Header.ContentType()), "application/json") {
ctx.SetStatusCode(fasthttp.StatusBadRequest) ctx.SetStatusCode(fasthttp.StatusBadRequest)
return return
} }
@@ -486,6 +548,11 @@ func UnRegister(ctx *fasthttp.RequestCtx) {
ctx.SetStatusCode(fasthttp.StatusBadRequest) ctx.SetStatusCode(fasthttp.StatusBadRequest)
return return
} }
legit, _ := GheddoAuth(register.Username, string(auth))
if !legit {
ctx.SetStatusCode(fasthttp.StatusUnauthorized)
return
}
calcSauce := Sauce(register.MalID, register.Username) calcSauce := Sauce(register.MalID, register.Username)
if calcSauce != strings.ToLower(register.Sauce) { if calcSauce != strings.ToLower(register.Sauce) {
ctx.WriteString("Möge die Sauce mit dir sein") ctx.WriteString("Möge die Sauce mit dir sein")
@@ -514,9 +581,7 @@ func UnRegister(ctx *fasthttp.RequestCtx) {
return return
} }
ctx.SetBody(body) setResponseJson(ctx, body)
ctx.SetContentType("application/json; charset=utf-8")
ctx.SetStatusCode(fasthttp.StatusOK)
} }
func WatchDelete(ctx *fasthttp.RequestCtx) { func WatchDelete(ctx *fasthttp.RequestCtx) {
@@ -579,11 +644,17 @@ func watchGetLogic(ctx *fasthttp.RequestCtx) ([]AnimeUser, error) {
} }
animesUsers = filteredAnimes animesUsers = filteredAnimes
} }
sort.Slice(animesUsers, func(i, j int) bool { return animesUsers[i].Anime < animesUsers[j].Anime })
sort.SliceStable(animesUsers, func(i, j int) bool {
return AverageEpPerUser(animesUsers[i].Users) > AverageEpPerUser(animesUsers[j].Users)
})
return animesUsers, err return animesUsers, err
} }
func processUpdateReq(ctx *fasthttp.RequestCtx, update bool) { func processUpdateReq(ctx *fasthttp.RequestCtx, update bool) {
auth := ctx.Request.Header.Peek("X-HUSO-AUTH") auth := ctx.Request.Header.Peek(xHusoAuth)
if ctx.UserValue("user") == nil || auth == nil || string(auth) == "" || !strings.Contains(string(ctx.Request.Header.ContentType()), "application/json") { if ctx.UserValue("user") == nil || auth == nil || string(auth) == "" || !strings.Contains(string(ctx.Request.Header.ContentType()), "application/json") {
ctx.SetStatusCode(fasthttp.StatusBadRequest) ctx.SetStatusCode(fasthttp.StatusBadRequest)
return return
@@ -599,7 +670,9 @@ func processUpdateReq(ctx *fasthttp.RequestCtx, update bool) {
var animes []AnimeUser var animes []AnimeUser
err := json.Unmarshal(body, &animes) err := json.Unmarshal(body, &animes)
if err != nil || len(animes) == 0 { if err != nil || len(animes) == 0 {
ctx.WriteString(err.Error()) if err != nil {
ctx.WriteString(err.Error())
}
ctx.SetStatusCode(fasthttp.StatusBadRequest) ctx.SetStatusCode(fasthttp.StatusBadRequest)
return return
} }
@@ -619,7 +692,7 @@ func processUpdateReq(ctx *fasthttp.RequestCtx, update bool) {
if update { if update {
// anime exitsts => save // anime exitsts => save
// get watch progress // get watch progress
progress, updated, listState, _ := FetchProgress(anime.Anime, userId, username, 0) progress, updated, _, listState, _ := FetchProgress(anime.Anime, username)
// anime is already completed big baka user // anime is already completed big baka user
if listState == malApiStatusC { if listState == malApiStatusC {
ctx.WriteString("Du hast schon fertig geschaut bro") ctx.WriteString("Du hast schon fertig geschaut bro")
@@ -649,16 +722,11 @@ func processUpdateReq(ctx *fasthttp.RequestCtx, update bool) {
addErrorToCtx(ctx, err) addErrorToCtx(ctx, err)
return return
} }
err = writeResponseBody(ctx, data) setResponseJson(ctx, data)
if err != nil {
addErrorToCtx(ctx, err)
return
}
ctx.SetContentType("application/json; charset=utf-8")
} }
func processUpdateAppointmentReq(ctx *fasthttp.RequestCtx, update bool) { func processUpdateAppointmentReq(ctx *fasthttp.RequestCtx, update bool) {
auth := ctx.Request.Header.Peek("X-HUSO-AUTH") auth := ctx.Request.Header.Peek(xHusoAuth)
if ctx.UserValue("user") == nil || auth == nil || string(auth) == "" || !strings.Contains(string(ctx.Request.Header.ContentType()), "application/json") { if ctx.UserValue("user") == nil || auth == nil || string(auth) == "" || !strings.Contains(string(ctx.Request.Header.ContentType()), "application/json") {
ctx.SetStatusCode(fasthttp.StatusBadRequest) ctx.SetStatusCode(fasthttp.StatusBadRequest)
return return
@@ -674,7 +742,9 @@ func processUpdateAppointmentReq(ctx *fasthttp.RequestCtx, update bool) {
var appoints []Appointment var appoints []Appointment
err := json.Unmarshal(body, &appoints) err := json.Unmarshal(body, &appoints)
if err != nil || len(appoints) == 0 { if err != nil || len(appoints) == 0 {
ctx.WriteString(err.Error()) if err != nil {
ctx.WriteString(err.Error())
}
ctx.SetStatusCode(fasthttp.StatusBadRequest) ctx.SetStatusCode(fasthttp.StatusBadRequest)
return return
} }
@@ -683,6 +753,7 @@ func processUpdateAppointmentReq(ctx *fasthttp.RequestCtx, update bool) {
for i, appointment := range appoints { for i, appointment := range appoints {
var appData *Appointment var appData *Appointment
var found bool var found bool
fresh := false
if update { if update {
// kann sich (noch) nicht in der Vergagenheit verabreden // kann sich (noch) nicht in der Vergagenheit verabreden
@@ -704,7 +775,7 @@ func processUpdateAppointmentReq(ctx *fasthttp.RequestCtx, update bool) {
} }
// save appointment and get list // save appointment and get list
appData, err = AddUserToAppointment(username, appointment.Anime, appointment.Time) appData, fresh, err = AddUserToAppointment(username, appointment.Anime, appointment.Time)
} else { } else {
found, err = CheckAnimeExistInDb(appointment.Anime) found, err = CheckAnimeExistInDb(appointment.Anime)
if err != nil { if err != nil {
@@ -724,6 +795,10 @@ func processUpdateAppointmentReq(ctx *fasthttp.RequestCtx, update bool) {
return return
} }
appoints[i].Users = appData.Users appoints[i].Users = appData.Users
if update && fresh {
SendAppointBroadcast(username, appData)
}
} }
data, err := json.Marshal(appoints) data, err := json.Marshal(appoints)
@@ -731,22 +806,58 @@ func processUpdateAppointmentReq(ctx *fasthttp.RequestCtx, update bool) {
addErrorToCtx(ctx, err) addErrorToCtx(ctx, err)
return return
} }
err = writeResponseBody(ctx, data) setResponseJson(ctx, data)
if err != nil {
addErrorToCtx(ctx, err)
return
}
ctx.SetContentType("application/json; charset=utf-8")
} }
func writeResponseBody(ctx *fasthttp.RequestCtx, bytes []byte) error { func processUserStashReq(ctx *fasthttp.RequestCtx, update bool) {
_, err := ctx.Write(bytes) auth := ctx.Request.Header.Peek(xHusoAuth)
if err != nil { dataPath := fmt.Sprintf("%s", ctx.UserValue("data"))
return err if auth == nil || string(auth) == "" || dataPath == "" {
ctx.SetStatusCode(fasthttp.StatusBadRequest)
return
} }
ctx.SetContentType("application/json; charset=utf-8")
username := fmt.Sprintf("%s", ctx.UserValue("user"))
legit, _ := GheddoAuth(username, string(auth))
if !legit {
ctx.SetStatusCode(fasthttp.StatusUnauthorized)
return
}
if update {
compressed, err := CompressZstd(ctx.PostBody())
if err != nil {
addErrorToCtx(ctx, err)
return
}
err = DbSave(bucketStash, fmt.Sprintf("%s.%s", username, dataPath), compressed)
if err != nil {
addErrorToCtx(ctx, err)
return
}
ctx.SetStatusCode(fasthttp.StatusAccepted)
} else {
compressed, err := DbRead(bucketStash, []byte(fmt.Sprintf("%s.%s", username, dataPath)))
if err != nil {
addErrorToCtx(ctx, err)
return
}
decompressed, err := DecompressZstd(compressed)
if err != nil {
addErrorToCtx(ctx, err)
return
}
ctx.SetBody(decompressed)
ctx.SetStatusCode(fasthttp.StatusOK)
}
}
func setResponseJson(ctx *fasthttp.RequestCtx, bytes []byte) {
ctx.SetBody(bytes)
ctx.SetContentType(jsonContentType)
ctx.SetStatusCode(fasthttp.StatusOK) ctx.SetStatusCode(fasthttp.StatusOK)
return err
} }
func addErrorToCtx(ctx *fasthttp.RequestCtx, err error) { func addErrorToCtx(ctx *fasthttp.RequestCtx, err error) {

View File

@@ -12,7 +12,7 @@ import (
) )
func Arbeiten() { func Arbeiten() {
for range time.Tick(time.Hour) { for range time.Tick(10 * time.Minute) {
Arbeit() Arbeit()
} }
} }
@@ -27,8 +27,11 @@ func Arbeit() {
} }
} else { } else {
cleared := 0 cleared := 0
lastAnnounce.Lock()
defer lastAnnounce.Unlock()
newCheckDate := time.Now()
for _, a := range appoints { for _, a := range appoints {
if a.Time.Before(time.Now()) { if a.Time.Add(22 * time.Hour).Before(newCheckDate) {
// appointment expired // appointment expired
keyBytes := Int64AndDateToBytes(a.Anime, a.Time) keyBytes := Int64AndDateToBytes(a.Anime, a.Time)
err = DbDelete(bucketAppoint, string(keyBytes)) err = DbDelete(bucketAppoint, string(keyBytes))
@@ -38,8 +41,14 @@ func Arbeit() {
} else { } else {
cleared++ cleared++
} }
} else {
if lastAnnounce.stamp.Add(time.Hour).Before(a.Time) && newCheckDate.Add(time.Hour).After(a.Time) {
// This has not happened and is happening soon
go AnnounceBomb(a.Anime, a.Time.Unix(), time.Until(a.Time.Add(-25*time.Minute)))
}
} }
} }
lastAnnounce.stamp = newCheckDate
if cleared > 0 { if cleared > 0 {
color.Infof("Cleared %d expired appointments\n", cleared) color.Infof("Cleared %d expired appointments\n", cleared)
logOut.WriteLine(fmt.Sprintf("♻️ Cleared %d expired appointments", cleared)) logOut.WriteLine(fmt.Sprintf("♻️ Cleared %d expired appointments", cleared))
@@ -55,11 +64,19 @@ func Arbeit() {
} }
return return
} }
// ACTUALLY STUPID ENOUGH TO FIX A LOT OF PROBLEMS
animeListCache.Reset()
color.Infoln("Animelisten abfragen...")
//logOut.WriteLine("📜 Animelisten abfragen...")
count := 0
// iterate anime // iterate anime
for _, a := range animesUsers { for _, a := range animesUsers {
// iterate users // iterate users
for _, u := range a.Users { for _, u := range a.Users {
newProgress, updated, listState, err := FetchProgress(a.Anime, u.MalID, u.Username, u.Progress) newProgress, updated, score, listState, err := FetchProgress(a.Anime, u.Username)
if err != nil { if err != nil {
color.Errorln(err.Error()) color.Errorln(err.Error())
logOut.WriteError(err) logOut.WriteError(err)
@@ -67,41 +84,49 @@ func Arbeit() {
} }
// check if user set anime as completed // check if user set anime as completed
if listState == malApiStatusC { if listState == malApiStatusC {
color.Infof("%s finished %d\n", u.Username, a.Anime) color.Infof("%s finished %d scored %d\n", u.Username, a.Anime, score)
logOut.WriteLine(fmt.Sprintf("📜 %s finished %d !", u.Username, a.Anime)) logOut.WriteLine(fmt.Sprintf("📝 %s finished %d scored %d !", u.Username, a.Anime, score))
// delete user from anime // delete user from anime
_, err = DeleteUserFromAnime(u.Username, u.MalID, a.Anime) _, err = DeleteUserFromAnime(u.Username, u.MalID, a.Anime)
if err != nil { if err != nil {
color.Errorln(err.Error()) color.Errorln(err.Error())
logOut.WriteError(err) logOut.WriteError(err)
} }
count++
continue continue
} }
// check if user set anime as dropped // check if user set anime as dropped
if listState == malApiStatusD { if listState == malApiStatusD {
color.Infof("%s dropped %d\n", u.Username, a.Anime) color.Infof("%s dropped %d\n", u.Username, a.Anime)
logOut.WriteLine(fmt.Sprintf("📜 %s dropped %d !", u.Username, a.Anime)) logOut.WriteLine(fmt.Sprintf("📝 %s dropped %d !", u.Username, a.Anime))
// delete user from anime // delete user from anime
_, err = DeleteUserFromAnime(u.Username, u.MalID, a.Anime) _, err = DeleteUserFromAnime(u.Username, u.MalID, a.Anime)
if err != nil { if err != nil {
color.Errorln(err.Error()) color.Errorln(err.Error())
logOut.WriteError(err) logOut.WriteError(err)
} }
count++
continue continue
} }
if newProgress == u.Progress { if newProgress == u.Progress {
continue continue
} }
color.Infof("%s progress von %d: %d -> %d\n", u.Username, a.Anime, u.Progress, newProgress) 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)) logOut.WriteLine(fmt.Sprintf("📝 %s progress von %d: %d -> %d", u.Username, a.Anime, u.Progress, newProgress))
// update db // update db
err = UpdateUserAnimeProgress(a.Anime, u.MalID, newProgress, updated) err = UpdateUserAnimeProgress(a.Anime, u.MalID, newProgress, updated)
if err != nil { if err != nil {
color.Errorln(err.Error()) color.Errorln(err.Error())
logOut.WriteError(err) logOut.WriteError(err)
} }
count++
} }
} }
if count > 0 {
color.Infof("%d Sachen aktualisiert\n", count)
logOut.WriteLine(fmt.Sprintf("📜 %d Sachen aktualisiert", count))
}
} }
func BissleArbeiten() { func BissleArbeiten() {
@@ -122,7 +147,7 @@ func BissleArbeit() {
} }
} else { } else {
for _, u := range regUsers { for _, u := range regUsers {
_, _, err = GetUserData(u.Username) err = refreshUser(u.Username)
if err != nil { if err != nil {
color.Errorln(err.Error()) color.Errorln(err.Error())
logOut.WriteError(err) logOut.WriteError(err)
@@ -143,30 +168,31 @@ func LangeArbeiten() {
func LangeArbeit() { func LangeArbeit() {
// season data // season data
_, bytes, err := GetSeasonDataAll() err := refreshSeason(GetCurrentSeasonString())
if err != nil { if err != nil {
color.Errorln(err.Error()) color.Errorln(err.Error())
logOut.WriteError(err) logOut.WriteError(err)
} else {
err = seasoncache.Set(seasonApiJikan, bytes)
if err != nil {
color.Errorln(err.Error())
logOut.WriteError(err)
}
} }
// next season data // next season data
nextSeason := GetNextSeasonString() err = refreshSeason(GetNextSeasonString())
_, bytes, err = GetNextSeasonDataAll(nextSeason) if err != nil {
color.Errorln(err.Error())
logOut.WriteError(err)
}
// next next season data
err = refreshSeason(GetNextNextSeasonString())
if err != nil {
color.Errorln(err.Error())
logOut.WriteError(err)
}
// last season data
err = refreshSeason(GetLastSeasonString())
if err != nil { if err != nil {
color.Errorln(err.Error()) color.Errorln(err.Error())
logOut.WriteError(err) logOut.WriteError(err)
} else {
err = seasoncache.Set(nextSeason, bytes)
if err != nil {
color.Errorln(err.Error())
logOut.WriteError(err)
}
} }
// refresh anime cache with watched // refresh anime cache with watched
@@ -179,8 +205,8 @@ func LangeArbeit() {
} }
} else { } else {
for _, a := range animesUsers { for _, a := range animesUsers {
// search season first // search seasons first
_, err = SearchSeason(a.Anime) _, err = SearchSeasons(a.Anime)
if err == nil { if err == nil {
continue continue
} }
@@ -225,6 +251,21 @@ func refreshAnime(animeId int64) error {
if err != nil { if err != nil {
return err return err
} }
animeCache.Set(key, data) return animeCache.Set(key, data)
return nil }
func refreshSeason(season string) error {
_, bytes, err := GetSeasonDataAll(season)
if err != nil {
return err
}
return seasoncache.Set(season, bytes)
}
func refreshUser(username string) error {
_, data, err := GetUserData(username)
if err != nil {
return err
}
return userCache.Set(username, data)
} }

View File

@@ -1,12 +1,16 @@
package main package main
import ( import (
"bytes"
"crypto/sha512" "crypto/sha512"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"io"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/klauspost/compress/zstd"
) )
func Sauce(malid int64, username string) string { func Sauce(malid int64, username string) string {
@@ -43,3 +47,43 @@ func BytesToInt64AndDate(bytes []byte) (int64, time.Time, error) {
func Int64AndDateToBytes(num int64, appoint time.Time) []byte { func Int64AndDateToBytes(num int64, appoint time.Time) []byte {
return []byte(fmt.Sprintf("%d%s%s", num, AppointSplit, appoint.Format(time.RFC3339))) return []byte(fmt.Sprintf("%d%s%s", num, AppointSplit, appoint.Format(time.RFC3339)))
} }
func CompressZstd(src []byte) ([]byte, error) {
var buf bytes.Buffer
encoder, err := zstd.NewWriter(&buf)
if err != nil {
return nil, err
}
reader := bytes.NewReader(src)
_, err = io.Copy(encoder, reader)
if err != nil {
encoder.Close()
return nil, err
}
err = encoder.Close()
return buf.Bytes(), err
}
func DecompressZstd(src []byte) ([]byte, error) {
reader := bytes.NewReader(src)
decoder, err := zstd.NewReader(reader)
if err != nil {
return nil, err
}
defer decoder.Close()
var buf bytes.Buffer
_, err = io.Copy(&buf, decoder)
return buf.Bytes(), err
}
func AverageEpPerUser(users []WatchUser) float64 {
if len(users) == 0 {
return 0.0
}
res := 0.0
for _, u := range users {
res += float64(u.Progress)
}
return res / float64(len(users))
}

View File

@@ -4,9 +4,11 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"sort"
"strings" "strings"
"time" "time"
"github.com/gookit/color"
"github.com/xujiajun/nutsdb" "github.com/xujiajun/nutsdb"
) )
@@ -121,14 +123,11 @@ func GetSeasonCache(key string) ([]Anime, error) {
return seasonData, err return seasonData, err
} }
func SearchSeason(animeId int64) (*Anime, error) { func SearchSeasons(animeId int64) (*Anime, error) {
season, err := GetSeasonCache(seasonApiJikan) season, err := GetSeasonCache(GetCurrentSeasonString())
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(season) == 0 {
return nil, errors.New("no seasonal anime")
}
for _, a := range season { for _, a := range season {
if a.Anime == animeId { if a.Anime == animeId {
return &a, err return &a, err
@@ -138,8 +137,23 @@ func SearchSeason(animeId int64) (*Anime, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(season) == 0 { for _, a := range season {
return nil, errors.New("no seasonal anime") if a.Anime == animeId {
return &a, err
}
}
season, err = GetSeasonCache(GetNextNextSeasonString())
if err != nil {
return nil, err
}
for _, a := range season {
if a.Anime == animeId {
return &a, err
}
}
season, err = GetSeasonCache(GetLastSeasonString())
if err != nil {
return nil, err
} }
for _, a := range season { for _, a := range season {
if a.Anime == animeId { if a.Anime == animeId {
@@ -151,7 +165,7 @@ func SearchSeason(animeId int64) (*Anime, error) {
func SearchAnime(animeId int64) (*Anime, error) { func SearchAnime(animeId int64) (*Anime, error) {
// search season first // search season first
anime, err := SearchSeason(animeId) anime, err := SearchSeasons(animeId)
if err != nil { if err != nil {
// get from MAL // get from MAL
anime, _, err = GetAnimeDetailData(animeId) anime, _, err = GetAnimeDetailData(animeId)
@@ -180,57 +194,56 @@ func SearchAppointments(animeId int64) ([]Appointment, error) {
return result, nil return result, nil
} }
func FetchProgress(animeId, userId int64, username string, progress int) (int, time.Time, string, error) { func FetchProgress(animeId int64, username string) (int, time.Time, int, string, error) {
// check watching first // check watching first
newProgress, updated, err := fetchProgressOnState(animeId, userId, progress, username, malApiStatusW) newProgress, updated, score, err := FetchProgressOnState(animeId, username, malApiStatusW)
if err != nil { if err != nil {
return newProgress, updated, "", err return newProgress, updated, score, "", err
} }
if newProgress != -1 { if newProgress != -1 {
return newProgress, updated, malApiStatusW, err return newProgress, updated, score, 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 // check completed
newProgress, updated, err = fetchProgressOnState(animeId, userId, progress, username, malApiStatusC) newProgress, updated, score, err = FetchProgressOnState(animeId, username, malApiStatusC)
if err != nil { if err != nil {
return newProgress, updated, "", err return newProgress, updated, score, "", err
} }
if newProgress != -1 { if newProgress != -1 {
return newProgress, updated, malApiStatusC, err return newProgress, updated, score, malApiStatusC, err
}
// check on hold
newProgress, updated, score, err = FetchProgressOnState(animeId, username, malApiStatusH)
if err != nil {
return newProgress, updated, score, "", err
}
if newProgress != -1 {
return newProgress, updated, score, malApiStatusH, err
} }
// check dropped // check dropped
newProgress, updated, err = fetchProgressOnState(animeId, userId, progress, username, malApiStatusD) newProgress, updated, score, err = FetchProgressOnState(animeId, username, malApiStatusD)
if err != nil { if err != nil {
return newProgress, updated, "", err return newProgress, updated, score, "", err
} }
if newProgress != -1 { if newProgress != -1 {
return newProgress, updated, malApiStatusD, err return newProgress, updated, score, malApiStatusD, err
} }
// has no progress or PTW // has no progress or PTW
return 0, updated, "", nil return 0, updated, 0, "", nil
} }
func fetchProgressOnState(animeId, userId int64, progress int, username, malStatus string) (int, time.Time, error) { func FetchProgressOnState(animeId int64, username, malStatus string) (int, time.Time, int, error) {
list, _, err := GetUserAnimeListData(username, malStatus) list, _, err := GetUserAnimeListData(username, malStatus)
if err != nil { if err != nil {
return 0, time.Time{}, err return 0, time.Time{}, 0, err
} }
for _, a := range list.Data { for _, a := range list.Data {
// check if found // check if found
if a.Node.ID == animeId { if a.Node.ID == animeId {
// check if progress changed return a.ListStatus.NumEpisodesWatched, a.ListStatus.UpdatedAt, a.ListStatus.Score, nil
return a.ListStatus.NumEpisodesWatched, a.ListStatus.UpdatedAt, nil
} }
} }
// no progess found // no progess found
return -1, time.Now(), nil return -1, time.Now(), 0, nil
} }
func AddToChat(old, new, user string) string { func AddToChat(old, new, user string) string {
@@ -240,6 +253,19 @@ func AddToChat(old, new, user string) string {
return buf.String() return buf.String()
} }
func GetAnimeWatchFromDb(animeId int64) (*AnimeUser, error) {
dbAnime, err := ReadAnimeUsers()
if err != nil {
return nil, err
}
for _, a := range dbAnime {
if a.Anime == animeId {
return &a, err
}
}
return nil, errors.New("anime not found")
}
func CheckAnimeExistInDb(animeId int64) (bool, error) { func CheckAnimeExistInDb(animeId int64) (bool, error) {
dbAnime, err := ReadAnimeUsers() dbAnime, err := ReadAnimeUsers()
if err != nil { if err != nil {
@@ -270,3 +296,62 @@ func CheckAnimeExistInDbAndUserWatches(animeId, userId int64) (bool, error) {
} }
return false, err return false, err
} }
func BuildMovieCharts() ([]MovieChart, error) {
key := "charts"
var charts []MovieChart
data, err := mmCache.Get(key)
if err == nil {
err = json.Unmarshal(data, &charts)
if err == nil {
return charts, err
}
}
movieList, err := MmReadCharts()
if err != nil {
return nil, err
}
users, err := ReadRegisteredUsers()
if err != nil {
return nil, err
}
charts = make([]MovieChart, 0)
for _, m := range movieList {
c := MovieChart{
MmId: m.Id,
Anime: m.Anime,
Title: m.Title,
}
scoreSum := 0
for _, u := range users {
progress, _, score, err := FetchProgressOnState(c.Anime, u.Username, malApiStatusC)
if err != nil {
color.Errorln(err.Error())
continue
}
if progress == -1 || score == 0 {
// user has no progress/score
continue
}
scoreSum += score
c.UserCount++
}
if c.UserCount > 0 {
c.AvgScore = float64(scoreSum) / float64(c.UserCount)
}
charts = append(charts, c)
}
sort.SliceStable(charts, func(i, j int) bool { return charts[i].AvgScore > charts[j].AvgScore })
bytes, err := json.Marshal(charts)
if err == nil {
mmCache.Set(key, bytes)
}
return charts, nil
}

View File

@@ -1,5 +1,5 @@
{% package main %} {% package main %}
{% func Index(animes []Anime, oracles []MmOracle, log string) %} {% func Index(animes []Anime, oracles []MmOracle, charts []MovieChart, log string) %}
{% collapsespace %} {% collapsespace %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
@@ -30,23 +30,52 @@ body { background-color: #1a1a1a; color: #fff; }
</table> </table>
</br> </br>
<table> <table>
<tr>
<th>MALID</th>
<th>SCORE</th>
<th>TITLE</th>
</tr>
{% for _, anime := range animes %} {% for _, anime := range animes %}
<tr> <tr>
<td><a href="{%s anime.URL %}" target="_blank" rel="noopener noreferrer">{%dl anime.Anime %}</a></td> <td><a href="{%s anime.URL %}" target="_blank" rel="noopener noreferrer">{%dl anime.Anime %}</a></td>
<td>{%f.2 anime.Score %}</td>
<td><strong>{%s anime.Title %}</strong></td> <td><strong>{%s anime.Title %}</strong></td>
<td>{%d anime.Episodes %}</td> </tr>
<td>{%f anime.Score %}</td> {% endfor %}
</table>
<h2>MovieManager Charts</h2>
<table>
<tr>
<th>MMID</th>
<th>MALID</th>
<th>USER</th>
<th>SCORE</th>
<th>TITLE</th>
</tr>
{% for _, chart := range charts %}
<tr>
<td><a href="https://movies.hanami.family/movie/{%d chart.MmId %}" target="_blank" rel="noopener noreferrer">{%d chart.MmId %}</a></td>
<td><a href="https://myanimelist.net/anime/{%dl chart.Anime %}" target="_blank" rel="noopener noreferrer">{%dl chart.Anime %}</a></td>
<td>{%d chart.UserCount %}</td>
<td><strong>{%f.2 chart.AvgScore %}</strong></td>
<td><strong>{%s chart.Title %}</strong></td>
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>
<h2>MovieManager Oracle</h2> <h2>MovieManager Oracle</h2>
<table> <table>
<tr>
<th>MMID</th>
<th>MALID</th>
<th>SCORE</th>
<th>TITLE</th>
</tr>
{% for _, oracle := range oracles %} {% for _, oracle := range oracles %}
<tr> <tr>
<td><a href="https://movies.hanami.family/movie/{%d oracle.Id %}" target="_blank" rel="noopener noreferrer">{%d oracle.Id %}</a></td> <td><a href="https://movies.hanami.family/movie/{%d oracle.Id %}" target="_blank" rel="noopener noreferrer">{%d oracle.Id %}</a></td>
<td>{%dl oracle.Anime %}</td>
<td>{%d oracle.AvgScore %}</td> <td>{%d oracle.AvgScore %}</td>
<td><strong>{%s oracle.Title %}</strong></td> <td><strong>{%s oracle.Title %}</strong></td>
<td>{%dl oracle.Anime %}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>

View File

@@ -18,7 +18,7 @@ var (
) )
//line season.qtpl:2 //line season.qtpl:2
func StreamIndex(qw422016 *qt422016.Writer, animes []Anime, oracles []MmOracle, log string) { func StreamIndex(qw422016 *qt422016.Writer, animes []Anime, oracles []MmOracle, charts []MovieChart, log string) {
//line season.qtpl:2 //line season.qtpl:2
qw422016.N().S(` qw422016.N().S(`
`) `)
@@ -31,91 +31,123 @@ func StreamIndex(qw422016 *qt422016.Writer, animes []Anime, oracles []MmOracle,
//line season.qtpl:28 //line season.qtpl:28
qw422016.N().D(len(animes)) qw422016.N().D(len(animes))
//line season.qtpl:28 //line season.qtpl:28
qw422016.N().S(`</td> </tr> </table> </br> <table> `) qw422016.N().S(`</td> </tr> </table> </br> <table> <tr> <th>MALID</th> <th>SCORE</th> <th>TITLE</th> </tr> `)
//line season.qtpl:33 //line season.qtpl:38
for _, anime := range animes { for _, anime := range animes {
//line season.qtpl:33 //line season.qtpl:38
qw422016.N().S(` <tr> <td><a href="`) qw422016.N().S(` <tr> <td><a href="`)
//line season.qtpl:35 //line season.qtpl:40
qw422016.E().S(anime.URL) qw422016.E().S(anime.URL)
//line season.qtpl:35 //line season.qtpl:40
qw422016.N().S(`" target="_blank" rel="noopener noreferrer">`) qw422016.N().S(`" target="_blank" rel="noopener noreferrer">`)
//line season.qtpl:35 //line season.qtpl:40
qw422016.N().DL(anime.Anime) qw422016.N().DL(anime.Anime)
//line season.qtpl:35
qw422016.N().S(`</a></td> <td><strong>`)
//line season.qtpl:36
qw422016.E().S(anime.Title)
//line season.qtpl:36
qw422016.N().S(`</strong></td> <td>`)
//line season.qtpl:37
qw422016.N().D(anime.Episodes)
//line season.qtpl:37
qw422016.N().S(`</td> <td>`)
//line season.qtpl:38
qw422016.N().F(anime.Score)
//line season.qtpl:38
qw422016.N().S(`</td> </tr> `)
//line season.qtpl:40 //line season.qtpl:40
}
//line season.qtpl:40
qw422016.N().S(` </table> <h2>MovieManager Oracle</h2> <table> `)
//line season.qtpl:44
for _, oracle := range oracles {
//line season.qtpl:44
qw422016.N().S(` <tr> <td><a href="https://movies.hanami.family/movie/`)
//line season.qtpl:46
qw422016.N().D(oracle.Id)
//line season.qtpl:46
qw422016.N().S(`" target="_blank" rel="noopener noreferrer">`)
//line season.qtpl:46
qw422016.N().D(oracle.Id)
//line season.qtpl:46
qw422016.N().S(`</a></td> <td>`) qw422016.N().S(`</a></td> <td>`)
//line season.qtpl:47 //line season.qtpl:41
qw422016.N().D(oracle.AvgScore) qw422016.N().FPrec(anime.Score, 2)
//line season.qtpl:47 //line season.qtpl:41
qw422016.N().S(`</td> <td><strong>`) qw422016.N().S(`</td> <td><strong>`)
//line season.qtpl:48 //line season.qtpl:42
qw422016.E().S(oracle.Title) qw422016.E().S(anime.Title)
//line season.qtpl:48 //line season.qtpl:42
qw422016.N().S(`</strong></td> <td>`) qw422016.N().S(`</strong></td> </tr> `)
//line season.qtpl:49 //line season.qtpl:44
qw422016.N().DL(oracle.Anime)
//line season.qtpl:49
qw422016.N().S(`</td> </tr> `)
//line season.qtpl:51
} }
//line season.qtpl:51 //line season.qtpl:44
qw422016.N().S(` </table> </body> </html> `) qw422016.N().S(` </table> <h2>MovieManager Charts</h2> <table> <tr> <th>MMID</th> <th>MALID</th> <th>USER</th> <th>SCORE</th> <th>TITLE</th> </tr> `)
//line season.qtpl:55 //line season.qtpl:55
for _, chart := range charts {
//line season.qtpl:55
qw422016.N().S(` <tr> <td><a href="https://movies.hanami.family/movie/`)
//line season.qtpl:57
qw422016.N().D(chart.MmId)
//line season.qtpl:57
qw422016.N().S(`" target="_blank" rel="noopener noreferrer">`)
//line season.qtpl:57
qw422016.N().D(chart.MmId)
//line season.qtpl:57
qw422016.N().S(`</a></td> <td><a href="https://myanimelist.net/anime/`)
//line season.qtpl:58
qw422016.N().DL(chart.Anime)
//line season.qtpl:58
qw422016.N().S(`" target="_blank" rel="noopener noreferrer">`)
//line season.qtpl:58
qw422016.N().DL(chart.Anime)
//line season.qtpl:58
qw422016.N().S(`</a></td> <td>`)
//line season.qtpl:59
qw422016.N().D(chart.UserCount)
//line season.qtpl:59
qw422016.N().S(`</td> <td><strong>`)
//line season.qtpl:60
qw422016.N().FPrec(chart.AvgScore, 2)
//line season.qtpl:60
qw422016.N().S(`</strong></td> <td><strong>`)
//line season.qtpl:61
qw422016.E().S(chart.Title)
//line season.qtpl:61
qw422016.N().S(`</strong></td> </tr> `)
//line season.qtpl:63
}
//line season.qtpl:63
qw422016.N().S(` </table> <h2>MovieManager Oracle</h2> <table> <tr> <th>MMID</th> <th>MALID</th> <th>SCORE</th> <th>TITLE</th> </tr> `)
//line season.qtpl:73
for _, oracle := range oracles {
//line season.qtpl:73
qw422016.N().S(` <tr> <td><a href="https://movies.hanami.family/movie/`)
//line season.qtpl:75
qw422016.N().D(oracle.Id)
//line season.qtpl:75
qw422016.N().S(`" target="_blank" rel="noopener noreferrer">`)
//line season.qtpl:75
qw422016.N().D(oracle.Id)
//line season.qtpl:75
qw422016.N().S(`</a></td> <td>`)
//line season.qtpl:76
qw422016.N().DL(oracle.Anime)
//line season.qtpl:76
qw422016.N().S(`</td> <td>`)
//line season.qtpl:77
qw422016.N().D(oracle.AvgScore)
//line season.qtpl:77
qw422016.N().S(`</td> <td><strong>`)
//line season.qtpl:78
qw422016.E().S(oracle.Title)
//line season.qtpl:78
qw422016.N().S(`</strong></td> </tr> `)
//line season.qtpl:80
}
//line season.qtpl:80
qw422016.N().S(` </table> </body> </html> `)
//line season.qtpl:84
qw422016.N().S(` qw422016.N().S(`
`) `)
//line season.qtpl:56 //line season.qtpl:85
} }
//line season.qtpl:56 //line season.qtpl:85
func WriteIndex(qq422016 qtio422016.Writer, animes []Anime, oracles []MmOracle, log string) { func WriteIndex(qq422016 qtio422016.Writer, animes []Anime, oracles []MmOracle, charts []MovieChart, log string) {
//line season.qtpl:56 //line season.qtpl:85
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line season.qtpl:56 //line season.qtpl:85
StreamIndex(qw422016, animes, oracles, log) StreamIndex(qw422016, animes, oracles, charts, log)
//line season.qtpl:56 //line season.qtpl:85
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line season.qtpl:56 //line season.qtpl:85
} }
//line season.qtpl:56 //line season.qtpl:85
func Index(animes []Anime, oracles []MmOracle, log string) string { func Index(animes []Anime, oracles []MmOracle, charts []MovieChart, log string) string {
//line season.qtpl:56 //line season.qtpl:85
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line season.qtpl:56 //line season.qtpl:85
WriteIndex(qb422016, animes, oracles, log) WriteIndex(qb422016, animes, oracles, charts, log)
//line season.qtpl:56 //line season.qtpl:85
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line season.qtpl:56 //line season.qtpl:85
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line season.qtpl:56 //line season.qtpl:85
return qs422016 return qs422016
//line season.qtpl:56 //line season.qtpl:85
} }

View File

@@ -5,6 +5,7 @@ import (
_ "embed" _ "embed"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
) )
@@ -30,6 +31,10 @@ func MmReadOracle() ([]MmOracle, error) {
} }
} }
if mmDb == nil {
return oracles, errors.New("no DB connection")
}
oracleQuery := `SELECT m.id, m.title, m.mal_id, COALESCE(SUM(pv.score), 0) AS avgScore FROM movie m oracleQuery := `SELECT m.id, m.title, m.mal_id, COALESCE(SUM(pv.score), 0) AS avgScore FROM movie m
JOIN vote pv ON (m.id = pv.movie_id) JOIN vote pv ON (m.id = pv.movie_id)
JOIN promise pp ON (pp.user_id = pv.user_id) JOIN promise pp ON (pp.user_id = pv.user_id)
@@ -85,3 +90,33 @@ func MmReadOracle() ([]MmOracle, error) {
return oracles, err return oracles, err
} }
func MmReadCharts() ([]MmOracle, error) {
if mmDb == nil {
return nil, errors.New("no DB connection")
}
chartsQuery := `SELECT m.id, m.title, m.mal_id FROM movie m
JOIN evening_movie em ON em.movie_id = m.id
JOIN evening e ON em.evening_id = e.id
WHERE e.date <= CURDATE() AND m.mal_id IS NOT NULL;`
rows, err := mmDb.Query(chartsQuery)
if err != nil {
return nil, err
}
defer rows.Close()
charts := make([]MmOracle, 0)
for rows.Next() {
var chart MmOracle
err = rows.Scan(&chart.Id, &chart.Title, &chart.Anime)
if err != nil {
return charts, err
}
charts = append(charts, chart)
}
err = rows.Err()
return charts, err
}