EHentai

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 2, 2025 License: Apache-2.0 Imports: 29 Imported by: 0

README

EHentai-go

EHentai access for go, with EhTagTranslation support, fully leveraging Go's concurrency advantages.

给机器人做着玩的 部分功能有所缺失, 比如搜索没有翻页之类的

Features

  • 完全并发, 可配置并发数
  • 集成 EhTagTranslation
  • 指定下载画廊的某(几)页
  • 本地画廊缓存
  • 域名前置

用法

// 环境变量:
// "EHENTAI_COOKIE"
// OR
// "EHENTAI_COOKIE_IPB_MEMBER_ID"
// "EHENTAI_COOKIE_IPB_PASS_HASH"
// "EHENTAI_COOKIE_IGNEOUS"
// "EHENTAI_COOKIE_SK"

// igneous 为空时, 会尝试使用 exh 下发的 igneous
// sk 为空时, 搜索结果标题只有英文
EHentai.SetCookie("ipb_member_id", "ipb_pass_hash", "igneous", "sk")
// 也可以直接设置字符串
EHentai.SetCookieFromString("ipb_member_id=123; ipb_pass_hash=abc; igneous=456; sk=efg")

// 注册 igneous 更新回调
EHentai.RegisterIgneousUpdate(func(igneous string) {
    log.Printf("igneous updated: %s\n", igneous)
})
// 允许 igneous 被下发覆盖为 "mystery", 默认 false
EHentai.SetAcceptIgneousMystery(false)
初始化 EhTagTranslation 数据库
tStart := time.Now()
// 在 AMD Ryzen 5600x(6c12t) 上, 解析数据大概耗时 4ms
// 要更新的话再调用一次
err := EHentai.InitEhTagDb()
if err != nil {
    log.Fatalln(err)
}
// 总时间包括从 GitHub 下载数据
log.Printf("InitEhTagDb took %s\n", time.Since(tStart))

// 释放数据库
EHentai.FreeEhTagDb()
设置下载并发数
// 默认为 4
EHentai.SetThreads(4)
设置是否使用系统环境变量中的代理
// 默认为 true
// 配合域名前置设置为 false 食用
EHentai.SetUseEnvPorxy(true)
设置 query nl 的重试次数
// 默认只尝试两次
EHentai.SetRetryDepth(2)

s/b7a3ead2d6/3138775-24 为例, 图片加载失败时, 页面会根据 #loadfail 的内容前往新页面 s/b7a3ead2d6/3138775-24?nl=45453-483314

<a href="#" id="loadfail" onclick="return nl('45453-483314')">Reload broken image</a>
设置域名前置

抄的jiangtian616/JHenTai

// 默认为 false
EHentai.SetDomainFronting(false)
自定义域名前置所使用的 ip 获取器

IpProvider 实现示例: internal/usage/network.go:MyIpProvider

// type IpProvider interface {
//     Supports(host string) bool
//     NextIp(host string) string
//     AddUnavailableIp(host, ip string)
// }
myIpProvider := EHentai.IpProvider(&MyIpProvider{})
EHentai.SetCustomIpProvider(myIpProvider)
设置缓存
// 设置元数据缓存启用状态
// 默认为 true
// 以避免频繁请求官方 api
EHentai.SetMetadataCacheEnabled(true)

// 设置自动缓存启用状态
// 默认为 false
// 启用时会同时启用元数据缓存
// 下载画廊时: 自动缓存所有下载的页
// 下载页时: 存在该画廊的缓存时, 自动缓存所下载的页
EHentai.SetAutoCacheEnabled(false)

// 设置缓存文件夹路径
// 留空默认为 "./EHentaiCache/"
// 路径形如 "EHentaiCache/3138775/metadata",
// "EHentaiCache/3138775/1.webp",
// "EHentaiCache/3138775/2.webp"...
EHentai.SetCacheDir("path/to/cache")
自行管理缓存

internal/usage/cache.go:UsageManageCache

搜索 E(x)Hentai
const keyword = "耳で恋した同僚〜オナサポ音声オタク女が同僚の声に反応してイキまくり〜"

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

// 没做翻页, results 可能比 total 要少
total, results, err := EHentai.FSearch(ctx, EHentai.EHENTAI_URL, keyword)
// total, results, err := EHentai.FSearch(ctx, EHentai.EXHENTAI_URL, keyword)
if err != nil {
    log.Fatalln(err)
}
log.Printf("Total results: %d\n", total)
for _, result := range results {
    log.Println(result.Title)
}

// 两种传法
cate1 := EHentai.CATEGORY_DOUJINSHI | EHentai.CATEGORY_MANGA
cate2 := []EHentai.Category{EHentai.CATEGORY_DOUJINSHI, EHentai.CATEGORY_MANGA}

// 也可以分类搜索
EHentai.FSearch(ctx, EHentai.EHENTAI_URL, keyword, cate1)
EHentai.FSearch(ctx, EHentai.EXHENTAI_URL, keyword, cate2...)

// 搜索同时通过官方 api 获取详细信息
EHentai.SearchDetail(ctx, EHentai.EHENTAI_URL, keyword, cate1)
EHentai.SearchDetail(ctx, EHentai.EXHENTAI_URL, keyword, cate2...)
下载画廊 / 下载页
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

gUrl := "https://e-hentai.org/g/3138775/30b0285f9b"
pageUrls := []string{
    "https://e-hentai.org/s/859299c9ef/3138775-7",
    "https://e-hentai.org/s/0b2127ea05/3138775-8",
}

// 两种下载方式都是一样的根据线程数并发下载

// 以迭代器模式:
// 下载整个画廊
for pageData, err := range EHentai.DownloadGalleryIter(ctx, gUrl) {
    if err != nil {
        log.Println(err)
        // 获取画廊信息出错时, 第一次循环就会返回 err 然后跳出
        // 如果是下载过程出错, 可以由外部决定是否取消下载
        break
        // continue
    }
    log.Println(pageData.String())
}
// 下载画廊中的指定页
for pageData, err := range EHentai.DownloadGalleryIter(ctx, gUrl, 9, 10, 11) {
    _ = pageData
    _ = err
}
// 下载画廊页
for pageData, err := range EHentai.DownloadPagesIter(ctx, pageUrls...) {
    _ = pageData
    _ = err
}

// 下载全部一起返回:
pageDatas, err := EHentai.DownloadGallery(ctx, gUrl)
_ = pageDatas
_ = err
_, _ = EHentai.DownloadGallery(ctx, gUrl, 9, 10, 11)
_, _ = EHentai.DownloadPages(ctx, pageUrls...)
通过回调函数完全异步地下载
// 以一个GUI程序为例
reader := myReader{}
reader.Ctx, reader.Cancel = context.WithCancel(context.Background())
reader.Images = make([]widgetsImage, reader.Gallery.Length)
go EHentai.DownloadPagesTo(reader.Ctx, reader.Gallery.PageUrls,
    func(i int, pd EHentai.PageData, err error) {
        if err != nil {
            reader.Images[i].Err = err
            log.Printf("page %d error: %v", i, err)
            return
        }
        reader.Images[i].Image, reader.Images[i].Err = pd.Image.Decode()
        reader.Window.Invalidate() // 触发 GUI 重新渲染
    },
)
// ...
获取画廊详细信息与所有页链接
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

const gUrl = "https://e-hentai.org/g/3138775/30b0285f9b"
galleryDetails, err := EHentai.FetchGalleryDetails(ctx, gUrl)
if err != nil {
    panic(err)
}
log.Println(galleryDetails.Title, galleryDetails.TitleJpn, galleryDetails.Cat)
for _, pageUrl := range galleryDetails.PageUrls {
    log.Println(pageUrl)
}

Documentation

Index

Constants

View Source
const (
	DEFAULT_CACHE_DIR  = `EHentaiCache`
	METADATA_FILE_NAME = `metadata`
)
View Source
const (
	ENV_COOKIE = "EHENTAI_COOKIE"
	// OR
	ENV_COOKIE_IPB_MEMBER_ID = "EHENTAI_COOKIE_IPB_MEMBER_ID"
	ENV_COOKIE_IPB_PASS_HASH = "EHENTAI_COOKIE_IPB_PASS_HASH"
	ENV_COOKIE_IGNEOUS       = "EHENTAI_COOKIE_IGNEOUS"
	ENV_COOKIE_SK            = "EHENTAI_COOKIE_SK"

	ENV_DOMAIN_FRONTING = "EHENTAI_DOMAIN_FRONTING"
)
View Source
const API_URL = `https://api.e-hentai.org/api.php`
View Source
const CATEGORY_COUNT = 10
View Source
const EHTAG_DATABASE_URL = `https://github.com/EhTagTranslation/Database/releases/latest/download/db.text.json`

Variables

View Source
var (
	ErrNoGalleryProvided = errors.New("no gallery provided")
	ErrNoPageProvided    = errors.New("no page provided")
)
View Source
var (
	ErrFailedToGetGalleryMetadata = errors.New("failed to get gallery metadata")
	ErrMismatchPageUrls           = errors.New("mismatch page urls")
	ErrGalleryMetadataError       = errors.New("gallery metadata error")
	ErrEmptyCache                 = errors.New("empty cache")
	ErrEmptyData                  = errors.New("empty data")
	ErrInvalidCacheMetadata       = errors.New("invalid cache metadata")
	ErrInvalidPageNum             = errors.New("invalid page number")
	ErrUnknownImageType           = errors.New("unknown image type")
	ErrPageNotCached              = errors.New("page not cached")
)
View Source
var (
	ErrNoPageUrlProvided   = errors.New("no page url provided")
	ErrNoImageUrlProvided  = errors.New("no image url provided")
	ErrTooManyGIds         = errors.New("too many gallery ids")
	ErrInvalidContentType  = errors.New("invalid content type")
	ErrEmptyBody           = errors.New("empty body")
	ErrFoundEmptyImageData = errors.New("found empty image data")
)
View Source
var (
	ErrSadPanda            = errors.New("sad panda")
	ErrIpBanned            = errors.New("ip banned")
	ErrNoHitsFound         = errors.New("no hits found")
	ErrRegMismatch         = errors.New("regexp mismatch")
	ErrRegEmptyMatch       = errors.New("regexp empty match")
	ErrNoResult            = errors.New("no result")
	ErrEmptyTable          = errors.New("empty table")
	ErrNoImage             = errors.New("no image")
	ErrEndGreaterThanTotal = errors.New("end > total")
	ErrFoundEmptyPageUrl   = errors.New("found empty page url")
	ErrNotANumber          = errors.New("not a number")
)
View Source
var ErrDownloadUnreachableCase = fmt.Errorf("download: unreachable case")
View Source
var ErrInvalidTag = errors.New("invalid tag")
View Source
var ErrUnknownCategory = errors.New("unknown category")

Functions

func AddInterceptors

func AddInterceptors(interceptors ...Interceptor)

func CreateCache

func CreateCache(domain Domain, gMeta *GalleryMetadata, pageUrls []string) (cache *cacheGallery, err error)

CreateCache 覆盖创建画廊缓存元数据

domain 为空时使用 EXHENTAI_DOMAIN

pageUrls 需要完整以保证读取失败时直接下载, 不提供时尝试从缓存中读取

func CreateCacheFromUrl

func CreateCacheFromUrl(ctx context.Context, gUrl string) (cache *cacheGallery, err error)

CreateCacheFromUrl 从画廊 url 覆盖创建缓存

func DeleteCache

func DeleteCache(gId int) (err error)

func DownloadCoversIter

func DownloadCoversIter(ctx context.Context, results coverProviders) iter.Seq2[Image, error]

DownloadCoversIter 以迭代器模式通过搜索结果下载封面

func DownloadCoversTo added in v0.0.3

func DownloadCoversTo(ctx context.Context, results coverProviders, f func(int, Image, error)) error

DownloadCoversTo 完全异步地通过搜索结果下载封面

func DownloadGalleryIter

func DownloadGalleryIter(ctx context.Context, galleryUrl string, pageNums ...int) iter.Seq2[PageData, error]

DownlaodGalleryIter 以迭代器模式下载画廊下所有图片, 下载失败时自动尝试备链

不传入 pageNums 参数时下载所有页, 传入时按其顺序下载指定页, 重复、越界页将被忽略

func DownloadPagesIter

func DownloadPagesIter(ctx context.Context, pageUrls ...string) iter.Seq2[PageData, error]

DownloadPagesIter 以迭代器模式下载画廊某页的图片, 下载失败时自动尝试备链

func DownloadPagesTo added in v0.0.3

func DownloadPagesTo(ctx context.Context, pageUrls []string, f func(int, PageData, error)) error

DownloadGalleryTo 完全异步地下载画廊下所有图片, 下载失败时自动尝试备链

func EhTagDbOk added in v0.1.0

func EhTagDbOk() bool

func FetchGalleryPageUrls deprecated

func FetchGalleryPageUrls(ctx context.Context, galleryUrl string) (pageUrls []string, err error)

FetchGalleryPageUrls 获取画廊下所有页链接

Deprecated: use FetchGalleryDetails GalleryDetails.PageUrls instead

func FreeEhTagDb added in v0.1.0

func FreeEhTagDb()

FreeEhTagDb 释放 EhTagTranslation 数据库

func GetCache

func GetCache(gId int) *cacheGallery

GetCache 获取画廊缓存

func InitEhTagDb added in v0.1.0

func InitEhTagDb() error

InitEhTagDb 初始化 EhTagTranslation 数据库

func MetaCacheRead

func MetaCacheRead(gId int) *metaCache

MetaCacheRead 从缓存中读取画廊元数据与页链接

gallery 与 pageUrls 不一定同时存在

TODO: cache switch to [gDetailsCache]

func RegisterIgneousUpdate added in v0.0.3

func RegisterIgneousUpdate(f func(igneous string))

RegisterIgneousUpdate 注册 igneous 更新时的回调通知

回调函数将在独立协程中执行

func SetAcceptIgneousMystery added in v0.0.3

func SetAcceptIgneousMystery(b bool)

SetAcceptIgneousMystery 设置是否接受 exhentai 下发的 igneous=mystery

默认为 false

func SetAutoCacheEnabled

func SetAutoCacheEnabled(b bool)

SetAutoCacheEnabled 设置自动缓存启用状态

默认为 false

启用时会同时启用元数据缓存

下载画廊时: 自动缓存所有下载的页

下载页时: 存在该画廊的缓存时, 自动缓存所下载的页

func SetCacheDir

func SetCacheDir(dir string)

SetCacheDir 设置缓存文件夹路径

留空默认为 "./EHentaiCache/"

路径形如 "EHentaiCache/3138775/metadata", "EHentaiCache/3138775/1.webp", "EHentaiCache/3138775/2.webp"...

func SetCookie

func SetCookie(memberId, passHash, igneous, sk string)

SetCookie 设置 cookie, 访问 exhentai 时必需

func SetCookieFromString

func SetCookieFromString(s string) (n int, err error)

SetCookieFromString 从字符串解析 cookie

返回值 n 为解析的 cookie 数量

func SetCustomIpProvider

func SetCustomIpProvider(iPprovider IpProvider)

SetCustomIpProvider 自定义域名前置所使用的 ip 获取器

func SetDomainFronting

func SetDomainFronting(b bool)

SetDomainFronting 设置是否使用域名前置

func SetInterceptors

func SetInterceptors(interceptors ...Interceptor)

func SetMetadataCacheEnabled

func SetMetadataCacheEnabled(b bool)

SetMetadataCacheEnabled 设置元数据缓存启用状态

默认为 true

以避免频繁请求官方 api

func SetRetryDepth

func SetRetryDepth(depth int)

SetRetryDepth 设置重试次数

默认为 2

直链下载失败时使用页备链重试

func SetThreads

func SetThreads(n int)

SetThreads 设置下载并发数

func SetUseEnvProxy added in v0.0.3

func SetUseEnvProxy(b bool)

SetUseEnvProxy 设置是否使用系统环境变量中的代理

默认为 true

func Translate

func Translate(tag string) string

Translate 翻译 tag, 输入格式应为: namespace:tag, 若数据库未初始化, 则返回入参

func TranslateMulti

func TranslateMulti(tags []string) []string

TranslateMulti 翻译多个 tag, 输入格式应为: namespace:tag, 若数据库未初始化, 则返回入参

func UnmarshalEhTagDb added in v0.1.0

func UnmarshalEhTagDb(data string) error

UnmarshalEhTagDb 使用外部的 json 数据反序列化至 EhTagTranslation 数据库

主要用于 cli 在外部缓存文件

func WaitForWrite added in v0.0.3

func WaitForWrite()

Types

type CacheGalleryMetadata

type CacheGalleryMetadata struct {
	Url     string          `json:"url"` // 画廊 URL
	Gallery GalleryMetadata `json:"gallery"`

	// PageUrls []string `json:"page_urls"`
	// 画廊所有页的 URL, 读取缓存图片出错时可以直接用
	// 保证一定存在
	PageUrls map[string]string `json:"page_urls"`

	Files struct {
		Dir      string         `json:"dir"`       // 画廊路径 `root/gid/`
		Count    int            `json:"count"`     // 缓存数量
		TotalLen int            `json:"total_len"` // 缓存总大小
		Pages    CachePageInfos `json:"pages"`     // 缓存的页码列表
	} `json:"files"` // 缓存文件列表
}

type CachePageInfo

type CachePageInfo struct {
	Num  int `json:"num"`
	Type int `json:"type"` // see [ImageType]
	Len  int `json:"len"`
}

type CachePageInfos

type CachePageInfos []CachePageInfo

func (*CachePageInfos) Exts added in v0.0.2

func (cpi *CachePageInfos) Exts() []ImageType

Exts 返回存在的图片扩展名

func (*CachePageInfos) Get

func (cpi *CachePageInfos) Get(pageNum int) (pageInfo CachePageInfo)

Get 返回页码对应的缓存信息

Len 为 0 表示不存在

func (*CachePageInfos) Lookup

func (cpi *CachePageInfos) Lookup(pageNums []int) (pageInfos CachePageInfos)

Lookup 构造哈希表查找页码

len(pageNums) == 0 时返回所有

len(pageInfos) == len(pageNums) , 对应页不存在时 Len 为 0

type CacheState

type CacheState int
const (
	CACHE_STATE_UNKNOWN CacheState = iota
	CACHE_STATE_NONE
	CACHE_STATE_PARTIAL
	CACHE_STATE_FULL
)

func (CacheState) String

func (cs CacheState) String() string

type Category

type Category uint
const (
	CATEGORY_MISC Category = 1 << iota
	CATEGORY_DOUJINSHI
	CATEGORY_MANGA
	CATEGORY_ARTIST_CG
	CATEGORY_GAME_CG
	CATEGORY_IMAGE_SET
	CATEGORY_COSPLAY
	CATEGORY_ASIAN_PORN
	CATEGORY_NON_H
	CATEGORY_WESTERN
)

func ParseCategory added in v0.0.3

func ParseCategory(ss ...string) (cat Category, err error)

func (Category) Format

func (c Category) Format() string

func (Category) Str

func (c Category) Str() string

func (Category) String

func (c Category) String() string

type Domain

type Domain = string
const (
	EHENTAI_DOMAIN  Domain = `e-hentai.org`
	EXHENTAI_DOMAIN Domain = `exhentai.org`
)

func UrlGetGIdGToken

func UrlGetGIdGToken(u string) (domain Domain, gId string, gToken string)

func UrlGetPTokenGIdPNum

func UrlGetPTokenGIdPNum(u string) (domain Domain, pToken string, gId string, pNum string)

type DomainFrontingCtxKey

type DomainFrontingCtxKey struct{}

for context kv

type DomainFrontingCtxValue

type DomainFrontingCtxValue struct {
	Host string `json:"host"`
	Ip   string `json:"ip"`
}

for context kv

type DomainFrontingInterceptor

type DomainFrontingInterceptor struct {
	Enabled    bool
	IpProvider IpProvider
}

DomainFrontingInterceptor 用于实现 e-hentai 的域名前置功能

func (*DomainFrontingInterceptor) OnError

func (d *DomainFrontingInterceptor) OnError(req *http.Request, err error)

func (*DomainFrontingInterceptor) OnRequest

func (d *DomainFrontingInterceptor) OnRequest(req *http.Request) error

func (*DomainFrontingInterceptor) OnResponse

func (d *DomainFrontingInterceptor) OnResponse(resp *http.Response) error

type EHRoundRobinIpProvider

type EHRoundRobinIpProvider struct {
	// contains filtered or unexported fields
}

EHRoundRobinIpProvider 用于轮询提供 e-hentai 相关域名的 ip

func (*EHRoundRobinIpProvider) AddUnavailableIp

func (p *EHRoundRobinIpProvider) AddUnavailableIp(host, ip string)

func (*EHRoundRobinIpProvider) NextIp

func (p *EHRoundRobinIpProvider) NextIp(host string) (ip string)

func (*EHRoundRobinIpProvider) Supports

func (p *EHRoundRobinIpProvider) Supports(host string) bool

type EhTagDatabase

type EhTagDatabase map[string]map[string]string

map[namespace]map[<tag>]name

func (*EhTagDatabase) Free

func (db *EhTagDatabase) Free()

func (*EhTagDatabase) Info

func (db *EhTagDatabase) Info() map[string]int

Info 返回数据库统计信息

func (*EhTagDatabase) Init

func (db *EhTagDatabase) Init() error

func (*EhTagDatabase) Ok

func (db *EhTagDatabase) Ok() bool

func (*EhTagDatabase) Translate

func (db *EhTagDatabase) Translate(tag string) string

func (*EhTagDatabase) TranslateMulti

func (db *EhTagDatabase) TranslateMulti(tags []string) []string

func (*EhTagDatabase) TranslateTag added in v0.0.3

func (db *EhTagDatabase) TranslateTag(tag Tag) Tag

func (*EhTagDatabase) TranslateTags added in v0.0.3

func (db *EhTagDatabase) TranslateTags(tags []Tag) []Tag

func (*EhTagDatabase) Unmarshal

func (db *EhTagDatabase) Unmarshal(data string) error

Unmarshal 解析 json 到数据库

type Error

type Error struct {
	// contains filtered or unexported fields
}

Error 附带了更详细的信息

ehErr := EHentai.UnwarpErr(err)
isXxx := ehErr.Is(EHentai.ErrXXX)

-

func UnwrapErr

func UnwrapErr(err error) *Error

func (*Error) Error

func (e *Error) Error() string

func (*Error) Is

func (e *Error) Is(target error) bool

func (*Error) Unwrap

func (e *Error) Unwrap() error

type FSearchResult

type FSearchResult struct {
	Domain Domain
	Gallery

	Cat string // 分类

	Cover  string  // url
	Posted string  // "2006-01-02 15:04"
	Rating float64 // 精确到 0.5

	Url   string   // 画廊 URL
	Title string   // 根据 cookie 中的 sk, 结果可能为英文或日文
	Tags  []string // namespace:tag

	Uploader string // 上传者
	Pages    int    // 64
}

FSearchResult 来自搜索结果页

type FSearchResults

type FSearchResults []FSearchResult

func EHSearch deprecated

func EHSearch(ctx context.Context, keyword string, categories ...Category) (total int, results FSearchResults, err error)

EHSearch 搜索 EHentai, results 只有第一页结果

Deprecated: simply use FSearch instead

EHentai.FSearch(ctx, EHentai.EHENTAI_URL, keyword, categories...)

func ExHSearch deprecated

func ExHSearch(ctx context.Context, keyword string, categories ...Category) (total int, results FSearchResults, err error)

ExHSearch 搜索 ExHentai, results 只有第一页结果

Deprecated: simply use FSearch instead

EHentai.FSearch(ctx, EHentai.EXHENTAI_URL, keyword, categories...)

func FSearch added in v0.1.0

func FSearch(ctx context.Context, url Url, keyword string, categories ...Category) (total int, results FSearchResults, err error)

FSearch 搜索并返回基本信息, results 只有第一页的结果

func (FSearchResults) GetCover

func (fsr FSearchResults) GetCover() (urls []string)

type GIdList

type GIdList = Gallery

GIdList is the official alias of Gallery

type Gallery struct {
	GalleryId    int
	GalleryToken string
}

Gallery describes the gallery url

https://e-hentai.org/g/{gallery_id}/{gallery_token}/

func UrlToGallery

func UrlToGallery(u string) Gallery

type GalleryDetails added in v0.0.3

type GalleryDetails struct {
	Domain Domain
	Gallery

	Cover    string // url
	Title    string // 英文标题
	TitleJpn string // 日文标题

	Cat      string // 分类
	Uploader string // 上传者

	Posted     string // "2006-01-02 15:04"
	Parent     int    // galleryId, 0 for "None"
	Visible    string // "Yes" / "No (Replaced)"
	Language   string // "Chinese" / "Japanese"
	Translated string // "TR" / ""
	FileSize   string // "449.9 MiB"
	Length     int    // 65
	Favorited  int    // 3745

	RatingCount int     // 271
	Rating      float64 // 4.86

	// Tags []string // namespace:tag
	Tags []Tag

	PageUrls []string
}

GalleryDetails 来自画廊详情页

func DetailsCacheRead added in v0.0.3

func DetailsCacheRead(gId int) *GalleryDetails

func FetchGalleryDetails added in v0.0.3

func FetchGalleryDetails(ctx context.Context, galleryUrl string) (gallery GalleryDetails, err error)

FetchGalleryDetails 获取画廊详细信息与所有页链接

func (*GalleryDetails) GetCover added in v0.0.3

func (gd *GalleryDetails) GetCover() (urls []string)

type GalleryMetadata

type GalleryMetadata struct {
	GId          int       `json:"gid"`
	Token        string    `json:"token"`
	ArchiverKey  string    `json:"archiver_key"`
	Title        string    `json:"title"`
	TitleJpn     string    `json:"title_jpn"`
	Category     string    `json:"category"`
	Thumb        string    `json:"thumb"`
	Uploader     string    `json:"uploader"`
	Posted       string    `json:"posted"`
	FileCount    string    `json:"filecount"`
	FileSize     int       `json:"filesize"`
	Expunged     bool      `json:"expunged"`
	Rating       string    `json:"rating"`
	TorrentCount string    `json:"torrentcount"`
	Torrents     []Torrent `json:"torrents"`
	Tags         []string  `json:"tags"`
	ParentGId    string    `json:"parent_gid"`
	ParentKey    string    `json:"parent_key"`
	FirstGId     string    `json:"first_gid"`
	FirstKey     string    `json:"first_key"`
	Error        string    `json:"error,omitzero"`
}

GalleryMetadata 来自官方 api

type GalleryMetadataRequest

type GalleryMetadataRequest struct {
	Method    string  `json:"method"`
	GIdList   [][]any `json:"gidlist"`
	Namespace int     `json:"namespace"`
}

type GalleryMetadataResponse

type GalleryMetadataResponse struct {
	GMetadata []GalleryMetadata `json:"gmetadata"`
}

func PostGalleryMetadata

func PostGalleryMetadata(ctx context.Context, g ...GIdList) (resp *GalleryMetadataResponse, err error)

PostGalleryMetadata posts to the official API and returns gallery metadata

type GalleryMetadatas

type GalleryMetadatas []GalleryMetadata

func EHSearchDetail deprecated

func EHSearchDetail(ctx context.Context, keyword string, categories ...Category) (total int, galleries GalleryMetadatas, err error)

EHSearchDetail 搜索 EHentai 并返回详细信息, galleries 只有第一页结果

Deprecated: simply use SearchDetail instead

EHentai.SearchDetail(ctx, EHentai.EHENTAI_URL, keyword, categories...)

func ExHSearchDetail deprecated

func ExHSearchDetail(ctx context.Context, keyword string, categories ...Category) (total int, galleries GalleryMetadatas, err error)

ExHSearchDetail 搜索 ExHentai 并返回详细信息, galleries 只有第一页结果

Deprecated: simply use SearchDetail instead

EHentai.SearchDetail(ctx, EHentai.EXHENTAI_URL, keyword, categories...)

func SearchDetail added in v0.1.0

func SearchDetail(ctx context.Context, url Url, keyword string, categories ...Category) (total int, galleries GalleryMetadatas, err error)

SearchDetail 搜索并返回详细信息, galleries 只有第一页的结果

func (GalleryMetadatas) GetCover

func (gm GalleryMetadatas) GetCover() (urls []string)

type GalleryTokenRequest

type GalleryTokenRequest struct {
	Method   string  `json:"method"`
	PageList [][]any `json:"pagelist"`
}

type GalleryTokenResponse

type GalleryTokenResponse struct {
	TokenLists []TokenList `json:"tokenlist"`
}

func PostGalleryToken

func PostGalleryToken(ctx context.Context, p ...PageList) (*GalleryTokenResponse, error)

PostGalleryToken posts to the official API and returns gallery token

type Image

type Image struct {
	Data    []byte
	Type    ImageType
	TypeRaw string
}

func DownloadCovers

func DownloadCovers(ctx context.Context, results coverProviders) ([]Image, error)

DownloadCovers 通过搜索结果下载封面

func (*Image) Decode added in v0.0.3

func (i *Image) Decode() (image.Image, error)

func (*Image) String

func (i *Image) String() string

type ImageType

type ImageType int
const (
	IMAGE_TYPE_UNKNOWN ImageType = iota

	IMAGE_TYPE_WEBP // 某个时期之后似乎都是 webp
	IMAGE_TYPE_JPEG
	IMAGE_TYPE_PNG // 好像没有
)

func ParseImageMimeType added in v0.1.0

func ParseImageMimeType(mimeType string) ImageType

func ParseImageType

func ParseImageType(s string) ImageType

func (ImageType) String

func (it ImageType) String() string

type Interceptor

type Interceptor interface {
	OnRequest(req *http.Request) error    // 返回 error 可中断请求
	OnResponse(resp *http.Response) error // 返回 error 可中断请求
	OnError(req *http.Request, err error)
}

Interceptor is used to modify the request and interpret the response

Implemented by DomainFrontingInterceptor

type InterceptorRoundTrip

type InterceptorRoundTrip struct {
	RoundTripper http.RoundTripper
	Interceptors []Interceptor
}

InterceptorRoundTrip implements http.RoundTripper, but it WILL modify the request and interpret the response

func (*InterceptorRoundTrip) OnError

func (i *InterceptorRoundTrip) OnError(req *http.Request, err error)

func (*InterceptorRoundTrip) OnRequest

func (i *InterceptorRoundTrip) OnRequest(req *http.Request) error

func (*InterceptorRoundTrip) OnResponse

func (i *InterceptorRoundTrip) OnResponse(resp *http.Response) error

func (*InterceptorRoundTrip) RoundTrip

func (i *InterceptorRoundTrip) RoundTrip(req *http.Request) (*http.Response, error)

type IpProvider

type IpProvider interface {
	Supports(host string) bool
	NextIp(host string) string
	AddUnavailableIp(host, ip string)
}

IpProvider 用于轮询提供某个域名的 ip 地址

全都不可用时, NextIp 也应返回下一个 ip

Implemented by EHRoundRobinIpProvider

type Page

type Page struct {
	PageToken string `json:"page_token"`
	GalleryId int    `json:"gallery_id"`
	PageNum   int    `json:"page_num"`
}

Page describes the page url

https://e-hentai.org/s/{page_token}/{gallery_id}-{pagenumber}

func UrlToPage

func UrlToPage(u string) Page

func (*Page) String

func (p *Page) String() string

type PageData

type PageData struct {
	Page
	Image
	FromCache bool
}

PageData carrys page info and image data

func DownloadGallery

func DownloadGallery(ctx context.Context, galleryUrl string, pageNums ...int) ([]PageData, error)

DownloadGallery 下载画廊下所有图片, 下载失败时自动尝试备链

不传入 pageNums 参数时下载所有页, 传入时按其顺序下载指定页, 重复、越界页将被忽略

func DownloadPages

func DownloadPages(ctx context.Context, pageUrls ...string) ([]PageData, error)

DownloadPages 下载画廊某页的图片, 下载失败时自动尝试备链

func (*PageData) String

func (pd *PageData) String() string

type PageList

type PageList = Page

PageList is the official alias of Page

type Tag added in v0.0.3

type Tag struct {
	Namespace string
	Name      string
}

func ParseTag added in v0.0.3

func ParseTag(tag string) (Tag, error)

func TranslateTag added in v0.0.3

func TranslateTag(tag Tag) Tag

func (Tag) String added in v0.0.3

func (t Tag) String() string

type TagSet added in v0.0.3

type TagSet struct {
	Namespace string
	Tags      []string
}

type Tags added in v0.0.3

type Tags []Tag

func ParseTags added in v0.0.3

func ParseTags(tags []string) (Tags, error)

func TranslateTags added in v0.0.3

func TranslateTags(tags Tags) Tags

func (Tags) Names added in v0.0.3

func (t Tags) Names() (names []string)

func (Tags) Namespaces added in v0.0.3

func (t Tags) Namespaces() (namespaces []string)

func (Tags) Set added in v0.0.3

func (t Tags) Set() (ts []TagSet)

type TokenList

type TokenList struct {
	GId   int    `json:"gid"`
	Token string `json:"token"`
}

func (*TokenList) ToGallery

func (tl *TokenList) ToGallery() Gallery

type Torrent

type Torrent struct {
	Hash  string `json:"hash"`
	Added string `json:"added"`
	Name  string `json:"name"`
	TSize string `json:"tsize"`
	FSize string `json:"fsize"`
}

type Url added in v0.1.0

type Url = string
const (
	EHENTAI_URL  Url = `https://` + EHENTAI_DOMAIN
	EXHENTAI_URL Url = `https://` + EXHENTAI_DOMAIN
)

Directories

Path Synopsis
cmd
EHentai-cli command
internal
env

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL