webapi

package
v0.4.6 Latest Latest
Warning

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

Go to latest
Published: May 9, 2023 License: MIT Imports: 10 Imported by: 0

Documentation

Index

Constants

View Source
const (
	ApiUserInfo  = "https://my.115.com/?ct=ajax&ac=nav"
	ApiIndexInfo = "https://webapi.115.com/files/index_info"

	ApiLoginCheck = "https://passportapi.115.com/app/1.0/web/1.0/check/sso"

	ApiLoginGetKey   = "https://passportapi.115.com/app/1.0/web/5.0.1/login/getKey"
	ApiPasswordLogin = "https://passportapi.115.com/app/1.0/web/1.0/login/login"

	// Deprecated APIs
	ApiSmsSendCode = "https://passportapi.115.com/app/1.0/web/1.0/code/sms/login"
	ApiSmsLogin    = "https://passportapi.115.com/app/1.0/web/1.0/login/vip"

	ApiQrcodeStatus = "https://qrcodeapi.115.com/get/status/"

	ApiFileList       = "https://webapi.115.com/files"
	ApiFileListByName = "https://aps.115.com/natsort/files.php"
	ApiFileSearch     = "https://webapi.115.com/files/search"

	ApiFileInfo = "https://webapi.115.com/files/get_info"
	ApiFileStat = "https://webapi.115.com/category/get"

	ApiFileStar = "https://webapi.115.com/files/star"
	ApiFileEdit = "https://webapi.115.com/files/edit"

	ApiFileMove   = "https://webapi.115.com/files/move"
	ApiFileCopy   = "https://webapi.115.com/files/copy"
	ApiFileRename = "https://webapi.115.com/files/batch_rename"
	ApiFileDelete = "https://webapi.115.com/rb/delete"

	ApiFileFindDuplicate = "https://webapi.115.com/files/get_repeat_sha"

	ApiLabelList   = "https://webapi.115.com/label/list"
	ApiLabelAdd    = "https://webapi.115.com/label/add_multi"
	ApiLabelEdit   = "https://webapi.115.com/label/edit"
	ApiLabelDelete = "https://webapi.115.com/label/delete"

	ApiDirAdd      = "https://webapi.115.com/files/add"
	ApiDirGetId    = "https://webapi.115.com/files/getid"
	ApiDirSetOrder = "https://webapi.115.com/files/order"

	ApiFileImage = "https://webapi.115.com/files/image"
	ApiFileVideo = "https://v.anxia.com/webapi/files/video"

	ApiOfflineSpace   = "https://115.com/?ct=offline&ac=space"
	ApiOfflineList    = "https://115.com/web/lixian/?ct=lixian&ac=task_lists"
	ApiOfflineAddUrl  = "https://115.com/web/lixian/?ct=lixian&ac=add_task_url"
	ApiOfflineAddUrls = "https://115.com/web/lixian/?ct=lixian&ac=add_task_urls"
	ApiOfflineDelete  = "https://115.com/web/lixian/?ct=lixian&ac=task_del"
	ApiOfflineClear   = "https://115.com/web/lixian/?ct=lixian&ac=task_clear"
	// New offline API
	ApiOfflineAddUrlsNew = "https://lixian.115.com/lixianssp/?ac=add_task_urls"

	ApiDownloadGetUrl = "https://proapi.115.com/app/chrome/downurl"

	ApiUploadInfo     = "https://proapi.115.com/app/uploadinfo"
	ApiUploadOssToken = "https://uplb.115.com/3.0/gettoken.php"
	ApiUploadInit     = "https://uplb.115.com/4.0/initupload.php"

	ApiUploadSimpleInit = "https://uplb.115.com/3.0/sampleinitupload.php"

	ApiCaptchaPage        = "https://captchaapi.115.com/?ac=security_code&type=web"
	ApiCaptchaCodeImage   = "https://captchaapi.115.com/?ct=index&ac=code&ctype=0"
	ApiCaptchaAllKeyImage = "https://captchaapi.115.com/?ct=index&ac=code&t=all"
	ApiCaptchaOneKeyImage = "https://captchaapi.115.com/?ct=index&ac=code&t=single"
	ApiCaptchaSign        = "https://captchaapi.115.com/?ac=code&t=sign"
	ApiCaptchaSubmit      = "https://webapi.115.com/user/captcha"

	ApiGetVersion = "https://appversion.115.com/1/web/1.0/api/chrome"
)
View Source
const (
	FileOrderByTime = "user_ptime"
	FileOrderByType = "file_type"
	FileOrderBySize = "file_size"
	FileOrderByName = "file_name"

	FileListLimit = 32
)
View Source
const (
	CookieDomain115   = ".115.com"
	CookieDomainAnxia = ".anxia.com"

	CookieUrl      = "https://115.com"
	CookieNameUid  = "UID"
	CookieNameCid  = "CID"
	CookieNameSeid = "SEID"
)
View Source
const (
	UploadMaxSize       = 5 * 1024 * 1024 * 1024
	UploadSimplyMaxSize = 200 * 1024 * 1024

	UploadStatusNormal       = 1
	UploadStatusExist        = 2
	UploadStatusRequireCheck = 7
)

Variables

View Source
var (
	ErrNotLogin = errors.New("user not login")

	ErrCaptchaRequired = errors.New("please resolve captcha")

	ErrOfflineInvalidLink = errors.New("invalid download link")
	ErrOfflineTaskExisted = errors.New("offline task existed")

	ErrOrderNotSupport = errors.New("file order not supported")

	ErrPasswordIncorrect    = errors.New("password incorrect")
	ErrLoginTwoStepVerify   = errors.New("requires two-step verification")
	ErrAccountNotBindMobile = errors.New("account not binds mobile")
	ErrCredentialInvalid    = errors.New("credential invalid")
	ErrSessionExited        = errors.New("session exited")

	ErrQrcodeExpired = errors.New("qrcode expired")

	// ErrUnexpected is the fall-back error whose code is not handled.
	ErrUnexpected = errors.New("unexpected error")

	// ErrExist means an item which you want to create is already existed.
	ErrExist = errors.New("target already exists")
	// ErrNotExist means an item which you find is not existed.
	ErrNotExist = errors.New("target does not exist")

	// ErrReachEnd means there are no more item.
	ErrReachEnd = errors.New("reach the end")

	ErrUploadDisabled = errors.New("upload function is disabled")

	ErrUploadTooLarge = errors.New("upload reach the limit")

	ErrInitUploadUnknowStatus = errors.New("unknown status from initupload")

	ErrImportDirectory = errors.New("can not import directory")

	ErrDownloadEmpty = errors.New("can not get download URL")

	ErrDownloadDirectory = errors.New("can not download directory")

	ErrVideoNotReady = errors.New("video is not ready")

	ErrEmptyList = errors.New("list is empty")
)
View Source
var (
	LabelColors = []string{

		"#000000",

		"#FF4B30",

		"#F78C26",

		"#FFC032",

		"#43BA80",

		"#2670FC",

		"#8B69FE",

		"#CCCCCC",
	}

	LabelColorMap = map[string]int{
		"#000000": 0,
		"#FF4B30": 1,
		"#F78C26": 2,
		"#FFC032": 3,
		"#43BA80": 4,
		"#2670FC": 5,
		"#8B69FE": 6,
		"#CCCCCC": 7,
	}
)

Functions

func BoolToInt

func BoolToInt(b bool) int

func MakeUserAgent added in v0.3.1

func MakeUserAgent(name, appVer string) string

func ParseFileTime added in v0.3.2

func ParseFileTime(str string) time.Time

func QrcodeImageUrl added in v0.4.6

func QrcodeImageUrl(platform, userId string) string

func QrcodeLoginApi added in v0.4.6

func QrcodeLoginApi(platform string) string

func QrcodeTokenApi added in v0.4.6

func QrcodeTokenApi(platform string) string

func UploadDigest added in v0.4.1

func UploadDigest(r io.Reader, result *UploadDigestResult) (err error)

func UploadDigestRange added in v0.4.1

func UploadDigestRange(r io.ReadSeeker, rangeSpec string) (result string, err error)

Types

type BasicResponse

type BasicResponse struct {
	// Response state
	State bool `json:"state"`
	// Error code
	ErrorCode  StringInt `json:"errno,omitempty"`
	ErrorCode2 int       `json:"errNo,omitempty"`
	ErrorCode3 int       `json:"code,omitempty"`
	// Error message
	ErrorMessage  string `json:"error,omitempty"`
	ErrorMessage2 string `json:"message,omitempty"`
	// Response data
	Data json.RawMessage `json:"data,omitempty"`
}

func (*BasicResponse) Decode

func (r *BasicResponse) Decode(result interface{}) error

func (*BasicResponse) Err

func (r *BasicResponse) Err() error

type BoolInt added in v0.3.1

type BoolInt int

func (*BoolInt) UnmarshalJSON added in v0.3.1

func (v *BoolInt) UnmarshalJSON(b []byte) (err error)

type CaptchaSignResponse

type CaptchaSignResponse struct {
	BasicResponse
	Sign string `json:"sign"`
}

type DirLocateResponse

type DirLocateResponse struct {
	BasicResponse
	DirId     IntString `json:"id"`
	IsPrivate StringInt `json:"is_private"`
}

type DirMakeResponse

type DirMakeResponse struct {
	BasicResponse

	AreaId IntString `json:"aid"`

	CategoryId   string `json:"cid"`
	CategoryName string `json:"cname"`

	FileId   string `json:"file_id"`
	FileName string `json:"file_name"`
}

type DownloadData

type DownloadData map[string]*DownloadInfo

func (DownloadData) IsValid added in v0.3.1

func (d DownloadData) IsValid() bool

type DownloadInfo

type DownloadInfo struct {
	FileName string      `json:"file_name"`
	FileSize StringInt64 `json:"file_size"`
	PickCode string      `json:"pick_code"`
	Url      DownloadUrl `json:"url"`
}

type DownloadRequest

type DownloadRequest struct {
	Pickcode string `json:"pickcode,omitempty"`
}

type DownloadUrl added in v0.3.1

type DownloadUrl struct {
	Url string
}

func (*DownloadUrl) UnmarshalJSON added in v0.3.1

func (u *DownloadUrl) UnmarshalJSON(b []byte) (err error)

type FileDownloadUrl added in v0.3.1

type FileDownloadUrl struct {
	Client int    `json:"client"`
	OssId  string `json:"oss_id"`
	Url    string `json:"url"`
}

type FileDuplication

type FileDuplication struct {
	FileId   string      `json:"fid"`
	Name     string      `json:"fn"`
	Type     string      `json:"ico"`
	Size     StringInt64 `json:"fs"`
	Sha1     string      `json:"sha1"`
	PickCode string      `json:"pc"`

	UpdateTime StringInt64 `json:"te"`

	Parent FileParentInfo `json:"paths"`
}

type FileInfo

type FileInfo struct {
	AreaId     IntString `json:"aid"`
	CategoryId string    `json:"cid"`
	FileId     string    `json:"fid"`
	ParentId   string    `json:"pid"`

	Name     string      `json:"n"`
	Type     string      `json:"ico"`
	Size     StringInt64 `json:"s"`
	Sha1     string      `json:"sha"`
	PickCode string      `json:"pc"`

	IsStar StringInt    `json:"m"`
	Labels []*LabelInfo `json:"fl"`

	CreatedTime  string `json:"tp"`
	UpdatedTime  string `json:"te"`
	ModifiedTime string `json:"t"`

	// MediaDuration describes duration in seconds for audio / video.
	MediaDuration float64 `json:"play_long"`

	// Special fields for video
	IsVideo         int `json:"iv"`
	VideoDefinition int `json:"vdi"`
}

type FileListResponse

type FileListResponse struct {
	BasicResponse

	AreaId     string    `json:"aid"`
	CategoryId IntString `json:"cid"`

	Count int    `json:"count"`
	Order string `json:"order"`
	IsAsc int    `json:"is_asc"`

	Offset   int `json:"offset"`
	Limit    int `json:"limit"`
	PageSize int `json:"page_size"`
}

type FileParentInfo

type FileParentInfo struct {
	FileId   IntString `json:"file_id"`
	FileName string    `json:"file_name"`
}

type FileStatResponse

type FileStatResponse struct {
	BasicResponse

	FileName string    `json:"file_name"`
	PickCode string    `json:"pick_code"`
	Sha1     string    `json:"sha1"`
	IsFile   StringInt `json:"file_category"`

	CreateTime StringInt64 `json:"ptime"`
	UpdateTime StringInt64 `json:"utime"`
	AccessTime int64       `json:"open_time"`

	Paths []*FileParentInfo `json:"paths"`

	FileCount StringInt `json:"count"`
	DirCount  StringInt `json:"folder_count"`
}

func (*FileStatResponse) Err added in v0.2.2

func (r *FileStatResponse) Err() error

type ImageData

type ImageData struct {
	Name string `json:"file_name"`
	Sha1 string `json:"file_sha1"`

	AllUrl    []string `json:"all_url"`
	OriginUrl string   `json:"origin_url"`
	SourceUrl string   `json:"source_url"`
	Url       string   `json:"url"`
}

type IndexData

type IndexData struct {
	LoginDevices struct {
		Last struct {
			Device   string `json:"device"`
			DeviceId string `json:"device_id"`
			Ip       string `json:"ip"`
			City     string `json:"city"`
			Os       string `json:"os"`
			Network  string `json:"network"`
			Time     int64  `json:"utime"`
		} `json:"last"`
		List []LoginDeviceInfo `json:"list"`
	} `json:"login_devices_info"`
	Space struct {
		Total  SizeInfo `json:"all_total"`
		Used   SizeInfo `json:"all_use"`
		Remain SizeInfo `json:"all_remain"`
	} `json:"space_info"`
}

type IntString

type IntString string

func (*IntString) UnmarshalJSON

func (v *IntString) UnmarshalJSON(b []byte) (err error)

type LabelInfo

type LabelInfo struct {
	Id    string `json:"id"`
	Name  string `json:"name"`
	Color string `json:"color"`

	Sort StringInt `json:"sort"`

	CreateTime int64 `json:"create_time"`
	UpdateTime int64 `json:"update_time"`
}

type LabelListData

type LabelListData struct {
	Total int          `json:"total"`
	List  []*LabelInfo `json:"list"`
}

type LoginBasicResponse

type LoginBasicResponse struct {
	State   int    `json:"state"`
	Code    int    `json:"code"`
	Message string `json:"message"`

	ErrorCode    int    `json:"errno"`
	ErrorMessage string `json:"error"`

	Data json.RawMessage `json:"data"`
}

func (*LoginBasicResponse) Decode

func (r *LoginBasicResponse) Decode(data interface{}) error

func (*LoginBasicResponse) Err

func (r *LoginBasicResponse) Err() error

type LoginCheckData

type LoginCheckData struct {
	UserId int    `json:"user_id"`
	Link   string `json:"link"`
	Expire int    `json:"expire"`
}

type LoginCheckResponse

type LoginCheckResponse struct {
	LoginBasicResponse
}

func (*LoginCheckResponse) Err

func (r *LoginCheckResponse) Err() error

type LoginDeviceInfo

type LoginDeviceInfo struct {
	SsoId     string `json:"ssoent"`
	Name      string `json:"name"`
	Desc      string `json:"desc"`
	Device    string `json:"device"`
	Ip        string `json:"ip"`
	City      string `json:"city"`
	Time      int64  `json:"utime"`
	IsCurrent int    `json:"is_current"`
}

type LoginUserData

type LoginUserData struct {
	Id     int    `json:"user_id"`
	Name   string `json:"user_name"`
	Email  string `json:"email"`
	Mobile string `json:"mobile"`
	Cookie struct {
		CID  string `json:"CID"`
		UID  string `json:"UID"`
		SEID string `json:"SEID"`
	} `json:"cookie"`
}

type M115Response added in v0.4.6

type M115Response struct {
	State      bool   `json:"state"`
	ErrorCode  int    `json:"errcode,omitempty"`
	ErrorCode2 int    `json:"errno,omitempty"`
	ErrorMsg   string `json:"msg"`
	Data       string `json:"data"`
}

func (*M115Response) Err added in v0.4.6

func (r *M115Response) Err() error

type OfflineAddUrlResponse

type OfflineAddUrlResponse struct {
	OfflineBasicResponse

	ErrorType string `json:"errtype"`
	InfoHash  string `json:"info_hash"`
	Name      string `json:"name"`
	Url       string `json:"url"`
}

type OfflineAddUrlsResponse added in v0.4.1

type OfflineAddUrlsResponse struct {
	OfflineBasicResponse

	Result []*OfflineAddUrlResponse `json:"result"`
}

type OfflineBasicResponse

type OfflineBasicResponse struct {
	State     bool `json:"state"`
	ErrorNum  int  `json:"errno"`
	ErrorCode int  `json:"errcode"`
}

func (*OfflineBasicResponse) Err

func (r *OfflineBasicResponse) Err() error

type OfflineListResponse

type OfflineListResponse struct {
	OfflineBasicResponse

	Tasks []*OfflineTask `json:"tasks"`

	TaskCount int `json:"count"`
	PageIndex int `json:"page"`
	PageCount int `json:"page_count"`
	PageSize  int `json:"page_row"`

	QuotaTotal  int `json:"total"`
	QuotaRemain int `json:"quota"`
}

type OfflineSpaceResponse

type OfflineSpaceResponse struct {
	BasicResponse

	Sign  string `json:"sign"`
	Time  int64  `json:"time"`
	Size  string `json:"size"`
	Limit int64  `json:"limit"`
	Url   string `json:"url"`
	BtUrl string `json:"bt_url"`
}

type OfflineTask

type OfflineTask struct {
	InfoHash   string  `json:"info_hash"`
	Name       string  `json:"name"`
	Size       int64   `json:"size"`
	FileId     string  `json:"file_id"`
	DirId      string  `json:"wp_path_id"`
	Url        string  `json:"url"`
	Status     int     `json:"status"`
	Percent    float64 `json:"percentDone"`
	AddTime    int64   `json:"add_time"`
	UpdateTime int64   `json:"last_update"`
}

type OfflineToken

type OfflineToken struct {
	Sign string
	Time int64
}

type QrcodeStatusData

type QrcodeStatusData struct {
	Status  int    `json:"status,omitempty"`
	Msg     string `json:"msg,omitempty"`
	Version string `json:"version,omitempty"`
}

type QrcodeTokenData

type QrcodeTokenData struct {
	Uid    string `json:"uid"`
	Time   int64  `json:"time"`
	Sign   string `json:"sign"`
	Qrcode string `json:"qrcode"`
}

type SizeInfo

type SizeInfo struct {
	Size       float64 `json:"size"`
	FormatSize string  `json:"size_format"`
}

type StringFloat64

type StringFloat64 float64

StringFloat64 uses for json field which maybe a string or a float64.

func (*StringFloat64) UnmarshalJSON

func (v *StringFloat64) UnmarshalJSON(b []byte) (err error)

type StringInt

type StringInt int64

StringInt uses for json field which maybe a string or an int.

func (*StringInt) UnmarshalJSON

func (v *StringInt) UnmarshalJSON(b []byte) (err error)

type StringInt64

type StringInt64 int64

StringInt64 uses for json field which maybe a string or an int64.

func (*StringInt64) UnmarshalJSON

func (v *StringInt64) UnmarshalJSON(b []byte) (err error)

type UploadDigestResult added in v0.4.1

type UploadDigestResult struct {
	Size int64
	SHA1 string
	MD5  string
}

type UploadHelper added in v0.4.1

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

UploadHelper is a helper object for upload function.

func (*UploadHelper) AppVersion added in v0.4.2

func (h *UploadHelper) AppVersion() string

func (*UploadHelper) CalculateSignature added in v0.4.1

func (h *UploadHelper) CalculateSignature(fileId, target string) string

func (*UploadHelper) CalculateToken added in v0.4.1

func (h *UploadHelper) CalculateToken(
	fileId string, fileSize int64,
	signKey, signValue string,
	timestamp int64,
) string

func (*UploadHelper) IsReady added in v0.4.1

func (h *UploadHelper) IsReady() bool

func (*UploadHelper) SetAppVersion added in v0.4.2

func (h *UploadHelper) SetAppVersion(appVer string)

func (*UploadHelper) SetUserData added in v0.4.2

func (h *UploadHelper) SetUserData(userId int, userKey string)

func (*UploadHelper) UserId added in v0.4.1

func (h *UploadHelper) UserId() string

type UploadInfoResponse

type UploadInfoResponse struct {
	BasicResponse
	AppId       IntString `json:"app_id"`
	AppVersion  IntString `json:"app_version"`
	UploadLimit int64     `json:"size_limit"`
	IspType     int       `json:"isp_type"`
	UserId      int       `json:"user_id"`
	UserKey     string    `json:"userkey"`
}

type UploadInitData added in v0.4.2

type UploadInitData struct {
	// File metadata
	FileId   string
	FileName string
	FileSize int64
	// Target directory
	Target string
	// Upload signature
	Signature string
	// Sign parameters
	SignKey   string
	SignValue string
}

UploadInitData contains IN/OUT parameters during initupload.

type UploadInitResponse

type UploadInitResponse struct {
	Request   string `json:"request"`
	Version   string `json:"version"`
	ErrorCode int    `json:"statuscode"`
	ErrorMsg  string `json:"statusmsg"`

	Status   BoolInt `json:"status"`
	PickCode string  `json:"pickcode"`

	// New fields in upload v4.0
	SignKey   string `json:"sign_key"`
	SignCheck string `json:"sign_check"`

	// OSS upload fields
	Bucket   string `json:"bucket"`
	Object   string `json:"object"`
	Callback struct {
		Callback    string `json:"callback"`
		CallbackVar string `json:"callback_var"`
	} `json:"callback"`

	// Useless fields
	FileId   int    `json:"fileid"`
	FileInfo string `json:"fileinfo"`
	Target   string `json:"target"`
}

func (*UploadInitResponse) Err

func (r *UploadInitResponse) Err() error

type UploadOssData added in v0.4.2

type UploadOssData struct {
	Bucket      string
	Object      string
	Callback    string
	CallbackVar string
}

type UploadOssTokenResponse

type UploadOssTokenResponse struct {
	StatusCode      string `json:"StatusCode"`
	AccessKeyId     string `json:"AccessKeyId"`
	AccessKeySecret string `json:"AccessKeySecret"`
	SecurityToken   string `json:"SecurityToken"`
	Expiration      string `json:"Expiration"`
}

func (*UploadOssTokenResponse) Err

func (r *UploadOssTokenResponse) Err() error

type UploadResultData

type UploadResultData struct {
	AreaId     IntString   `json:"aid"`
	CategoryId IntString   `json:"cid"`
	FileId     string      `json:"file_id"`
	FileName   string      `json:"file_name"`
	FileSize   StringInt64 `json:"file_size"`
	PickCode   string      `json:"pick_code"`
	Sha1       string      `json:"sha1"`
}

type UploadSimpleInitResponse

type UploadSimpleInitResponse struct {
	Host     string `json:"host"`
	Object   string `json:"object"`
	Callback string `json:"callback"`

	AccessKeyId string `json:"accessid"`
	Policy      string `json:"policy"`
	Expire      int64  `json:"expire"`
	Signature   string `json:"signature"`
}

func (*UploadSimpleInitResponse) Err

type UserInfoData

type UserInfoData struct {
	UserId     int    `json:"user_id"`
	UserName   string `json:"user_name"`
	UserAvatar string `json:"face"`

	// Unused fields
	Device     int   `json:"device"`
	Rank       int   `json:"rank"`
	VipFlag    int   `json:"vip"`
	VipExpire  int64 `json:"expire"`
	VipForever int   `json:"forever"`
	Global     int   `json:"global"`
}

type VersionData added in v0.3.1

type VersionData struct {
	Android    VersionInfo `json:"android"`
	LinuxApp   VersionInfo `json:"linux_115"`
	MacBrowser VersionInfo `json:"mac"`
	MacApp     VersionInfo `json:"mac_115"`
	WinBrowser VersionInfo `json:"win"`
	WinApp     VersionInfo `json:"window_115"`
}

type VersionInfo added in v0.3.1

type VersionInfo struct {
	CreatedTime int64  `json:"created_time"`
	VersionCode string `json:"version_code"`
	VersionUrl  string `json:"version_url"`
}

type VideoResponse

type VideoResponse struct {
	BasicResponse
	// Video information
	Width    StringInt     `json:"width"`
	Height   StringInt     `json:"height"`
	Duration StringFloat64 `json:"play_long"`
	VideoUrl string        `json:"video_url"`
	// File information
	FileId     string      `json:"file_id"`
	FileName   string      `json:"file_name"`
	FileSize   StringInt64 `json:"file_size"`
	FileStatus int         `json:"file_status"`
	PickCode   string      `json:"pick_code"`
	Sha1       string      `json:"sha1"`
}

Jump to

Keyboard shortcuts

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