mirror of
https://github.com/ultrasn0w/huso.git
synced 2025-12-13 10:29:52 +01:00
Compare commits
6 Commits
cc525f2460
...
8df72d6f18
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8df72d6f18 | ||
|
|
d87b658837 | ||
|
|
be04fb92e5 | ||
|
|
f968c1b98e | ||
|
|
fd84193d9d | ||
|
|
f078c93b9e |
@@ -43,6 +43,8 @@ _X-HUSO-AUTH_ -> _secret_ des users
|
||||
| - | - | - | - | - | - | - |
|
||||
| GET | /api/season | Aktuelle 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/animesearch | Anime auf MAL suchen | []Anime JSON | Query parameter {q} = Suchtext | | |
|
||||
|
||||
|
||||
3
huso.go
3
huso.go
@@ -21,7 +21,6 @@ import (
|
||||
const (
|
||||
husoVersion = "1.3"
|
||||
registerSecret = "綾波レイ"
|
||||
seasonApiJikan = "seasons/now"
|
||||
seasonStrJikan = "seasons/"
|
||||
userApiJikan = "users/"
|
||||
userApiMal = "users/"
|
||||
@@ -74,7 +73,7 @@ func main() {
|
||||
color.Notice.Printf("huso %s built on %s with %s\n", husoVersion, buildTime, runtime.Version())
|
||||
logOut.WriteLine(fmt.Sprintf("🎉 huso %s built on %s with %s", husoVersion, buildTime, runtime.Version()))
|
||||
|
||||
jikanLimiter = rate.NewLimiter(rate.Every(time.Second), 1)
|
||||
jikanLimiter = rate.NewLimiter(rate.Every(time.Second+(time.Millisecond*420)), 1)
|
||||
|
||||
// cache init
|
||||
var err error
|
||||
|
||||
3
klotz.go
3
klotz.go
@@ -320,7 +320,8 @@ type MmOracle struct {
|
||||
|
||||
type MovieChart struct {
|
||||
MmId int `json:"mmId"`
|
||||
Anime int64 `json:"anime"`
|
||||
Title string `json:"title"`
|
||||
AvgScore float64 `json:"avgScore"`
|
||||
UserCount int `json:"userCount"`
|
||||
Data Anime `json:"data"`
|
||||
}
|
||||
|
||||
78
knecht.go
78
knecht.go
@@ -75,7 +75,9 @@ func GetUserAnimeListData(username, status string) (*AnimeListMal, []byte, error
|
||||
var list AnimeListMal
|
||||
data, err := animeListCache.Get(username + status)
|
||||
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 {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -133,45 +135,9 @@ func GetUserData(username string) (*User, []byte, error) {
|
||||
return &user, data, err
|
||||
}
|
||||
|
||||
func GetSeasonDataAll() ([]Anime, []byte, error) {
|
||||
seasonStr := GetCurrentSeasonString()
|
||||
color.Infoln("Aktuelle Season (" + seasonStr + ") abfragen...")
|
||||
logOut.WriteLine("📺 Aktuelle Season (" + seasonStr + ") abfragen...")
|
||||
data, _, err := GetSeasonData(seasonApiJikan, 1)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
color.Infof("%d Anime auf %d Seiten\n", data.Pagination.Items.Total, data.Pagination.LastVisiblePage)
|
||||
logOut.WriteLine(fmt.Sprintf("📺 %d Anime auf %d Seiten", data.Pagination.Items.Total, data.Pagination.LastVisiblePage))
|
||||
animes := make([]Anime, 0)
|
||||
// convert to anime
|
||||
for _, a := range data.Data {
|
||||
animes = append(animes, JikanConvert(&a))
|
||||
}
|
||||
for i := 2; data.Pagination.HasNextPage; i++ {
|
||||
color.Infof("Seite %d abfragen...\n", i)
|
||||
logOut.WriteLine(fmt.Sprintf("📺 Seite %d abfragen...", i))
|
||||
newData, _, err := GetSeasonData(seasonApiJikan, i)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
data.Pagination.CurrentPage = newData.Pagination.CurrentPage
|
||||
data.Pagination.HasNextPage = newData.Pagination.HasNextPage
|
||||
data.Pagination.Items.Count += newData.Pagination.Items.Count
|
||||
// convert to anime
|
||||
for _, a := range newData.Data {
|
||||
animes = append(animes, JikanConvert(&a))
|
||||
}
|
||||
}
|
||||
color.Infof("%d Anime bekommen\n", len(animes))
|
||||
logOut.WriteLine(fmt.Sprintf("📺 %d Anime bekommen", len(animes)))
|
||||
bytes, err := json.Marshal(animes)
|
||||
return animes, bytes, err
|
||||
}
|
||||
|
||||
func GetNextSeasonDataAll(season string) ([]Anime, []byte, error) {
|
||||
color.Infoln("Nächste Season (" + season + ") abfragen...")
|
||||
logOut.WriteLine("📺 Nächste Season (" + season + ") abfragen...")
|
||||
func GetSeasonDataAll(season string) ([]Anime, []byte, error) {
|
||||
color.Infoln("Season " + season + " abfragen...")
|
||||
logOut.WriteLine("📺 Season (" + season + ") abfragen...")
|
||||
data, _, err := GetSeasonData(seasonStrJikan+season, 1)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -292,3 +258,35 @@ func GetNextSeasonString() string {
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
40
ober.go
40
ober.go
@@ -19,6 +19,8 @@ func RunWebserv() {
|
||||
r.GET("/", Headers(Start))
|
||||
r.GET("/api/season", Headers(Season))
|
||||
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/anime/{id}", Headers(AnimeGet))
|
||||
r.GET("/api/animesearch", Headers(AnimeSearchGet))
|
||||
@@ -39,7 +41,7 @@ func RunWebserv() {
|
||||
}
|
||||
|
||||
func Start(ctx *fasthttp.RequestCtx) {
|
||||
season, err := GetSeasonCache(seasonApiJikan)
|
||||
season, err := GetSeasonCache(GetCurrentSeasonString())
|
||||
if err != nil {
|
||||
addErrorToCtx(ctx, err)
|
||||
return
|
||||
@@ -77,7 +79,7 @@ func AuthTest(ctx *fasthttp.RequestCtx) {
|
||||
}
|
||||
|
||||
func Season(ctx *fasthttp.RequestCtx) {
|
||||
data, err := seasoncache.Get(seasonApiJikan)
|
||||
data, err := seasoncache.Get(GetCurrentSeasonString())
|
||||
if err != nil {
|
||||
addErrorToCtx(ctx, err)
|
||||
return
|
||||
@@ -110,6 +112,40 @@ func SeasonNext(ctx *fasthttp.RequestCtx) {
|
||||
ctx.SetStatusCode(fasthttp.StatusOK)
|
||||
}
|
||||
|
||||
func SeasonNextNext(ctx *fasthttp.RequestCtx) {
|
||||
data, err := seasoncache.Get(GetNextNextSeasonString())
|
||||
if err != nil {
|
||||
addErrorToCtx(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = writeResponseBody(ctx, data)
|
||||
if err != nil {
|
||||
addErrorToCtx(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.SetContentType("application/json; charset=utf-8")
|
||||
ctx.SetStatusCode(fasthttp.StatusOK)
|
||||
}
|
||||
|
||||
func SeasonLast(ctx *fasthttp.RequestCtx) {
|
||||
data, err := seasoncache.Get(GetLastSeasonString())
|
||||
if err != nil {
|
||||
addErrorToCtx(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = writeResponseBody(ctx, data)
|
||||
if err != nil {
|
||||
addErrorToCtx(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.SetContentType("application/json; charset=utf-8")
|
||||
ctx.SetStatusCode(fasthttp.StatusOK)
|
||||
}
|
||||
|
||||
func AnimeGet(ctx *fasthttp.RequestCtx) {
|
||||
idVal := ctx.UserValue("id")
|
||||
if idVal == nil {
|
||||
|
||||
@@ -55,6 +55,9 @@ func Arbeit() {
|
||||
}
|
||||
return
|
||||
}
|
||||
color.Infoln("Animelisten abfragen...")
|
||||
logOut.WriteLine("📜 Animelisten abfragen...")
|
||||
count := 0
|
||||
// iterate anime
|
||||
for _, a := range animesUsers {
|
||||
// iterate users
|
||||
@@ -68,40 +71,45 @@ func Arbeit() {
|
||||
// check if user set anime as completed
|
||||
if listState == malApiStatusC {
|
||||
color.Infof("%s finished %d scored %d\n", u.Username, a.Anime, score)
|
||||
logOut.WriteLine(fmt.Sprintf("📜 %s finished %d scored %d !", u.Username, a.Anime, score))
|
||||
logOut.WriteLine(fmt.Sprintf("📝 %s finished %d scored %d !", u.Username, a.Anime, score))
|
||||
// delete user from anime
|
||||
_, err = DeleteUserFromAnime(u.Username, u.MalID, a.Anime)
|
||||
if err != nil {
|
||||
color.Errorln(err.Error())
|
||||
logOut.WriteError(err)
|
||||
}
|
||||
count++
|
||||
continue
|
||||
}
|
||||
// check if user set anime as dropped
|
||||
if listState == malApiStatusD {
|
||||
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
|
||||
_, err = DeleteUserFromAnime(u.Username, u.MalID, a.Anime)
|
||||
if err != nil {
|
||||
color.Errorln(err.Error())
|
||||
logOut.WriteError(err)
|
||||
}
|
||||
count++
|
||||
continue
|
||||
}
|
||||
if newProgress == u.Progress {
|
||||
continue
|
||||
}
|
||||
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
|
||||
err = UpdateUserAnimeProgress(a.Anime, u.MalID, newProgress, updated)
|
||||
if err != nil {
|
||||
color.Errorln(err.Error())
|
||||
logOut.WriteError(err)
|
||||
}
|
||||
count++
|
||||
}
|
||||
}
|
||||
color.Infof("%d Sachen aktualisiert\n", count)
|
||||
logOut.WriteLine(fmt.Sprintf("📜 %d Sachen aktualisiert", count))
|
||||
}
|
||||
|
||||
func BissleArbeiten() {
|
||||
@@ -143,30 +151,31 @@ func LangeArbeiten() {
|
||||
|
||||
func LangeArbeit() {
|
||||
// season data
|
||||
_, bytes, err := GetSeasonDataAll()
|
||||
err := refreshSeason(GetCurrentSeasonString())
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// next season data
|
||||
nextSeason := GetNextSeasonString()
|
||||
_, bytes, err = GetNextSeasonDataAll(nextSeason)
|
||||
err = refreshSeason(GetNextSeasonString())
|
||||
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 {
|
||||
color.Errorln(err.Error())
|
||||
logOut.WriteError(err)
|
||||
} else {
|
||||
err = seasoncache.Set(nextSeason, bytes)
|
||||
if err != nil {
|
||||
color.Errorln(err.Error())
|
||||
logOut.WriteError(err)
|
||||
}
|
||||
}
|
||||
|
||||
// refresh anime cache with watched
|
||||
@@ -179,8 +188,8 @@ func LangeArbeit() {
|
||||
}
|
||||
} else {
|
||||
for _, a := range animesUsers {
|
||||
// search season first
|
||||
_, err = SearchSeason(a.Anime)
|
||||
// search seasons first
|
||||
_, err = SearchSeasons(a.Anime)
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
@@ -194,27 +203,6 @@ func LangeArbeit() {
|
||||
}
|
||||
}
|
||||
|
||||
charts, err := BuildMovieCharts()
|
||||
if err != nil {
|
||||
color.Errorln(err.Error())
|
||||
logOut.WriteError(err)
|
||||
} else {
|
||||
for _, c := range charts {
|
||||
// search season first
|
||||
_, err = SearchSeason(c.Data.Anime)
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
err = refreshAnime(c.Data.Anime)
|
||||
if err != nil {
|
||||
color.Errorln(err.Error())
|
||||
logOut.WriteError(err)
|
||||
continue
|
||||
}
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
color.Infof("%d Anime aktualisiert\n", count)
|
||||
logOut.WriteLine(fmt.Sprintf("🔃 %d Anime aktualisiert", count))
|
||||
}
|
||||
@@ -249,3 +237,11 @@ func refreshAnime(animeId int64) error {
|
||||
animeCache.Set(key, data)
|
||||
return nil
|
||||
}
|
||||
|
||||
func refreshSeason(season string) error {
|
||||
_, bytes, err := GetSeasonDataAll(season)
|
||||
if err == nil {
|
||||
err = seasoncache.Set(season, bytes)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
56
schaffer.go
56
schaffer.go
@@ -123,14 +123,11 @@ func GetSeasonCache(key string) ([]Anime, error) {
|
||||
return seasonData, err
|
||||
}
|
||||
|
||||
func SearchSeason(animeId int64) (*Anime, error) {
|
||||
season, err := GetSeasonCache(seasonApiJikan)
|
||||
func SearchSeasons(animeId int64) (*Anime, error) {
|
||||
season, err := GetSeasonCache(GetCurrentSeasonString())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(season) == 0 {
|
||||
return nil, errors.New("no seasonal anime")
|
||||
}
|
||||
for _, a := range season {
|
||||
if a.Anime == animeId {
|
||||
return &a, err
|
||||
@@ -140,8 +137,23 @@ func SearchSeason(animeId int64) (*Anime, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(season) == 0 {
|
||||
return nil, errors.New("no seasonal anime")
|
||||
for _, a := range season {
|
||||
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 {
|
||||
if a.Anime == animeId {
|
||||
@@ -153,7 +165,7 @@ func SearchSeason(animeId int64) (*Anime, error) {
|
||||
|
||||
func SearchAnime(animeId int64) (*Anime, error) {
|
||||
// search season first
|
||||
anime, err := SearchSeason(animeId)
|
||||
anime, err := SearchSeasons(animeId)
|
||||
if err != nil {
|
||||
// get from MAL
|
||||
anime, _, err = GetAnimeDetailData(animeId)
|
||||
@@ -191,14 +203,6 @@ func FetchProgress(animeId int64, username string) (int, time.Time, int, string,
|
||||
if newProgress != -1 {
|
||||
return newProgress, updated, score, malApiStatusW, 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 completed
|
||||
newProgress, updated, score, err = FetchProgressOnState(animeId, username, malApiStatusC)
|
||||
if err != nil {
|
||||
@@ -207,6 +211,14 @@ func FetchProgress(animeId int64, username string) (int, time.Time, int, string,
|
||||
if newProgress != -1 {
|
||||
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
|
||||
newProgress, updated, score, err = FetchProgressOnState(animeId, username, malApiStatusD)
|
||||
if err != nil {
|
||||
@@ -295,18 +307,14 @@ func BuildMovieCharts() ([]MovieChart, error) {
|
||||
charts = make([]MovieChart, 0)
|
||||
for _, m := range movieList {
|
||||
c := MovieChart{
|
||||
MmId: m.Id,
|
||||
}
|
||||
anime, err := SearchAnime(m.Anime)
|
||||
if err != nil {
|
||||
color.Errorln(err.Error())
|
||||
continue
|
||||
MmId: m.Id,
|
||||
Anime: m.Anime,
|
||||
Title: m.Title,
|
||||
}
|
||||
|
||||
c.Data = *anime
|
||||
scoreSum := 0
|
||||
for _, u := range users {
|
||||
progress, _, score, err := FetchProgressOnState(anime.Anime, u.Username, malApiStatusC)
|
||||
progress, _, score, err := FetchProgressOnState(c.Anime, u.Username, malApiStatusC)
|
||||
if err != nil {
|
||||
color.Errorln(err.Error())
|
||||
continue
|
||||
|
||||
@@ -55,10 +55,10 @@ body { background-color: #1a1a1a; color: #fff; }
|
||||
{% 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="{%s chart.Data.URL %}" target="_blank" rel="noopener noreferrer">{%dl chart.Data.Anime %}</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.Data.Title %}</strong></td>
|
||||
<td><strong>{%s chart.Title %}</strong></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
@@ -67,13 +67,13 @@ func StreamIndex(qw422016 *qt422016.Writer, animes []Anime, oracles []MmOracle,
|
||||
//line season.qtpl:57
|
||||
qw422016.N().D(chart.MmId)
|
||||
//line season.qtpl:57
|
||||
qw422016.N().S(`</a></td> <td><a href="`)
|
||||
qw422016.N().S(`</a></td> <td><a href="https://myanimelist.net/anime/`)
|
||||
//line season.qtpl:58
|
||||
qw422016.E().S(chart.Data.URL)
|
||||
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.Data.Anime)
|
||||
qw422016.N().DL(chart.Anime)
|
||||
//line season.qtpl:58
|
||||
qw422016.N().S(`</a></td> <td>`)
|
||||
//line season.qtpl:59
|
||||
@@ -85,7 +85,7 @@ func StreamIndex(qw422016 *qt422016.Writer, animes []Anime, oracles []MmOracle,
|
||||
//line season.qtpl:60
|
||||
qw422016.N().S(`</strong></td> <td><strong>`)
|
||||
//line season.qtpl:61
|
||||
qw422016.E().S(chart.Data.Title)
|
||||
qw422016.E().S(chart.Title)
|
||||
//line season.qtpl:61
|
||||
qw422016.N().S(`</strong></td> </tr> `)
|
||||
//line season.qtpl:63
|
||||
|
||||
Reference in New Issue
Block a user