yt_urls

package module
v0.1.46 Latest Latest
Warning

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

Go to latest
Published: Apr 30, 2024 License: MIT Imports: 14 Imported by: 9

README

yt_urls

yt_urls extracts a single video stream URL ("the best" quality one) from a YouTube video page. This module has the following exported methods:

WatchUrl

func WatchUrl(videoId string) *url.URL

WatchUrl provides a URL for a video-id, e.g. http://www.youtube.com/watch?v=video-id1 for "video-id1".

BestStreamingUrl

func BestStreamingUrl(videoId string) (*url.URL, error)

BestStreamingUrl extracts the URL for "the best" streaming format for a given YouTube video-id.

Here are the key steps to make that happen:

  1. convert video-id to a full YouTube.com/watch URL
  2. request page content at that URL
  3. parse response as HTML document and find required node (iprScriptTextContent contains selection criteria)
  4. decode ytInitialPlayerResponse object (to a minimal data struct)
  5. select "the best" streaming format available (bestFormatByBitrate contains selection criteria)
  6. return URL for that format

Documentation

Index

Constants

View Source
const (
	YoutubeHost = "youtube.com"

	DefaultVideoExt     = mp4Ext
	DefaultThumbnailExt = jpegExt
)
View Source
const (
	StatusOK = "OK"
)

Variables

View Source
var (
	ErrMissingRequiredNode = errors.New("missing required node")
)

Functions

func BrowseUrl added in v0.1.3

func BrowseUrl(path, apiKey string) *url.URL

func ChannelUrl added in v0.1.39

func ChannelUrl(channelId string) *url.URL

func PlayerUrl added in v0.1.23

func PlayerUrl(path string) *url.URL

func PlaylistId added in v0.1.3

func PlaylistId(ytUrlStr string) (string, error)

PlaylistId extracts playlist-id from a PlaylistUrl conforming URL

func PlaylistUrl added in v0.1.3

func PlaylistUrl(playlistId string) *url.URL

PlaylistUrl provides a URL for a playlist-id, e.g. http://www.youtube.com/playlist?list=playlist-id1 for "playlist-id1"

func SearchResultsUrl added in v0.1.36

func SearchResultsUrl(terms ...string) *url.URL

SearchResultsUrl provides a URL for a set of search terms, e.g. http://www.youtube.com/results?search_query=some+terms

func ThumbnailUrl added in v0.1.14

func ThumbnailUrl(videoId string, tq ThumbnailQuality) *url.URL

func ThumbnailUrls added in v0.1.14

func ThumbnailUrls(videoId string) []*url.URL

func VideoId added in v0.1.3

func VideoId(ytUrlStr string) (string, error)

VideoId extracts video-id from a VideoUrl conforming URL

func VideoUrl added in v0.1.3

func VideoUrl(videoId string) *url.URL

VideoUrl provides a URL for a video-id, e.g. http://www.youtube.com/watch?v=video-id1 for "video-id1"

Types

type CaptionTrack added in v0.1.30

type CaptionTrack struct {
	BaseUrl        string     `json:"baseUrl"`
	Name           SimpleText `json:"name"`
	VSSId          string     `json:"vssId"`
	LanguageCode   string     `json:"languageCode"`
	Kind           string     `json:"kind"`
	IsTranslatable bool       `json:"isTranslatable"`
	TrackName      string     `json:"trackName"`
}

type ChannelInitialData added in v0.1.39

type ChannelInitialData struct {
	Contents struct {
		TwoColumnBrowseResultsRenderer struct {
			Tabs []Tab `json:"tabs"`
		} `json:"twoColumnBrowseResultsRenderer"`
	} `json:"contents"`
	Metadata struct {
		ChannelMetadataRenderer ChannelMetadataRenderer `json:"channelMetadataRenderer"`
	} `json:"metadata"`
	Context *ytCfgInnerTubeContext
}

func GetChannelPage added in v0.1.39

func GetChannelPage(client *http.Client, channelId string) (*ChannelInitialData, error)

func (*ChannelInitialData) ChannelMetadataRenderer added in v0.1.40

func (cid *ChannelInitialData) ChannelMetadataRenderer() *ChannelMetadataRenderer

func (*ChannelInitialData) Tabs added in v0.1.40

func (cid *ChannelInitialData) Tabs() []Tab

type ChannelMetadataRenderer added in v0.1.40

type ChannelMetadataRenderer struct {
	Title                 string   `json:"title"`
	Description           string   `json:"description"`
	RSSUrl                string   `json:"rssUrl"`
	ExternalId            string   `json:"externalId"`
	Keywords              string   `json:"keywords"`
	OwnerUrls             []string `json:"ownerUrls"`
	ChannelUrl            string   `json:"channelUrl"`
	IsFamilySafe          bool     `json:"isFamilySafe"`
	AvailableCountryCodes []string `json:"availableCountryCodes"`
	VanityChannelUrl      string   `json:"vanityChannelUrl"`
}

type ChannelRenderer added in v0.1.36

type ChannelRenderer struct {
	ChannelId           string             `json:"channelId"`
	Title               SimpleText         `json:"title"`
	NavigationEndpoint  NavigationEndpoint `json:"navigationEndpoint"`
	DescriptionSnippet  TextRuns           `json:"descriptionSnippet"`
	ShortBylineText     TextRuns           `json:"shortBylineText"`
	VideoCountText      SimpleText         `json:"videoCountText"`
	SubscriberCountText SimpleText         `json:"subscriberCountText"`
	LongBylineText      TextRuns           `json:"longBylineText"`
}

type ContinuationEndpoint added in v0.1.3

type ContinuationEndpoint struct {
	CommandMetadata struct {
		WebCommandMetadata struct {
			SendPost bool   `json:"sendPost"`
			ApiUrl   string `json:"apiUrl"`
		} `json:"webCommandMetadata"`
	} `json:"commandMetadata"`
	ContinuationCommand struct {
		Token   string `json:"token"`
		Request string `json:"request"`
	} `json:"continuationCommand"`
}

type ContinuationItemRenderer added in v0.1.3

type ContinuationItemRenderer struct {
	Trigger              string               `json:"trigger"`
	ContinuationEndpoint ContinuationEndpoint `json:"continuationEndpoint"`
}

type Format added in v0.1.8

type Format struct {
	Url              string  `json:"url"`
	MIMEType         string  `json:"mimeType"`
	Bitrate          int     `json:"bitrate"`
	Width            int     `json:"width"`
	Height           int     `json:"height"`
	InitRange        Range   `json:"initRange"`
	IndexRange       Range   `json:"indexRange"`
	LastModified     string  `json:"lastModified"`
	ContentLength    string  `json:"contentLength"`
	Quality          string  `json:"quality"`
	FPS              int     `json:"fps"`
	QualityLabel     string  `json:"qualityLabel"`
	ProjectionType   string  `json:"projectionType"`
	AverageBitrate   int     `json:"averageBitrate"`
	HighReplication  bool    `json:"highReplication"`
	AudioQuality     string  `json:"audioQuality"`
	ApproxDurationMs string  `json:"approxDurationMs"`
	AudioSampleRate  string  `json:"audioSampleRate"`
	AudioChannels    int     `json:"audioChannels"`
	LoudnessDb       float64 `json:"loudnessDb"`
	SignatureCipher  string  `json:"signatureCipher"`
	// contains filtered or unexported fields
}

Format captures stream data provided by YouTube

func (Format) Ext added in v0.1.8

func (f Format) Ext() string

type Formats added in v0.1.8

type Formats []Format

func (Formats) Audio added in v0.1.8

func (fs Formats) Audio() Formats

func (Formats) Len added in v0.1.8

func (fs Formats) Len() int

func (Formats) Less added in v0.1.8

func (fs Formats) Less(i, j int) bool

func (Formats) PreferredAudioFormats added in v0.1.42

func (fs Formats) PreferredAudioFormats() Formats

func (Formats) PreferredVideoFormats added in v0.1.42

func (fs Formats) PreferredVideoFormats() Formats

func (Formats) Swap added in v0.1.8

func (fs Formats) Swap(i, j int)

func (Formats) Video added in v0.1.8

func (fs Formats) Video() Formats

type GridChannelRenderer added in v0.1.39

type GridChannelRenderer struct {
	ChannelId           string             `json:"channelId"`
	VideoCountText      TextRuns           `json:"videoCountText"`
	SubscriberCountText SimpleText         `json:"subscriberCountText"`
	NavigationEndpoint  NavigationEndpoint `json:"navigationEndpoint"`
	Title               SimpleText         `json:"title"`
}

type GridPlaylistRenderer added in v0.1.39

type GridPlaylistRenderer struct {
	PlaylistId          string             `json:"playlistId"`
	Title               SimpleText         `json:"title"`
	VideoCountText      TextRuns           `json:"videoCountText"`
	NavigationEndpoint  NavigationEndpoint `json:"navigationEndpoint"`
	VideoCountShortText SimpleText         `json:"videoCountShortText"`
	ViewPlaylistText    TextRuns           `json:"viewPlaylistText"`
	PublishedTimeText   SimpleText         `json:"publishedTimeText,omitempty"`
}

type GridVideoRenderer added in v0.1.39

type GridVideoRenderer struct {
	VideoId            string             `json:"videoId"`
	Title              SimpleText         `json:"title"`
	PublishedTimeText  SimpleText         `json:"publishedTimeText"`
	ViewCountText      SimpleText         `json:"viewCountText"`
	NavigationEndpoint NavigationEndpoint `json:"navigationEndpoint"`
	ShortViewCountText SimpleText         `json:"shortViewCountText"`
	ShortBylineText    TextRuns           `json:"shortBylineText,omitempty"`
}

type InitialPlayerResponse added in v0.1.3

type InitialPlayerResponse struct {
	PlayerUrl         string
	PlayabilityStatus struct {
		Status      string `json:"status"`
		Reason      string `json:"reason"`
		ErrorScreen struct {
			PlayerErrorMessageRenderer struct {
				SubReason SimpleText `json:"subreason"`
			} `json:"playerErrorMessageRenderer"`
		} `json:"errorScreen"`
	} `json:"playabilityStatus"`
	StreamingData struct {
		ExpiresInSeconds string  `json:"expiresInSeconds"`
		Formats          Formats `json:"formats"`
		AdaptiveFormats  Formats `json:"adaptiveFormats"`
		HLSManifestUrl   string  `json:"hlsManifestUrl"`
	} `json:"streamingData"`
	VideoDetails struct {
		VideoId          string   `json:"videoId"`
		Title            string   `json:"title"`
		LengthSeconds    string   `json:"lengthSeconds"`
		Keywords         []string `json:"keywords"`
		ChannelId        string   `json:"channelId"`
		ShortDescription string   `json:"shortDescription"`
		Thumbnail        struct {
			Thumbnails []Thumbnail `json:"thumbnails"`
		} `json:"thumbnail"`
		ViewCount string `json:"viewCount"`
		Author    string `json:"author"`
		IsPrivate bool   `json:"isPrivate"`
	} `json:"videoDetails"`
	Microformat struct {
		PlayerMicroformatRenderer struct {
			Thumbnail struct {
				Thumbnails []Thumbnail `json:"thumbnails"`
			} `json:"thumbnail"`
			Title              SimpleText `json:"title"`
			Description        SimpleText `json:"description"`
			OwnerProfileUrl    string     `json:"ownerProfileUrl"`
			OwnerChannelName   string     `json:"ownerChannelName"`
			ExternalChannelId  string     `json:"externalChannelId"`
			IsFamilySafe       bool       `json:"IsFamilySafe"`
			AvailableCountries []string   `json:"availableCountries"`
			IsUnlisted         bool       `json:"isUnlisted"`
			ViewCount          string     `json:"viewCount"`
			Category           string     `json:"category"`
			PublishDate        string     `json:"publishDate"`
			UploadDate         string     `json:"uploadDate"`
		} `json:"playerMicroformatRenderer"`
	} `json:"microformat"`
	Captions struct {
		PlayerCaptionsTracklistRenderer struct {
			CaptionTracks []CaptionTrack `json:"captionTracks"`
			AudioTracks   []struct {
				CaptionTrackIndices []int `json:"captionTrackIndices"`
			} `json:"audioTracks"`
			TranslationLanguages []struct {
				LanguageCode string     `json:"languageCode"`
				LanguageName SimpleText `json:"languageName"`
			} `json:"translationLanguages"`
			DefaultAudioTrackIndex int `json:"defaultAudioTrackIndex"`
		} `json:"playerCaptionsTracklistRenderer"`
	} `json:"captions"`
}

InitialPlayerResponse is a minimal set of data structures required to decode and extract streaming data formats for video URL ytInitialPlayerResponse

func GetVideoPage added in v0.1.3

func GetVideoPage(client *http.Client, videoId string) (*InitialPlayerResponse, error)

func (*InitialPlayerResponse) BestAdaptiveAudioFormat added in v0.1.42

func (ipr *InitialPlayerResponse) BestAdaptiveAudioFormat() *Format

func (*InitialPlayerResponse) BestAdaptiveVideoFormat added in v0.1.42

func (ipr *InitialPlayerResponse) BestAdaptiveVideoFormat() *Format

func (*InitialPlayerResponse) BestFormat added in v0.1.42

func (ipr *InitialPlayerResponse) BestFormat() *Format

func (*InitialPlayerResponse) PublishDate added in v0.1.3

func (ipr *InitialPlayerResponse) PublishDate() time.Time

func (*InitialPlayerResponse) SignatureCipher added in v0.1.46

func (ipr *InitialPlayerResponse) SignatureCipher() bool

func (*InitialPlayerResponse) UploadDate added in v0.1.3

func (ipr *InitialPlayerResponse) UploadDate() time.Time

type ItemSectionRenderer added in v0.1.39

type ItemSectionRenderer struct {
	Contents []struct {
		ShelfRenderer ShelfRenderer `json:"shelfRenderer,omitempty"`
	} `json:"contents"`
}
type NavigationEndpoint struct {
	ClickTrackingParams string `json:"clickTrackingParams"`
	CommandMetadata     struct {
		WebCommandMetadata struct {
			Url         string `json:"url"`
			WebPageType string `json:"webPageType"`
			RootVe      int    `json:"rootVe"`
			ApiUrl      string `json:"apiUrl"`
		} `json:"webCommandMetadata"`
	} `json:"commandMetadata"`
	BrowseEndpoint struct {
		BrowseId         string `json:"browseId"`
		CanonicalBaseUrl string `json:"canonicalBaseUrl"`
	} `json:"browseEndpoint"`
	WatchEndpoint struct {
		VideoId    string `json:"videoId"`
		PlaylistId string `json:"playlistId"`
	}
}

type PlaylistHeaderRenderer added in v0.1.29

type PlaylistHeaderRenderer struct {
	PlaylistId      string     `json:"playlistId"`
	Title           SimpleText `json:"title"`
	DescriptionText SimpleText `json:"descriptionText"`
	OwnerText       TextRuns   `json:"ownerText"`
	ViewCountText   SimpleText `json:"viewCountText"`
	Privacy         string     `json:"privacy"`
}

type PlaylistInitialData added in v0.1.29

type PlaylistInitialData struct {
	Contents struct {
		TwoColumnBrowseResultsRenderer struct {
			Tabs []struct {
				TabRenderer struct {
					Content struct {
						SectionListRenderer struct {
							Contents []struct {
								ItemSectionRenderer struct {
									Contents []struct {
										PlaylistVideoListRenderer struct {
											PlaylistId string                             `json:"playlistId"`
											Contents   []PlaylistVideoListRendererContent `json:"contents"`
										} `json:"playlistVideoListRenderer"`
									} `json:"contents"`
								} `json:"itemSectionRenderer"`
							} `json:"contents"`
						} `json:"sectionListRenderer"`
					} `json:"content"`
				} `json:"tabRenderer"`
			} `json:"tabs"`
		} `json:"twoColumnBrowseResultsRenderer"`
	} `json:"contents"`
	Header struct {
		PlaylistHeaderRenderer PlaylistHeaderRenderer `json:"playlistHeaderRenderer"`
	} `json:"header"`

	Context *ytCfgInnerTubeContext
	// contains filtered or unexported fields
}

PlaylistInitialData is a minimal set of data structures required to decode and extract videoIds for playlist URL ytInitialData

func GetPlaylistPage added in v0.1.3

func GetPlaylistPage(client *http.Client, playlistId string) (*PlaylistInitialData, error)

func (*PlaylistInitialData) Continue added in v0.1.36

func (pid *PlaylistInitialData) Continue(client *http.Client) error

func (*PlaylistInitialData) HasContinuation added in v0.1.36

func (pid *PlaylistInitialData) HasContinuation() bool

func (*PlaylistInitialData) PlaylistContent added in v0.1.32

func (id *PlaylistInitialData) PlaylistContent() []PlaylistVideoListRendererContent

func (*PlaylistInitialData) PlaylistHeaderRenderer added in v0.1.40

func (id *PlaylistInitialData) PlaylistHeaderRenderer() *PlaylistHeaderRenderer

func (*PlaylistInitialData) PlaylistOwner added in v0.1.32

func (id *PlaylistInitialData) PlaylistOwner() string

func (*PlaylistInitialData) SetContent added in v0.1.32

func (*PlaylistInitialData) Videos added in v0.1.36

type PlaylistRenderer added in v0.1.36

type PlaylistRenderer struct {
	PlaylistId         string             `json:"playlistId"`
	Title              SimpleText         `json:"title"`
	VideoCount         string             `json:"videoCount"`
	NavigationEndpoint NavigationEndpoint `json:"navigationEndpoint"`
	ViewPlaylistText   TextRuns           `json:"viewPlaylistText"`
	ShortBylineText    TextRuns           `json:"shortBylineText"`
	Videos             []struct {
		ChildVideoRenderer struct {
			Title              SimpleText         `json:"title"`
			NavigationEndpoint NavigationEndpoint `json:"navigationEndpoint"`
			LengthText         SimpleText         `json:"lengthText"`
			VideoId            string             `json:"videoId"`
		} `json:"childVideoRenderer"`
	} `json:"videos"`
	VideoCountText TextRuns `json:"videoCountText"`
	TrackingParams string   `json:"trackingParams"`
	ThumbnailText  TextRuns `json:"thumbnailText"`
	LongBylineText TextRuns `json:"longBylineText"`
}

type PlaylistVideoListRendererContent added in v0.1.3

type PlaylistVideoListRendererContent struct {
	PlaylistVideoRenderer    PlaylistVideoRenderer
	ContinuationItemRenderer ContinuationItemRenderer
}

type PlaylistVideoRenderer added in v0.1.3

type PlaylistVideoRenderer struct {
	VideoId       string   `json:"videoId"`
	Title         TextRuns `json:"title"`
	LengthSeconds string   `json:"lengthSeconds"`
	// normally contains video channel title
	ShortBylineText TextRuns `json:"shortBylineText"`
}

type Range added in v0.1.8

type Range struct {
	Start string `json:"start"`
	End   string `json:"end"`
}

type SearchInitialData added in v0.1.36

type SearchInitialData struct {
	EstimatedResults string `json:"estimatedResults"`
	Contents         struct {
		TwoColumnSearchResultsRenderer struct {
			PrimaryContents struct {
				SectionListRenderer struct {
					Contents []struct {
						ItemSectionRenderer struct {
							Contents []struct {
								ChannelRenderer  ChannelRenderer  `json:"channelRenderer,omitempty"`
								PlaylistRenderer PlaylistRenderer `json:"playlistRenderer,omitempty"`
								VideoRenderer    VideoRenderer    `json:"videoRenderer,omitempty"`
							} `json:"contents"`
						} `json:"itemSectionRenderer,omitempty"`
						ContinuationItemRenderer ContinuationItemRenderer `json:"continuationItemRenderer,omitempty"`
					} `json:"contents"`
				} `json:"sectionListRenderer"`
			} `json:"primaryContents"`
		} `json:"twoColumnSearchResultsRenderer"`
	} `json:"contents"`
	Refinements []string `json:"refinements"`
	Context     *ytCfgInnerTubeContext
}

func GetSearchResultsPage added in v0.1.36

func GetSearchResultsPage(client *http.Client, terms ...string) (*SearchInitialData, error)

func (*SearchInitialData) ChannelRenderers added in v0.1.37

func (sid *SearchInitialData) ChannelRenderers() []ChannelRenderer

func (*SearchInitialData) PlaylistRenderers added in v0.1.37

func (sid *SearchInitialData) PlaylistRenderers() []PlaylistRenderer

func (*SearchInitialData) VideoRenderers added in v0.1.37

func (sid *SearchInitialData) VideoRenderers() []VideoRenderer

type ShelfRenderer added in v0.1.40

type ShelfRenderer struct {
	Title   TextRuns `json:"title"`
	Content struct {
		HorizontalListRenderer struct {
			Items []struct {
				GridVideoRenderer    GridVideoRenderer    `json:"gridVideoRenderer,omitempty"`
				GridPlaylistRenderer GridPlaylistRenderer `json:"gridPlaylistRenderer,omitempty"`
				GridChannelRenderer  GridChannelRenderer  `json:"gridChannelRenderer,omitempty"`
			} `json:"items"`
		} `json:"horizontalListRenderer"`
	} `json:"content"`
	PlayAllButton struct {
		ButtonRenderer struct {
			Style      string `json:"style"`
			Size       string `json:"size"`
			IsDisabled bool   `json:"isDisabled"`
			Text       struct {
				Runs []struct {
					Text string `json:"text"`
				} `json:"runs"`
			} `json:"text"`
			Icon struct {
				IconType string `json:"iconType"`
			} `json:"icon"`
			NavigationEndpoint NavigationEndpoint `json:"navigationEndpoint"`
		} `json:"buttonRenderer"`
	} `json:"playAllButton"`
}

func (*ShelfRenderer) GridVideoRenderers added in v0.1.40

func (sr *ShelfRenderer) GridVideoRenderers() []GridVideoRenderer

func (*ShelfRenderer) PlaylistId added in v0.1.40

func (sr *ShelfRenderer) PlaylistId() string

type SimpleText added in v0.1.26

type SimpleText struct {
	SimpleText string `json:"simpleText"`
}

type Tab added in v0.1.40

type Tab struct {
	TabRenderer struct {
		Content struct {
			SectionListRenderer struct {
				Contents []struct {
					ItemSectionRenderer ItemSectionRenderer `json:"itemSectionRenderer"`
				} `json:"contents"`
			} `json:"sectionListRenderer"`
		} `json:"content,omitempty"`
	} `json:"tabRenderer,omitempty"`
}

func (*Tab) Sections added in v0.1.40

func (tab *Tab) Sections() []ShelfRenderer

type Text added in v0.1.37

type Text struct {
	Text string `json:"text"`
}

type TextRuns added in v0.1.37

type TextRuns struct {
	Runs []struct {
		Text string `json:"text"`
	} `json:"runs"`
	Accessibility struct {
		AccessibilityData struct {
			Label string `json:"label"`
		} `json:"accessibilityData"`
	} `json:"accessibility"`
}

func (*TextRuns) String added in v0.1.37

func (tr *TextRuns) String() string

type Thumbnail added in v0.1.26

type Thumbnail struct {
	Url    string `json:"url"`
	Width  int    `json:"width"`
	Height int    `json:"height"`
}

type ThumbnailQuality added in v0.1.14

type ThumbnailQuality int
const (
	ThumbnailQualityUnknown ThumbnailQuality = iota
	ThumbnailQualitySD
	ThumbnailQualityMQ
	ThumbnailQualityHQ
	ThumbnailQualityMaxRes
)

func AllThumbnailQualities added in v0.1.34

func AllThumbnailQualities() []ThumbnailQuality

func LowerQuality added in v0.1.41

func LowerQuality(q ThumbnailQuality) ThumbnailQuality

func ParseThumbnailQuality added in v0.1.35

func ParseThumbnailQuality(tqs string) ThumbnailQuality

func (ThumbnailQuality) String added in v0.1.33

func (tq ThumbnailQuality) String() string

type VideoIdTitleLengthChannel added in v0.1.45

type VideoIdTitleLengthChannel struct {
	VideoId string
	Title   string
	Length  string
	Channel string
}

type VideoRenderer added in v0.1.36

type VideoRenderer struct {
	VideoId            string             `json:"videoId"`
	Title              TextRuns           `json:"title"`
	LongBylineText     TextRuns           `json:"longBylineText"`
	PublishedTimeText  SimpleText         `json:"publishedTimeText"`
	LengthText         SimpleText         `json:"lengthText"`
	ViewCountText      SimpleText         `json:"viewCountText"`
	NavigationEndpoint NavigationEndpoint `json:"navigationEndpoint"`
	OwnerText          TextRuns           `json:"ownerText"`
	ShortBylineText    TextRuns           `json:"shortBylineText"`
	ShortViewCountText SimpleText         `json:"shortViewCountText"`
	IsWatched          bool               `json:"isWatched,omitempty"`
}

Jump to

Keyboard shortcuts

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