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) }