Files
2026-05-11 06:48:23 +00:00

60 lines
1.6 KiB
Go
Executable File

package source
import (
"crypto/md5"
"strings"
)
// Source is the base interface for all sources.
type Source interface {
ID() int64
Name() string
Lang() string
}
// CatalogueSource is the full interface every source must implement.
type CatalogueSource interface {
Source
SupportsLatest() bool
GetPopularManga(page int) (MangasPage, error)
GetLatestUpdates(page int) (MangasPage, error)
GetSearchManga(page int, query string, filters []Filter) (MangasPage, error)
GetMangaDetails(manga SManga) (SManga, error)
GetChapterList(manga SManga) ([]SChapter, error)
GetPageList(chapter SChapter) ([]Page, error)
// GetImageURL resolves the final image URL for a page.
// Sources that embed image URLs directly in pages return page.ImageURL unchanged.
GetImageURL(page Page) (string, error)
GetFilterList() []Filter
}
// GenerateSourceID replicates Tachiyomi/Suwayomi HttpSource.generateId:
//
// key = "${name.lowercase()}/$lang/$versionId"
// MD5(key) → first 8 bytes as big-endian int64, sign bit cleared (& Long.MAX_VALUE)
func GenerateSourceID(name, lang string) int64 {
return GenerateSourceIDv(name, lang, 1)
}
func GenerateSourceIDv(name, lang string, versionID int) int64 {
key := strings.ToLower(name) + "/" + lang + "/" + itoa(versionID)
b := md5.Sum([]byte(key))
var id int64
for i := 0; i < 8; i++ {
id |= int64(b[i]) << (8 * (7 - i))
}
return id & int64(^uint64(0)>>1) // clear sign bit (& Long.MAX_VALUE)
}
func itoa(n int) string {
if n == 0 {
return "0"
}
digits := []byte{}
for n > 0 {
digits = append([]byte{byte('0' + n%10)}, digits...)
n /= 10
}
return string(digits)
}