source

package
v0.4.4 Latest Latest
Warning

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

Go to latest
Published: Mar 21, 2026 License: MIT Imports: 21 Imported by: 0

Documentation

Overview

Package source provides metadata fetching from various sources.

Package source handles fetching APKs from various sources.

Index

Constants

View Source
const MaxDownloadSize int64 = 600 * 1024 * 1024 // 600MB

MaxDownloadSize is the hard cap for APK downloads (and any HTTP downloads via DownloadHTTP) to avoid excessive bandwidth or disk usage.

View Source
const MaxRemoteDownloadSize = 20 * 1024 * 1024 // 20MB

MaxRemoteDownloadSize is the maximum size for remote downloads (images, metadata, etc.) This prevents memory exhaustion from malicious or unexpectedly large responses.

Variables

View Source
var ErrNotModified = fmt.Errorf("release not modified")

ErrNotModified is returned when the release hasn't changed since the last check.

Functions

func DefaultMetadataSources

func DefaultMetadataSources(cfg *config.Config) []string

DefaultMetadataSources returns the metadata sources to use. The base source type (github, gitlab, fdroid) is always included automatically. Any additional sources from metadata_sources config are appended. Returns nil if no metadata source applies.

func DeleteCachedDownload

func DeleteCachedDownload(downloadURL, filename string) error

DeleteCachedDownload removes a cached download file. Returns nil if the file doesn't exist or was successfully deleted.

func DetectPlayStore

func DetectPlayStore(url string) bool

DetectPlayStore checks if a URL is a Play Store URL.

func DownloadCacheDir

func DownloadCacheDir() string

DownloadCacheDir returns the directory for caching downloaded APKs.

func DownloadCacheKey

func DownloadCacheKey(downloadURL string) string

DownloadCacheKey generates a cache key for a download URL. The key is a hex-encoded SHA256 hash prefix of the URL.

func DownloadHTTP

func DownloadHTTP(ctx context.Context, client *http.Client, url, destPath string, expectedSize int64, progress DownloadProgress) error

DownloadHTTP downloads a file from a URL with optional progress reporting. This is a shared helper for all HTTP-based sources. Uses stall-based timeout: fails only if no data received for 30s, not after a fixed total time.

func FetchReleaseNotes

func FetchReleaseNotes(ctx context.Context, pathOrURL string, version string, baseDir string) (string, error)

FetchReleaseNotes fetches release notes from a URL or local file. If the content follows the Keep a Changelog format and a version is provided, only the section for that version is extracted.

func GetCachedDownload

func GetCachedDownload(downloadURL, filename string) string

GetCachedDownload checks if a download is already cached. Returns the path if cached and valid, empty string otherwise.

func GetPlayStorePackageID

func GetPlayStorePackageID(url string) string

GetPlayStorePackageID extracts the package ID from a Play Store URL.

func HasUnsupportedArchitecture

func HasUnsupportedArchitecture(filename string) bool

HasUnsupportedArchitecture returns true if the filename explicitly indicates an unsupported architecture (x86, x86_64, etc.).

func HasValidAPKs added in v0.3.1

func HasValidAPKs(assets []*Asset) bool

HasValidAPKs returns true if the assets contain at least one APK file. Used to determine if a release is a valid mobile release (vs desktop-only).

func IsAPKAsset added in v0.3.1

func IsAPKAsset(name, url string) bool

IsAPKAsset checks if an asset (by name or URL) is an APK file.

func IsAPKURL added in v0.3.1

func IsAPKURL(rawURL string) bool

IsAPKURL checks if a URL points to an APK file. Properly handles URLs with query parameters.

func SaveToDownloadCache

func SaveToDownloadCache(downloadURL, filename, srcPath string) (string, error)

SaveToDownloadCache saves a downloaded file to the cache. Returns the cached path on success.

Types

type AppMetadata

type AppMetadata struct {
	Name        string
	Description string
	Summary     string
	Website     string
	License     string
	Tags        []string
	ImageURLs   []string
	IconURL     string // URL to app icon (from Play Store or F-Droid)
}

AppMetadata contains enriched app metadata from external sources.

func FetchPlayStoreMetadata

func FetchPlayStoreMetadata(ctx context.Context, packageID string) (*AppMetadata, error)

FetchPlayStoreMetadata is a convenience function to fetch metadata by package ID.

type Asset

type Asset struct {
	Name        string // Filename
	URL         string // Download URL (empty for local files)
	Size        int64  // Size in bytes (0 if unknown)
	LocalPath   string // Local file path (set after download or for local sources)
	ContentType string // MIME type (if known)
	ExcludeURL  bool   // If true, don't include URL in event (use Blossom URL only)
}

Asset represents a downloadable APK asset.

func FilterUnsupportedArchitectures

func FilterUnsupportedArchitectures(assets []*Asset) []*Asset

FilterUnsupportedArchitectures removes APK assets that explicitly indicate unsupported architectures (x86, x86_64, etc.) in their filename. Assets without architecture indicators or with supported architectures (arm64-v8a, armeabi-v7a) are kept.

type CacheClearer

type CacheClearer interface {
	// ClearCache removes any cached release data.
	ClearCache() error
}

CacheClearer is an optional interface for sources that support cache clearing. Sources that cache release data (like GitHub with ETags) should implement this to allow clearing the cache when publishing fails.

type CacheCommitter

type CacheCommitter interface {
	// CommitCache persists the pending cache data to disk.
	// Should be called after successful publishing.
	CommitCache() error
}

CacheCommitter is an optional interface for sources that support deferred cache commits. Sources like GitHub store cache data in memory during fetch, then commit to disk only after successful publishing via CommitCache().

type CacheSkipper added in v0.4.3

type CacheSkipper interface {
	SetSkipCache(bool)
}

CacheSkipper is an optional interface for sources that support bypassing their ETag/version cache. Used as a fallback when ErrNotModified is returned but no cached release is available — the workflow retries with the cache skipped.

type CachedReleaseProvider added in v0.4.1

type CachedReleaseProvider interface {
	GetCachedRelease() *Release
}

CachedReleaseProvider is an optional interface for sources that can return a previously cached release. Used when FetchLatestRelease returns ErrNotModified so the workflow can proceed with the cached data instead of aborting.

type DownloadProgress

type DownloadProgress func(downloaded, total int64)

DownloadProgress is called during downloads to report progress.

type FDroid

type FDroid struct {
	SkipCache bool
	// contains filtered or unexported fields
}

FDroid implements Source for F-Droid compatible repositories. Supports: f-droid.org, IzzyOnDroid (apt.izzysoft.de), and other F-Droid repos.

func NewFDroid

func NewFDroid(cfg *config.Config) (*FDroid, error)

NewFDroid creates a new F-Droid source.

func (*FDroid) CommitCache added in v0.4.2

func (f *FDroid) CommitCache() error

CommitCache persists the pending cache to disk after successful publishing.

func (*FDroid) Download

func (f *FDroid) Download(ctx context.Context, asset *Asset, destDir string, progress DownloadProgress) (string, error)

Download downloads an APK from F-Droid. Uses a download cache to avoid re-downloading the same file.

func (*FDroid) FetchLatestRelease

func (f *FDroid) FetchLatestRelease(ctx context.Context) (*Release, error)

FetchLatestRelease fetches the latest release from an F-Droid compatible repository. For repos with a per-package API (f-droid.org), uses a lightweight API call. For others (IzzyOnDroid), fetches the shared index with ETag caching to avoid re-downloading the full 14–50 MB file when unchanged.

func (*FDroid) FetchMetadata

func (f *FDroid) FetchMetadata(ctx context.Context) (*fdroidMetadata, error)

FetchMetadata fetches app metadata from the repository's metadata source.

func (*FDroid) GetCachedRelease added in v0.4.2

func (f *FDroid) GetCachedRelease() *Release

GetCachedRelease returns the cached release for this package if available.

func (*FDroid) PackageID

func (f *FDroid) PackageID() string

PackageID returns the package ID.

func (*FDroid) RepoInfo

func (f *FDroid) RepoInfo() *config.FDroidRepoInfo

RepoInfo returns the repository information.

func (*FDroid) SetSkipCache added in v0.4.3

func (f *FDroid) SetSkipCache(v bool)

SetSkipCache implements CacheSkipper.

func (*FDroid) Type

func (f *FDroid) Type() config.SourceType

Type returns the source type.

type GitHub

type GitHub struct {
	SkipCache          bool // Set to true to bypass ETag cache (--overwrite-release)
	IncludePreReleases bool // Set to true to include pre-releases (--pre-release)
	// contains filtered or unexported fields
}

GitHub implements Source for GitHub releases.

func NewGitHub

func NewGitHub(cfg *config.Config) (*GitHub, error)

NewGitHub creates a new GitHub source.

func (*GitHub) ClearCache

func (g *GitHub) ClearCache() error

ClearCache removes the cached release data. This should be called when publishing fails so the next run can retry.

func (*GitHub) CommitCache

func (g *GitHub) CommitCache() error

CommitCache saves the pending cache to disk. This should be called after successful publishing to persist the ETag.

func (*GitHub) Download

func (g *GitHub) Download(ctx context.Context, asset *Asset, destDir string, progress DownloadProgress) (string, error)

Download downloads an asset from GitHub. Uses a download cache to avoid re-downloading the same file.

func (*GitHub) FetchLatestRelease

func (g *GitHub) FetchLatestRelease(ctx context.Context) (*Release, error)

FetchLatestRelease fetches the latest release from GitHub that contains valid APKs. First tries /releases/latest (single request, fast path). If that release is a draft, a pre-release (when not opted in), or carries no valid APKs, falls back to scanning the most recent releases list to find one that qualifies. Uses conditional requests (ETag/If-None-Match) on the fast path to reduce rate limit usage. Returns ErrNotModified if the latest release hasn't changed since the last check. Set SkipCache to true to bypass the ETag check and always fetch fresh data.

func (*GitHub) GetCachedRelease

func (g *GitHub) GetCachedRelease() *Release

GetCachedRelease returns the cached release if available.

func (*GitHub) SetSkipCache added in v0.4.3

func (g *GitHub) SetSkipCache(v bool)

SetSkipCache implements CacheSkipper.

func (*GitHub) Type

func (g *GitHub) Type() config.SourceType

Type returns the source type.

type GitLab

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

GitLab implements Source for GitLab releases. Supports both gitlab.com and self-hosted GitLab instances.

func NewGitLab

func NewGitLab(cfg *config.Config) (*GitLab, error)

NewGitLab creates a new GitLab source.

func (*GitLab) Download

func (g *GitLab) Download(ctx context.Context, asset *Asset, destDir string, progress DownloadProgress) (string, error)

Download downloads an asset from GitLab. Uses a download cache to avoid re-downloading the same file.

func (*GitLab) FetchLatestRelease

func (g *GitLab) FetchLatestRelease(ctx context.Context) (*Release, error)

FetchLatestRelease fetches the latest release from GitLab that contains valid APKs. Iterates through up to 10 releases to find one with APK assets (for repos that publish desktop and mobile releases separately).

func (*GitLab) Type

func (g *GitLab) Type() config.SourceType

Type returns the source type.

type Gitea

type Gitea struct {
	IncludePreReleases bool // Set to true to include pre-releases (--pre-release)
	// contains filtered or unexported fields
}

Gitea implements Source for Gitea/Forgejo/Codeberg releases. This covers any Gitea-compatible forge (Gitea, Forgejo, Codeberg, etc.)

func NewGitea

func NewGitea(cfg *config.Config) (*Gitea, error)

NewGitea creates a new Gitea source.

func (*Gitea) Download

func (g *Gitea) Download(ctx context.Context, asset *Asset, destDir string, progress DownloadProgress) (string, error)

Download downloads an asset from a Gitea-compatible forge. Uses a download cache to avoid re-downloading the same file.

func (*Gitea) FetchLatestRelease

func (g *Gitea) FetchLatestRelease(ctx context.Context) (*Release, error)

FetchLatestRelease fetches the latest release from a Gitea-compatible forge that contains valid APKs. Iterates through up to 10 releases to find one with APK assets (for repos that publish desktop and mobile releases separately).

func (*Gitea) Type

func (g *Gitea) Type() config.SourceType

Type returns the source type.

type Local

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

Local implements Source for local filesystem APKs.

func NewLocal

func NewLocal(pattern string) (*Local, error)

NewLocal creates a new local source.

func NewLocalWithBase

func NewLocalWithBase(pattern, baseDir string) (*Local, error)

NewLocalWithBase creates a new local source with a base directory for relative paths.

func (*Local) Download

func (l *Local) Download(ctx context.Context, asset *Asset, destDir string, progress DownloadProgress) (string, error)

Download returns the local path (no download needed for local files).

func (*Local) FetchLatestRelease

func (l *Local) FetchLatestRelease(ctx context.Context) (*Release, error)

FetchLatestRelease finds local APK files matching the pattern.

func (*Local) Type

func (l *Local) Type() config.SourceType

Type returns the source type.

type MetadataError

type MetadataError struct {
	Source string
	Err    error
}

MetadataError represents a non-fatal error when fetching metadata from a source.

func (*MetadataError) Error

func (e *MetadataError) Error() string

type MetadataFetcher

type MetadataFetcher struct {
	PackageID string // App package ID (e.g., "com.example.app") - set from APK parsing
	APKName   string // App name from APK - takes priority over metadata sources
	// contains filtered or unexported fields
}

MetadataFetcher fetches metadata from external sources.

func NewMetadataFetcher

func NewMetadataFetcher(cfg *config.Config) *MetadataFetcher

NewMetadataFetcher creates a new metadata fetcher.

func NewMetadataFetcherWithPackageID

func NewMetadataFetcherWithPackageID(cfg *config.Config, packageID string) *MetadataFetcher

NewMetadataFetcherWithPackageID creates a new metadata fetcher with a known package ID.

func (*MetadataFetcher) FetchMetadata

func (f *MetadataFetcher) FetchMetadata(ctx context.Context, sources []string) error

FetchMetadata fetches metadata from the specified sources and merges into config. Sources can be: "github", "gitlab", "fdroid", "playstore" Only empty fields in config are populated (existing values are preserved). Returns a MetadataResult containing any non-fatal errors from individual sources.

func (*MetadataFetcher) FetchMetadataWithResult

func (f *MetadataFetcher) FetchMetadataWithResult(ctx context.Context, sources []string) *MetadataResult

FetchMetadataWithResult fetches metadata and returns detailed results including partial failures.

type MetadataResult

type MetadataResult struct {
	// Errors contains non-fatal errors from individual sources.
	// The fetch continues even if some sources fail.
	Errors []*MetadataError
}

MetadataResult contains the result of fetching metadata from multiple sources.

func (*MetadataResult) HasErrors

func (r *MetadataResult) HasErrors() bool

HasErrors returns true if any metadata sources failed.

type Options

type Options struct {
	// BaseDir is the base directory for resolving relative paths.
	// Typically the directory containing the config file.
	BaseDir string

	// SkipCache bypasses ETag cache for GitHub sources (--overwrite-release).
	SkipCache bool

	// IncludePreReleases includes pre-releases when fetching the latest release (--pre-release).
	IncludePreReleases bool
}

Options contains options for creating a source.

type ParsedRelease

type ParsedRelease struct {
	Release *Release
	APK     *apk.APKInfo
	Asset   *Asset
}

ParsedRelease contains a release with its parsed APK information.

type PlayStore

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

PlayStore provides metadata fetching from Google Play Store.

func NewPlayStore

func NewPlayStore(packageID string) *PlayStore

NewPlayStore creates a new Play Store metadata fetcher.

func (*PlayStore) FetchMetadata

func (p *PlayStore) FetchMetadata(ctx context.Context) (*PlayStoreMetadata, error)

FetchMetadata fetches app metadata from the Google Play Store.

func (*PlayStore) PackageID

func (p *PlayStore) PackageID() string

PackageID returns the package ID.

func (*PlayStore) Type

func (p *PlayStore) Type() config.SourceType

Type returns the source type for Play Store.

type PlayStoreMetadata

type PlayStoreMetadata struct {
	Name        string
	Description string
	IconURL     string
	ImageURLs   []string
}

PlayStoreMetadata contains metadata scraped from the Play Store.

type ProgressReader

type ProgressReader struct {
	Reader     io.Reader
	Total      int64
	Downloaded int64
	OnProgress DownloadProgress
}

Downloader wraps an io.Reader to track download progress.

func (*ProgressReader) Read

func (pr *ProgressReader) Read(p []byte) (int, error)

type Release

type Release struct {
	Version    string    // Version string (e.g., "1.2.3" or "v1.2.3")
	TagName    string    // Git tag name (if applicable)
	Changelog  string    // Release notes/changelog
	Assets     []*Asset  // Available APK assets
	PreRelease bool      // Whether this is a pre-release
	URL        string    // Release page URL (e.g., https://github.com/user/repo/releases/tag/v1.0)
	CreatedAt  time.Time // Release creation/publish date (zero if unknown)
}

Release represents a release containing one or more APK assets.

type Source

type Source interface {
	// Type returns the source type.
	Type() config.SourceType

	// FetchLatestRelease fetches the latest release information.
	FetchLatestRelease(ctx context.Context) (*Release, error)

	// Download downloads an asset and returns the local path.
	// For local sources, this may just return the existing path.
	// The optional progress callback is called during download.
	Download(ctx context.Context, asset *Asset, destDir string, progress DownloadProgress) (string, error)
}

Source is the interface for APK sources.

func New

func New(cfg *config.Config) (Source, error)

New creates a new source based on the config.

func NewWithOptions

func NewWithOptions(cfg *config.Config, opts Options) (Source, error)

NewWithOptions creates a new source with options.

type StallTimeoutReader added in v0.3.3

type StallTimeoutReader struct {
	Reader  io.Reader
	Timeout time.Duration
	// contains filtered or unexported fields
}

StallTimeoutReader wraps an io.Reader and returns an error if no data is received for the specified duration. Unlike http.Client.Timeout, this only triggers when the download stalls — not after a fixed total time.

func (*StallTimeoutReader) Read added in v0.3.3

func (r *StallTimeoutReader) Read(p []byte) (int, error)

type Web

type Web struct {
	SkipCache bool // Set to true to bypass version/HTTP cache
	// contains filtered or unexported fields
}

Web implements Source for web scraping with version extraction.

func NewWeb

func NewWeb(cfg *config.Config) (*Web, error)

NewWeb creates a new web scraping source.

func (*Web) ClearCache

func (w *Web) ClearCache() error

ClearCache removes the cached data.

func (*Web) CommitCache

func (w *Web) CommitCache() error

CommitCache saves the pending cache to disk. This should be called after successful publishing to persist the cache.

func (*Web) Download

func (w *Web) Download(ctx context.Context, asset *Asset, destDir string, progress DownloadProgress) (string, error)

Download downloads an APK from the web. Uses a download cache to avoid re-downloading the same file.

func (*Web) FetchLatestRelease

func (w *Web) FetchLatestRelease(ctx context.Context) (*Release, error)

FetchLatestRelease fetches the latest release from a web source.

The method supports four modes:

1. Version extraction mode (version + asset_url with {version} template):

  • Fetches the URL and extracts version using the configured extractor
  • Substitutes {version} in asset_url to get the download URL
  • Caches by version - skips if version hasn't changed

2. Asset extraction mode (version + asset extractor):

  • Extracts version from page for caching (skips if unchanged)
  • Extracts download URL dynamically from page using asset extractor
  • Used for sites with dynamic/expiring download URLs (e.g., CDN tokens)

3. Direct URL mode (asset_url only, no version extractor):

  • Uses asset_url directly as the download URL
  • Uses HTTP caching (ETag/Last-Modified) to detect changes
  • Version is extracted from the downloaded APK

4. Direct URL shorthand (release_source: "https://example.com/app.apk"):

  • Same as mode 3, but specified as a simple string

func (*Web) GetCachedRelease added in v0.4.1

func (w *Web) GetCachedRelease() *Release

GetCachedRelease returns the cached release if available.

func (*Web) SetSkipCache added in v0.4.3

func (w *Web) SetSkipCache(v bool)

SetSkipCache implements CacheSkipper.

func (*Web) Type

func (w *Web) Type() config.SourceType

Type returns the source type.

Jump to

Keyboard shortcuts

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