sinac

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Sep 24, 2026 License: MIT Imports: 16 Imported by: 0

README

ScrapingIsNotACrime Go SDK

Go Reference license

Official Go SDK for the ScrapingIsNotACrime public data API — typed access to Instagram, TikTok, YouTube, App Store, GitHub, Hacker News, Bluesky, Twitch and Linktree.

Install

go get github.com/ScrapingIsNotACrime/sdk-go

Requires Go 1.23+. The module has no third-party dependencies: only the standard library.

Quick start

package main

import (
	"context"
	"errors"
	"fmt"
	"log"

	sinac "github.com/ScrapingIsNotACrime/sdk-go"
)

func main() {
	client, err := sinac.NewClient(sinac.WithAPIKey("sinac_..."))
	if err != nil {
		log.Fatal(err)
	}

	profile, err := client.Instagram.Profile(context.Background(), "nasa")
	if errors.Is(err, sinac.ErrNotFound) {
		fmt.Println("no such profile")
		return
	}
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(profile.Username, profile.Followers)
}

NewClient also reads the API key from the SCRAPINGISNOTACRIME_API_KEY environment variable, so sinac.NewClient() with no options works when that variable is set. Get a key at scrapingisnotacrime.com/dashboard/api-keys. Keys start with sinac_.

More runnable examples are in example_test.go (Example and ExamplePage_All), and on pkg.go.dev.

Configuration

client, err := sinac.NewClient(
	sinac.WithAPIKey("sinac_..."),
	sinac.WithBaseURL("https://api.scrapingisnotacrime.com/v1"),
	sinac.WithTimeout(30*time.Second),
	sinac.WithMaxRetries(2),
	sinac.WithHTTPClient(nil),
)
Option Default Description
WithAPIKey SCRAPINGISNOTACRIME_API_KEY env var Your API key (sinac_…). NewClient returns an error if none is set.
WithBaseURL https://api.scrapingisnotacrime.com/v1 API base URL. Should be the final HTTPS URL (http is accepted for local testing); redirects are not followed, so a base URL that itself redirects fails (see Errors) — following one would forward the X-Api-Key header to whatever host it points to.
WithTimeout 30 * time.Second Per attempt, covering connect, headers and the whole body read.
WithMaxRetries 2 Extra attempts for 429, 502 and network errors. 0 disables retries.
WithHTTPClient a new *http.Client Bring your own *http.Client (for tests, proxies or connection pooling). It is never modified: the SDK works with a shallow copy so it can set its own CheckRedirect.

Methods

Client groups its methods under a service field per platform: client.Instagram, client.TikTok, client.YouTube, client.AppStore, client.GitHub, client.HackerNews, client.Bluesky, client.Twitch, client.Linktree. Every method takes a context.Context as its first argument (omitted below for brevity) and returns the response envelope's data, decoded into a typed struct.

Methods marked Page return *Page[T, R] (see Pagination) instead of a plain struct.

Namespace Method Route Page
Instagram Profile(username) /instagram/profile/{username}
Instagram Contact(username) /instagram/profile/{username}/contact
Instagram LatestPosts(username) /instagram/profile/{username}/timeline/latest
Instagram Posts(username, p *InstagramPostsParams) — Count 1-50 (default 12), Cursor /instagram/profile/{username}/timeline Page
Instagram Highlights(username) /instagram/profile/{username}/highlights
Instagram Highlight(highlightID) /instagram/highlights/{highlightId}
Instagram MediaByID(username, mediaID) /instagram/profile/{username}/media/{mediaId}
Instagram Media(shortcode) /instagram/media/{shortcode}
Instagram Download(shortcode) /instagram/media/{shortcode}/download
Instagram ShortcodeToID(shortcode) /instagram/media/{shortcode}/id
Instagram IDToShortcode(mediaID) /instagram/media/id/{mediaId}
Instagram Reel(shortcode) /instagram/reels/{shortcode}
TikTok Profile(username) /tiktok/profile/{username}
TikTok Video(videoID) /tiktok/video/{videoId}
YouTube Videos(handle) /youtube/channel/{handle}/videos
App Store Search(term, p *AppStoreSearchParams) — Country (default "us"), Limit 1-200 (default 10) /appstore/search
App Store Reviews(appID, p *AppStoreReviewsParams) — Country (default "us"), Page 1-10 (default 1) /appstore/reviews Page
GitHub Profile(handle) /github/profiles/{handle}
GitHub Followers(handle, p *GitHubListParams) — Limit 1-100 (default 30), Page 1-based (default 1) /github/profiles/{handle}/followers Page
GitHub Following(handle, p *GitHubListParams) — same params as Followers /github/profiles/{handle}/following Page
GitHub Repositories(handle, p *GitHubListParams) — same params as Followers /github/profiles/{handle}/repositories Page
GitHub SearchRepositories(q, p *GitHubListParams) — q in GitHub search syntax; same params as Followers /github/repositories Page
GitHub Trending(p *GitHubTrendingParams) — Since (default daily), Language, Limit 1-100 (default 30) /github/trending/repositories
Hacker News Feed(feed, p *HackerNewsListParams) — Limit 1-50 (default 20), Page 0-based (default 0) /hackernews/feeds/{feed} Page
Hacker News Item(id) /hackernews/items/{id}
Hacker News Search(q, p *HackerNewsListParams) — same params as Feed /hackernews/search Page
Hacker News User(username) /hackernews/users/{username}
Hacker News Submissions(username, p *HackerNewsListParams) — same params as Feed /hackernews/users/{username}/submissions Page
Hacker News Comments(username, p *HackerNewsListParams) — same params as Feed /hackernews/users/{username}/comments Page
Bluesky Profile(handle) /bluesky/profiles/{handle}
Bluesky Posts(handle, p *BlueskyPostsParams) — Limit 1-100 (default 25), Cursor /bluesky/profiles/{handle}/posts Page
Twitch Profile(handle) /twitch/profiles/{handle}
Twitch Videos(handle, p *TwitchVideosParams) — Limit 1-100 (default 20) /twitch/profiles/{handle}/videos
Linktree Profile(handle) /linktree/profiles/{handle}

GitHubTrendingSince (Trending's Since) and HackerNewsFeed (Feed's feed) are typed string constants — sinac.TrendingDaily/TrendingWeekly/TrendingMonthly and sinac.FeedTop/FeedNew/FeedBest/FeedAsk/FeedShow/FeedJob. Pass nil for any p *...Params to use every default. Path arguments (username, handle, shortcode, …) are validated before any request: an empty string, "." or ".." returns *sinac.Error with Status: 0 and errors.Is(err, sinac.ErrBadRequest), and the value is percent-encoded the way JavaScript's encodeURIComponent would encode it.

Pagination

Methods marked Page return *sinac.Page[T, R], where T is the item type and R is the full page response:

type Page[T, R any] struct {
	Items      []T
	HasMore    bool
	NextCursor string // cursor endpoints; "" when there is none
	NextPage   int    // page-number endpoints; 0 when there is none
	Data       R      // the untouched response of this page

	// Next fetches the following page. It returns (nil, nil) when there is none.
	// All yields every item from this page onward, fetching later pages lazily.
}

HasMore is true exactly when Next will fetch another page (an empty final page, or an announced next page/cursor with no items, both leave it false).

Iterate every item with All, using Go 1.23's range-over-func. Iterating fetches every remaining page; each page is one billed request, so bound the loop:

page, err := client.GitHub.Followers(ctx, "torvalds", &sinac.GitHubListParams{Limit: 100})
if err != nil {
	log.Fatal(err)
}

n := 0
for user, err := range page.All(ctx) {
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(user.Username)
	if n++; n >= 250 {
		break // stop early; no further pages are fetched
	}
}

Or walk pages one at a time with Next:

page, err := client.Bluesky.Posts(ctx, "bsky.app", &sinac.BlueskyPostsParams{Limit: 25})
// Each page fetched is one billed request, so stop after a bounded number of pages.
for fetched := 0; page != nil && err == nil && fetched < 3; fetched++ {
	for _, post := range page.Items {
		fmt.Println(post.Text)
	}
	page, err = page.Next(ctx)
}
if err != nil {
	log.Fatal(err)
}

page.Data gives you the untouched response of the current page, so fields such as Total stay reachable. Breaking out of the All loop, or simply not calling Next again, stops fetching — no further pages are requested.

Errors

Every API, network and argument failure is a *sinac.Error; when your ctx is cancelled or past its deadline the error wraps ctx.Err() instead (errors.Is(err, context.Canceled)). Otherwise it carries Status (the HTTP status, or 0 for network errors and invalid arguments), Message and RequestID (the X-Request-Id header, when the API sends one). Match it by kind with errors.Is against the sentinels below, or unwrap it with errors.As to read Status and RequestID:

var apiErr *sinac.Error
if errors.As(err, &apiErr) {
	fmt.Println(apiErr.Status, apiErr.RequestID)
}
Sentinel Status Retried
ErrBadRequest 400, or an invalid path argument no
ErrAuthentication 401 no
ErrQuotaExceeded 402 no
ErrNotFound 404 no
ErrRateLimit 429 yes
ErrUpstream 502 yes
ErrConnection network failure or per-attempt timeout yes
ErrAPI any other status, a 2xx without the JSON envelope, or a redirect (not followed) no
profile, err := client.TikTok.Profile(ctx, "this-user-does-not-exist-123")
switch {
case errors.Is(err, sinac.ErrNotFound):
	fmt.Println("no such profile")
case errors.Is(err, sinac.ErrQuotaExceeded):
	log.Fatal(err) // includes the pricing link in Message
case errors.Is(err, sinac.ErrRateLimit):
	fmt.Println("TikTok is rate limiting; already retried, try again later")
case err != nil:
	log.Fatal(err)
default:
	fmt.Println(profile.Username)
}

Context and cancellation

Every method takes the caller's context.Context and honors it: a ctx that is already cancelled, or that reaches its deadline — including while the client is sleeping between retries — makes the call return immediately with an error satisfying errors.Is(err, ctx.Err()). Cancellation is never retried, even when the underlying failure would otherwise be.

A per-attempt timeout is reported as ErrConnection, and its cause also matches context.DeadlineExceeded — check errors.Is(err, sinac.ErrConnection) or ctx.Err() != nil to tell the SDK's timeout from your own deadline.

Retries

ErrRateLimit (429), ErrUpstream (502) and ErrConnection (network failure or per-attempt timeout) are retried automatically, up to WithMaxRetries additional attempts (default 2). These failures don't consume credits, so retrying them costs you nothing.

The delay before each retry uses the Retry-After header when the API sends one (seconds or an HTTP date); otherwise it's exponential backoff with full jitter, starting around 500 ms and doubling per attempt. Every wait is capped at 10 seconds. Set WithMaxRetries(0) to disable retries entirely.

Releases and changelog

Every merge to main is released automatically: the version comes from the commit messages since the last release, following Conventional Commits.

Commits since the last release New version (while in 0.x)
only docs:, chore:, test:, ci:, build:, refactor: none
at least one fix: patch (0.1.0 → 0.1.1)
at least one feat: minor (0.1.1 → 0.2.0)
feat!: or a BREAKING CHANGE: footer minor while in 0.x

The pipeline tags vX.Y.Z and publishes the GitHub Release with the notes; in Go the tag is the release — pkg.go.dev picks it up, no separate publish step. The changelog is the Releases page; the version is resolved from git tags at build time (runtime/debug.BuildInfo), so no version number is written into the repository.

Merge PRs with a merge commit or rebase so each Conventional Commit is analysed; if you squash, the PR title must be a Conventional Commit (e.g. feat: ...).

Documentation

Overview

Package sinac is the Go client for the ScrapingIsNotACrime API (https://scrapingisnotacrime.com): public data from Instagram, TikTok, YouTube, the App Store, GitHub, Hacker News, Bluesky, Twitch and Linktree.

import sinac "github.com/ScrapingIsNotACrime/sdk-go"

client, err := sinac.NewClient(sinac.WithAPIKey("sinac_..."))
profile, err := client.Instagram.Profile(ctx, "nasa")

Errors are *Error values matched with errors.Is against the Err* sentinels. Paginated methods return a *Page; Page.All iterates every item lazily, and each page fetched is one billed request.

Example
package main

import (
	"context"
	"errors"
	"fmt"
	"log"

	sinac "github.com/ScrapingIsNotACrime/sdk-go"
)

func main() {
	client, err := sinac.NewClient(sinac.WithAPIKey("sinac_..."))
	if err != nil {
		log.Fatal(err)
	}
	profile, err := client.Instagram.Profile(context.Background(), "nasa")
	if errors.Is(err, sinac.ErrNotFound) {
		fmt.Println("no such profile")
		return
	}
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(profile.Username, profile.Followers)
}

Index

Examples

Constants

View Source
const (
	// DefaultBaseURL is the production API.
	DefaultBaseURL = "https://api.scrapingisnotacrime.com/v1"
	// APIKeyEnv is read when WithAPIKey is not given.
	APIKeyEnv = "SCRAPINGISNOTACRIME_API_KEY"
	// DefaultTimeout bounds each attempt: connect, headers and body.
	DefaultTimeout = 30 * time.Second
	// DefaultMaxRetries is the number of extra attempts for 429, 502 and network errors.
	DefaultMaxRetries = 2
)
View Source
const PricingURL = "https://scrapingisnotacrime.com/#pricing"

PricingURL is appended to quota-exceeded messages.

Variables

View Source
var (
	ErrBadRequest     = errors.New("sinac: bad request")           // 400, or an invalid path argument
	ErrAuthentication = errors.New("sinac: authentication failed") // 401
	ErrQuotaExceeded  = errors.New("sinac: quota exceeded")        // 402
	ErrNotFound       = errors.New("sinac: not found")             // 404
	ErrRateLimit      = errors.New("sinac: rate limited")          // 429, retried, not charged
	ErrUpstream       = errors.New("sinac: upstream failure")      // 502, retried, not charged
	ErrConnection     = errors.New("sinac: connection error")      // network failure or timeout, retried
	ErrAPI            = errors.New("sinac: API error")             // anything else
)

Sentinel kinds, matched with errors.Is on any error the client returns.

View Source
var Version = moduleVersion()

Version is this module's version as recorded in the importing program's build info (for example "0.1.0"), or "dev" when it is unknown.

Functions

This section is empty.

Types

type AppStoreApp

type AppStoreApp struct {
	ID          int64    `json:"id"`
	BundleID    string   `json:"bundleId"`
	Name        string   `json:"name"`
	Developer   string   `json:"developer"`
	URL         string   `json:"url"`
	IconURL     string   `json:"iconUrl"`
	Price       float64  `json:"price"`
	Currency    string   `json:"currency"`
	Rating      float64  `json:"rating"`
	RatingCount int64    `json:"ratingCount"`
	Version     string   `json:"version"`
	Genres      []string `json:"genres"`
	Screenshots []string `json:"screenshots"`
}

AppStoreApp is one app in an App Store search result.

type AppStoreReview

type AppStoreReview struct {
	ID        string  `json:"id"`
	Author    string  `json:"author"`
	Rating    float64 `json:"rating"`
	Title     string  `json:"title"`
	Content   string  `json:"content"`
	Version   string  `json:"version"`
	UpdatedAt string  `json:"updatedAt"`
	VoteCount int64   `json:"voteCount"`
	VoteSum   int64   `json:"voteSum"`
}

AppStoreReview is one customer review of an app.

type AppStoreReviewPage

type AppStoreReviewPage struct {
	AppID   string           `json:"appId"`
	Country string           `json:"country"`
	Page    int64            `json:"page"`
	Reviews []AppStoreReview `json:"reviews"`
}

AppStoreReviewPage is GET /appstore/reviews.

type AppStoreReviewsParams

type AppStoreReviewsParams struct {
	Country string
	Page    int
}

AppStoreReviewsParams: Country (default "us"); Page 1-10 (default 1).

type AppStoreSearch

type AppStoreSearch struct {
	Term        string        `json:"term"`
	Country     string        `json:"country"`
	ResultCount int64         `json:"resultCount"`
	Apps        []AppStoreApp `json:"apps"`
}

AppStoreSearch is GET /appstore/search.

type AppStoreSearchParams

type AppStoreSearchParams struct {
	Country string
	Limit   int
}

AppStoreSearchParams: Country is a two-letter store code (default "us"); Limit 1-200 (default 10).

type AppStoreService

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

AppStoreService groups the App Store endpoints.

func (*AppStoreService) Reviews

Reviews is GET /appstore/reviews — pages 1-10 (Apple's cap); the API returns 400 past page 10.

func (*AppStoreService) Search

Search is GET /appstore/search — Country defaults to "us", Limit 1-200 (default 10).

type BlueskyPost

type BlueskyPost struct {
	URI       string `json:"uri"`
	CID       string `json:"cid"`
	Text      string `json:"text"`
	Author    string `json:"author"`
	Likes     int64  `json:"likes"`
	Reposts   int64  `json:"reposts"`
	Replies   int64  `json:"replies"`
	Quotes    int64  `json:"quotes"`
	CreatedAt string `json:"created_at"`
	IndexedAt string `json:"indexed_at"`
	URL       string `json:"url"`
}

BlueskyPost is one post in a profile's posts feed.

type BlueskyPostPage

type BlueskyPostPage struct {
	Posts      []BlueskyPost `json:"posts"`
	NextCursor *string       `json:"next_cursor,omitempty"`
	HasMore    bool          `json:"has_more"`
}

BlueskyPostPage is GET /bluesky/profiles/{handle}/posts.

type BlueskyPostsParams

type BlueskyPostsParams struct {
	Limit  int
	Cursor string
}

BlueskyPostsParams: Limit 1-100 (default 25); Cursor from Page.NextCursor.

type BlueskyProfile

type BlueskyProfile struct {
	DID         string `json:"did"`
	Handle      string `json:"handle"`
	DisplayName string `json:"display_name"`
	// Null when the profile has no bio.
	Description *string `json:"description,omitempty"`
	// Null when the profile has no avatar set.
	Avatar *string `json:"avatar,omitempty"`
	// Banner image URL; null when the profile has none set.
	Banner    *string `json:"banner,omitempty"`
	Followers int64   `json:"followers"`
	Following int64   `json:"following"`
	Posts     int64   `json:"posts"`
	CreatedAt string  `json:"created_at"`
	URL       string  `json:"url"`
}

BlueskyProfile is GET /bluesky/profiles/{handle}.

type BlueskyService

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

BlueskyService groups the Bluesky endpoints.

func (*BlueskyService) Posts

Posts is GET /bluesky/profiles/{handle}/posts — limit 1-100 (default 25), cursor-paginated.

func (*BlueskyService) Profile

func (s *BlueskyService) Profile(ctx context.Context, handle string) (*BlueskyProfile, error)

Profile is GET /bluesky/profiles/{handle} — full handle including the domain.

type Client

type Client struct {
	Instagram  *InstagramService
	TikTok     *TikTokService
	YouTube    *YouTubeService
	AppStore   *AppStoreService
	GitHub     *GitHubService
	HackerNews *HackerNewsService
	Bluesky    *BlueskyService
	Twitch     *TwitchService
	Linktree   *LinktreeService
}

Client is the ScrapingIsNotACrime API client. It is safe for concurrent use.

func NewClient

func NewClient(opts ...Option) (*Client, error)

NewClient builds a client. It returns an error for a missing API key (pass WithAPIKey or set SCRAPINGISNOTACRIME_API_KEY) or an invalid option.

type Error

type Error struct {
	Status    int    // HTTP status; 0 when there was no response
	Message   string // the API's message, or a description of the failure
	RequestID string // the x-request-id header, "" when absent
	// contains filtered or unexported fields
}

Error is returned for every API, network and argument failure.

func (*Error) Error

func (e *Error) Error() string

func (*Error) Is

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

Is reports whether target is this error's kind sentinel.

func (*Error) Unwrap

func (e *Error) Unwrap() error

Unwrap returns the underlying cause (network errors), or nil.

type GitHubListParams

type GitHubListParams struct {
	Limit int
	Page  int
}

GitHubListParams: Limit 1-100 (default 30); Page is 1-based (default 1).

type GitHubProfile

type GitHubProfile struct {
	Username string `json:"username"`
	ID       int64  `json:"id"`
	// Null when the user has not set a display name.
	Name *string `json:"name,omitempty"`
	// Null when the user has not set a bio.
	Bio      *string `json:"bio,omitempty"`
	Company  *string `json:"company,omitempty"`
	Location *string `json:"location,omitempty"`
	// Website URL; empty or null when the user has not set one.
	Blog        *string `json:"blog,omitempty"`
	PublicRepos int64   `json:"public_repos"`
	Followers   int64   `json:"followers"`
	Following   int64   `json:"following"`
	Avatar      string  `json:"avatar"`
	CreatedAt   string  `json:"created_at"`
	URL         string  `json:"url"`
}

GitHubProfile is GET /github/profiles/{handle}.

type GitHubRepository

type GitHubRepository struct {
	Name     string `json:"name"`
	FullName string `json:"full_name"`
	// Null when the repository has no description.
	Description *string `json:"description,omitempty"`
	Stars       int64   `json:"stars"`
	Forks       int64   `json:"forks"`
	// Primary language; null when GitHub has not detected one.
	Language   *string  `json:"language,omitempty"`
	Topics     []string `json:"topics"`
	IsFork     bool     `json:"is_fork"`
	IsArchived bool     `json:"is_archived"`
	CreatedAt  string   `json:"created_at"`
	UpdatedAt  string   `json:"updated_at"`
	URL        string   `json:"url"`
}

GitHubRepository is a repository, as returned by the profile repositories list, search, and trending endpoints.

type GitHubRepositoryPage

type GitHubRepositoryPage struct {
	Items []GitHubRepository `json:"items"`
	// Always null: GitHub's REST API does not report a count for this collection.
	Total   *int64 `json:"total,omitempty"`
	HasMore bool   `json:"has_more"`
}

GitHubRepositoryPage is GET /github/profiles/{handle}/repositories.

type GitHubRepositorySearchPage

type GitHubRepositorySearchPage struct {
	Items   []GitHubRepository `json:"items"`
	Total   int64              `json:"total"`
	Page    int64              `json:"page"`
	HasMore bool               `json:"has_more"`
}

GitHubRepositorySearchPage is GET /github/repositories.

type GitHubService

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

GitHubService groups the GitHub endpoints.

func (*GitHubService) Followers

Followers is GET /github/profiles/{handle}/followers — limit 1-100 (default 30), 1-based pages.

func (*GitHubService) Following

Following is GET /github/profiles/{handle}/following — limit 1-100 (default 30), 1-based pages.

func (*GitHubService) Profile

func (s *GitHubService) Profile(ctx context.Context, handle string) (*GitHubProfile, error)

Profile is GET /github/profiles/{handle}.

func (*GitHubService) Repositories

Repositories is GET /github/profiles/{handle}/repositories — limit 1-100 (default 30), 1-based pages.

func (*GitHubService) SearchRepositories

SearchRepositories is GET /github/repositories — q in GitHub search syntax; limit 1-100 (default 30), 1-based pages.

func (*GitHubService) Trending

Trending is GET /github/trending/repositories — Since defaults to "daily"; Limit 1-100 (default 30). Not paginated.

type GitHubTrending

type GitHubTrending struct {
	Items   []GitHubRepository `json:"items"`
	Total   int64              `json:"total"`
	Page    int64              `json:"page"`
	HasMore bool               `json:"has_more"`
}

GitHubTrending is GET /github/trending/repositories.

type GitHubTrendingParams

type GitHubTrendingParams struct {
	Since    GitHubTrendingSince
	Language string
	Limit    int
}

GitHubTrendingParams: Since (default daily), Language (e.g. "go"), Limit 1-100 (default 30).

type GitHubTrendingSince

type GitHubTrendingSince string

GitHubTrendingSince is the period of GitHub's trending list.

const (
	TrendingDaily   GitHubTrendingSince = "daily"
	TrendingWeekly  GitHubTrendingSince = "weekly"
	TrendingMonthly GitHubTrendingSince = "monthly"
)

type GitHubUser

type GitHubUser struct {
	Username string `json:"username"`
	ID       int64  `json:"id"`
	Avatar   string `json:"avatar"`
	URL      string `json:"url"`
}

GitHubUser is a user in a followers/following page.

type GitHubUserPage

type GitHubUserPage struct {
	Items []GitHubUser `json:"items"`
	// Always null: GitHub's REST API does not report a count for this collection.
	Total   *int64 `json:"total,omitempty"`
	HasMore bool   `json:"has_more"`
}

GitHubUserPage is GET /github/profiles/{handle}/followers and /following.

type HackerNewsComment

type HackerNewsComment struct {
	ID        int64               `json:"id"`
	Author    string              `json:"author"`
	Text      string              `json:"text"`
	CreatedAt string              `json:"created_at"`
	Replies   []HackerNewsComment `json:"replies"`
}

HackerNewsComment is a comment inside an item's comment tree; replies nest recursively.

type HackerNewsFeed

type HackerNewsFeed string

HackerNewsFeed is one of Hacker News' story lists.

const (
	FeedTop  HackerNewsFeed = "top"
	FeedNew  HackerNewsFeed = "new"
	FeedBest HackerNewsFeed = "best"
	FeedAsk  HackerNewsFeed = "ask"
	FeedShow HackerNewsFeed = "show"
	FeedJob  HackerNewsFeed = "job"
)

type HackerNewsItem

type HackerNewsItem struct {
	ID   int64  `json:"id"`
	Type string `json:"type"`
	// Null for comments and other untitled item types.
	Title  *string `json:"title,omitempty"`
	Author string  `json:"author"`
	Points *int64  `json:"points,omitempty"`
	// External link; null for self-posts.
	URL *string `json:"url,omitempty"`
	// Self-post body as HTML; null for link posts.
	Text      *string             `json:"text,omitempty"`
	CreatedAt string              `json:"created_at"`
	HNURL     string              `json:"hn_url"`
	Comments  []HackerNewsComment `json:"comments"`
}

HackerNewsItem is GET /hackernews/items/{id}.

type HackerNewsListParams

type HackerNewsListParams struct {
	Limit int
	Page  int
}

HackerNewsListParams: Limit 1-50 (default 20); Page is 0-based (default 0).

type HackerNewsService

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

HackerNewsService groups the Hacker News endpoints.

func (*HackerNewsService) Comments

Comments is GET /hackernews/users/{username}/comments — limit 1-50 (default 20), 0-based pages.

func (*HackerNewsService) Feed

Feed is GET /hackernews/feeds/{feed} — limit 1-50 (default 20), 0-based pages.

func (*HackerNewsService) Item

Item is GET /hackernews/items/{id} — the item with its full comment tree.

func (*HackerNewsService) Search

Search is GET /hackernews/search — limit 1-50 (default 20), 0-based pages.

func (*HackerNewsService) Submissions

Submissions is GET /hackernews/users/{username}/submissions — limit 1-50 (default 20), 0-based pages.

func (*HackerNewsService) User

func (s *HackerNewsService) User(ctx context.Context, username string) (*HackerNewsUser, error)

User is GET /hackernews/users/{username}.

type HackerNewsStory

type HackerNewsStory struct {
	ID          int64  `json:"id"`
	Title       string `json:"title"`
	Author      string `json:"author"`
	Points      int64  `json:"points"`
	NumComments int64  `json:"num_comments"`
	// External link; null for self-posts (Ask HN, etc.).
	URL *string `json:"url,omitempty"`
	// Self-post body as HTML; null for link posts.
	Text      *string `json:"text,omitempty"`
	CreatedAt string  `json:"created_at"`
	HNURL     string  `json:"hn_url"`
}

HackerNewsStory is a story, as returned by feeds, search, and a user's submissions.

type HackerNewsStoryPage

type HackerNewsStoryPage struct {
	Items   []HackerNewsStory `json:"items"`
	Total   int64             `json:"total"`
	Page    int64             `json:"page"`
	HasMore bool              `json:"has_more"`
}

HackerNewsStoryPage is GET /hackernews/feeds/{feed}, /hackernews/search, and /hackernews/users/{username}/submissions.

type HackerNewsUser

type HackerNewsUser struct {
	Username string `json:"username"`
	Karma    int64  `json:"karma"`
	// Profile bio as HTML; null when the user has not written one.
	About           *string `json:"about,omitempty"`
	CreatedAt       string  `json:"created_at"`
	SubmissionCount int64   `json:"submission_count"`
	HNURL           string  `json:"hn_url"`
}

HackerNewsUser is GET /hackernews/users/{username}.

type HackerNewsUserComment

type HackerNewsUserComment struct {
	ID        int64               `json:"id"`
	Author    string              `json:"author"`
	Text      string              `json:"text"`
	CreatedAt string              `json:"created_at"`
	Replies   []HackerNewsComment `json:"replies,omitempty"`
}

HackerNewsUserComment is a comment in a user's comment listing; unlike item-tree nodes, Replies may be absent.

type HackerNewsUserCommentPage

type HackerNewsUserCommentPage struct {
	Items   []HackerNewsUserComment `json:"items"`
	Total   int64                   `json:"total"`
	Page    int64                   `json:"page"`
	HasMore bool                    `json:"has_more"`
}

HackerNewsUserCommentPage is GET /hackernews/users/{username}/comments — same page envelope as the other listings.

type InstagramClipsMusicAttribution

type InstagramClipsMusicAttribution struct {
	ArtistName        string `json:"artist_name"`
	SongName          string `json:"song_name"`
	UsesOriginalAudio bool   `json:"uses_original_audio"`
}

InstagramClipsMusicAttribution is music attribution for a video/reel; null for original audio.

type InstagramContact

type InstagramContact struct {
	Username    string  `json:"username"`
	FullName    string  `json:"full_name"`
	Biography   string  `json:"biography"`
	IsVerified  bool    `json:"is_verified"`
	IsBusiness  bool    `json:"is_business"`
	Category    string  `json:"category"`
	Email       *string `json:"email,omitempty"`
	Phone       *string `json:"phone,omitempty"`
	ExternalURL string  `json:"external_url"`
	// Null for a profile with no contact information (per the endpoint's docs, all fields in that case are null).
	Address     *InstagramContactAddress `json:"address,omitempty"`
	EmailsFound []InstagramFoundContact  `json:"emails_found"`
	PhonesFound []InstagramFoundContact  `json:"phones_found"`
}

InstagramContact is GET /instagram/profile/{username}/contact.

type InstagramContactAddress

type InstagramContactAddress struct {
	StreetAddress string `json:"street_address"`
	ZipCode       string `json:"zip_code"`
	CityName      string `json:"city_name"`
	RegionName    string `json:"region_name"`
	CountryCode   string `json:"country_code"`
}

InstagramContactAddress is a profile's structured business address, from the contact endpoint.

type InstagramDownload

type InstagramDownload struct {
	Shortcode string                   `json:"shortcode"`
	Type      string                   `json:"type"`
	ExpiresAt string                   `json:"expires_at"`
	Assets    []InstagramDownloadAsset `json:"assets"`
}

InstagramDownload is GET /instagram/media/{shortcode}/download — assets[0] is always the best primary asset.

type InstagramDownloadAsset

type InstagramDownloadAsset struct {
	Kind   string `json:"kind"`
	Index  int64  `json:"index"`
	URL    string `json:"url"`
	Width  int64  `json:"width"`
	Height int64  `json:"height"`
	// Null for non-video assets (e.g. thumbnails).
	Quality   *string `json:"quality,omitempty"`
	ExpiresAt string  `json:"expires_at"`
}

InstagramDownloadAsset is one downloadable asset behind a post, reel, or carousel.

type InstagramFoundContact

type InstagramFoundContact struct {
	Value  string `json:"value"`
	Source string `json:"source"`
}

InstagramFoundContact is an email address or phone number found written into a profile's bio.

type InstagramHighlight

type InstagramHighlight struct {
	ID    string           `json:"id"`
	Title string           `json:"title"`
	Items []InstagramMedia `json:"items"`
}

InstagramHighlight is GET /instagram/highlights/{highlightId}.

type InstagramHighlightSummary

type InstagramHighlightSummary struct {
	ID    string `json:"id"`
	Title string `json:"title"`
	Cover string `json:"cover"`
}

InstagramHighlightSummary is a highlight reel's summary, as listed on a profile.

type InstagramHighlights

type InstagramHighlights struct {
	Username   string                      `json:"username"`
	UserID     string                      `json:"user_id"`
	Highlights []InstagramHighlightSummary `json:"highlights"`
}

InstagramHighlights is GET /instagram/profile/{username}/highlights.

type InstagramLatestPostMedia

type InstagramLatestPostMedia struct {
	ID        string `json:"id"`
	Shortcode string `json:"shortcode"`
	Type      string `json:"type"`
	// Video-only field.
	VideoViews *int64 `json:"video_views,omitempty"`
	Comments   int64  `json:"comments"`
	Likes      int64  `json:"likes"`
	// Null when the post has no caption.
	Caption            *string `json:"caption,omitempty"`
	Location           any     `json:"location,omitempty"`
	ThumbnailResources any     `json:"thumbnail_resources,omitempty"`
	DisplayURL         string  `json:"display_url"`
	// Video-only field.
	VideoURL *string `json:"video_url,omitempty"`
	// Video-only field.
	HasAudio *bool `json:"has_audio,omitempty"`
	// Video-only field.
	ClipsMusicAttributionInfo *InstagramClipsMusicAttribution `json:"clips_music_attribution_info,omitempty"`
	TakenAtTimestamp          string                          `json:"taken_at_timestamp"`
}

InstagramLatestPostMedia is a post in a profile's latest-posts timeline — an image post or a video post; Type tells them apart. Video-only fields (VideoViews, VideoURL, HasAudio, ClipsMusicAttributionInfo) are nil for image posts.

type InstagramLatestPosts

type InstagramLatestPosts struct {
	Count       int64                      `json:"count"`
	LatestCount int64                      `json:"latest_count"`
	Medias      []InstagramLatestPostMedia `json:"medias"`
}

InstagramLatestPosts is GET /instagram/profile/{username}/timeline/latest.

type InstagramMedia

type InstagramMedia struct {
	ID        string `json:"id"`
	Shortcode string `json:"shortcode"`
	Type      string `json:"type"`
	// Null when the post has no caption.
	Caption          *string `json:"caption,omitempty"`
	Likes            int64   `json:"likes"`
	Comments         int64   `json:"comments"`
	PreviewComments  []any   `json:"preview_comments"`
	Location         any     `json:"location,omitempty"`
	DisplayURL       string  `json:"display_url"`
	TakenAtTimestamp string  `json:"taken_at_timestamp"`
}

InstagramMedia is a post, as returned by the paged timeline and the highlight-content endpoints.

type InstagramMediaDetail

type InstagramMediaDetail struct {
	ID        string `json:"id"`
	Shortcode string `json:"shortcode"`
	Type      string `json:"type"`
	Comments  int64  `json:"comments"`
	Likes     int64  `json:"likes"`
	// Null when the post has no caption.
	Caption                   *string                         `json:"caption,omitempty"`
	Location                  any                             `json:"location,omitempty"`
	ThumbnailResources        any                             `json:"thumbnail_resources,omitempty"`
	DisplayURL                string                          `json:"display_url"`
	TakenAtTimestamp          string                          `json:"taken_at_timestamp"`
	VideoViews                *int64                          `json:"video_views,omitempty"`
	VideoURL                  *string                         `json:"video_url,omitempty"`
	HasAudio                  *bool                           `json:"has_audio,omitempty"`
	ClipsMusicAttributionInfo *InstagramClipsMusicAttribution `json:"clips_music_attribution_info,omitempty"`
}

InstagramMediaDetail is GET /instagram/profile/{username}/media/{mediaId} — the same media object the timeline endpoints return, including the video-only fields when the media is a video.

type InstagramPostsParams

type InstagramPostsParams struct {
	Count  int
	Cursor string
}

InstagramPostsParams: Count 1-50 (default 12); Cursor from Page.NextCursor.

type InstagramProfile

type InstagramProfile struct {
	ID                 string   `json:"id"`
	FBID               string   `json:"fbid"`
	Username           string   `json:"username"`
	FullName           string   `json:"full_name"`
	Bio                string   `json:"bio"`
	BioLinks           []string `json:"bio_links"`
	Followers          int64    `json:"followers"`
	Following          int64    `json:"following"`
	Medias             int64    `json:"medias"`
	HighlightReelCount int64    `json:"highlight_reel_count"`
	ProfilePic         string   `json:"profile_pic"`
	HasAREffects       bool     `json:"has_ar_effects"`
	HasClips           bool     `json:"has_clips"`
	HasGuides          bool     `json:"has_guides"`
	HasChannel         bool     `json:"has_channel"`
	HasBlockedViewer   bool     `json:"has_blocked_viewer"`
	IsBusinessAccount  bool     `json:"is_business_account"`
	// Null in every observed example; no evidence of its populated shape.
	BusinessAddressJSON   any     `json:"business_address_json,omitempty"`
	BusinessContactMethod string  `json:"business_contact_method"`
	BusinessEmail         *string `json:"business_email,omitempty"`
	BusinessPhoneNumber   *string `json:"business_phone_number,omitempty"`
	BusinessCategoryName  string  `json:"business_category_name"`
	IsProfessionalAccount bool    `json:"is_professional_account"`
	CategoryName          string  `json:"category_name"`
	IsPrivate             bool    `json:"is_private"`
	IsVerified            bool    `json:"is_verified"`
}

InstagramProfile is GET /instagram/profile/{username}.

type InstagramReel

type InstagramReel struct {
	ID         string `json:"id"`
	Shortcode  string `json:"shortcode"`
	Type       string `json:"type"`
	VideoViews int64  `json:"video_views"`
	Comments   int64  `json:"comments"`
	Likes      int64  `json:"likes"`
	// Null when the post has no caption.
	Caption                   *string                         `json:"caption,omitempty"`
	Location                  any                             `json:"location,omitempty"`
	ThumbnailResources        any                             `json:"thumbnail_resources,omitempty"`
	DisplayURL                string                          `json:"display_url"`
	VideoURL                  string                          `json:"video_url"`
	HasAudio                  bool                            `json:"has_audio"`
	ClipsMusicAttributionInfo *InstagramClipsMusicAttribution `json:"clips_music_attribution_info,omitempty"`
	TakenAtTimestamp          string                          `json:"taken_at_timestamp"`
}

InstagramReel is GET /instagram/reels/{shortcode} — clips_music_attribution_info is null for original audio.

type InstagramService

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

InstagramService groups the Instagram endpoints.

func (*InstagramService) Contact

func (s *InstagramService) Contact(ctx context.Context, username string) (*InstagramContact, error)

Contact is GET /instagram/profile/{username}/contact — public business contact (email, phone, address).

func (*InstagramService) Download

func (s *InstagramService) Download(ctx context.Context, shortcode string) (*InstagramDownload, error)

Download is GET /instagram/media/{shortcode}/download — assets[0] is the best primary asset.

func (*InstagramService) Highlight

func (s *InstagramService) Highlight(ctx context.Context, highlightID string) (*InstagramHighlight, error)

Highlight is GET /instagram/highlights/{highlightId}.

func (*InstagramService) Highlights

func (s *InstagramService) Highlights(ctx context.Context, username string) (*InstagramHighlights, error)

Highlights is GET /instagram/profile/{username}/highlights.

func (*InstagramService) IDToShortcode

func (s *InstagramService) IDToShortcode(ctx context.Context, mediaID string) (*InstagramShortcodeID, error)

IDToShortcode is GET /instagram/media/id/{mediaId}.

func (*InstagramService) LatestPosts

func (s *InstagramService) LatestPosts(ctx context.Context, username string) (*InstagramLatestPosts, error)

LatestPosts is GET /instagram/profile/{username}/timeline/latest — the first page of posts.

func (*InstagramService) Media

func (s *InstagramService) Media(ctx context.Context, shortcode string) (*InstagramMediaDetail, error)

Media is GET /instagram/media/{shortcode} — shortcode from instagram.com/p/{shortcode}/.

func (*InstagramService) MediaByID

func (s *InstagramService) MediaByID(ctx context.Context, username, mediaID string) (*InstagramMediaDetail, error)

MediaByID is GET /instagram/profile/{username}/media/{mediaId}.

func (*InstagramService) Posts

Posts is GET /instagram/profile/{username}/timeline — full history, cursor-paginated; Count 1-50 (default 12).

func (*InstagramService) Profile

func (s *InstagramService) Profile(ctx context.Context, username string) (*InstagramProfile, error)

Profile is GET /instagram/profile/{username} — username without @.

func (*InstagramService) Reel

func (s *InstagramService) Reel(ctx context.Context, shortcode string) (*InstagramReel, error)

Reel is GET /instagram/reels/{shortcode}.

func (*InstagramService) ShortcodeToID

func (s *InstagramService) ShortcodeToID(ctx context.Context, shortcode string) (*InstagramShortcodeID, error)

ShortcodeToID is GET /instagram/media/{shortcode}/id.

type InstagramShortcodeID

type InstagramShortcodeID struct {
	Shortcode string `json:"shortcode"`
	MediaID   string `json:"media_id"`
}

InstagramShortcodeID is GET /instagram/media/{shortcode}/id and /instagram/media/id/{mediaId}.

type InstagramTimelinePage

type InstagramTimelinePage struct {
	Medias     []InstagramMedia `json:"medias"`
	HasMore    bool             `json:"has_more"`
	NextCursor *string          `json:"next_cursor,omitempty"`
}

InstagramTimelinePage is GET /instagram/profile/{username}/timeline.

type LinktreeLink struct {
	ID    string `json:"id"`
	Title string `json:"title"`
	URL   string `json:"url"`
	Type  string `json:"type"`
}

LinktreeLink is one link in a Linktree profile.

type LinktreeProfile

type LinktreeProfile struct {
	Username    string         `json:"username"`
	Title       string         `json:"title"`
	Description string         `json:"description"`
	Avatar      string         `json:"avatar"`
	IsVerified  bool           `json:"is_verified"`
	URL         string         `json:"url"`
	Links       []LinktreeLink `json:"links"`
}

LinktreeProfile is GET /linktree/profiles/{handle}.

type LinktreeService

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

LinktreeService groups the Linktree endpoints.

func (*LinktreeService) Profile

func (s *LinktreeService) Profile(ctx context.Context, handle string) (*LinktreeProfile, error)

Profile is GET /linktree/profiles/{handle}.

type Option

type Option func(*config)

Option configures a Client.

func WithAPIKey

func WithAPIKey(key string) Option

WithAPIKey sets the API key (sinac_…). Defaults to the SCRAPINGISNOTACRIME_API_KEY environment variable.

func WithBaseURL

func WithBaseURL(baseURL string) Option

WithBaseURL overrides the API base URL. It must be the final HTTPS URL: redirects are not followed.

func WithHTTPClient

func WithHTTPClient(client *http.Client) Option

WithHTTPClient sets the *http.Client used for requests (proxies, tests). The client is never modified.

func WithMaxRetries

func WithMaxRetries(n int) Option

WithMaxRetries sets the extra attempts for 429, 502 and network errors. Defaults to 2; 0 disables retries.

func WithTimeout

func WithTimeout(timeout time.Duration) Option

WithTimeout sets the per-attempt timeout (connect, headers and body). Defaults to 30 s.

type Page

type Page[T, R any] struct {
	Items      []T
	HasMore    bool
	NextCursor string
	NextPage   int
	Data       R
	// contains filtered or unexported fields
}

Page is one page of results. Data is the full page object; Items, HasMore, NextCursor ("" = none) and NextPage (0 = none) are read from it. HasMore is true exactly when Next will fetch another page.

func (*Page[T, R]) All

func (p *Page[T, R]) All(ctx context.Context) iter.Seq2[T, error]

All yields every item from this page onward, fetching later pages lazily (each page is one billed request). Breaking out of the loop stops fetching. A fetch error is yielded once, with the zero item, and ends the iteration.

Example
package main

import (
	"context"
	"fmt"
	"log"

	sinac "github.com/ScrapingIsNotACrime/sdk-go"
)

func main() {
	client, err := sinac.NewClient() // reads SCRAPINGISNOTACRIME_API_KEY
	if err != nil {
		log.Fatal(err)
	}
	ctx := context.Background()
	page, err := client.GitHub.Followers(ctx, "torvalds", &sinac.GitHubListParams{Limit: 100})
	if err != nil {
		log.Fatal(err)
	}
	// Each page fetched is one billed request, so stop at a bound.
	n := 0
	for user, err := range page.All(ctx) {
		if err != nil {
			log.Fatal(err)
		}
		fmt.Println(user.Username)
		if n++; n >= 250 {
			break
		}
	}
}

func (*Page[T, R]) Next

func (p *Page[T, R]) Next(ctx context.Context) (*Page[T, R], error)

Next fetches the following page. It returns nil, nil when there is none.

type TikTokProfile

type TikTokProfile struct {
	ID         string `json:"id"`
	Username   string `json:"username"`
	Nickname   string `json:"nickname"`
	Bio        string `json:"bio"`
	BioLink    string `json:"bio_link"`
	Avatar     string `json:"avatar"`
	SecUID     string `json:"sec_uid"`
	Followers  int64  `json:"followers"`
	Following  int64  `json:"following"`
	Hearts     int64  `json:"hearts"`
	Videos     int64  `json:"videos"`
	IsPrivate  bool   `json:"is_private"`
	IsVerified bool   `json:"is_verified"`
}

TikTokProfile is GET /tiktok/profile/{username}.

type TikTokService

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

TikTokService groups the TikTok endpoints.

func (*TikTokService) Profile

func (s *TikTokService) Profile(ctx context.Context, username string) (*TikTokProfile, error)

Profile is GET /tiktok/profile/{username}.

func (*TikTokService) Video

func (s *TikTokService) Video(ctx context.Context, videoID string) (*TikTokVideo, error)

Video is GET /tiktok/video/{videoId}.

type TikTokVideo

type TikTokVideo struct {
	ID    *string        `json:"id,omitempty"`
	Extra map[string]any `json:"-"`
}

TikTokVideo is GET /tiktok/video/{videoId}. No documented example yet; Extra holds every other key.

func (TikTokVideo) MarshalJSON

func (v TikTokVideo) MarshalJSON() ([]byte, error)

MarshalJSON re-encodes Extra plus id (when ID is set), so round-tripping through the SDK never loses data.

func (*TikTokVideo) UnmarshalJSON

func (v *TikTokVideo) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes id into ID only when it is a JSON string; any other shape (number, null, ...) leaves ID nil and keeps the raw value in Extra, alongside every other key, so no data is ever dropped.

type TwitchBroadcast

type TwitchBroadcast struct {
	Title     string `json:"title"`
	StartedAt string `json:"started_at"`
}

TwitchBroadcast is a channel's most recent broadcast.

type TwitchProfile

type TwitchProfile struct {
	ID          string `json:"id"`
	Login       string `json:"login"`
	DisplayName string `json:"display_name"`
	Description string `json:"description"`
	Avatar      string `json:"avatar"`
	Followers   int64  `json:"followers"`
	IsPartner   bool   `json:"is_partner"`
	IsAffiliate bool   `json:"is_affiliate"`
	CreatedAt   string `json:"created_at"`
	IsLive      bool   `json:"is_live"`
	// Current viewer count while live; null whenever is_live is false.
	LiveViewers *int64 `json:"live_viewers,omitempty"`
	// Null when the channel has never broadcast (or the info is unavailable).
	LastBroadcast *TwitchBroadcast `json:"last_broadcast,omitempty"`
	URL           string           `json:"url"`
}

TwitchProfile is GET /twitch/profiles/{handle}.

type TwitchService

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

TwitchService groups the Twitch endpoints.

func (*TwitchService) Profile

func (s *TwitchService) Profile(ctx context.Context, handle string) (*TwitchProfile, error)

Profile is GET /twitch/profiles/{handle}.

func (*TwitchService) Videos

func (s *TwitchService) Videos(ctx context.Context, handle string, p *TwitchVideosParams) (*TwitchVideos, error)

Videos is GET /twitch/profiles/{handle}/videos — limit 1-100, default 20.

type TwitchVideo

type TwitchVideo struct {
	ID              string `json:"id"`
	Title           string `json:"title"`
	DurationSeconds int64  `json:"duration_seconds"`
	Views           int64  `json:"views"`
	PublishedAt     string `json:"published_at"`
	Thumbnail       string `json:"thumbnail"`
	URL             string `json:"url"`
}

TwitchVideo is one video in a channel's published videos list.

type TwitchVideos

type TwitchVideos struct {
	Videos []TwitchVideo `json:"videos"`
	Count  int64         `json:"count"`
}

TwitchVideos is GET /twitch/profiles/{handle}/videos.

type TwitchVideosParams

type TwitchVideosParams struct {
	Limit int
}

TwitchVideosParams: Limit 1-100 (default 20).

type YouTubeChannel

type YouTubeChannel struct {
	Title       string `json:"title"`
	Description string `json:"description"`
	ExternalID  string `json:"externalId"`
	Avatar      string `json:"avatar"`
}

YouTubeChannel is the channel block in a channel-videos response.

type YouTubeChannelVideos

type YouTubeChannelVideos struct {
	Channel YouTubeChannel `json:"channel"`
	Videos  []YouTubeVideo `json:"videos"`
}

YouTubeChannelVideos is GET /youtube/channel/{handle}/videos.

type YouTubeService

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

YouTubeService groups the YouTube endpoints.

func (*YouTubeService) Videos

func (s *YouTubeService) Videos(ctx context.Context, handle string) (*YouTubeChannelVideos, error)

Videos is GET /youtube/channel/{handle}/videos.

type YouTubeVideo

type YouTubeVideo struct {
	ID           string   `json:"id"`
	Title        string   `json:"title"`
	URL          string   `json:"url"`
	Thumbnail    string   `json:"thumbnail"`
	MetadataText []string `json:"metadataText"`
}

YouTubeVideo is one video in a channel's video list.

Jump to

Keyboard shortcuts

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