Initial commit
This commit is contained in:
803
.golangci.yml
Normal file
803
.golangci.yml
Normal file
@@ -0,0 +1,803 @@
|
|||||||
|
version: "2"
|
||||||
|
run:
|
||||||
|
go: "1.25"
|
||||||
|
modules-download-mode: readonly
|
||||||
|
tests: false
|
||||||
|
allow-parallel-runners: true
|
||||||
|
linters:
|
||||||
|
default: none
|
||||||
|
enable:
|
||||||
|
- asasalint
|
||||||
|
- asciicheck
|
||||||
|
- bodyclose
|
||||||
|
- containedctx
|
||||||
|
- contextcheck
|
||||||
|
- decorder
|
||||||
|
- durationcheck
|
||||||
|
- errcheck
|
||||||
|
- errchkjson
|
||||||
|
- errorlint
|
||||||
|
- exhaustive
|
||||||
|
- exptostd
|
||||||
|
- ginkgolinter
|
||||||
|
- gocheckcompilerdirectives
|
||||||
|
- gocritic
|
||||||
|
- godoclint
|
||||||
|
- godot
|
||||||
|
- godox
|
||||||
|
- goprintffuncname
|
||||||
|
- gosec
|
||||||
|
- gosmopolitan
|
||||||
|
- govet
|
||||||
|
- grouper
|
||||||
|
- importas
|
||||||
|
- ineffassign
|
||||||
|
- intrange
|
||||||
|
- ireturn
|
||||||
|
- makezero
|
||||||
|
- mirror
|
||||||
|
- misspell
|
||||||
|
- modernize
|
||||||
|
- nakedret
|
||||||
|
- nilerr
|
||||||
|
- nilnil
|
||||||
|
- nlreturn
|
||||||
|
- noctx
|
||||||
|
- nolintlint
|
||||||
|
- nonamedreturns
|
||||||
|
- perfsprint
|
||||||
|
- predeclared
|
||||||
|
- promlinter
|
||||||
|
- reassign
|
||||||
|
- revive
|
||||||
|
- rowserrcheck
|
||||||
|
- staticcheck
|
||||||
|
- unconvert
|
||||||
|
- unused
|
||||||
|
- usestdlibvars
|
||||||
|
- wastedassign
|
||||||
|
- wsl_v5
|
||||||
|
settings:
|
||||||
|
decorder:
|
||||||
|
dec-order:
|
||||||
|
- type
|
||||||
|
- const
|
||||||
|
- var
|
||||||
|
- func
|
||||||
|
gocritic:
|
||||||
|
disable-all: false
|
||||||
|
enable-all: true
|
||||||
|
disabled-checks:
|
||||||
|
- singleCaseSwitch
|
||||||
|
- octalLiteral
|
||||||
|
- regexpSimplify
|
||||||
|
- paramTypeCombine
|
||||||
|
- whyNoLint
|
||||||
|
- hugeParam
|
||||||
|
godoclint:
|
||||||
|
default: all
|
||||||
|
ignore-unexported: false
|
||||||
|
ignore-exported: false
|
||||||
|
include-unexported: true
|
||||||
|
disable:
|
||||||
|
- max-len
|
||||||
|
godot:
|
||||||
|
scope: all
|
||||||
|
exclude:
|
||||||
|
# Exclude todo and fixme comments.
|
||||||
|
- "^fixme:"
|
||||||
|
- "^todo:"
|
||||||
|
period: true
|
||||||
|
capital: false
|
||||||
|
godox:
|
||||||
|
keywords:
|
||||||
|
- FIXME
|
||||||
|
gosec: # go-fucking-sic
|
||||||
|
excludes:
|
||||||
|
# Боже, как же вы затрахали своей крипто-ебучкой, наркоманы хреновы!
|
||||||
|
- G405 # Detect the usage of DES or RC4
|
||||||
|
- G406 # Detect the usage of MD4 or RIPEMD160
|
||||||
|
# Оно не используется для повышения безопасности или каких-то таких секъюрных вещей, оно используется для выборки
|
||||||
|
# произвольного элемента из массива, так что ИДИ НАХУЙ с этой проверкой и не еби мозги липовой безопасностью.
|
||||||
|
- G404 # Insecure random number source (rand)
|
||||||
|
- G501 # Import blocklist: crypto/md5
|
||||||
|
- G502 # Import blocklist: crypto/des
|
||||||
|
- G503 # Import blocklist: crypto/rc4
|
||||||
|
- G504 # Import blocklist: net/http/cgi
|
||||||
|
- G505 # Import blocklist: crypto/sha1
|
||||||
|
- G506 # Import blocklist: golang.org/x/crypto/md4
|
||||||
|
- G507 # Import blocklist: golang.org/x/crypto/ripemd160
|
||||||
|
config:
|
||||||
|
global:
|
||||||
|
audit: true
|
||||||
|
# Ваша сраная паранойя при создании файлов и каталогов мешает нормальной работе сервисов. Группы придуманы для
|
||||||
|
# удобства, группе удобно давать право чтения. Поэтому идите нахуй со своими ограничениями на чтение для
|
||||||
|
# группы, "безопасные" клоуны.
|
||||||
|
G301: "0755"
|
||||||
|
G306: "0644"
|
||||||
|
govet:
|
||||||
|
disable-all: false
|
||||||
|
enable-all: true
|
||||||
|
disable:
|
||||||
|
- shadow
|
||||||
|
- fieldalignment
|
||||||
|
grouper:
|
||||||
|
# Require the use of a single global 'const' declaration only.
|
||||||
|
# Default: false
|
||||||
|
const-require-single-const: true
|
||||||
|
# Require the use of grouped global 'const' declarations.
|
||||||
|
# Default: false
|
||||||
|
const-require-grouping: true
|
||||||
|
# Require the use of a single 'import' declaration only.
|
||||||
|
# Default: false
|
||||||
|
import-require-single-import: true
|
||||||
|
# Require the use of grouped 'import' declarations.
|
||||||
|
# Default: false
|
||||||
|
import-require-grouping: true
|
||||||
|
# Require the use of a single global 'type' declaration only.
|
||||||
|
# Default: false
|
||||||
|
type-require-single-type: true
|
||||||
|
# Require the use of grouped global 'type' declarations.
|
||||||
|
# Default: false
|
||||||
|
type-require-grouping: true
|
||||||
|
# Require the use of a single global 'var' declaration only.
|
||||||
|
# Default: false
|
||||||
|
var-require-single-var: true
|
||||||
|
# Require the use of grouped global 'var' declarations.
|
||||||
|
# Default: false
|
||||||
|
var-require-grouping: true
|
||||||
|
misspell:
|
||||||
|
# Correct spellings using locale preferences for US or UK.
|
||||||
|
# Setting locale to US will correct the British spelling of 'colour' to 'color'.
|
||||||
|
# Default is to use a neutral variety of English.
|
||||||
|
locale: US
|
||||||
|
# Typos to ignore.
|
||||||
|
# Should be in lower case.
|
||||||
|
# Default: []
|
||||||
|
ignore-rules:
|
||||||
|
- someword
|
||||||
|
# Extra word corrections.
|
||||||
|
# `typo` and `correction` should only contain letters.
|
||||||
|
# The words are case-insensitive.
|
||||||
|
# Default: []
|
||||||
|
extra-words:
|
||||||
|
- typo: "iff"
|
||||||
|
correction: "if"
|
||||||
|
- typo: "cancelation"
|
||||||
|
correction: "cancellation"
|
||||||
|
# Mode of the analysis:
|
||||||
|
# - default: checks all the file content.
|
||||||
|
# - restricted: checks only comments.
|
||||||
|
# Default: ""
|
||||||
|
mode: restricted
|
||||||
|
nonamedreturns:
|
||||||
|
# Report named error if it is assigned inside defer.
|
||||||
|
# Default: false
|
||||||
|
report-error-in-defer: true
|
||||||
|
perfsprint:
|
||||||
|
integer-format: true
|
||||||
|
int-conversion: true
|
||||||
|
error-format: true
|
||||||
|
err-error: false
|
||||||
|
errorf: true
|
||||||
|
string-format: true
|
||||||
|
sprintf1: true
|
||||||
|
strconcat: true
|
||||||
|
bool-format: true
|
||||||
|
hex-format: true
|
||||||
|
concat-loop: true
|
||||||
|
loop-other-ops: false
|
||||||
|
revive:
|
||||||
|
enable-all-rules: false
|
||||||
|
staticcheck:
|
||||||
|
# https://staticcheck.dev/docs/configuration/options/#dot_import_whitelist
|
||||||
|
# Default: ["github.com/mmcloughlin/avo/build", "github.com/mmcloughlin/avo/operand", "github.com/mmcloughlin/avo/reg"]
|
||||||
|
dot-import-whitelist:
|
||||||
|
- fmt
|
||||||
|
# https://staticcheck.dev/docs/configuration/options/#initialisms
|
||||||
|
# Default: ["ACL", "API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", "IP", "JSON", "QPS", "RAM", "RPC", "SLA", "SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", "UDP", "UI", "GID", "UID", "UUID", "URI", "URL", "UTF8", "VM", "XML", "XMPP", "XSRF", "XSS", "SIP", "RTP", "AMQP", "DB", "TS"]
|
||||||
|
initialisms:
|
||||||
|
[
|
||||||
|
"ACL",
|
||||||
|
"API",
|
||||||
|
"ASCII",
|
||||||
|
"CPU",
|
||||||
|
"CSS",
|
||||||
|
"DNS",
|
||||||
|
"EOF",
|
||||||
|
"GUID",
|
||||||
|
"HTML",
|
||||||
|
"HTTP",
|
||||||
|
"HTTPS",
|
||||||
|
"ID",
|
||||||
|
"IP",
|
||||||
|
"JSON",
|
||||||
|
"QPS",
|
||||||
|
"RAM",
|
||||||
|
"RPC",
|
||||||
|
"SLA",
|
||||||
|
"SMTP",
|
||||||
|
"SQL",
|
||||||
|
"SSH",
|
||||||
|
"TCP",
|
||||||
|
"TLS",
|
||||||
|
"TTL",
|
||||||
|
"UDP",
|
||||||
|
"UI",
|
||||||
|
"GID",
|
||||||
|
"UID",
|
||||||
|
"UUID",
|
||||||
|
"URI",
|
||||||
|
"URL",
|
||||||
|
"UTF8",
|
||||||
|
"VM",
|
||||||
|
"XML",
|
||||||
|
"XMPP",
|
||||||
|
"XSRF",
|
||||||
|
"XSS",
|
||||||
|
"SIP",
|
||||||
|
"RTP",
|
||||||
|
"AMQP",
|
||||||
|
"DB",
|
||||||
|
"TS",
|
||||||
|
]
|
||||||
|
# https://staticcheck.dev/docs/configuration/options/#http_status_code_whitelist
|
||||||
|
# Default: ["200", "400", "404", "500"]
|
||||||
|
http-status-code-whitelist: ["200", "400", "404", "500"]
|
||||||
|
# SAxxxx checks in https://staticcheck.dev/docs/configuration/options/#checks
|
||||||
|
# Example (to disable some checks): [ "all", "-SA1000", "-SA1001"]
|
||||||
|
# Run `GL_DEBUG=staticcheck golangci-lint run --enable=staticcheck` to see all available checks and enabled by config checks.
|
||||||
|
# Default: ["all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022"]
|
||||||
|
checks:
|
||||||
|
# Invalid regular expression.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1000
|
||||||
|
- SA1000
|
||||||
|
# Invalid template.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1001
|
||||||
|
- SA1001
|
||||||
|
# Invalid format in 'time.Parse'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1002
|
||||||
|
- SA1002
|
||||||
|
# Unsupported argument to functions in 'encoding/binary'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1003
|
||||||
|
- SA1003
|
||||||
|
# Suspiciously small untyped constant in 'time.Sleep'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1004
|
||||||
|
- SA1004
|
||||||
|
# Invalid first argument to 'exec.Command'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1005
|
||||||
|
- SA1005
|
||||||
|
# 'Printf' with dynamic first argument and no further arguments.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1006
|
||||||
|
- SA1006
|
||||||
|
# Invalid URL in 'net/url.Parse'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1007
|
||||||
|
- SA1007
|
||||||
|
# Non-canonical key in 'http.Header' map.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1008
|
||||||
|
- SA1008
|
||||||
|
# '(*regexp.Regexp).FindAll' called with 'n == 0', which will always return zero results.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1010
|
||||||
|
- SA1010
|
||||||
|
# Various methods in the "strings" package expect valid UTF-8, but invalid input is provided.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1011
|
||||||
|
- SA1011
|
||||||
|
# A nil 'context.Context' is being passed to a function, consider using 'context.TODO' instead.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1012
|
||||||
|
- SA1012
|
||||||
|
# 'io.Seeker.Seek' is being called with the whence constant as the first argument, but it should be the second.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1013
|
||||||
|
- SA1013
|
||||||
|
# Non-pointer value passed to 'Unmarshal' or 'Decode'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1014
|
||||||
|
- SA1014
|
||||||
|
# Using 'time.Tick' in a way that will leak. Consider using 'time.NewTicker', and only use 'time.Tick' in tests, commands and endless functions.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1015
|
||||||
|
- SA1015
|
||||||
|
# Trapping a signal that cannot be trapped.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1016
|
||||||
|
- SA1016
|
||||||
|
# Channels used with 'os/signal.Notify' should be buffered.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1017
|
||||||
|
- SA1017
|
||||||
|
# 'strings.Replace' called with 'n == 0', which does nothing.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1018
|
||||||
|
- SA1018
|
||||||
|
# Using a deprecated function, variable, constant or field.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1019
|
||||||
|
- SA1019
|
||||||
|
# Using an invalid host:port pair with a 'net.Listen'-related function.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1020
|
||||||
|
- SA1020
|
||||||
|
# Using 'bytes.Equal' to compare two 'net.IP'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1021
|
||||||
|
- SA1021
|
||||||
|
# Modifying the buffer in an 'io.Writer' implementation.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1023
|
||||||
|
- SA1023
|
||||||
|
# A string cutset contains duplicate characters.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1024
|
||||||
|
- SA1024
|
||||||
|
# It is not possible to use '(*time.Timer).Reset''s return value correctly.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1025
|
||||||
|
- SA1025
|
||||||
|
# Cannot marshal channels or functions.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1026
|
||||||
|
- SA1026
|
||||||
|
# Atomic access to 64-bit variable must be 64-bit aligned.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1027
|
||||||
|
- SA1027
|
||||||
|
# 'sort.Slice' can only be used on slices.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1028
|
||||||
|
- SA1028
|
||||||
|
# Inappropriate key in call to 'context.WithValue'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1029
|
||||||
|
- SA1029
|
||||||
|
# Invalid argument in call to a 'strconv' function.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1030
|
||||||
|
- SA1030
|
||||||
|
# Overlapping byte slices passed to an encoder.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1031
|
||||||
|
- SA1031
|
||||||
|
# Wrong order of arguments to 'errors.Is'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA1032
|
||||||
|
- SA1032
|
||||||
|
# 'sync.WaitGroup.Add' called inside the goroutine, leading to a race condition.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA2000
|
||||||
|
- SA2000
|
||||||
|
# Empty critical section, did you mean to defer the unlock?.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA2001
|
||||||
|
- SA2001
|
||||||
|
# Called 'testing.T.FailNow' or 'SkipNow' in a goroutine, which isn't allowed.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA2002
|
||||||
|
- SA2002
|
||||||
|
# Deferred 'Lock' right after locking, likely meant to defer 'Unlock' instead.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA2003
|
||||||
|
- SA2003
|
||||||
|
# 'TestMain' doesn't call 'os.Exit', hiding test failures.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA3000
|
||||||
|
- SA3000
|
||||||
|
# Assigning to 'b.N' in benchmarks distorts the results.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA3001
|
||||||
|
- SA3001
|
||||||
|
# Binary operator has identical expressions on both sides.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4000
|
||||||
|
- SA4000
|
||||||
|
# '&*x' gets simplified to 'x', it does not copy 'x'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4001
|
||||||
|
- SA4001
|
||||||
|
# Comparing unsigned values against negative values is pointless.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4003
|
||||||
|
- SA4003
|
||||||
|
# The loop exits unconditionally after one iteration.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4004
|
||||||
|
- SA4004
|
||||||
|
# Field assignment that will never be observed. Did you mean to use a pointer receiver?.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4005
|
||||||
|
- SA4005
|
||||||
|
# A value assigned to a variable is never read before being overwritten. Forgotten error check or dead code?.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4006
|
||||||
|
- SA4006
|
||||||
|
# The variable in the loop condition never changes, are you incrementing the wrong variable?.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4008
|
||||||
|
- SA4008
|
||||||
|
# A function argument is overwritten before its first use.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4009
|
||||||
|
- SA4009
|
||||||
|
# The result of 'append' will never be observed anywhere.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4010
|
||||||
|
- SA4010
|
||||||
|
# Break statement with no effect. Did you mean to break out of an outer loop?.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4011
|
||||||
|
- SA4011
|
||||||
|
# Comparing a value against NaN even though no value is equal to NaN.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4012
|
||||||
|
- SA4012
|
||||||
|
# Negating a boolean twice ('!!b') is the same as writing 'b'. This is either redundant, or a typo.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4013
|
||||||
|
- SA4013
|
||||||
|
# An if/else if chain has repeated conditions and no side-effects; if the condition didn't match the first time, it won't match the second time, either.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4014
|
||||||
|
- SA4014
|
||||||
|
# Calling functions like 'math.Ceil' on floats converted from integers doesn't do anything useful.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4015
|
||||||
|
- SA4015
|
||||||
|
# Certain bitwise operations, such as 'x ^ 0', do not do anything useful.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4016
|
||||||
|
- SA4016
|
||||||
|
# Discarding the return values of a function without side effects, making the call pointless.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4017
|
||||||
|
- SA4017
|
||||||
|
# Self-assignment of variables.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4018
|
||||||
|
- SA4018
|
||||||
|
# Multiple, identical build constraints in the same file.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4019
|
||||||
|
- SA4019
|
||||||
|
# Unreachable case clause in a type switch.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4020
|
||||||
|
- SA4020
|
||||||
|
# "x = append(y)" is equivalent to "x = y".
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4021
|
||||||
|
- SA4021
|
||||||
|
# Comparing the address of a variable against nil.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4022
|
||||||
|
- SA4022
|
||||||
|
# Impossible comparison of interface value with untyped nil.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4023
|
||||||
|
- SA4023
|
||||||
|
# Checking for impossible return value from a builtin function.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4024
|
||||||
|
- SA4024
|
||||||
|
# Integer division of literals that results in zero.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4025
|
||||||
|
- SA4025
|
||||||
|
# Go constants cannot express negative zero.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4026
|
||||||
|
- SA4026
|
||||||
|
# '(*net/url.URL).Query' returns a copy, modifying it doesn't change the URL.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4027
|
||||||
|
- SA4027
|
||||||
|
# 'x % 1' is always zero.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4028
|
||||||
|
- SA4028
|
||||||
|
# Ineffective attempt at sorting slice.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4029
|
||||||
|
- SA4029
|
||||||
|
# Ineffective attempt at generating random number.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4030
|
||||||
|
- SA4030
|
||||||
|
# Checking never-nil value against nil.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4031
|
||||||
|
- SA4031
|
||||||
|
# Comparing 'runtime.GOOS' or 'runtime.GOARCH' against impossible value.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA4032
|
||||||
|
- SA4032
|
||||||
|
# Assignment to nil map.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA5000
|
||||||
|
- SA5000
|
||||||
|
# Deferring 'Close' before checking for a possible error.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA5001
|
||||||
|
- SA5001
|
||||||
|
# The empty for loop ("for {}") spins and can block the scheduler.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA5002
|
||||||
|
- SA5002
|
||||||
|
# Defers in infinite loops will never execute.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA5003
|
||||||
|
- SA5003
|
||||||
|
# "for { select { ..." with an empty default branch spins.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA5004
|
||||||
|
- SA5004
|
||||||
|
# The finalizer references the finalized object, preventing garbage collection.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA5005
|
||||||
|
- SA5005
|
||||||
|
# Infinite recursive call.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA5007
|
||||||
|
- SA5007
|
||||||
|
# Invalid struct tag.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA5008
|
||||||
|
- SA5008
|
||||||
|
# Invalid Printf call.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA5009
|
||||||
|
- SA5009
|
||||||
|
# Impossible type assertion.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA5010
|
||||||
|
- SA5010
|
||||||
|
# Possible nil pointer dereference.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA5011
|
||||||
|
- SA5011
|
||||||
|
# Passing odd-sized slice to function expecting even size.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA5012
|
||||||
|
- SA5012
|
||||||
|
# Using 'regexp.Match' or related in a loop, should use 'regexp.Compile'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA6000
|
||||||
|
- SA6000
|
||||||
|
# Missing an optimization opportunity when indexing maps by byte slices.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA6001
|
||||||
|
- SA6001
|
||||||
|
# Storing non-pointer values in 'sync.Pool' allocates memory.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA6002
|
||||||
|
- SA6002
|
||||||
|
# Converting a string to a slice of runes before ranging over it.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA6003
|
||||||
|
- SA6003
|
||||||
|
# Inefficient string comparison with 'strings.ToLower' or 'strings.ToUpper'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA6005
|
||||||
|
- SA6005
|
||||||
|
# Using io.WriteString to write '[]byte'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA6006
|
||||||
|
- SA6006
|
||||||
|
# Defers in range loops may not run when you expect them to.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA9001
|
||||||
|
- SA9001
|
||||||
|
# Using a non-octal 'os.FileMode' that looks like it was meant to be in octal.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA9002
|
||||||
|
- SA9002
|
||||||
|
# Empty body in an if or else branch.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA9003
|
||||||
|
- SA9003
|
||||||
|
# Only the first constant has an explicit type.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA9004
|
||||||
|
- SA9004
|
||||||
|
# Trying to marshal a struct with no public fields nor custom marshaling.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA9005
|
||||||
|
- SA9005
|
||||||
|
# Dubious bit shifting of a fixed size integer value.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA9006
|
||||||
|
- SA9006
|
||||||
|
# Deleting a directory that shouldn't be deleted.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA9007
|
||||||
|
- SA9007
|
||||||
|
# 'else' branch of a type assertion is probably not reading the right value.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA9008
|
||||||
|
- SA9008
|
||||||
|
# Ineffectual Go compiler directive.
|
||||||
|
# https://staticcheck.dev/docs/checks/#SA9009
|
||||||
|
- SA9009
|
||||||
|
# Incorrect or missing package comment.
|
||||||
|
# https://staticcheck.dev/docs/checks/#ST1000
|
||||||
|
- ST1000
|
||||||
|
# Dot imports are discouraged.
|
||||||
|
# https://staticcheck.dev/docs/checks/#ST1001
|
||||||
|
- ST1001
|
||||||
|
# Poorly chosen identifier.
|
||||||
|
# https://staticcheck.dev/docs/checks/#ST1003
|
||||||
|
- ST1003
|
||||||
|
# Incorrectly formatted error string.
|
||||||
|
# https://staticcheck.dev/docs/checks/#ST1005
|
||||||
|
# Commenting out this bullshit does not work, looks like golangci-lint fucking broken in this particular place :(
|
||||||
|
#- ST1005
|
||||||
|
# Poorly chosen receiver name.
|
||||||
|
# https://staticcheck.dev/docs/checks/#ST1006
|
||||||
|
- ST1006
|
||||||
|
# A function's error value should be its last return value.
|
||||||
|
# https://staticcheck.dev/docs/checks/#ST1008
|
||||||
|
- ST1008
|
||||||
|
# Poorly chosen name for variable of type 'time.Duration'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#ST1011
|
||||||
|
- ST1011
|
||||||
|
# Poorly chosen name for error variable.
|
||||||
|
# https://staticcheck.dev/docs/checks/#ST1012
|
||||||
|
- ST1012
|
||||||
|
# Should use constants for HTTP error codes, not magic numbers.
|
||||||
|
# https://staticcheck.dev/docs/checks/#ST1013
|
||||||
|
- ST1013
|
||||||
|
# A switch's default case should be the first or last case.
|
||||||
|
# https://staticcheck.dev/docs/checks/#ST1015
|
||||||
|
- ST1015
|
||||||
|
# Use consistent method receiver names.
|
||||||
|
# https://staticcheck.dev/docs/checks/#ST1016
|
||||||
|
- ST1016
|
||||||
|
# Don't use Yoda conditions.
|
||||||
|
# https://staticcheck.dev/docs/checks/#ST1017
|
||||||
|
- ST1017
|
||||||
|
# Avoid zero-width and control characters in string literals.
|
||||||
|
# https://staticcheck.dev/docs/checks/#ST1018
|
||||||
|
- ST1018
|
||||||
|
# Importing the same package multiple times.
|
||||||
|
# https://staticcheck.dev/docs/checks/#ST1019
|
||||||
|
- ST1019
|
||||||
|
# The documentation of an exported function should start with the function's name.
|
||||||
|
# https://staticcheck.dev/docs/checks/#ST1020
|
||||||
|
- ST1020
|
||||||
|
# The documentation of an exported type should start with type's name.
|
||||||
|
# https://staticcheck.dev/docs/checks/#ST1021
|
||||||
|
- ST1021
|
||||||
|
# The documentation of an exported variable or constant should start with variable's name.
|
||||||
|
# https://staticcheck.dev/docs/checks/#ST1022
|
||||||
|
- ST1022
|
||||||
|
# Redundant type in variable declaration.
|
||||||
|
# https://staticcheck.dev/docs/checks/#ST1023
|
||||||
|
- ST1023
|
||||||
|
# Use plain channel send or receive instead of single-case select.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1000
|
||||||
|
- S1000
|
||||||
|
# Replace for loop with call to copy.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1001
|
||||||
|
- S1001
|
||||||
|
# Omit comparison with boolean constant.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1002
|
||||||
|
- S1002
|
||||||
|
# Replace call to 'strings.Index' with 'strings.Contains'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1003
|
||||||
|
- S1003
|
||||||
|
# Replace call to 'bytes.Compare' with 'bytes.Equal'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1004
|
||||||
|
- S1004
|
||||||
|
# Drop unnecessary use of the blank identifier.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1005
|
||||||
|
- S1005
|
||||||
|
# Use "for { ... }" for infinite loops.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1006
|
||||||
|
- S1006
|
||||||
|
# Simplify regular expression by using raw string literal.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1007
|
||||||
|
- S1007
|
||||||
|
# Simplify returning boolean expression.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1008
|
||||||
|
- S1008
|
||||||
|
# Omit redundant nil check on slices, maps, and channels.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1009
|
||||||
|
- S1009
|
||||||
|
# Omit default slice index.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1010
|
||||||
|
- S1010
|
||||||
|
# Use a single 'append' to concatenate two slices.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1011
|
||||||
|
- S1011
|
||||||
|
# Replace 'time.Now().Sub(x)' with 'time.Since(x)'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1012
|
||||||
|
- S1012
|
||||||
|
# Use a type conversion instead of manually copying struct fields.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1016
|
||||||
|
- S1016
|
||||||
|
# Replace manual trimming with 'strings.TrimPrefix'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1017
|
||||||
|
- S1017
|
||||||
|
# Use "copy" for sliding elements.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1018
|
||||||
|
- S1018
|
||||||
|
# Simplify "make" call by omitting redundant arguments.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1019
|
||||||
|
- S1019
|
||||||
|
# Omit redundant nil check in type assertion.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1020
|
||||||
|
- S1020
|
||||||
|
# Merge variable declaration and assignment.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1021
|
||||||
|
- S1021
|
||||||
|
# Omit redundant control flow.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1023
|
||||||
|
- S1023
|
||||||
|
# Replace 'x.Sub(time.Now())' with 'time.Until(x)'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1024
|
||||||
|
- S1024
|
||||||
|
# Don't use 'fmt.Sprintf("%s", x)' unnecessarily.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1025
|
||||||
|
- S1025
|
||||||
|
# Simplify error construction with 'fmt.Errorf'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1028
|
||||||
|
- S1028
|
||||||
|
# Range over the string directly.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1029
|
||||||
|
- S1029
|
||||||
|
# Use 'bytes.Buffer.String' or 'bytes.Buffer.Bytes'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1030
|
||||||
|
- S1030
|
||||||
|
# Omit redundant nil check around loop.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1031
|
||||||
|
- S1031
|
||||||
|
# Use 'sort.Ints(x)', 'sort.Float64s(x)', and 'sort.Strings(x)'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1032
|
||||||
|
- S1032
|
||||||
|
# Unnecessary guard around call to "delete".
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1033
|
||||||
|
- S1033
|
||||||
|
# Use result of type assertion to simplify cases.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1034
|
||||||
|
- S1034
|
||||||
|
# Redundant call to 'net/http.CanonicalHeaderKey' in method call on 'net/http.Header'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1035
|
||||||
|
- S1035
|
||||||
|
# Unnecessary guard around map access.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1036
|
||||||
|
- S1036
|
||||||
|
# Elaborate way of sleeping.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1037
|
||||||
|
- S1037
|
||||||
|
# Unnecessarily complex way of printing formatted string.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1038
|
||||||
|
- S1038
|
||||||
|
# Unnecessary use of 'fmt.Sprint'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1039
|
||||||
|
- S1039
|
||||||
|
# Type assertion to current type.
|
||||||
|
# https://staticcheck.dev/docs/checks/#S1040
|
||||||
|
- S1040
|
||||||
|
# Apply De Morgan's law.
|
||||||
|
# https://staticcheck.dev/docs/checks/#QF1001
|
||||||
|
- QF1001
|
||||||
|
# Convert untagged switch to tagged switch.
|
||||||
|
# https://staticcheck.dev/docs/checks/#QF1002
|
||||||
|
- QF1002
|
||||||
|
# Convert if/else-if chain to tagged switch.
|
||||||
|
# https://staticcheck.dev/docs/checks/#QF1003
|
||||||
|
- QF1003
|
||||||
|
# Use 'strings.ReplaceAll' instead of 'strings.Replace' with 'n == -1'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#QF1004
|
||||||
|
- QF1004
|
||||||
|
# Expand call to 'math.Pow'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#QF1005
|
||||||
|
- QF1005
|
||||||
|
# Lift 'if'+'break' into loop condition.
|
||||||
|
# https://staticcheck.dev/docs/checks/#QF1006
|
||||||
|
- QF1006
|
||||||
|
# Merge conditional assignment into variable declaration.
|
||||||
|
# https://staticcheck.dev/docs/checks/#QF1007
|
||||||
|
- QF1007
|
||||||
|
# Omit embedded fields from selector expression.
|
||||||
|
# https://staticcheck.dev/docs/checks/#QF1008
|
||||||
|
- QF1008
|
||||||
|
# Use 'time.Time.Equal' instead of '==' operator.
|
||||||
|
# https://staticcheck.dev/docs/checks/#QF1009
|
||||||
|
- QF1009
|
||||||
|
# Convert slice of bytes to string when printing it.
|
||||||
|
# https://staticcheck.dev/docs/checks/#QF1010
|
||||||
|
- QF1010
|
||||||
|
# Omit redundant type from variable declaration.
|
||||||
|
# https://staticcheck.dev/docs/checks/#QF1011
|
||||||
|
- QF1011
|
||||||
|
# Use 'fmt.Fprintf(x, ...)' instead of 'x.Write(fmt.Sprintf(...))'.
|
||||||
|
# https://staticcheck.dev/docs/checks/#QF1012
|
||||||
|
- QF1012
|
||||||
|
unconvert:
|
||||||
|
# Remove conversions that force intermediate rounding.
|
||||||
|
# Default: false
|
||||||
|
fast-math: true
|
||||||
|
# Be more conservative (experimental).
|
||||||
|
# Default: false
|
||||||
|
safe: true
|
||||||
|
usestdlibvars:
|
||||||
|
# Suggest the use of http.MethodXX.
|
||||||
|
# Default: true
|
||||||
|
http-method: false
|
||||||
|
# Suggest the use of http.StatusXX.
|
||||||
|
# Default: true
|
||||||
|
http-status-code: false
|
||||||
|
# Suggest the use of time.Month in time.Date.
|
||||||
|
# Default: false
|
||||||
|
time-date-month: true
|
||||||
|
# Suggest the use of time.Weekday.String().
|
||||||
|
# Default: true
|
||||||
|
time-weekday: true
|
||||||
|
# Suggest the use of time.Month.String().
|
||||||
|
# Default: false
|
||||||
|
time-month: true
|
||||||
|
# Suggest the use of time.Layout.
|
||||||
|
# Default: false
|
||||||
|
time-layout: true
|
||||||
|
# Suggest the use of crypto.Hash.String().
|
||||||
|
# Default: false
|
||||||
|
crypto-hash: true
|
||||||
|
# Suggest the use of rpc.DefaultXXPath.
|
||||||
|
# Default: false
|
||||||
|
default-rpc-path: true
|
||||||
|
# Suggest the use of sql.LevelXX.String().
|
||||||
|
# Default: false
|
||||||
|
sql-isolation-level: true
|
||||||
|
# Suggest the use of tls.SignatureScheme.String().
|
||||||
|
# Default: false
|
||||||
|
tls-signature-scheme: true
|
||||||
|
# Suggest the use of constant.Kind.String().
|
||||||
|
# Default: false
|
||||||
|
constant-kind: true
|
||||||
|
wsl_v5:
|
||||||
|
allow-first-in-block: true
|
||||||
|
allow-whole-block: true
|
||||||
|
branch-max-lines: 2
|
||||||
|
case-max-lines: 2
|
||||||
|
default: all
|
||||||
|
disable:
|
||||||
|
- if
|
||||||
|
- err
|
||||||
|
exclusions:
|
||||||
|
generated: lax
|
||||||
|
presets:
|
||||||
|
- comments
|
||||||
|
- common-false-positives
|
||||||
|
- legacy
|
||||||
|
- std-error-handling
|
||||||
|
paths:
|
||||||
|
- third_party$
|
||||||
|
- builtin$
|
||||||
|
- examples$
|
||||||
|
formatters:
|
||||||
|
enable:
|
||||||
|
- gofmt
|
||||||
|
- goimports
|
||||||
|
exclusions:
|
||||||
|
generated: lax
|
||||||
|
paths:
|
||||||
|
- third_party$
|
||||||
|
- builtin$
|
||||||
|
- examples$
|
||||||
76
Makefile
Normal file
76
Makefile
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
#!/usr/bin/env gmake -f
|
||||||
|
|
||||||
|
GOOPTS=CGO_ENABLED=0
|
||||||
|
BUILDOPTS=-ldflags="-s -w" -a -gcflags=all=-l -trimpath -buildvcs=false
|
||||||
|
MYNAME1=tg-holy-bot
|
||||||
|
BINARY1=${MYNAME1}
|
||||||
|
UNIX_BINARY1=${MYNAME1}
|
||||||
|
WINDOWS_BINARY1=${MYNAME1}.exe
|
||||||
|
RMCMD=rm -rf
|
||||||
|
|
||||||
|
# On windows binary name can depend not only on platform selected, but target too, we need no .exe suffix for linux bin.
|
||||||
|
ifeq ($(OS),Windows_NT)
|
||||||
|
ifdef GOOS
|
||||||
|
ifeq ($(GOOS),windows)
|
||||||
|
BINARY1=${WINDOWS_BINARY1}
|
||||||
|
else # not ifeq ($(GOOS),windows)
|
||||||
|
BINARY1=${MYNAME1}
|
||||||
|
endif # ifeq ($(GOOS),windows)
|
||||||
|
else # not ifdef GOOS
|
||||||
|
BINARY1=${WINDOWS_BINARY1}
|
||||||
|
endif # ifdef GOOS
|
||||||
|
ifeq ($(SHELL), sh.exe)
|
||||||
|
RMCMD=DEL /Q /F
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Set newline symbol explicitly to aboud undefined behaviour in windows.
|
||||||
|
define IFS
|
||||||
|
|
||||||
|
endef
|
||||||
|
|
||||||
|
|
||||||
|
all: clean build
|
||||||
|
|
||||||
|
|
||||||
|
build:
|
||||||
|
ifeq ($(OS),Windows_NT)
|
||||||
|
# Looks like on windows gnu make explicitly set SHELL to sh.exe, if it was not set.
|
||||||
|
ifeq ($(SHELL), sh.exe)
|
||||||
|
# # Vanilla cmd.exe / powershell.
|
||||||
|
SET "CGO_ENABLED=0"
|
||||||
|
go build ${BUILDOPTS} -o ${BINARY1} ./cmd/${MYNAME1}
|
||||||
|
else ifeq (,$(findstring(Git/usr/bin/sh.exe, $(SHELL))))
|
||||||
|
# # git-bash
|
||||||
|
CGO_ENABLED=0 go build ${BUILDOPTS} -o ${BINARY1} ./cmd/${MYNAME1}
|
||||||
|
else # not ifeq (,$(findstring(Git/usr/bin/sh.exe, $(SHELL))))
|
||||||
|
# # Some other shell.
|
||||||
|
# # TODO: handle it.
|
||||||
|
$(info "-- Dunno how to handle this shell: ${SHELL}")
|
||||||
|
endif # ifeq (,$(findstring(Git/usr/bin/sh.exe, $(SHELL))))
|
||||||
|
else # not ($(OS),Windows_NT)
|
||||||
|
CGO_ENABLED=0 go build ${BUILDOPTS} -o ${BINARY1} ./cmd/${MYNAME1}
|
||||||
|
endif # ifeq ($(OS),Windows_NT)
|
||||||
|
|
||||||
|
|
||||||
|
clean:
|
||||||
|
ifeq ($(OS),Windows_NT)
|
||||||
|
ifeq ($(SHELL),sh.exe)
|
||||||
|
# # Vanilla cmd.exe / powershell.
|
||||||
|
if exist ${WINDOWS_BINARY1} ${RMCMD} ${WINDOWS_BINARY1}
|
||||||
|
if exist ${UNIX_BINARY1} ${RMCMD} ${UNIX_BINARY1}
|
||||||
|
else # not ifeq ($(SHELL),sh.exe)
|
||||||
|
${RMCMD} ./${WINDOWS_BINARY1}
|
||||||
|
${RMCMD} ./${UNIX_BINARY1}
|
||||||
|
endif # ifeq ($(SHELL),sh.exe)
|
||||||
|
else # not ifeq ($(OS),Windows_NT)
|
||||||
|
${RMCMD} ./${UNIX_BINARY1}
|
||||||
|
endif
|
||||||
|
|
||||||
|
|
||||||
|
upgrade:
|
||||||
|
go get -u ./...
|
||||||
|
go mod tidy
|
||||||
|
go mod vendor
|
||||||
|
|
||||||
|
# vim: set ft=make noet ai ts=4 sw=4 sts=4:
|
||||||
55
README.md
Normal file
55
README.md
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
## tg-holy-bot
|
||||||
|
|
||||||
|
Телеграммный бот для канала с праздниками. Раз в сутки забирает список праздников с к сайта celebratoday.com и
|
||||||
|
показывает его на канальчике в телеграме.
|
||||||
|
|
||||||
|
## Как это собрать?
|
||||||
|
|
||||||
|
Понадобится компилятор go версии 1.25.3 или более новый.
|
||||||
|
|
||||||
|
Сам процесс сборки простой, достаточно запустить make или gmake, в зависимости от платформы:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
и через некоторое время мы получаем бинарник **tg-holy-bot**. Это и есть наше приложение.
|
||||||
|
|
||||||
|
### Как это запустить?
|
||||||
|
|
||||||
|
Приложение тестировалось на alpine linux x86_64 3.16, но должно работать на любом (более свежем) linux-е.
|
||||||
|
|
||||||
|
Предполагается, что конфиг написан в формате [human json](https://hjson.github.io), который допускает коментарии и
|
||||||
|
некоторые другие вольности в форматировании json-а. Конфиг ищется последовательно по следующим путям по принципу "кто
|
||||||
|
первый того и тапки":
|
||||||
|
|
||||||
|
```
|
||||||
|
~/.tg-holy-bot.json
|
||||||
|
/etc/tg-holy-bot.json
|
||||||
|
data/config.json
|
||||||
|
```
|
||||||
|
|
||||||
|
В каталоге contrib находится скрипт для alpine linux, системы инициализации openrc. Этот скрипт достаточно положить в
|
||||||
|
**/etc/init.d/tg-holy-bot**. После чего скопировать бинарник в каталог **/var/lib/tg-holy-bot**, положить
|
||||||
|
конфиг в одну из дефолтных локаций (тестировалась **/var/lib/tg-holy-bot/data/config.json**) и после этого сервис
|
||||||
|
запускается командой:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
service tg-holy-bot start
|
||||||
|
```
|
||||||
|
|
||||||
|
В автозагрузку его можно прописать через команду:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rc-update add tg-holy-bot default
|
||||||
|
```
|
||||||
|
|
||||||
|
### Nota Bene
|
||||||
|
|
||||||
|
Go не поддерживает системный вызов fork() из-за чего демонизация программ на гошке средствами самой гошки - это в
|
||||||
|
некотором роде проблема. Решается она с помощью утилиты [daemonize](https://github.com/bmc/daemonize).
|
||||||
|
|
||||||
|
Соответствено, если для запуска этой программы предполагается использовать возможности демонизации, то можно
|
||||||
|
воспользоваться этой программой.
|
||||||
|
|
||||||
|
В alpine linux есть средства openrc для супервайзинга foreground-сервисов.
|
||||||
71
cmd/tg-holy-bot/main.go
Normal file
71
cmd/tg-holy-bot/main.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
// Package main implements entry point for this telegram bot service.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
"tg-holy-bot/internal/holy"
|
||||||
|
"tg-holy-bot/internal/log"
|
||||||
|
|
||||||
|
"github.com/NicoNex/echotron/v3"
|
||||||
|
"github.com/robfig/cron/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
if err = holy.ReadConfig(); err != nil {
|
||||||
|
log.Fatalf("%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = log.Init(holy.Config.Loglevel, holy.Config.Log); err != nil {
|
||||||
|
log.Fatalf("%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Starting bot")
|
||||||
|
|
||||||
|
// Самое время поставить трапы на сигналы.
|
||||||
|
signal.Notify(holy.SigChan,
|
||||||
|
syscall.SIGINT,
|
||||||
|
syscall.SIGTERM,
|
||||||
|
syscall.SIGQUIT)
|
||||||
|
|
||||||
|
// Запустим обработчик сигналов.
|
||||||
|
log.Info("Install signal handler")
|
||||||
|
|
||||||
|
go holy.SigHandler()
|
||||||
|
|
||||||
|
// Инициализируем бота.
|
||||||
|
log.Info("Initialize bot api client")
|
||||||
|
|
||||||
|
holy.Bot = echotron.NewAPI(holy.Config.Token)
|
||||||
|
|
||||||
|
log.Info("Create cron")
|
||||||
|
|
||||||
|
c := cron.New()
|
||||||
|
|
||||||
|
log.Infof("Add cron task %s to periodically run holiday client", holy.Config.When)
|
||||||
|
|
||||||
|
if _, err := c.AddFunc(holy.Config.When, holy.CronTask); err != nil {
|
||||||
|
log.Fatalf("Unable to add task to cron: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Run cron")
|
||||||
|
|
||||||
|
c.Start()
|
||||||
|
|
||||||
|
// Залочимся до момента завершения программы.
|
||||||
|
<-holy.Run
|
||||||
|
|
||||||
|
log.Infof("Stop cron")
|
||||||
|
|
||||||
|
// Jobs usually runs in a fraction of second, don't bother waiting for their completion.
|
||||||
|
_ = c.Stop()
|
||||||
|
|
||||||
|
log.Infof("Shutdown bot")
|
||||||
|
log.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
21
contrib/tg-holy-bot.openrc
Normal file
21
contrib/tg-holy-bot.openrc
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/sbin/openrc-run
|
||||||
|
|
||||||
|
name="$SVCNAME"
|
||||||
|
command="/var/lib/$SVCNAME/$SVCNAME"
|
||||||
|
supervisor="supervise-daemon"
|
||||||
|
pidfile="/run/$SVCNAME/$SVCNAME.pid"
|
||||||
|
command_user=nobody:nogroup
|
||||||
|
|
||||||
|
depend() {
|
||||||
|
need net
|
||||||
|
use dns
|
||||||
|
}
|
||||||
|
|
||||||
|
start_pre() {
|
||||||
|
mkdir -p /var/log/$SVCNAME
|
||||||
|
chown -R nobody:nogroup /var/log/$SVCNAME
|
||||||
|
mkdir -p /run/$SVCNAME
|
||||||
|
chown -R nobody:nogroup /run/$SVCNAME
|
||||||
|
}
|
||||||
|
|
||||||
|
# vim: set ft=sh noet ai ts=4 sw=4 sts=4:
|
||||||
51
data/config.json
Normal file
51
data/config.json
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
/* Put here bot api token that is generated by @bot_father. */
|
||||||
|
"token": "something:something",
|
||||||
|
|
||||||
|
/* Put here valid channel id. */
|
||||||
|
"channel_id": -12345678901234,
|
||||||
|
|
||||||
|
/* See https://pkg.go.dev/github.com/robfig/cron/v3#section-documentation for format of crontabs. */
|
||||||
|
"when": "CRON_TZ=Europe/Moscow 01 03 * * *",
|
||||||
|
|
||||||
|
/* There are error, warn, info and debug log levels with different vebosity. Default is info. */
|
||||||
|
"loglevel": "info",
|
||||||
|
|
||||||
|
/* Log file location. If omited or empty stderr is used. */
|
||||||
|
"log": "/var/log/tg-holy-bot/tg-holy-bot.log",
|
||||||
|
|
||||||
|
/* List of user agents. */
|
||||||
|
"user_agents": [
|
||||||
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 12_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.81 Safari/537.36 OPR/83.0.4254.27",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.81 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Vivaldi/3.5",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36 Edg/88.0.705.81",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.2 Safari/605.1.15",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36 Edg/95.0.1020.44",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 Edg/96.0.1054.62",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 OPR/82.0.4227.50",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36 Edg/97.0.1072.55",
|
||||||
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 12_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.81 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.81 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.81 Safari/537.36 Edg/97.0.1072.69",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.81 Safari/537.36 Vivaldi/4.3",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.81 YaBrowser/22.1.0 Yowser/2.5 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101 Firefox/91.0",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:95.0) Gecko/20100101 Firefox/95.0",
|
||||||
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Safari/605.1.15",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:96.0) Gecko/20100101 Firefox/96.0",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:97.0) Gecko/20100101 Firefox/97.0",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Vivaldi/3.5",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; WOW64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.81 Safari/537.36 OPR/83.0.4254.27",
|
||||||
|
"Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0",
|
||||||
|
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.81 Safari/537.36 OPR/83.0.4254.27",
|
||||||
|
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.81 Safari/537.36 Vivaldi/4.3",
|
||||||
|
"Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:91.0) Gecko/20100101 Firefox/91.0",
|
||||||
|
"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:97.0) Gecko/20100101 Firefox/97.0",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36"
|
||||||
|
]
|
||||||
|
}
|
||||||
13
go.mod
Normal file
13
go.mod
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
module tg-holy-bot
|
||||||
|
|
||||||
|
go 1.25.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/NicoNex/echotron/v3 v3.45.0
|
||||||
|
github.com/carlmjohnson/requests v0.25.1
|
||||||
|
github.com/hjson/hjson-go v3.3.0+incompatible
|
||||||
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
|
golang.org/x/net v0.51.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require golang.org/x/time v0.5.0 // indirect
|
||||||
12
go.sum
Normal file
12
go.sum
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
github.com/NicoNex/echotron/v3 v3.45.0 h1:hORujGwc6X2yaiHZIt8/rYZR48L524YtdiHxpqBwlko=
|
||||||
|
github.com/NicoNex/echotron/v3 v3.45.0/go.mod h1:7LvjveJmezuUOeaoA3nzQduNlSPQYfq219Z+baKY04Q=
|
||||||
|
github.com/carlmjohnson/requests v0.25.1 h1:17zNRLecxtAjhtdEIV+F+wrYfe+AGZUjWJtpndcOUYA=
|
||||||
|
github.com/carlmjohnson/requests v0.25.1/go.mod h1:z3UEf8IE4sZxZ78spW6/tLdqBkfCu1Fn4RaYMnZ8SRM=
|
||||||
|
github.com/hjson/hjson-go v3.3.0+incompatible h1:Rqr+Ya+0aCJMjaE4s8E9YKvuJLuLVpEvz4ONum52vnI=
|
||||||
|
github.com/hjson/hjson-go v3.3.0+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio=
|
||||||
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
|
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||||
|
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
|
||||||
|
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||||
|
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
110
internal/holy/celebratoday.go
Normal file
110
internal/holy/celebratoday.go
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
// Package holy implements main business logic of bot service.
|
||||||
|
package holy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/carlmjohnson/requests"
|
||||||
|
"golang.org/x/net/html"
|
||||||
|
)
|
||||||
|
|
||||||
|
// APIClient клиент сервиса https://celebratoday.com/.
|
||||||
|
func APIClient() (string, error) {
|
||||||
|
var (
|
||||||
|
ctx = context.Background()
|
||||||
|
url = "https://celebratoday.com/ru/today"
|
||||||
|
respBody string
|
||||||
|
)
|
||||||
|
|
||||||
|
// Формальная попытка притвориться браузером.
|
||||||
|
_, _, day := time.Now().Date()
|
||||||
|
userAgent := Config.UserAgents[day]
|
||||||
|
|
||||||
|
if err := requests.URL(url).UserAgent(userAgent).
|
||||||
|
Header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8").
|
||||||
|
Header("Accept-Language", "en-US,en;q=0.5").
|
||||||
|
Header("User-Agent", "ru-RU").
|
||||||
|
Header("Accept-Charset", "utf-8").ToString(&respBody).Fetch(ctx); err != nil {
|
||||||
|
return "", fmt.Errorf("Unable to GET %s: %w", url, err) //nolint: staticcheck // Looks like golangci-lint ignoring settings about fucking ST1005 check. We want readable errors despite what wants author of linter or assholes from go dev team.
|
||||||
|
}
|
||||||
|
|
||||||
|
holidays, err := parser(respBody)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Unable to parse response from celebratoday.com: %w", err) //nolint: staticcheck // Looks like golangci-lint ignoring settings about fucking ST1005 check. We want readable errors despite what wants author of linter or assholes from go dev team.
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(holidays) == 0 {
|
||||||
|
return "", errors.New("Unable to parse response from celebratoday.com: no holidays found") //nolint: staticcheck // Looks like golangci-lint ignoring settings about fucking ST1005 check. We want readable errors despite what wants author of linter or assholes from go dev team.
|
||||||
|
}
|
||||||
|
|
||||||
|
slices.Sort(holidays)
|
||||||
|
|
||||||
|
holidays = slices.Compact(holidays)
|
||||||
|
|
||||||
|
return "* " + strings.Join(holidays, "\n* "), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parser разбирает буфер с html-ем, выискивая праздники.
|
||||||
|
func parser(data string) ([]string, error) {
|
||||||
|
var (
|
||||||
|
holidays = make([]string, 0, 1)
|
||||||
|
err error
|
||||||
|
s = make([]Holy, 0, 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
doc, err := html.Parse(strings.NewReader(data))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return holidays, err
|
||||||
|
}
|
||||||
|
|
||||||
|
traverse(doc, &s)
|
||||||
|
|
||||||
|
// А теперь пробежимся по полученной структуре и вытянем из неё все строки с праздниками.
|
||||||
|
for _, ass := range s {
|
||||||
|
for _, entity := range ass.MainEntity {
|
||||||
|
for _, sugestedAnswer := range entity.SuggestedAnswer {
|
||||||
|
holidays = append(holidays, sugestedAnswer.Text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return holidays, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// traverse рекурсивная функция, ищущая <script type="application/ld+json"> в структуре документа. Там внутри будет json
|
||||||
|
// со списком праздников и это и есть цель нашего поиска.
|
||||||
|
func traverse(n *html.Node, h *[]Holy) {
|
||||||
|
// Если найденный элемент в поле Data содержит script (то есть это тэг <script>).
|
||||||
|
if n.Type == html.ElementNode && n.Data == "script" {
|
||||||
|
// Ищем атрибут type со значением application/ld+json.
|
||||||
|
for _, attr := range n.Attr {
|
||||||
|
if attr.Key == "type" && attr.Val == "application/ld+json" {
|
||||||
|
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||||
|
if c.Type == html.TextNode {
|
||||||
|
// Добавлять рановато, надо вначале попробовать распарсить этот json
|
||||||
|
// используя целевую структуру.
|
||||||
|
var holiday Holy
|
||||||
|
|
||||||
|
if err := json.Unmarshal([]byte(c.Data), &holiday); err == nil {
|
||||||
|
*h = append(*h, holiday)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||||
|
traverse(c, h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
30
internal/holy/configLocations.go
Normal file
30
internal/holy/configLocations.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package holy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// defaultConfigFileLocations выдаёт варианты путей, по которым может находиться конфиг-файл.
|
||||||
|
func defaultConfigFileLocations() ([]string, error) {
|
||||||
|
var locations []string
|
||||||
|
|
||||||
|
executablePath, err := os.Executable()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return locations, fmt.Errorf("Unable to get my executable path: %w", err) //nolint: staticcheck // Looks like golangci-lint ignoring settings about fucking ST1005 check. We want readable errors despite what wants author of linter or assholes from go dev team.
|
||||||
|
}
|
||||||
|
|
||||||
|
configJSONPath := filepath.Dir(executablePath) + "/data/config.json"
|
||||||
|
|
||||||
|
locations = []string{
|
||||||
|
"~/.tg-holy-bot.json",
|
||||||
|
"/etc/tg-holy-bot.json",
|
||||||
|
configJSONPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
return locations, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
24
internal/holy/cronTask.go
Normal file
24
internal/holy/cronTask.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package holy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"tg-holy-bot/internal/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CronTask sends generated hiliday list to the channel.
|
||||||
|
func CronTask() {
|
||||||
|
log.Info("Cron Task triggered")
|
||||||
|
|
||||||
|
phrase, err := APIClient()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Unable to get holidays: %s", err)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = Bot.SendMessage(phrase, Config.ChannelID, nil); err != nil {
|
||||||
|
log.Errorf("Unable to send message to channel: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
36
internal/holy/globals.go
Normal file
36
internal/holy/globals.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package holy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/NicoNex/echotron/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Bot структура-объект с тележным ботом.
|
||||||
|
Bot echotron.API
|
||||||
|
|
||||||
|
// ConfigFileSize максимальная длина конфиг-файла, предполагается что 64кб хватит всем.
|
||||||
|
ConfigFileSize int64 = 65535
|
||||||
|
|
||||||
|
// Loglevel уровень логгирования, по-умолчанию, info.
|
||||||
|
Loglevel = "info"
|
||||||
|
|
||||||
|
// NetworkTimeout таймаут сетевого соединения.
|
||||||
|
NetworkTimeout = 10
|
||||||
|
|
||||||
|
// Config структурка, содержащая данные конфига.
|
||||||
|
Config *MyConfig
|
||||||
|
|
||||||
|
// Shutdown ставится в true, если мы получили сигнал на выключение.
|
||||||
|
Shutdown = false
|
||||||
|
|
||||||
|
// SigChan канал, в который приходят уведомления для хэндлера сигналов от траппера сигналов.
|
||||||
|
SigChan = make(chan os.Signal, 1)
|
||||||
|
|
||||||
|
// Run канал, чтобы залочить исполнение основной задачи, в него приходит значение в момент его закрытия и по задумке
|
||||||
|
// это событие должно разлочить main(), после чего программа завершится.
|
||||||
|
Run = make(chan bool, 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
116
internal/holy/readConfig.go
Normal file
116
internal/holy/readConfig.go
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
package holy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"tg-holy-bot/internal/log"
|
||||||
|
|
||||||
|
"github.com/hjson/hjson-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReadConfig читает и валидирует конфиг, а также выставляет некоторые default-ы, если значений для параметров в конфиге
|
||||||
|
// нет.
|
||||||
|
func ReadConfig() error {
|
||||||
|
locations, err := defaultConfigFileLocations()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, location := range locations {
|
||||||
|
fileInfo, err := os.Stat(location)
|
||||||
|
|
||||||
|
// Предполагаем, что файла либо нет, либо мы не можем его прочитать, второе надо бы логгировать, но пока забьём.
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Конфиг-файл длинноват для конфига, попробуем следующего кандидата.
|
||||||
|
if fileInfo.Size() > ConfigFileSize {
|
||||||
|
log.Warnf("Config file %s is too long for config, skipping", location)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := os.ReadFile(location)
|
||||||
|
|
||||||
|
// Не удалось прочитать, попробуем следующего кандидата.
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Skip reading config file %s: %s", location, err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Исходя из документации, hjson какбы умеет парсить "кривой" json, но парсит его в map-ку.
|
||||||
|
// Интереснее на выходе получить структурку: то есть мы вначале конфиг преобразуем в map-ку, затем эту map-ку
|
||||||
|
// сериализуем в json, а потом json преврщааем в стркутурку. Не очень эффективно, но он и не часто требуется.
|
||||||
|
var (
|
||||||
|
sampleConfig *MyConfig
|
||||||
|
tmp map[string]any
|
||||||
|
)
|
||||||
|
|
||||||
|
err = hjson.Unmarshal(buf, &tmp)
|
||||||
|
|
||||||
|
// Не удалось распарсить - попробуем следующего кандидата.
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Skip parsing config file %s: %s", location, err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpjson, err := json.Marshal(tmp)
|
||||||
|
|
||||||
|
// Не удалось преобразовать map-ку в json.
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Skip parsing config file %s: %s", location, err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(tmpjson, &sampleConfig); err != nil {
|
||||||
|
log.Warnf("Skip parsing config file %s: %s", location, err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if sampleConfig.Token == "" {
|
||||||
|
return errors.New("Config option token is empty. Refusing to start") //nolint: staticcheck // Looks like golangci-lint ignoring settings about fucking ST1005 check. We want readable errors despite what wants author of linter or assholes from go dev team.
|
||||||
|
}
|
||||||
|
|
||||||
|
if sampleConfig.ChannelID == 0 {
|
||||||
|
return errors.New("Config option channel_id is empty. Refusing to start") //nolint: staticcheck // Looks like golangci-lint ignoring settings about fucking ST1005 check. We want readable errors despite what wants author of linter or assholes from go dev team.
|
||||||
|
}
|
||||||
|
|
||||||
|
if sampleConfig.Loglevel == "" {
|
||||||
|
sampleConfig.Loglevel = Loglevel
|
||||||
|
|
||||||
|
log.Warnf("Config option loglevel not set, fallback to %s", sampleConfig.Loglevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sampleConfig.Log == "" {
|
||||||
|
log.Warn("Config option log is not set, fallback to stderr")
|
||||||
|
}
|
||||||
|
|
||||||
|
if sampleConfig.When == "" {
|
||||||
|
sampleConfig.When = "CRON_TZ=Europe/Moscow 01 00 * * *"
|
||||||
|
|
||||||
|
log.Warnf("Config option when is not set, fallback to %s", sampleConfig.When)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(sampleConfig.UserAgents) != 31 {
|
||||||
|
return errors.New("Amount of user_agent strings in config must be exactly 31! Refusing to start") //nolint: staticcheck // Looks like golangci-lint ignoring settings about fucking ST1005 check. We want readable errors despite what wants author of linter or assholes from go dev team.
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Using %s as config file", location)
|
||||||
|
|
||||||
|
Config = sampleConfig
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.New("Config was not loaded! Refusing to start") //nolint: staticcheck // Looks like golangci-lint ignoring settings about fucking ST1005 check. We want readable errors despite what wants author of linter or assholes from go dev team.
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
32
internal/holy/sigHandler.go
Normal file
32
internal/holy/sigHandler.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package holy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"tg-holy-bot/internal/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SigHandler хэндлер сигналов - сваливает из приложения.
|
||||||
|
func SigHandler() {
|
||||||
|
for {
|
||||||
|
var s = <-SigChan
|
||||||
|
switch s {
|
||||||
|
case syscall.SIGINT:
|
||||||
|
log.Info("Got SIGINT, quitting")
|
||||||
|
case syscall.SIGTERM:
|
||||||
|
log.Info("Got SIGTERM, quitting")
|
||||||
|
case syscall.SIGQUIT:
|
||||||
|
log.Info("Got SIGQUIT, quitting")
|
||||||
|
|
||||||
|
// Заходим на новую итерацию, если у нас "неинтересный" сигнал.
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
Shutdown = true
|
||||||
|
|
||||||
|
close(Run)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
37
internal/holy/types.go
Normal file
37
internal/holy/types.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package holy
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Holy структура, описывающая искомый json-чик.
|
||||||
|
Holy struct {
|
||||||
|
AtContext string `json:"@context"`
|
||||||
|
AtType string `json:"@type"`
|
||||||
|
MainEntity []struct {
|
||||||
|
AtType string `json:"@type"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
AcceptedAnswer struct {
|
||||||
|
AtType string `json:"@type"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
UpVoteCount int `json:"upvoteCount"`
|
||||||
|
URL string `json:"url,omitempty"`
|
||||||
|
} `json:"acceptedAnswer"`
|
||||||
|
SuggestedAnswer []struct {
|
||||||
|
AtType string `json:"@type"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
UpVoteCount int `json:"upvoteCount"`
|
||||||
|
URL string `json:"url,omitempty"`
|
||||||
|
} `json:"suggestedAnswer"`
|
||||||
|
} `json:"mainEntity"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MyConfig структурка, описывающая конфиг.
|
||||||
|
MyConfig struct {
|
||||||
|
Token string `json:"token,omitempty"`
|
||||||
|
ChannelID int64 `json:"channel_id,omitempty"`
|
||||||
|
When string `json:"when,omitempty"`
|
||||||
|
Loglevel string `json:"loglevel,omitempty"`
|
||||||
|
Log string `json:"log,omitempty"`
|
||||||
|
UserAgents []string `json:"user_agents,omitempty"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
148
internal/log/log.go
Normal file
148
internal/log/log.go
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
// Package log implements logging facility as golang slog module wrapper.
|
||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Log contains *[os.File] pointing to log file descriptor.
|
||||||
|
Log *os.File
|
||||||
|
)
|
||||||
|
|
||||||
|
// Init setup logger stuff.
|
||||||
|
// level can be error, warn, info, debug if something other supplied info level selected.
|
||||||
|
func Init(level, filename string) error {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
loglevel slog.Level
|
||||||
|
)
|
||||||
|
|
||||||
|
if filename != "" {
|
||||||
|
Log, err = os.OpenFile(filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Unable to open log file: %w", err) //nolint: staticcheck // Looks like golangci-lint ignoring settings about fucking ST1005 check. We want readable errors despite what wants author of linter or assholes from go dev team.
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log = os.Stderr
|
||||||
|
}
|
||||||
|
|
||||||
|
// no panic, no trace.
|
||||||
|
switch level {
|
||||||
|
case "error":
|
||||||
|
loglevel = slog.LevelError
|
||||||
|
|
||||||
|
case "warn":
|
||||||
|
loglevel = slog.LevelWarn
|
||||||
|
|
||||||
|
case "info":
|
||||||
|
loglevel = slog.LevelInfo
|
||||||
|
|
||||||
|
case "debug":
|
||||||
|
loglevel = slog.LevelDebug
|
||||||
|
|
||||||
|
default:
|
||||||
|
loglevel = slog.LevelInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := &slog.HandlerOptions{
|
||||||
|
// Use the ReplaceAttr function on the handler options
|
||||||
|
// to be able to replace any single attribute in the log output.
|
||||||
|
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { //nolint: revive
|
||||||
|
// Check that we are handling the time key.
|
||||||
|
if a.Key != slog.TimeKey {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
t := a.Value.Time()
|
||||||
|
|
||||||
|
// change the value from a time.Time to a String
|
||||||
|
// where the string has the correct time format.
|
||||||
|
a.Value = slog.StringValue(t.Format(time.DateTime))
|
||||||
|
|
||||||
|
return a
|
||||||
|
},
|
||||||
|
|
||||||
|
Level: loglevel,
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.SetDefault(
|
||||||
|
slog.New(
|
||||||
|
slog.NewTextHandler(
|
||||||
|
Log,
|
||||||
|
opts,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error passes message directly to [slog.Error()].
|
||||||
|
func Error(message string) {
|
||||||
|
slog.Error(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf logs message on error log level and allows to supply arguments in printf() style.
|
||||||
|
func Errorf(format string, a ...any) {
|
||||||
|
slog.Error(fmt.Sprintf(format, a...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn passes message directly to [slog.Warn()].
|
||||||
|
func Warn(message string) {
|
||||||
|
slog.Warn(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warnf logs message on warn log level and allows to supply arguments in printf() style.
|
||||||
|
func Warnf(format string, a ...any) {
|
||||||
|
slog.Warn(fmt.Sprintf(format, a...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info passes message directly to [slog.Info()].
|
||||||
|
func Info(message string) {
|
||||||
|
slog.Info(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infof logs message on info log level and allows to supply arguments in printf() style.
|
||||||
|
func Infof(format string, a ...any) {
|
||||||
|
slog.Info(fmt.Sprintf(format, a...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug passes message directly to [slog.Debug()].
|
||||||
|
func Debug(message string) {
|
||||||
|
slog.Debug(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debugf logs message on debug log level and allows to supply arguments in printf() style.
|
||||||
|
func Debugf(format string, a ...any) {
|
||||||
|
slog.Debug(fmt.Sprintf(format, a...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatal passes message directly to [slog.Error()] and quits with status code 1.
|
||||||
|
func Fatal(message string) {
|
||||||
|
slog.Error(message)
|
||||||
|
Close()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatalf logs message on error log level and allows to supply arguments in printf() style. After that it quits with
|
||||||
|
// status code 1.
|
||||||
|
func Fatalf(format string, a ...any) {
|
||||||
|
slog.Error(fmt.Sprintf(format, a...))
|
||||||
|
Close()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes log file descriptor.
|
||||||
|
func Close() {
|
||||||
|
if err := Log.Close(); err != nil {
|
||||||
|
msg := fmt.Sprintln("Unable to close log file: ", err)
|
||||||
|
slog.Error(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
165
vendor/github.com/NicoNex/echotron/v3/COPYING.LESSER
generated
vendored
Normal file
165
vendor/github.com/NicoNex/echotron/v3/COPYING.LESSER
generated
vendored
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
GNU LESSER GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
|
||||||
|
This version of the GNU Lesser General Public License incorporates
|
||||||
|
the terms and conditions of version 3 of the GNU General Public
|
||||||
|
License, supplemented by the additional permissions listed below.
|
||||||
|
|
||||||
|
0. Additional Definitions.
|
||||||
|
|
||||||
|
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||||
|
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||||
|
General Public License.
|
||||||
|
|
||||||
|
"The Library" refers to a covered work governed by this License,
|
||||||
|
other than an Application or a Combined Work as defined below.
|
||||||
|
|
||||||
|
An "Application" is any work that makes use of an interface provided
|
||||||
|
by the Library, but which is not otherwise based on the Library.
|
||||||
|
Defining a subclass of a class defined by the Library is deemed a mode
|
||||||
|
of using an interface provided by the Library.
|
||||||
|
|
||||||
|
A "Combined Work" is a work produced by combining or linking an
|
||||||
|
Application with the Library. The particular version of the Library
|
||||||
|
with which the Combined Work was made is also called the "Linked
|
||||||
|
Version".
|
||||||
|
|
||||||
|
The "Minimal Corresponding Source" for a Combined Work means the
|
||||||
|
Corresponding Source for the Combined Work, excluding any source code
|
||||||
|
for portions of the Combined Work that, considered in isolation, are
|
||||||
|
based on the Application, and not on the Linked Version.
|
||||||
|
|
||||||
|
The "Corresponding Application Code" for a Combined Work means the
|
||||||
|
object code and/or source code for the Application, including any data
|
||||||
|
and utility programs needed for reproducing the Combined Work from the
|
||||||
|
Application, but excluding the System Libraries of the Combined Work.
|
||||||
|
|
||||||
|
1. Exception to Section 3 of the GNU GPL.
|
||||||
|
|
||||||
|
You may convey a covered work under sections 3 and 4 of this License
|
||||||
|
without being bound by section 3 of the GNU GPL.
|
||||||
|
|
||||||
|
2. Conveying Modified Versions.
|
||||||
|
|
||||||
|
If you modify a copy of the Library, and, in your modifications, a
|
||||||
|
facility refers to a function or data to be supplied by an Application
|
||||||
|
that uses the facility (other than as an argument passed when the
|
||||||
|
facility is invoked), then you may convey a copy of the modified
|
||||||
|
version:
|
||||||
|
|
||||||
|
a) under this License, provided that you make a good faith effort to
|
||||||
|
ensure that, in the event an Application does not supply the
|
||||||
|
function or data, the facility still operates, and performs
|
||||||
|
whatever part of its purpose remains meaningful, or
|
||||||
|
|
||||||
|
b) under the GNU GPL, with none of the additional permissions of
|
||||||
|
this License applicable to that copy.
|
||||||
|
|
||||||
|
3. Object Code Incorporating Material from Library Header Files.
|
||||||
|
|
||||||
|
The object code form of an Application may incorporate material from
|
||||||
|
a header file that is part of the Library. You may convey such object
|
||||||
|
code under terms of your choice, provided that, if the incorporated
|
||||||
|
material is not limited to numerical parameters, data structure
|
||||||
|
layouts and accessors, or small macros, inline functions and templates
|
||||||
|
(ten or fewer lines in length), you do both of the following:
|
||||||
|
|
||||||
|
a) Give prominent notice with each copy of the object code that the
|
||||||
|
Library is used in it and that the Library and its use are
|
||||||
|
covered by this License.
|
||||||
|
|
||||||
|
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||||
|
document.
|
||||||
|
|
||||||
|
4. Combined Works.
|
||||||
|
|
||||||
|
You may convey a Combined Work under terms of your choice that,
|
||||||
|
taken together, effectively do not restrict modification of the
|
||||||
|
portions of the Library contained in the Combined Work and reverse
|
||||||
|
engineering for debugging such modifications, if you also do each of
|
||||||
|
the following:
|
||||||
|
|
||||||
|
a) Give prominent notice with each copy of the Combined Work that
|
||||||
|
the Library is used in it and that the Library and its use are
|
||||||
|
covered by this License.
|
||||||
|
|
||||||
|
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||||
|
document.
|
||||||
|
|
||||||
|
c) For a Combined Work that displays copyright notices during
|
||||||
|
execution, include the copyright notice for the Library among
|
||||||
|
these notices, as well as a reference directing the user to the
|
||||||
|
copies of the GNU GPL and this license document.
|
||||||
|
|
||||||
|
d) Do one of the following:
|
||||||
|
|
||||||
|
0) Convey the Minimal Corresponding Source under the terms of this
|
||||||
|
License, and the Corresponding Application Code in a form
|
||||||
|
suitable for, and under terms that permit, the user to
|
||||||
|
recombine or relink the Application with a modified version of
|
||||||
|
the Linked Version to produce a modified Combined Work, in the
|
||||||
|
manner specified by section 6 of the GNU GPL for conveying
|
||||||
|
Corresponding Source.
|
||||||
|
|
||||||
|
1) Use a suitable shared library mechanism for linking with the
|
||||||
|
Library. A suitable mechanism is one that (a) uses at run time
|
||||||
|
a copy of the Library already present on the user's computer
|
||||||
|
system, and (b) will operate properly with a modified version
|
||||||
|
of the Library that is interface-compatible with the Linked
|
||||||
|
Version.
|
||||||
|
|
||||||
|
e) Provide Installation Information, but only if you would otherwise
|
||||||
|
be required to provide such information under section 6 of the
|
||||||
|
GNU GPL, and only to the extent that such information is
|
||||||
|
necessary to install and execute a modified version of the
|
||||||
|
Combined Work produced by recombining or relinking the
|
||||||
|
Application with a modified version of the Linked Version. (If
|
||||||
|
you use option 4d0, the Installation Information must accompany
|
||||||
|
the Minimal Corresponding Source and Corresponding Application
|
||||||
|
Code. If you use option 4d1, you must provide the Installation
|
||||||
|
Information in the manner specified by section 6 of the GNU GPL
|
||||||
|
for conveying Corresponding Source.)
|
||||||
|
|
||||||
|
5. Combined Libraries.
|
||||||
|
|
||||||
|
You may place library facilities that are a work based on the
|
||||||
|
Library side by side in a single library together with other library
|
||||||
|
facilities that are not Applications and are not covered by this
|
||||||
|
License, and convey such a combined library under terms of your
|
||||||
|
choice, if you do both of the following:
|
||||||
|
|
||||||
|
a) Accompany the combined library with a copy of the same work based
|
||||||
|
on the Library, uncombined with any other library facilities,
|
||||||
|
conveyed under the terms of this License.
|
||||||
|
|
||||||
|
b) Give prominent notice with the combined library that part of it
|
||||||
|
is a work based on the Library, and explaining where to find the
|
||||||
|
accompanying uncombined form of the same work.
|
||||||
|
|
||||||
|
6. Revised Versions of the GNU Lesser General Public License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the GNU Lesser General Public License from time to time. Such new
|
||||||
|
versions will be similar in spirit to the present version, but may
|
||||||
|
differ in detail to address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Library as you received it specifies that a certain numbered version
|
||||||
|
of the GNU Lesser General Public License "or any later version"
|
||||||
|
applies to it, you have the option of following the terms and
|
||||||
|
conditions either of that published version or of any later version
|
||||||
|
published by the Free Software Foundation. If the Library as you
|
||||||
|
received it does not specify a version number of the GNU Lesser
|
||||||
|
General Public License, you may choose any version of the GNU Lesser
|
||||||
|
General Public License ever published by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Library as you received it specifies that a proxy can decide
|
||||||
|
whether future versions of the GNU Lesser General Public License shall
|
||||||
|
apply, that proxy's public statement of acceptance of any version is
|
||||||
|
permanent authorization for you to choose that version for the
|
||||||
|
Library.
|
||||||
674
vendor/github.com/NicoNex/echotron/v3/LICENSE
generated
vendored
Normal file
674
vendor/github.com/NicoNex/echotron/v3/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,674 @@
|
|||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
the GNU General Public License is intended to guarantee your freedom to
|
||||||
|
share and change all versions of a program--to make sure it remains free
|
||||||
|
software for all its users. We, the Free Software Foundation, use the
|
||||||
|
GNU General Public License for most of our software; it applies also to
|
||||||
|
any other work released this way by its authors. You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
them if you wish), that you receive source code or can get it if you
|
||||||
|
want it, that you can change the software or use pieces of it in new
|
||||||
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to prevent others from denying you
|
||||||
|
these rights or asking you to surrender the rights. Therefore, you have
|
||||||
|
certain responsibilities if you distribute copies of the software, or if
|
||||||
|
you modify it: responsibilities to respect the freedom of others.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must pass on to the recipients the same
|
||||||
|
freedoms that you received. You must make sure that they, too, receive
|
||||||
|
or can get the source code. And you must show them these terms so they
|
||||||
|
know their rights.
|
||||||
|
|
||||||
|
Developers that use the GNU GPL protect your rights with two steps:
|
||||||
|
(1) assert copyright on the software, and (2) offer you this License
|
||||||
|
giving you legal permission to copy, distribute and/or modify it.
|
||||||
|
|
||||||
|
For the developers' and authors' protection, the GPL clearly explains
|
||||||
|
that there is no warranty for this free software. For both users' and
|
||||||
|
authors' sake, the GPL requires that modified versions be marked as
|
||||||
|
changed, so that their problems will not be attributed erroneously to
|
||||||
|
authors of previous versions.
|
||||||
|
|
||||||
|
Some devices are designed to deny users access to install or run
|
||||||
|
modified versions of the software inside them, although the manufacturer
|
||||||
|
can do so. This is fundamentally incompatible with the aim of
|
||||||
|
protecting users' freedom to change the software. The systematic
|
||||||
|
pattern of such abuse occurs in the area of products for individuals to
|
||||||
|
use, which is precisely where it is most unacceptable. Therefore, we
|
||||||
|
have designed this version of the GPL to prohibit the practice for those
|
||||||
|
products. If such problems arise substantially in other domains, we
|
||||||
|
stand ready to extend this provision to those domains in future versions
|
||||||
|
of the GPL, as needed to protect the freedom of users.
|
||||||
|
|
||||||
|
Finally, every program is threatened constantly by software patents.
|
||||||
|
States should not allow patents to restrict development and use of
|
||||||
|
software on general-purpose computers, but in those that do, we wish to
|
||||||
|
avoid the special danger that patents applied to a free program could
|
||||||
|
make it effectively proprietary. To prevent this, the GPL assures that
|
||||||
|
patents cannot be used to render the program non-free.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you
|
||||||
|
receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Use with the GNU Affero General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU Affero General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the special requirements of the GNU Affero General Public License,
|
||||||
|
section 13, concerning interaction through a network will apply to the
|
||||||
|
combination as such.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Program specifies that a certain numbered version of the GNU General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
state the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
echotron
|
||||||
|
Copyright (C) 2019 Nicolò Santamaria
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program does terminal interaction, make it output a short
|
||||||
|
notice like this when it starts in an interactive mode:
|
||||||
|
|
||||||
|
echotron Copyright (C) 2019 Nicolò Santamaria
|
||||||
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, your program's commands
|
||||||
|
might be different; for a GUI interface, you would use an "about box".
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
|
For more information on this, and how to apply and follow the GNU GPL, see
|
||||||
|
<http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
The GNU General Public License does not permit incorporating your program
|
||||||
|
into proprietary programs. If your program is a subroutine library, you
|
||||||
|
may consider it more useful to permit linking proprietary applications with
|
||||||
|
the library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License. But first, please read
|
||||||
|
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||||
339
vendor/github.com/NicoNex/echotron/v3/README.md
generated
vendored
Normal file
339
vendor/github.com/NicoNex/echotron/v3/README.md
generated
vendored
Normal file
@@ -0,0 +1,339 @@
|
|||||||
|
| <img src="assets/readme_banner.png" alt="logo" width="800"><br/><br/> [](https://golang.org/) [](https://pkg.go.dev/github.com/NicoNex/echotron/v3) [](https://goreportcard.com/report/github.com/NicoNex/echotron/v3) [](https://codecov.io/gh/NicoNex/echotron) [](https://github.com/NicoNex/echotron/blob/master/LICENSE) [](https://github.com/avelino/awesome-go) [](https://t.me/echotronnews) |
|
||||||
|
| :------: |
|
||||||
|
|
||||||
|
**Echotron** is an elegant and concurrent library for the Telegram bot API in Go.
|
||||||
|
|
||||||
|
Fetch with
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go get github.com/NicoNex/echotron/v3
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example
|
||||||
|
### Simplest implementations
|
||||||
|
#### Long polling
|
||||||
|
```golang
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/NicoNex/echotron/v3"
|
||||||
|
|
||||||
|
const token = "MY TELEGRAM TOKEN"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
api := echotron.NewAPI(token)
|
||||||
|
|
||||||
|
for u := range echotron.PollingUpdates(token) {
|
||||||
|
if u.Message.Text == "/start" {
|
||||||
|
api.SendMessage("Hello world", u.ChatID(), nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
#### Webhook
|
||||||
|
```golang
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/NicoNex/echotron/v3"
|
||||||
|
|
||||||
|
const token = "MY TELEGRAM TOKEN"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
api := echotron.NewAPI(token)
|
||||||
|
|
||||||
|
for u := range echotron.WebhookUpdates("https://example.com:443/my_token", token) {
|
||||||
|
if u.Message.Text == "/start" {
|
||||||
|
api.SendMessage("Hello world", u.ChatID(), nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
For more scalable and recommended implementations see the other examples.
|
||||||
|
|
||||||
|
### Long Polling
|
||||||
|
|
||||||
|
```golang
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/NicoNex/echotron/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Struct useful for managing internal states in your bot, but it could be of
|
||||||
|
// any type such as `type bot int64` if you only need to store the chatID.
|
||||||
|
type bot struct {
|
||||||
|
chatID int64
|
||||||
|
echotron.API
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = "MY TELEGRAM TOKEN"
|
||||||
|
|
||||||
|
// This function needs to be of type 'echotron.NewBotFn' and is called by
|
||||||
|
// the echotron dispatcher upon any new message from a chatID that has never
|
||||||
|
// interacted with the bot before.
|
||||||
|
// This means that echotron keeps one instance of the echotron.Bot implementation
|
||||||
|
// for each chat where the bot is used.
|
||||||
|
func newBot(chatID int64) echotron.Bot {
|
||||||
|
return &bot{
|
||||||
|
chatID,
|
||||||
|
echotron.NewAPI(token),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method is needed to implement the echotron.Bot interface.
|
||||||
|
func (b *bot) Update(update *echotron.Update) {
|
||||||
|
if update.Message.Text == "/start" {
|
||||||
|
b.SendMessage("Hello world", b.chatID, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// This is the entry point of echotron library.
|
||||||
|
dsp := echotron.NewDispatcher(token, newBot)
|
||||||
|
for {
|
||||||
|
log.Println(dsp.Poll())
|
||||||
|
// In case of connection issues wait 5 seconds before trying to reconnect.
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Design
|
||||||
|
|
||||||
|
**Echotron** makes a new instance of the struct bot for each open chat with a Telegram user, channel or group.
|
||||||
|
This allows to:
|
||||||
|
- safely call the `Update(*echotron.Update)` method concurrently
|
||||||
|
- give to the user a convenient way to manage the bot internal states across all the chats
|
||||||
|
- make sure that, even if one instance of the bot is deadlocked, the other ones keep running just fine, making the bot work for other users without any issues and/or slowdowns.
|
||||||
|
|
||||||
|
Please note that the the aforementioned behaviour is dictated by the `echotron.Dispatcher` object whose usage is not mandatory and for special needs can be ignored and implemented in different ways still keeping all the methods in the `echotron.API` object.
|
||||||
|
|
||||||
|
**Echotron** is designed to be as similar to the official [Telegram API](https://core.telegram.org/bots/api) as possible, but there are some things to take into account before starting to work with this library.
|
||||||
|
|
||||||
|
- The methods have the exact same name, but with a capital first letter, since in Go methods have to start with a capital letter to be exported.
|
||||||
|
_Example: `sendMessage` becomes `SendMessage`_
|
||||||
|
- The order of the parameters in some methods is different than in the official Telegram API, so refer to the [docs](https://pkg.go.dev/github.com/NicoNex/echotron/v3) for the correct one.
|
||||||
|
- The only `chat_id` (or, in this case, `chatID`) type supported is `int64`, instead of the "Integer or String" requirement of the official API. That's because numeric IDs can't change in any way, which isn't the case with text-based usernames.
|
||||||
|
- In some methods, you might find a `InputFile` type parameter. [`InputFile`](https://pkg.go.dev/github.com/NicoNex/echotron/v3#InputFile) is a struct with unexported fields, since only three combination of fields are valid, which can be obtained through the methods [`NewInputFileID`](https://pkg.go.dev/github.com/NicoNex/echotron/v3#NewInputFileID), [`NewInputFilePath`](https://pkg.go.dev/github.com/NicoNex/echotron/v3#NewInputFilePath) and [`NewInputFileBytes`](https://pkg.go.dev/github.com/NicoNex/echotron/v3#NewInputFileBytes).
|
||||||
|
- In some methods, you might find a `MessageIDOptions` type parameter. [`MessageIDOptions`](https://pkg.go.dev/github.com/NicoNex/echotron/v3#MessageIDOptions) is another struct with unexported fields, since only two combination of field are valid, which can be obtained through the methods [`NewMessageID`](https://pkg.go.dev/github.com/NicoNex/echotron/v3#NewMessageID) and [`NewInlineMessageID`](https://pkg.go.dev/github.com/NicoNex/echotron/v3#NewInlineMessageID).
|
||||||
|
- Optional parameters can be added by passing the correct struct to each method that might request optional parameters. If you don't want to pass any optional parameter, `nil` is more than enough. Refer to the [docs](https://pkg.go.dev/github.com/NicoNex/echotron/v3) to check for each method's optional parameters struct: it's the type of the `opts` parameter.
|
||||||
|
- Some parameters are hardcoded to avoid putting random stuff which isn't recognized by the Telegram API. Some notable examples are [`ParseMode`](https://github.com/NicoNex/echotron/blob/master/options.go#L21), [`ChatAction`](https://github.com/NicoNex/echotron/blob/master/options.go#L54) and [`InlineQueryType`](https://github.com/NicoNex/echotron/blob/master/inline.go#L27). For a full list of custom hardcoded parameters, refer to the [docs](https://pkg.go.dev/github.com/NicoNex/echotron/v3) for each custom type: by clicking on the type's name, you'll get the source which contains the possible values for that type.
|
||||||
|
|
||||||
|
## Additional examples
|
||||||
|
### Functional approach to state management
|
||||||
|
```golang
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/NicoNex/echotron/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Recursive type definition of the bot state function.
|
||||||
|
type stateFn func(*echotron.Update) stateFn
|
||||||
|
|
||||||
|
type bot struct {
|
||||||
|
chatID int64
|
||||||
|
state stateFn
|
||||||
|
name string
|
||||||
|
echotron.API
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = "MY TELEGRAM TOKEN"
|
||||||
|
|
||||||
|
func newBot(chatID int64) echotron.Bot {
|
||||||
|
bot := &bot{
|
||||||
|
chatID: chatID,
|
||||||
|
API: echotron.NewAPI(token),
|
||||||
|
}
|
||||||
|
// We set the default state to the bot.handleMessage method.
|
||||||
|
bot.state = bot.handleMessage
|
||||||
|
return bot
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bot) Update(update *echotron.Update) {
|
||||||
|
// Here we execute the current state and set the next one.
|
||||||
|
b.state = b.state(update)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bot) handleMessage(update *echotron.Update) stateFn {
|
||||||
|
if strings.HasPrefix(update.Message.Text, "/set_name") {
|
||||||
|
b.SendMessage("Send me my new name!", b.chatID, nil)
|
||||||
|
// Here we return b.handleName since next time we receive a message it
|
||||||
|
// will be the new name.
|
||||||
|
return b.handleName
|
||||||
|
}
|
||||||
|
return b.handleMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bot) handleName(update *echotron.Update) stateFn {
|
||||||
|
b.name = update.Message.Text
|
||||||
|
b.SendMessage(fmt.Sprintf("My new name is %q", b.name), b.chatID, nil)
|
||||||
|
// Here we return b.handleMessage since the next time we receive a message
|
||||||
|
// it will be handled in the default way.
|
||||||
|
return b.handleMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
dsp := echotron.NewDispatcher(token, newBot)
|
||||||
|
log.Println(dsp.Poll())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Self destruction for lower memory footprint
|
||||||
|
```golang
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/NicoNex/echotron/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type bot struct {
|
||||||
|
chatID int64
|
||||||
|
echotron.API
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = "MY TELEGRAM TOKEN"
|
||||||
|
|
||||||
|
var dsp *echotron.Dispatcher
|
||||||
|
|
||||||
|
func newBot(chatID int64) echotron.Bot {
|
||||||
|
bot := &bot{
|
||||||
|
chatID,
|
||||||
|
echotron.NewAPI(token),
|
||||||
|
}
|
||||||
|
go bot.selfDestruct(time.After(time.Hour))
|
||||||
|
return bot
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bot) selfDestruct(timech <-chan time.Time) {
|
||||||
|
<-timech
|
||||||
|
b.SendMessage("goodbye", b.chatID, nil)
|
||||||
|
dsp.DelSession(b.chatID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bot) Update(update *echotron.Update) {
|
||||||
|
if update.Message.Text == "/start" {
|
||||||
|
b.SendMessage("Hello world", b.chatID, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
dsp = echotron.NewDispatcher(token, newBot)
|
||||||
|
log.Println(dsp.Poll())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Webhook
|
||||||
|
|
||||||
|
```golang
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/NicoNex/echotron/v3"
|
||||||
|
|
||||||
|
type bot struct {
|
||||||
|
chatID int64
|
||||||
|
echotron.API
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = "MY TELEGRAM TOKEN"
|
||||||
|
|
||||||
|
func newBot(chatID int64) echotron.Bot {
|
||||||
|
return &bot{
|
||||||
|
chatID,
|
||||||
|
echotron.NewAPI(token),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bot) Update(update *echotron.Update) {
|
||||||
|
if update.Message.Text == "/start" {
|
||||||
|
b.SendMessage("Hello world", b.chatID, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
dsp := echotron.NewDispatcher(token, newBot)
|
||||||
|
dsp.ListenWebhook("https://example.com:443/my_bot_token")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Webhook with a custom http.Server
|
||||||
|
|
||||||
|
This is an example for a custom http.Server which handles your own specified routes
|
||||||
|
and also the webhook route which is specified by ListenWebhook.
|
||||||
|
|
||||||
|
```golang
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/NicoNex/echotron/v3"
|
||||||
|
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
type bot struct {
|
||||||
|
chatID int64
|
||||||
|
echotron.API
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = "MY TELEGRAM TOKEN"
|
||||||
|
|
||||||
|
func newBot(chatID int64) echotron.Bot {
|
||||||
|
return &bot{
|
||||||
|
chatID,
|
||||||
|
echotron.NewAPI(token),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bot) Update(update *echotron.Update) {
|
||||||
|
if update.Message.Text == "/start" {
|
||||||
|
b.SendMessage("Hello world", b.chatID, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
termChan := make(chan os.Signal, 1) // Channel for terminating the app via os.Interrupt signal
|
||||||
|
signal.Notify(termChan, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Handle user login
|
||||||
|
})
|
||||||
|
mux.HandleFunc("/logout", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Handle user logout
|
||||||
|
})
|
||||||
|
mux.HandleFunc("/about", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Tell something about your awesome telegram bot
|
||||||
|
})
|
||||||
|
|
||||||
|
// Set custom http.Server
|
||||||
|
server := &http.Server{Addr: ":8080", Handler: mux}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
<-termChan
|
||||||
|
// Perform some cleanup..
|
||||||
|
if err := server.Shutdown(context.Background()); err != nil {
|
||||||
|
log.Print(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Capture the interrupt signal for app termination handling
|
||||||
|
dsp := echotron.NewDispatcher(token, newBot)
|
||||||
|
dsp.SetHTTPServer(server)
|
||||||
|
// Start your custom http.Server with a registered /my_bot_token handler.
|
||||||
|
log.Println(dsp.ListenWebhook("https://example.com/my_bot_token"))
|
||||||
|
}
|
||||||
|
```
|
||||||
1374
vendor/github.com/NicoNex/echotron/v3/api.go
generated
vendored
Normal file
1374
vendor/github.com/NicoNex/echotron/v3/api.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
42
vendor/github.com/NicoNex/echotron/v3/apierror.go
generated
vendored
Normal file
42
vendor/github.com/NicoNex/echotron/v3/apierror.go
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Echotron
|
||||||
|
* Copyright (C) 2018 The Echotron Contributors
|
||||||
|
*
|
||||||
|
* Echotron is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Echotron is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package echotron
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// APIError represents an error returned by the Telegram API.
|
||||||
|
type APIError struct {
|
||||||
|
desc string
|
||||||
|
code int
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorCode returns the error code received from the Telegram API.
|
||||||
|
func (a *APIError) ErrorCode() int {
|
||||||
|
return a.code
|
||||||
|
}
|
||||||
|
|
||||||
|
// Description returns the error description received from the Telegram API.
|
||||||
|
func (a *APIError) Description() string {
|
||||||
|
return a.desc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error returns the error string.
|
||||||
|
func (a *APIError) Error() string {
|
||||||
|
return fmt.Sprintf("API error: %d %s", a.code, a.desc)
|
||||||
|
}
|
||||||
65
vendor/github.com/NicoNex/echotron/v3/chatadminrights.go
generated
vendored
Normal file
65
vendor/github.com/NicoNex/echotron/v3/chatadminrights.go
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* Echotron
|
||||||
|
* Copyright (C) 2022 The Echotron Contributors
|
||||||
|
*
|
||||||
|
* Echotron is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Echotron is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package echotron
|
||||||
|
|
||||||
|
// ChatAdministratorRights represents the rights of an administrator in a chat.
|
||||||
|
type ChatAdministratorRights struct {
|
||||||
|
IsAnonymous bool `json:"is_anonymous"`
|
||||||
|
CanManageChat bool `json:"can_manage_chat"`
|
||||||
|
CanDeleteMessages bool `json:"can_delete_messages"`
|
||||||
|
CanManageVideoChats bool `json:"can_manage_video_chats"`
|
||||||
|
CanRestrictMembers bool `json:"can_restrict_members"`
|
||||||
|
CanPromoteMembers bool `json:"can_promote_members"`
|
||||||
|
CanChangeInfo bool `json:"can_change_info"`
|
||||||
|
CanInviteUsers bool `json:"can_invite_users"`
|
||||||
|
CanPostStories bool `json:"can_post_stories"`
|
||||||
|
CanEditStories bool `json:"can_edit_stories"`
|
||||||
|
CanDeleteStories bool `json:"can_delete_stories"`
|
||||||
|
CanPostMessages bool `json:"can_post_messages,omitempty"`
|
||||||
|
CanEditMessages bool `json:"can_edit_messages,omitempty"`
|
||||||
|
CanPinMessages bool `json:"can_pin_messages,omitempty"`
|
||||||
|
CanManageTopics bool `json:"can_manage_topics,omitempty"`
|
||||||
|
CanManageDirectMessages bool `json:"can_manage_direct_messages,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMyDefaultAdministratorRightsOptions contains the optional parameters used by
|
||||||
|
// the SetMyDefaultAdministratorRights method.
|
||||||
|
type SetMyDefaultAdministratorRightsOptions struct {
|
||||||
|
Rights ChatAdministratorRights `query:"rights"`
|
||||||
|
ForChannels bool `query:"for_channels"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMyDefaultAdministratorRightsOptions contains the optional parameters used by
|
||||||
|
// the GetMyDefaultAdministratorRights method.
|
||||||
|
type GetMyDefaultAdministratorRightsOptions struct {
|
||||||
|
ForChannels bool `query:"for_channels"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMyDefaultAdministratorRights is used to change the default administrator rights
|
||||||
|
// requested by the bot when it's added as an administrator to groups or channels.
|
||||||
|
// These rights will be suggested to users, but they are are free to modify the list
|
||||||
|
// before adding the bot.
|
||||||
|
func (a API) SetMyDefaultAdministratorRights(opts *SetMyDefaultAdministratorRightsOptions) (res APIResponseBool, err error) {
|
||||||
|
return res, a.lclient.get(a.base, "setMyDefaultAdministratorRights", urlValues(opts), &res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMyDefaultAdministratorRights is used to get the current default administrator rights of the bot.
|
||||||
|
func (a API) GetMyDefaultAdministratorRights(opts *GetMyDefaultAdministratorRightsOptions) (res APIResponseChatAdministratorRights, err error) {
|
||||||
|
return res, a.lclient.get(a.base, "getMyDefaultAdministratorRights", urlValues(opts), &res)
|
||||||
|
}
|
||||||
207
vendor/github.com/NicoNex/echotron/v3/dispatcher.go
generated
vendored
Normal file
207
vendor/github.com/NicoNex/echotron/v3/dispatcher.go
generated
vendored
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
/*
|
||||||
|
* Echotron
|
||||||
|
* Copyright (C) 2018 The Echotron Contributors
|
||||||
|
*
|
||||||
|
* Echotron is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Echotron is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package echotron
|
||||||
|
|
||||||
|
import (
|
||||||
|
"compress/gzip"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Bot is the interface that must be implemented by your definition of
|
||||||
|
// the struct thus it represent each open session with a user on Telegram.
|
||||||
|
type Bot interface {
|
||||||
|
// Update will be called upon receiving any update from Telegram.
|
||||||
|
Update(*Update)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBotFn is called every time echotron receives an update with a chat ID never
|
||||||
|
// encountered before.
|
||||||
|
type NewBotFn func(chatId int64) Bot
|
||||||
|
|
||||||
|
// The Dispatcher passes the updates from the Telegram Bot API to the Bot instance
|
||||||
|
// associated with each chatID. When a new chat ID is found, the provided function
|
||||||
|
// of type NewBotFn will be called.
|
||||||
|
type Dispatcher struct {
|
||||||
|
api API
|
||||||
|
newBot NewBotFn
|
||||||
|
updates chan *Update
|
||||||
|
httpServer *http.Server
|
||||||
|
sessions smap[int64, Bot]
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDispatcher returns a new instance of the Dispatcher object.
|
||||||
|
// Calls the Update function of the bot associated with each chat ID.
|
||||||
|
// If a new chat ID is found, newBotFn will be called first.
|
||||||
|
func NewDispatcher(token string, newBotFn NewBotFn) *Dispatcher {
|
||||||
|
d := &Dispatcher{
|
||||||
|
api: NewAPI(token),
|
||||||
|
newBot: newBotFn,
|
||||||
|
updates: make(chan *Update),
|
||||||
|
}
|
||||||
|
go d.listen()
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
// DelSession deletes the Bot instance, seen as a session, from the
|
||||||
|
// map with all of them.
|
||||||
|
func (d *Dispatcher) DelSession(chatID int64) {
|
||||||
|
d.sessions.delete(chatID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddSession allows to arbitrarily create a new Bot instance.
|
||||||
|
func (d *Dispatcher) AddSession(chatID int64) {
|
||||||
|
d.sessions.store(chatID, d.newBot(chatID))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Poll is a wrapper function for PollOptions.
|
||||||
|
func (d *Dispatcher) Poll() error {
|
||||||
|
return d.PollOptions(true, UpdateOptions{Timeout: 120})
|
||||||
|
}
|
||||||
|
|
||||||
|
// PollOptions starts the polling loop so that the dispatcher calls the function Update
|
||||||
|
// upon receiving any update from Telegram.
|
||||||
|
func (d *Dispatcher) PollOptions(dropPendingUpdates bool, opts UpdateOptions) error {
|
||||||
|
var (
|
||||||
|
timeout = opts.Timeout
|
||||||
|
isFirstRun = true
|
||||||
|
)
|
||||||
|
|
||||||
|
// deletes webhook if present to run in long polling mode
|
||||||
|
if _, err := d.api.DeleteWebhook(dropPendingUpdates); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
if isFirstRun {
|
||||||
|
opts.Timeout = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := d.api.GetUpdates(&opts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !dropPendingUpdates || !isFirstRun {
|
||||||
|
for _, u := range response.Result {
|
||||||
|
d.updates <- u
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if l := len(response.Result); l > 0 {
|
||||||
|
opts.Offset = response.Result[l-1].ID + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if isFirstRun {
|
||||||
|
isFirstRun = false
|
||||||
|
opts.Timeout = timeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dispatcher) instance(chatID int64) Bot {
|
||||||
|
bot, ok := d.sessions.load(chatID)
|
||||||
|
if !ok {
|
||||||
|
bot = d.newBot(chatID)
|
||||||
|
d.sessions.store(chatID, bot)
|
||||||
|
}
|
||||||
|
return bot
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dispatcher) listen() {
|
||||||
|
for update := range d.updates {
|
||||||
|
bot := d.instance(update.ChatID())
|
||||||
|
go bot.Update(update)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenWebhook is a wrapper function for ListenWebhookOptions.
|
||||||
|
func (d *Dispatcher) ListenWebhook(webhookURL string) error {
|
||||||
|
return d.ListenWebhookOptions(webhookURL, false, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenWebhookOptions sets a webhook and listens for incoming updates.
|
||||||
|
// The webhookUrl should be provided in the following format: '<hostname>:<port>/<path>',
|
||||||
|
// eg: 'https://example.com:443/bot_token'.
|
||||||
|
// ListenWebhook will then proceed to communicate the webhook url '<hostname>/<path>' to Telegram
|
||||||
|
// and run a webserver that listens to ':<port>' and handles the path.
|
||||||
|
func (d *Dispatcher) ListenWebhookOptions(webhookURL string, dropPendingUpdates bool, opts *WebhookOptions) error {
|
||||||
|
u, err := url.Parse(webhookURL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
whURL := fmt.Sprintf("%s%s", u.Hostname(), u.EscapedPath())
|
||||||
|
if _, err = d.api.SetWebhook(whURL, dropPendingUpdates, opts); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.httpServer != nil {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.Handle("/", d.httpServer.Handler)
|
||||||
|
mux.HandleFunc(u.EscapedPath(), d.HandleWebhook)
|
||||||
|
d.httpServer.Handler = mux
|
||||||
|
return d.httpServer.ListenAndServe()
|
||||||
|
}
|
||||||
|
http.HandleFunc(u.EscapedPath(), d.HandleWebhook)
|
||||||
|
return http.ListenAndServe(fmt.Sprintf(":%s", u.Port()), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetHTTPServer allows to set a custom http.Server for ListenWebhook and ListenWebhookOptions.
|
||||||
|
func (d *Dispatcher) SetHTTPServer(s *http.Server) {
|
||||||
|
d.httpServer = s
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleWebhook is the http.HandlerFunc for the webhook URL.
|
||||||
|
// Useful if you've already a http server running and want to handle the request yourself.
|
||||||
|
func (d *Dispatcher) HandleWebhook(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var update Update
|
||||||
|
|
||||||
|
jsn, err := readRequest(r)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("echotron.Dispatcher", "HandleWebhook", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(jsn, &update); err != nil {
|
||||||
|
log.Println("echotron.Dispatcher", "HandleWebhook", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
d.updates <- &update
|
||||||
|
}
|
||||||
|
|
||||||
|
func readRequest(r *http.Request) ([]byte, error) {
|
||||||
|
switch r.Header.Get("Content-Encoding") {
|
||||||
|
case "gzip":
|
||||||
|
reader, err := gzip.NewReader(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, err
|
||||||
|
}
|
||||||
|
defer reader.Close()
|
||||||
|
return io.ReadAll(reader)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return io.ReadAll(r.Body)
|
||||||
|
}
|
||||||
|
}
|
||||||
73
vendor/github.com/NicoNex/echotron/v3/games.go
generated
vendored
Normal file
73
vendor/github.com/NicoNex/echotron/v3/games.go
generated
vendored
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* Echotron
|
||||||
|
* Copyright (C) 2018 The Echotron Contributors
|
||||||
|
*
|
||||||
|
* Echotron is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Echotron is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package echotron
|
||||||
|
|
||||||
|
import "net/url"
|
||||||
|
|
||||||
|
// Game represents a game.
|
||||||
|
type Game struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Photo []PhotoSize `json:"photo"`
|
||||||
|
Text string `json:"text,omitempty"`
|
||||||
|
TextEntities []MessageEntity `json:"text_entities,omitempty"`
|
||||||
|
Animation Animation `json:"animation,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CallbackGame is a placeholder, currently holds no information.
|
||||||
|
type CallbackGame struct{}
|
||||||
|
|
||||||
|
// GameHighScore represents one row of the high scores table for a game.
|
||||||
|
type GameHighScore struct {
|
||||||
|
User User `json:"user"`
|
||||||
|
Position int `json:"position"`
|
||||||
|
Score int `json:"score"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GameScoreOptions contains the optional parameters used in SetGameScore method.
|
||||||
|
type GameScoreOptions struct {
|
||||||
|
Force bool `query:"force"`
|
||||||
|
DisableEditMessage bool `query:"disable_edit_message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendGame is used to send a Game.
|
||||||
|
func (a API) SendGame(gameShortName string, chatID int64, opts *BaseOptions) (res APIResponseMessage, err error) {
|
||||||
|
var vals = make(url.Values)
|
||||||
|
|
||||||
|
vals.Set("chat_id", itoa(chatID))
|
||||||
|
vals.Set("game_short_name", gameShortName)
|
||||||
|
return res, a.lclient.get(a.base, "sendGame", addValues(vals, opts), &res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetGameScore is used to set the score of the specified user in a game.
|
||||||
|
func (a API) SetGameScore(userID int64, score int, msgID MessageIDOptions, opts *GameScoreOptions) (res APIResponseMessage, err error) {
|
||||||
|
var vals = make(url.Values)
|
||||||
|
|
||||||
|
vals.Set("user_id", itoa(userID))
|
||||||
|
vals.Set("score", itoa(int64(score)))
|
||||||
|
return res, a.lclient.get(a.base, "setGameScore", addValues(addValues(vals, msgID), opts), &res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGameHighScores is used to get data for high score tables.
|
||||||
|
func (a API) GetGameHighScores(userID int64, opts MessageIDOptions) (res APIResponseGameHighScore, err error) {
|
||||||
|
var vals = make(url.Values)
|
||||||
|
|
||||||
|
vals.Set("user_id", itoa(userID))
|
||||||
|
return res, a.lclient.get(a.base, "getGameHighScores", addValues(vals, opts), &res)
|
||||||
|
}
|
||||||
214
vendor/github.com/NicoNex/echotron/v3/helpers.go
generated
vendored
Normal file
214
vendor/github.com/NicoNex/echotron/v3/helpers.go
generated
vendored
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
/*
|
||||||
|
* Echotron
|
||||||
|
* Copyright (C) 2018 The Echotron Contributors
|
||||||
|
*
|
||||||
|
* Echotron is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Echotron is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package echotron
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// content contains a file's name, its type and its data.
|
||||||
|
type content struct {
|
||||||
|
fname string
|
||||||
|
ftype string
|
||||||
|
fdata []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func check(r APIResponse) error {
|
||||||
|
if b := r.Base(); !b.Ok {
|
||||||
|
return &APIError{code: b.ErrorCode, desc: b.Description}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func processMedia(media, thumbnail InputFile) (im mediaEnvelope, cnt []content, err error) {
|
||||||
|
switch {
|
||||||
|
case media.id != "":
|
||||||
|
im = mediaEnvelope{
|
||||||
|
media: media.id,
|
||||||
|
thumbnail: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
case media.url != "":
|
||||||
|
im = mediaEnvelope{
|
||||||
|
media: media.url,
|
||||||
|
thumbnail: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
case media.path != "" && len(media.content) == 0:
|
||||||
|
if media.content, media.path, err = readFile(media); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
|
||||||
|
case media.path != "" && len(media.content) > 0:
|
||||||
|
cnt = append(cnt, content{media.path, media.path, media.content})
|
||||||
|
im = mediaEnvelope{
|
||||||
|
media: fmt.Sprintf("attach://%s", media.path),
|
||||||
|
thumbnail: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case thumbnail.path != "" && len(thumbnail.content) == 0:
|
||||||
|
if thumbnail.content, thumbnail.path, err = readFile(thumbnail); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
|
||||||
|
case thumbnail.path != "" && len(thumbnail.content) > 0:
|
||||||
|
cnt = append(cnt, content{thumbnail.path, thumbnail.path, thumbnail.content})
|
||||||
|
im.thumbnail = fmt.Sprintf("attach://%s", thumbnail.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func processSticker(sticker InputFile) (se stickerEnvelope, cnt []content, err error) {
|
||||||
|
switch {
|
||||||
|
case sticker.id != "":
|
||||||
|
se.Sticker = sticker.id
|
||||||
|
|
||||||
|
case sticker.url != "":
|
||||||
|
se.Sticker = sticker.url
|
||||||
|
|
||||||
|
case sticker.path != "" && len(sticker.content) == 0:
|
||||||
|
if sticker.content, sticker.path, err = readFile(sticker); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
|
||||||
|
case sticker.path != "" && len(sticker.content) > 0:
|
||||||
|
cnt = append(cnt, content{sticker.path, sticker.path, sticker.content})
|
||||||
|
se.Sticker = fmt.Sprintf("attach://%s", sticker.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func readFile(im InputFile) (content []byte, path string, err error) {
|
||||||
|
content, err = os.ReadFile(im.path)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
path = filepath.Base(im.path)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func toContent(ftype string, f InputFile) (content, error) {
|
||||||
|
if f.path != "" && len(f.content) == 0 {
|
||||||
|
var err error
|
||||||
|
if f.content, f.path, err = readFile(f); err != nil {
|
||||||
|
return content{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return content{f.path, ftype, f.content}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toInputMedia(media []GroupableInputMedia) (ret []InputMedia) {
|
||||||
|
ret = make([]InputMedia, len(media))
|
||||||
|
|
||||||
|
for i, v := range media {
|
||||||
|
ret[i] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func joinURL(base, endpoint string, vals url.Values) (addr string, err error) {
|
||||||
|
addr, err = url.JoinPath(base, endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if vals != nil {
|
||||||
|
if queries := vals.Encode(); queries != "" {
|
||||||
|
addr = fmt.Sprintf("%s?%s", addr, queries)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func processProfilePhoto(photo InputProfilePhoto) (env profilePhotoEnvelope, cnt []content, err error) {
|
||||||
|
env.content = photo
|
||||||
|
file := photo.profilePhotoFile()
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case file.id != "":
|
||||||
|
env.ref = file.id
|
||||||
|
|
||||||
|
case file.url != "":
|
||||||
|
env.ref = file.url
|
||||||
|
|
||||||
|
case file.path != "" && len(file.content) == 0:
|
||||||
|
if file.content, file.path, err = readFile(file); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
|
||||||
|
case file.path != "" && len(file.content) > 0:
|
||||||
|
cnt = append(cnt, content{file.path, file.path, file.content})
|
||||||
|
env.ref = fmt.Sprintf("attach://%s", file.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func processStoryContent(sc InputStoryContent) (env storyContentEnvelope, cnt []content, err error) {
|
||||||
|
env.content = sc
|
||||||
|
file := sc.storyContentFile()
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case file.id != "":
|
||||||
|
env.ref = file.id
|
||||||
|
|
||||||
|
case file.url != "":
|
||||||
|
env.ref = file.url
|
||||||
|
|
||||||
|
case file.path != "" && len(file.content) == 0:
|
||||||
|
if file.content, file.path, err = readFile(file); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
|
||||||
|
case file.path != "" && len(file.content) > 0:
|
||||||
|
cnt = append(cnt, content{file.path, file.path, file.content})
|
||||||
|
env.ref = fmt.Sprintf("attach://%s", file.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func itoa(i int64) string {
|
||||||
|
return strconv.FormatInt(i, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ftoa(f float64) string {
|
||||||
|
return strconv.FormatFloat(f, 'f', -1, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
func btoa(b bool) string {
|
||||||
|
return strconv.FormatBool(b)
|
||||||
|
}
|
||||||
601
vendor/github.com/NicoNex/echotron/v3/inline.go
generated
vendored
Normal file
601
vendor/github.com/NicoNex/echotron/v3/inline.go
generated
vendored
Normal file
@@ -0,0 +1,601 @@
|
|||||||
|
/*
|
||||||
|
* Echotron
|
||||||
|
* Copyright (C) 2018 The Echotron Contributors
|
||||||
|
*
|
||||||
|
* Echotron is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Echotron is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package echotron
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InlineQueryType is a custom type for the various InlineQueryResult*'s Type field.
|
||||||
|
type InlineQueryType string
|
||||||
|
|
||||||
|
// These are all the possible types for the various InlineQueryResult*'s Type field.
|
||||||
|
const (
|
||||||
|
InlineArticle InlineQueryType = "article"
|
||||||
|
InlinePhoto = "photo"
|
||||||
|
InlineGIF = "gif"
|
||||||
|
InlineMPEG4GIF = "mpeg4_gif"
|
||||||
|
InlineVideo = "video"
|
||||||
|
InlineAudio = "audio"
|
||||||
|
InlineVoice = "voice"
|
||||||
|
InlineDocument = "document"
|
||||||
|
InlineLocation = "location"
|
||||||
|
InlineVenue = "venue"
|
||||||
|
InlineContact = "contact"
|
||||||
|
InlineGame = "game"
|
||||||
|
InlineSticker = "sticker"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InlineQuery represents an incoming inline query.
|
||||||
|
// When the user sends an empty query, your bot could return some default or trending results.
|
||||||
|
type InlineQuery struct {
|
||||||
|
From *User `json:"from"`
|
||||||
|
Location *Location `json:"location,omitempty"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
Query string `json:"query"`
|
||||||
|
Offset string `json:"offset"`
|
||||||
|
ChatType string `json:"chat_type,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChosenInlineResult represents a result of an inline query that was chosen by the user and sent to their chat partner.
|
||||||
|
type ChosenInlineResult struct {
|
||||||
|
ResultID string `json:"result_id"`
|
||||||
|
From *User `json:"from"`
|
||||||
|
Location *Location `json:"location,omitempty"`
|
||||||
|
InlineMessageID string `json:"inline_message_id,omitempty"`
|
||||||
|
Query string `json:"query"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// InlineQueryResult represents an interface that implements all the various InlineQueryResult* types.
|
||||||
|
type InlineQueryResult interface {
|
||||||
|
ImplementsInlineQueryResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
// InlineQueryResultArticle represents a link to an article or web page.
|
||||||
|
type InlineQueryResultArticle struct {
|
||||||
|
ReplyMarkup ReplyMarkup `json:"reply_markup,omitempty"`
|
||||||
|
InputMessageContent InputMessageContent `json:"input_message_content,omitempty"`
|
||||||
|
Type InlineQueryType `json:"type"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
ThumbnailURL string `json:"thumbnail_url,omitempty"`
|
||||||
|
URL string `json:"url,omitempty"`
|
||||||
|
ThumbnailWidth int `json:"thumbnail_width,omitempty"`
|
||||||
|
ThumbnailHeight int `json:"thumbnail_height,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsInlineQueryResult is used to implement the InlineQueryResult interface.
|
||||||
|
func (i InlineQueryResultArticle) ImplementsInlineQueryResult() {}
|
||||||
|
|
||||||
|
// InlineQueryResultPhoto represents a link to a photo.
|
||||||
|
// By default, this photo will be sent by the user with optional caption.
|
||||||
|
// Alternatively, you can use InputMessageContent to send a message with the specified content instead of the photo.
|
||||||
|
type InlineQueryResultPhoto struct {
|
||||||
|
ReplyMarkup ReplyMarkup `json:"reply_markup,omitempty"`
|
||||||
|
InputMessageContent InputMessageContent `json:"input_message_content,omitempty"`
|
||||||
|
Title string `json:"title,omitempty"`
|
||||||
|
ThumbnailURL string `json:"thumbnail_url"`
|
||||||
|
PhotoURL string `json:"photo_url"`
|
||||||
|
ParseMode string `json:"parse_mode,omitempty"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
Caption string `json:"caption,omitempty"`
|
||||||
|
Type InlineQueryType `json:"type"`
|
||||||
|
CaptionEntities []*MessageEntity `json:"caption_entities,omitempty"`
|
||||||
|
PhotoHeight int `json:"photo_height,omitempty"`
|
||||||
|
PhotoWidth int `json:"photo_width,omitempty"`
|
||||||
|
ShowCaptionAboveMedia bool `json:"show_caption_above_media,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsInlineQueryResult is used to implement the InlineQueryResult interface.
|
||||||
|
func (i InlineQueryResultPhoto) ImplementsInlineQueryResult() {}
|
||||||
|
|
||||||
|
// InlineQueryResultGif represents a link to an animated GIF file.
|
||||||
|
// By default, this animated GIF file will be sent by the user with optional caption.
|
||||||
|
// Alternatively, you can use InputMessageContent to send a message with the specified content instead of the animation.
|
||||||
|
type InlineQueryResultGif struct {
|
||||||
|
InputMessageContent InputMessageContent `json:"input_message_content,omitempty"`
|
||||||
|
ReplyMarkup ReplyMarkup `json:"reply_markup,omitempty"`
|
||||||
|
Title string `json:"title,omitempty"`
|
||||||
|
GifURL string `json:"gif_url"`
|
||||||
|
ParseMode string `json:"parse_mode,omitempty"`
|
||||||
|
Caption string `json:"caption,omitempty"`
|
||||||
|
ThumbnailURL string `json:"thumbnail_url"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
ThumbnailMimeType string `json:"thumbnail_mime_type,omitempty"`
|
||||||
|
Type InlineQueryType `json:"type"`
|
||||||
|
CaptionEntities []*MessageEntity `json:"caption_entities,omitempty"`
|
||||||
|
GifDuration int `json:"gif_duration,omitempty"`
|
||||||
|
GifHeight int `json:"gif_height,omitempty"`
|
||||||
|
GifWidth int `json:"gif_width,omitempty"`
|
||||||
|
ShowCaptionAboveMedia bool `json:"show_caption_above_media,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsInlineQueryResult is used to implement the InlineQueryResult interface.
|
||||||
|
func (i InlineQueryResultGif) ImplementsInlineQueryResult() {}
|
||||||
|
|
||||||
|
// InlineQueryResultMpeg4Gif represents a link to a video animation (H.264/MPEG-4 AVC video without sound).
|
||||||
|
// By default, this animated MPEG-4 file will be sent by the user with optional caption.
|
||||||
|
// Alternatively, you can use InputMessageContent to send a message with the specified content instead of the animation.
|
||||||
|
type InlineQueryResultMpeg4Gif struct {
|
||||||
|
InputMessageContent InputMessageContent `json:"input_message_content,omitempty"`
|
||||||
|
ReplyMarkup ReplyMarkup `json:"reply_markup,omitempty"`
|
||||||
|
Title string `json:"title,omitempty"`
|
||||||
|
Mpeg4URL string `json:"mpeg4_url"`
|
||||||
|
ParseMode string `json:"parse_mode,omitempty"`
|
||||||
|
Caption string `json:"caption,omitempty"`
|
||||||
|
ThumbnailURL string `json:"thumbnail_url"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
ThumbnailMimeType string `json:"thumbnail_mime_type,omitempty"`
|
||||||
|
Type InlineQueryType `json:"type"`
|
||||||
|
CaptionEntities []*MessageEntity `json:"caption_entities,omitempty"`
|
||||||
|
Mpeg4Duration int `json:"mpeg4_duration,omitempty"`
|
||||||
|
Mpeg4Height int `json:"mpeg4_height,omitempty"`
|
||||||
|
Mpeg4Width int `json:"mpeg4_width,omitempty"`
|
||||||
|
ShowCaptionAboveMedia bool `json:"show_caption_above_media,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsInlineQueryResult is used to implement the InlineQueryResult interface.
|
||||||
|
func (i InlineQueryResultMpeg4Gif) ImplementsInlineQueryResult() {}
|
||||||
|
|
||||||
|
// InlineQueryResultVideo represents a link to a page containing an embedded video player or a video file.
|
||||||
|
// By default, this video file will be sent by the user with an optional caption.
|
||||||
|
// Alternatively, you can use InputMessageContent to send a message with the specified content instead of the video.
|
||||||
|
type InlineQueryResultVideo struct {
|
||||||
|
InputMessageContent InputMessageContent `json:"input_message_content,omitempty"`
|
||||||
|
ReplyMarkup ReplyMarkup `json:"reply_markup,omitempty"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
MimeType string `json:"mime_type"`
|
||||||
|
ThumbnailURL string `json:"thumbnail_url"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Caption string `json:"caption,omitempty"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
VideoURL string `json:"video_url"`
|
||||||
|
ParseMode string `json:"parse_mode,omitempty"`
|
||||||
|
Type InlineQueryType `json:"type"`
|
||||||
|
CaptionEntities []*MessageEntity `json:"caption_entities,omitempty"`
|
||||||
|
VideoHeight int `json:"video_height,omitempty"`
|
||||||
|
VideoDuration int `json:"video_duration,omitempty"`
|
||||||
|
VideoWidth int `json:"video_width,omitempty"`
|
||||||
|
ShowCaptionAboveMedia bool `json:"show_caption_above_media,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsInlineQueryResult is used to implement the InlineQueryResult interface.
|
||||||
|
func (i InlineQueryResultVideo) ImplementsInlineQueryResult() {}
|
||||||
|
|
||||||
|
// InlineQueryResultAudio represents a link to an MP3 audio file.
|
||||||
|
// By default, this audio file will be sent by the user.
|
||||||
|
// Alternatively, you can use InputMessageContent to send a message with the specified content instead of the audio.
|
||||||
|
type InlineQueryResultAudio struct {
|
||||||
|
ReplyMarkup ReplyMarkup `json:"reply_markup,omitempty"`
|
||||||
|
InputMessageContent InputMessageContent `json:"input_message_content,omitempty"`
|
||||||
|
Type InlineQueryType `json:"type"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
AudioURL string `json:"audio_url"`
|
||||||
|
ParseMode string `json:"parse_mode,omitempty"`
|
||||||
|
Performer string `json:"performer,omitempty"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Caption string `json:"caption,omitempty"`
|
||||||
|
CaptionEntities []*MessageEntity `json:"caption_entities,omitempty"`
|
||||||
|
AudioDuration int `json:"audio_duration,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsInlineQueryResult is used to implement the InlineQueryResult interface.
|
||||||
|
func (i InlineQueryResultAudio) ImplementsInlineQueryResult() {}
|
||||||
|
|
||||||
|
// InlineQueryResultVoice represents a link to a voice recording in an .OGG container encoded with OPUS.
|
||||||
|
// By default, this voice recording will be sent by the user.
|
||||||
|
// Alternatively, you can use InputMessageContent to send a message with the specified content instead of the the voice message.
|
||||||
|
type InlineQueryResultVoice struct {
|
||||||
|
ReplyMarkup ReplyMarkup `json:"reply_markup,omitempty"`
|
||||||
|
InputMessageContent InputMessageContent `json:"input_message_content,omitempty"`
|
||||||
|
Type InlineQueryType `json:"type"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
Caption string `json:"caption,omitempty"`
|
||||||
|
ParseMode string `json:"parse_mode,omitempty"`
|
||||||
|
VoiceURL string `json:"voice_url"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
CaptionEntities []*MessageEntity `json:"caption_entities,omitempty"`
|
||||||
|
VoiceDuration int `json:"voice_duration,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsInlineQueryResult is used to implement the InlineQueryResult interface.
|
||||||
|
func (i InlineQueryResultVoice) ImplementsInlineQueryResult() {}
|
||||||
|
|
||||||
|
// InlineQueryResultDocument represents a link to a file.
|
||||||
|
// By default, this file will be sent by the user with an optional caption.
|
||||||
|
// Alternatively, you can use InputMessageContent to send a message with the specified content instead of the file.
|
||||||
|
// Currently, only .PDF and .ZIP files can be sent using this method.
|
||||||
|
type InlineQueryResultDocument struct {
|
||||||
|
InputMessageContent InputMessageContent `json:"input_message_content,omitempty"`
|
||||||
|
ReplyMarkup ReplyMarkup `json:"reply_markup,omitempty"`
|
||||||
|
MimeType string `json:"mime_type"`
|
||||||
|
Caption string `json:"caption,omitempty"`
|
||||||
|
ParseMode string `json:"parse_mode,omitempty"`
|
||||||
|
ThumbnailURL string `json:"thumbnail_url,omitempty"`
|
||||||
|
DocumentURL string `json:"document_url"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
Type InlineQueryType `json:"type"`
|
||||||
|
CaptionEntities []*MessageEntity `json:"caption_entities,omitempty"`
|
||||||
|
ThumbnailWidth int `json:"thumbnail_width,omitempty"`
|
||||||
|
ThumbnailHeight int `json:"thumbnail_height,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsInlineQueryResult is used to implement the InlineQueryResult interface.
|
||||||
|
func (i InlineQueryResultDocument) ImplementsInlineQueryResult() {}
|
||||||
|
|
||||||
|
// InlineQueryResultLocation represents a location on a map.
|
||||||
|
// By default, the location will be sent by the user.
|
||||||
|
// Alternatively, you can use InputMessageContent to send a message with the specified content instead of the location.
|
||||||
|
type InlineQueryResultLocation struct {
|
||||||
|
InputMessageContent InputMessageContent `json:"input_message_content,omitempty"`
|
||||||
|
ReplyMarkup ReplyMarkup `json:"reply_markup,omitempty"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
ThumbnailURL string `json:"thumbnail_url,omitempty"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Type InlineQueryType `json:"type"`
|
||||||
|
LivePeriod int `json:"live_period,omitempty"`
|
||||||
|
HorizontalAccuracy float64 `json:"horizontal_accuracy,omitempty"`
|
||||||
|
ProximityAlertRadius int `json:"proximity_alert_radius,omitempty"`
|
||||||
|
Longitude float64 `json:"longitude"`
|
||||||
|
Latitude float64 `json:"latitude"`
|
||||||
|
ThumbnailWidth int `json:"thumbnail_width,omitempty"`
|
||||||
|
ThumbnailHeight int `json:"thumbnail_height,omitempty"`
|
||||||
|
Heading int `json:"heading,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsInlineQueryResult is used to implement the InlineQueryResult interface.
|
||||||
|
func (i InlineQueryResultLocation) ImplementsInlineQueryResult() {}
|
||||||
|
|
||||||
|
// InlineQueryResultVenue represents a venue.
|
||||||
|
// By default, the venue will be sent by the user.
|
||||||
|
// Alternatively, you can use InputMessageContent to send a message with the specified content instead of the venue.
|
||||||
|
type InlineQueryResultVenue struct {
|
||||||
|
InputMessageContent InputMessageContent `json:"input_message_content,omitempty"`
|
||||||
|
ReplyMarkup ReplyMarkup `json:"reply_markup,omitempty"`
|
||||||
|
GooglePlaceType string `json:"google_place_type,omitempty"`
|
||||||
|
ThumbnailURL string `json:"thumbnail_url,omitempty"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
FoursquareID string `json:"foursquare_id,omitempty"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
GooglePlaceID string `json:"google_place_id,omitempty"`
|
||||||
|
FoursquareType string `json:"foursquare_type,omitempty"`
|
||||||
|
Type InlineQueryType `json:"type"`
|
||||||
|
Longitude float64 `json:"longitude"`
|
||||||
|
Latitude float64 `json:"latitude"`
|
||||||
|
ThumbnailWidth int `json:"thumbnail_width,omitempty"`
|
||||||
|
ThumbnailHeight int `json:"thumbnail_height,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsInlineQueryResult is used to implement the InlineQueryResult interface.
|
||||||
|
func (i InlineQueryResultVenue) ImplementsInlineQueryResult() {}
|
||||||
|
|
||||||
|
// InlineQueryResultContact represents a contact with a phone number.
|
||||||
|
// By default, this contact will be sent by the user.
|
||||||
|
// Alternatively, you can use InputMessageContent to send a message with the specified content instead of the contact.
|
||||||
|
type InlineQueryResultContact struct {
|
||||||
|
ReplyMarkup ReplyMarkup `json:"reply_markup,omitempty"`
|
||||||
|
InputMessageContent InputMessageContent `json:"input_message_content,omitempty"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
PhoneNumber string `json:"phone_number"`
|
||||||
|
FirstName string `json:"first_name"`
|
||||||
|
VCard string `json:"vcard,omitempty"`
|
||||||
|
Type InlineQueryType `json:"type"`
|
||||||
|
ThumbnailURL string `json:"thumbnail_url,omitempty"`
|
||||||
|
LastName string `json:"last_name,omitempty"`
|
||||||
|
ThumbnailWidth int `json:"thumbnail_width,omitempty"`
|
||||||
|
ThumbnailHeight int `json:"thumbnail_height,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsInlineQueryResult is used to implement the InlineQueryResult interface.
|
||||||
|
func (i InlineQueryResultContact) ImplementsInlineQueryResult() {}
|
||||||
|
|
||||||
|
// InlineQueryResultGame represents a Game.
|
||||||
|
type InlineQueryResultGame struct {
|
||||||
|
ReplyMarkup ReplyMarkup `json:"reply_markup,omitempty"`
|
||||||
|
Type InlineQueryType `json:"type"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
GameShortName string `json:"game_short_name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsInlineQueryResult is used to implement the InlineQueryResult interface.
|
||||||
|
func (i InlineQueryResultGame) ImplementsInlineQueryResult() {}
|
||||||
|
|
||||||
|
// InlineQueryResultCachedPhoto represents a link to a photo stored on the Telegram servers.
|
||||||
|
// By default, this photo will be sent by the user with an optional caption.
|
||||||
|
// Alternatively, you can use InputMessageContent to send a message with the specified content instead of the photo.
|
||||||
|
type InlineQueryResultCachedPhoto struct {
|
||||||
|
ReplyMarkup ReplyMarkup `json:"reply_markup,omitempty"`
|
||||||
|
InputMessageContent InputMessageContent `json:"input_message_content,omitempty"`
|
||||||
|
Type InlineQueryType `json:"type"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
Caption string `json:"caption,omitempty"`
|
||||||
|
ParseMode string `json:"parse_mode,omitempty"`
|
||||||
|
PhotoFileID string `json:"photo_file_id"`
|
||||||
|
Title string `json:"title,omitempty"`
|
||||||
|
CaptionEntities []*MessageEntity `json:"caption_entities,omitempty"`
|
||||||
|
ShowCaptionAboveMedia bool `json:"show_caption_above_media,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsInlineQueryResult is used to implement the InlineQueryResult interface.
|
||||||
|
func (i InlineQueryResultCachedPhoto) ImplementsInlineQueryResult() {}
|
||||||
|
|
||||||
|
// InlineQueryResultCachedGif represents a link to an animated GIF file stored on the Telegram servers.
|
||||||
|
// By default, this animated GIF file will be sent by the user with an optional caption.
|
||||||
|
// Alternatively, you can use InputMessageContent to send a message with specified content instead of the animation.
|
||||||
|
type InlineQueryResultCachedGif struct {
|
||||||
|
ReplyMarkup ReplyMarkup `json:"reply_markup,omitempty"`
|
||||||
|
InputMessageContent InputMessageContent `json:"input_message_content,omitempty"`
|
||||||
|
Type InlineQueryType `json:"type"`
|
||||||
|
Title string `json:"title,omitempty"`
|
||||||
|
Caption string `json:"caption,omitempty"`
|
||||||
|
ParseMode string `json:"parse_mode,omitempty"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
GifFileID string `json:"gif_file_id"`
|
||||||
|
CaptionEntities []*MessageEntity `json:"caption_entities,omitempty"`
|
||||||
|
ShowCaptionAboveMedia bool `json:"show_caption_above_media,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsInlineQueryResult is used to implement the InlineQueryResult interface.
|
||||||
|
func (i InlineQueryResultCachedGif) ImplementsInlineQueryResult() {}
|
||||||
|
|
||||||
|
// InlineQueryResultCachedMpeg4Gif represents a link to a video animation (H.264/MPEG-4 AVC video without sound) stored on the Telegram servers.
|
||||||
|
// By default, this animated MPEG-4 file will be sent by the user with an optional caption.
|
||||||
|
// Alternatively, you can use InputMessageContent to send a message with the specified content instead of the animation.
|
||||||
|
type InlineQueryResultCachedMpeg4Gif struct {
|
||||||
|
ReplyMarkup ReplyMarkup `json:"reply_markup,omitempty"`
|
||||||
|
InputMessageContent InputMessageContent `json:"input_message_content,omitempty"`
|
||||||
|
Type InlineQueryType `json:"type"`
|
||||||
|
Title string `json:"title,omitempty"`
|
||||||
|
Caption string `json:"caption,omitempty"`
|
||||||
|
ParseMode string `json:"parse_mode,omitempty"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
Mpeg4FileID string `json:"mpeg4_file_id"`
|
||||||
|
CaptionEntities []*MessageEntity `json:"caption_entities,omitempty"`
|
||||||
|
ShowCaptionAboveMedia bool `json:"show_caption_above_media,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsInlineQueryResult is used to implement the InlineQueryResult interface.
|
||||||
|
func (i InlineQueryResultCachedMpeg4Gif) ImplementsInlineQueryResult() {}
|
||||||
|
|
||||||
|
// InlineQueryResultCachedSticker represents a link to a sticker stored on the Telegram servers.
|
||||||
|
// By default, this sticker will be sent by the user.
|
||||||
|
// Alternatively, you can use InputMessageContent to send a message with the specified content instead of the sticker.
|
||||||
|
type InlineQueryResultCachedSticker struct {
|
||||||
|
ReplyMarkup ReplyMarkup `json:"reply_markup,omitempty"`
|
||||||
|
InputMessageContent InputMessageContent `json:"input_message_content,omitempty"`
|
||||||
|
Type InlineQueryType `json:"type"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
StickerFileID string `json:"sticker_file_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsInlineQueryResult is used to implement the InlineQueryResult interface.
|
||||||
|
func (i InlineQueryResultCachedSticker) ImplementsInlineQueryResult() {}
|
||||||
|
|
||||||
|
// InlineQueryResultCachedDocument represents a link to a file stored on the Telegram servers.
|
||||||
|
// By default, this file will be sent by the user with an optional caption.
|
||||||
|
// Alternatively, you can use InputMessageContent to send a message with the specified content instead of the file.
|
||||||
|
type InlineQueryResultCachedDocument struct {
|
||||||
|
ReplyMarkup ReplyMarkup `json:"reply_markup,omitempty"`
|
||||||
|
InputMessageContent InputMessageContent `json:"input_message_content,omitempty"`
|
||||||
|
Type InlineQueryType `json:"type"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
Caption string `json:"caption,omitempty"`
|
||||||
|
ParseMode string `json:"parse_mode,omitempty"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
DocumentFileID string `json:"document_file_id"`
|
||||||
|
CaptionEntities []*MessageEntity `json:"caption_entities,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsInlineQueryResult is used to implement the InlineQueryResult interface.
|
||||||
|
func (i InlineQueryResultCachedDocument) ImplementsInlineQueryResult() {}
|
||||||
|
|
||||||
|
// InlineQueryResultCachedVideo represents a link to a video file stored on the Telegram servers.
|
||||||
|
// By default, this video file will be sent by the user with an optional caption.
|
||||||
|
// Alternatively, you can use InputMessageContent to send a message with the specified content instead of the video.
|
||||||
|
type InlineQueryResultCachedVideo struct {
|
||||||
|
ReplyMarkup ReplyMarkup `json:"reply_markup,omitempty"`
|
||||||
|
InputMessageContent InputMessageContent `json:"input_message_content,omitempty"`
|
||||||
|
Type InlineQueryType `json:"type"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
Caption string `json:"caption,omitempty"`
|
||||||
|
ParseMode string `json:"parse_mode,omitempty"`
|
||||||
|
VideoFileID string `json:"video_file_id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
CaptionEntities []*MessageEntity `json:"caption_entities,omitempty"`
|
||||||
|
ShowCaptionAboveMedia bool `json:"show_caption_above_media,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsInlineQueryResult is used to implement the InlineQueryResult interface.
|
||||||
|
func (i InlineQueryResultCachedVideo) ImplementsInlineQueryResult() {}
|
||||||
|
|
||||||
|
// InlineQueryResultCachedVoice represents a link to a voice message stored on the Telegram servers.
|
||||||
|
// By default, this voice message will be sent by the user.
|
||||||
|
// Alternatively, you can use InputMessageContent to send a message with the specified content instead of the voice message.
|
||||||
|
type InlineQueryResultCachedVoice struct {
|
||||||
|
ReplyMarkup ReplyMarkup `json:"reply_markup,omitempty"`
|
||||||
|
InputMessageContent InputMessageContent `json:"input_message_content,omitempty"`
|
||||||
|
Type InlineQueryType `json:"type"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Caption string `json:"caption,omitempty"`
|
||||||
|
ParseMode string `json:"parse_mode,omitempty"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
VoiceFileID string `json:"voice_file_id"`
|
||||||
|
CaptionEntities []*MessageEntity `json:"caption_entities,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsInlineQueryResult is used to implement the InlineQueryResult interface.
|
||||||
|
func (i InlineQueryResultCachedVoice) ImplementsInlineQueryResult() {}
|
||||||
|
|
||||||
|
// InlineQueryResultCachedAudio represents a link to an MP3 audio file stored on the Telegram servers.
|
||||||
|
// By default, this audio file will be sent by the user.
|
||||||
|
// Alternatively, you can use InputMessageContent to send a message with the specified content instead of the audio.
|
||||||
|
type InlineQueryResultCachedAudio struct {
|
||||||
|
ReplyMarkup ReplyMarkup `json:"reply_markup,omitempty"`
|
||||||
|
InputMessageContent InputMessageContent `json:"input_message_content,omitempty"`
|
||||||
|
AudioFileID string `json:"audio_file_id"`
|
||||||
|
Caption string `json:"caption,omitempty"`
|
||||||
|
ParseMode string `json:"parse_mode,omitempty"`
|
||||||
|
Type InlineQueryType `json:"type"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
CaptionEntities []*MessageEntity `json:"caption_entities,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsInlineQueryResult is used to implement the InlineQueryResult interface.
|
||||||
|
func (i InlineQueryResultCachedAudio) ImplementsInlineQueryResult() {}
|
||||||
|
|
||||||
|
// InputMessageContent represents an interface that implements all the various Input*MessageContent types.
|
||||||
|
type InputMessageContent interface {
|
||||||
|
ImplementsInputMessageContent()
|
||||||
|
}
|
||||||
|
|
||||||
|
// InputTextMessageContent represents the content of a text message to be sent as the result of an inline query.
|
||||||
|
type InputTextMessageContent struct {
|
||||||
|
LinkPreviewOptions *LinkPreviewOptions `json:"link_preview_options,omitempty"`
|
||||||
|
MessageText string `json:"message_text"`
|
||||||
|
ParseMode string `json:"parse_mode,omitempty"`
|
||||||
|
Entities []*MessageEntity `json:"entities,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsInputMessageContent is used to implement the InputMessageContent interface.
|
||||||
|
func (i InputTextMessageContent) ImplementsInputMessageContent() {}
|
||||||
|
|
||||||
|
// InputLocationMessageContent represents the content of a location message to be sent as the result of an inline query.
|
||||||
|
type InputLocationMessageContent struct {
|
||||||
|
Latitude float64 `json:"latitude"`
|
||||||
|
Longitude float64 `json:"longitude"`
|
||||||
|
HorizontalAccuracy float64 `json:"horizontal_accuracy,omitempty"`
|
||||||
|
LivePeriod int `json:"live_period,omitempty"`
|
||||||
|
Heading int `json:"heading,omitempty"`
|
||||||
|
ProximityAlertRadius int `json:"proximity_alert_radius,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsInputMessageContent is used to implement the InputMessageContent interface.
|
||||||
|
func (i InputLocationMessageContent) ImplementsInputMessageContent() {}
|
||||||
|
|
||||||
|
// InputVenueMessageContent represents the content of a venue message to be sent as the result of an inline query.
|
||||||
|
type InputVenueMessageContent struct {
|
||||||
|
GooglePlaceID string `json:"google_place_id,omitempty"`
|
||||||
|
GooglePlaceType string `json:"google_place_type,omitempty"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
FoursquareID string `json:"foursquare_id,omitempty"`
|
||||||
|
FoursquareType string `json:"foursquare_type,omitempty"`
|
||||||
|
Latitude float64 `json:"latitude"`
|
||||||
|
Longitude float64 `json:"longitude"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsInputMessageContent is used to implement the InputMessageContent interface.
|
||||||
|
func (i InputVenueMessageContent) ImplementsInputMessageContent() {}
|
||||||
|
|
||||||
|
// InputContactMessageContent represents the content of a contact message to be sent as the result of an inline query.
|
||||||
|
type InputContactMessageContent struct {
|
||||||
|
PhoneNumber string `json:"phone_number"`
|
||||||
|
FirstName string `json:"first_name"`
|
||||||
|
LastName string `json:"last_name,omitempty"`
|
||||||
|
VCard string `json:"vcard,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsInputMessageContent is used to implement the InputMessageContent interface.
|
||||||
|
func (i InputContactMessageContent) ImplementsInputMessageContent() {}
|
||||||
|
|
||||||
|
// InputInvoiceMessageContent represents the content of an invoice message to be sent as the result of an inline query.
|
||||||
|
type InputInvoiceMessageContent struct {
|
||||||
|
SuggestedTipAmounts *[]int `json:"suggested_tip_amounts,omitempty"`
|
||||||
|
PhotoURL string `json:"photo_url,omitempty"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Payload string `json:"string"`
|
||||||
|
ProviderToken string `json:"provider_token,omitempty"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
ProviderData string `json:"provider_data,omitempty"`
|
||||||
|
Prices []LabeledPrice `json:"prices"`
|
||||||
|
PhotoSize int `json:"photo_size,omitempty"`
|
||||||
|
MaxTipAmount int `json:"max_tip_amount,omitempty"`
|
||||||
|
PhotoWidth int `json:"photo_width,omitempty"`
|
||||||
|
PhotoHeight int `json:"photo_height,omitempty"`
|
||||||
|
NeedName bool `json:"need_name,omitempty"`
|
||||||
|
NeedPhoneNumber bool `json:"need_phone_number,omitempty"`
|
||||||
|
NeedEmail bool `json:"need_email,omitempty"`
|
||||||
|
NeedShippingAddress bool `json:"need_shipping_address,omitempty"`
|
||||||
|
SendPhoneNumberToProvider bool `json:"send_phone_number_to_provider,omitempty"`
|
||||||
|
SendEmailToProvider bool `json:"send_email_to_provider,omitempty"`
|
||||||
|
IsFlexible bool `json:"is_flexible,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsInputMessageContent is used to implement the InputMessageContent interface.
|
||||||
|
func (i InputInvoiceMessageContent) ImplementsInputMessageContent() {}
|
||||||
|
|
||||||
|
// InlineQueryResultsButton represents a button to be shown above inline query results.
|
||||||
|
// You MUST use exactly one of the fields.
|
||||||
|
type InlineQueryResultsButton struct {
|
||||||
|
WebApp WebAppInfo `json:"web_app,omitempty"`
|
||||||
|
StartParameter string `json:"start_parameter,omitempty"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PreparedInlineMessage describes an inline message to be sent by a user of a Mini App.
|
||||||
|
type PreparedInlineMessage struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
ExpirationDate int `json:"expiration_date"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// InlineQueryOptions is a custom type which contains the various options required by the AnswerInlineQuery method.
|
||||||
|
type InlineQueryOptions struct {
|
||||||
|
Button InlineQueryResultsButton `query:"button"`
|
||||||
|
NextOffset string `query:"next_offset"`
|
||||||
|
CacheTime int `query:"cache_time"`
|
||||||
|
IsPersonal bool `query:"is_personal"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PreparedInlineMessageOptions is a custom type which contains the various options required by the SavePreparedInlineMessage method.
|
||||||
|
type PreparedInlineMessageOptions struct {
|
||||||
|
AllowUserChats bool `query:"allow_user_chats"`
|
||||||
|
AllowBotChats bool `query:"allow_bot_chats"`
|
||||||
|
AllowGroupChats bool `query:"allow_group_chats"`
|
||||||
|
AllowChannelChats bool `query:"allow_channel_chats"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnswerInlineQuery is used to send answers to an inline query.
|
||||||
|
func (a API) AnswerInlineQuery(inlineQueryID string, results []InlineQueryResult, opts *InlineQueryOptions) (res APIResponseBase, err error) {
|
||||||
|
var vals = make(url.Values)
|
||||||
|
|
||||||
|
jsn, _ := json.Marshal(results)
|
||||||
|
vals.Set("inline_query_id", inlineQueryID)
|
||||||
|
vals.Set("results", string(jsn))
|
||||||
|
return res, a.lclient.get(a.base, "answerInlineQuery", addValues(vals, opts), &res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SavePreparedInlineMessage stores a message that can be sent by a user of a Mini App.
|
||||||
|
func (a API) SavePreparedInlineMessage(userID int64, result InlineQueryResult, opts *PreparedInlineMessageOptions) (res APIResponsePreparedInlineMessage, err error) {
|
||||||
|
var vals = make(url.Values)
|
||||||
|
|
||||||
|
jsn, _ := json.Marshal(result)
|
||||||
|
vals.Set("user_id", itoa(userID))
|
||||||
|
vals.Set("result", string(jsn))
|
||||||
|
return res, a.lclient.get(a.base, "savePreparedInlineMessage", addValues(vals, opts), &res)
|
||||||
|
}
|
||||||
57
vendor/github.com/NicoNex/echotron/v3/menubutton.go
generated
vendored
Normal file
57
vendor/github.com/NicoNex/echotron/v3/menubutton.go
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* Echotron
|
||||||
|
* Copyright (C) 2022 The Echotron Contributors
|
||||||
|
*
|
||||||
|
* Echotron is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Echotron is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package echotron
|
||||||
|
|
||||||
|
// MenuButtonType is a custom type for the various MenuButton*'s Type field.
|
||||||
|
type MenuButtonType string
|
||||||
|
|
||||||
|
// These are all the possible types for the various MenuButton*'s Type field.
|
||||||
|
const (
|
||||||
|
MenuButtonTypeCommands MenuButtonType = "commands"
|
||||||
|
MenuButtonTypeWebApp = "web_app"
|
||||||
|
MenuButtonTypeDefault = "default"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MenuButton is a unique type for MenuButtonCommands, MenuButtonWebApp and MenuButtonDefault
|
||||||
|
type MenuButton struct {
|
||||||
|
WebApp *WebAppInfo `json:"web_app,omitempty"`
|
||||||
|
Type MenuButtonType `json:"type"`
|
||||||
|
Text string `json:"text,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetChatMenuButtonOptions contains the optional parameters used by the SetChatMenuButton method.
|
||||||
|
type SetChatMenuButtonOptions struct {
|
||||||
|
MenuButton MenuButton `query:"menu_button"`
|
||||||
|
ChatID int64 `query:"chat_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetChatMenuButtonOptions contains the optional parameters used by the GetChatMenuButton method.
|
||||||
|
type GetChatMenuButtonOptions struct {
|
||||||
|
ChatID int64 `query:"chat_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetChatMenuButton is used to change the bot's menu button in a private chat, or the default menu button.
|
||||||
|
func (a API) SetChatMenuButton(opts *SetChatMenuButtonOptions) (res APIResponseBool, err error) {
|
||||||
|
return res, a.lclient.get(a.base, "setChatMenuButton", urlValues(opts), &res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetChatMenuButton is used to get the current value of the bot's menu button in a private chat, or the default menu button.
|
||||||
|
func (a API) GetChatMenuButton(opts *GetChatMenuButtonOptions) (res APIResponseMenuButton, err error) {
|
||||||
|
return res, a.lclient.get(a.base, "getChatMenuButton", urlValues(opts), &res)
|
||||||
|
}
|
||||||
442
vendor/github.com/NicoNex/echotron/v3/network.go
generated
vendored
Normal file
442
vendor/github.com/NicoNex/echotron/v3/network.go
generated
vendored
Normal file
@@ -0,0 +1,442 @@
|
|||||||
|
/*
|
||||||
|
* Echotron
|
||||||
|
* Copyright (C) 2018 The Echotron Contributors
|
||||||
|
*
|
||||||
|
* Echotron is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Echotron is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package echotron
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/time/rate"
|
||||||
|
)
|
||||||
|
|
||||||
|
// lclient is an HTTP client with built-in dual-level rate limiting.
|
||||||
|
// One instance is shared across all API objects for the same bot token.
|
||||||
|
type lclient struct {
|
||||||
|
cl map[string]*rate.Limiter
|
||||||
|
gl *rate.Limiter
|
||||||
|
climiter func() *rate.Limiter
|
||||||
|
http *http.Client
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// clients caches one lclient per base URL (i.e. one per bot token).
|
||||||
|
var clients smap[string, *lclient]
|
||||||
|
|
||||||
|
func loadClient(url string) *lclient {
|
||||||
|
// Fast path: client already exists for this base URL.
|
||||||
|
if lc, ok := clients.load(url); ok {
|
||||||
|
return lc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slow path: first access, create a new client and store it atomically.
|
||||||
|
// If two goroutines race here, loadOrStore guarantees only one wins and
|
||||||
|
// both get back the same *lclient.
|
||||||
|
lc, _ := clients.loadOrStore(
|
||||||
|
url,
|
||||||
|
&lclient{
|
||||||
|
http: new(http.Client),
|
||||||
|
cl: make(map[string]*rate.Limiter),
|
||||||
|
gl: rate.NewLimiter(rate.Every(time.Second/30), 30),
|
||||||
|
climiter: func() *rate.Limiter {
|
||||||
|
return rate.NewLimiter(rate.Every(time.Minute/20), 20)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return lc
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetGlobalRequestLimit sets the global rate limit for requests to the Telegram API.
|
||||||
|
// An interval of 0 disables the rate limiter, allowing unlimited requests.
|
||||||
|
// By default the interval of this limiter is set to time.Second/30 and the
|
||||||
|
// burstSize is set to 30.
|
||||||
|
func (c *lclient) SetGlobalRequestLimit(interval time.Duration, burstSize int) {
|
||||||
|
c.mu.Lock()
|
||||||
|
c.gl = rate.NewLimiter(rate.Every(interval), burstSize)
|
||||||
|
c.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetChatRequestLimit sets the per-chat rate limit for requests to the Telegram API.
|
||||||
|
// An interval of 0 disables the rate limiter, allowing unlimited requests.
|
||||||
|
// By default the interval of this limiter is set to time.Minute/20 and the
|
||||||
|
// burstSize is set to 20.
|
||||||
|
func (c *lclient) SetChatRequestLimit(interval time.Duration, burstSize int) {
|
||||||
|
c.mu.Lock()
|
||||||
|
// Reset the existing limiters so the new factory applies to all chats.
|
||||||
|
c.cl = make(map[string]*rate.Limiter)
|
||||||
|
c.climiter = func() *rate.Limiter {
|
||||||
|
return rate.NewLimiter(rate.Every(interval), burstSize)
|
||||||
|
}
|
||||||
|
c.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait blocks until both the per-chat and global rate limiters allow the request.
|
||||||
|
func (c *lclient) wait(chatID string) error {
|
||||||
|
ctx := context.Background()
|
||||||
|
// If the chatID is empty, it's a general API call like GetUpdates, GetMe
|
||||||
|
// and similar, so skip the per-chat request limit wait.
|
||||||
|
if chatID != "" {
|
||||||
|
c.mu.RLock()
|
||||||
|
l, ok := c.cl[chatID]
|
||||||
|
c.mu.RUnlock()
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
c.mu.Lock()
|
||||||
|
// Re-check after acquiring the write lock to avoid overwriting
|
||||||
|
// a limiter created by another goroutine in the meantime.
|
||||||
|
if l, ok = c.cl[chatID]; !ok {
|
||||||
|
l = c.climiter()
|
||||||
|
c.cl[chatID] = l
|
||||||
|
}
|
||||||
|
c.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure to respect the single chat limit of requests.
|
||||||
|
if err := l.Wait(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure to respect the global limit of requests.
|
||||||
|
return c.gl.Wait(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// dispatch is the common path for all API calls: rate-limit, send, decode, check.
|
||||||
|
func (c *lclient) dispatch(chatID string, send func() ([]byte, error), v APIResponse) error {
|
||||||
|
if err := c.wait(chatID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cnt, err := send()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(cnt, v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return check(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// doGet performs a raw HTTP GET and returns the response body.
|
||||||
|
func (c *lclient) doGet(reqURL string) ([]byte, error) {
|
||||||
|
resp, err := c.http.Get(reqURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
data, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// doPost sends a multipart/form-data POST with the given files and returns the response body.
|
||||||
|
func (c *lclient) doPost(reqURL string, files ...content) ([]byte, error) {
|
||||||
|
var (
|
||||||
|
buf = new(bytes.Buffer)
|
||||||
|
w = multipart.NewWriter(buf)
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, f := range files {
|
||||||
|
part, err := w.CreateFormFile(f.ftype, filepath.Base(f.fname))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
part.Write(f.fdata)
|
||||||
|
}
|
||||||
|
w.Close()
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodPost, reqURL, buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Add("Content-Type", w.FormDataContentType())
|
||||||
|
|
||||||
|
res, err := c.http.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
return io.ReadAll(res.Body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// doPostForm sends an application/x-www-form-urlencoded POST and returns the response body.
|
||||||
|
func (c *lclient) doPostForm(reqURL string, keyVals map[string]string) ([]byte, error) {
|
||||||
|
var form = make(url.Values)
|
||||||
|
|
||||||
|
for k, v := range keyVals {
|
||||||
|
form.Add(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodPost, reqURL, strings.NewReader(form.Encode()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
|
||||||
|
res, err := c.http.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
return io.ReadAll(res.Body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendFile sends a single file to Telegram.
|
||||||
|
// If the file is identified by an ID or URL it is passed as a query parameter;
|
||||||
|
// otherwise it is uploaded via multipart. The thumbnail, if present, is always uploaded.
|
||||||
|
func (c *lclient) sendFile(file, thumbnail InputFile, url, fileType string) (res []byte, err error) {
|
||||||
|
var cnt []content
|
||||||
|
|
||||||
|
if file.id != "" {
|
||||||
|
url = fmt.Sprintf("%s&%s=%s", url, fileType, file.id)
|
||||||
|
} else if file.url != "" {
|
||||||
|
url = fmt.Sprintf("%s&%s=%s", url, fileType, file.url)
|
||||||
|
} else if f, e := toContent(fileType, file); e == nil {
|
||||||
|
cnt = append(cnt, f)
|
||||||
|
} else {
|
||||||
|
err = e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Thumbnail is optional; conversion errors are silently ignored.
|
||||||
|
if t, e := toContent("thumbnail", thumbnail); e == nil {
|
||||||
|
cnt = append(cnt, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cnt) > 0 {
|
||||||
|
res, err = c.doPost(url, cnt...)
|
||||||
|
} else {
|
||||||
|
res, err = c.doGet(url)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// get calls a Telegram API endpoint that requires no file upload.
|
||||||
|
func (c *lclient) get(base, endpoint string, vals url.Values, v APIResponse) error {
|
||||||
|
u, err := url.JoinPath(base, endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if vals != nil {
|
||||||
|
if q := vals.Encode(); q != "" {
|
||||||
|
u = fmt.Sprintf("%s?%s", u, q)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.dispatch(vals.Get("chat_id"), func() ([]byte, error) {
|
||||||
|
return c.doGet(u)
|
||||||
|
}, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// postFile calls a Telegram API endpoint that requires uploading a single file.
|
||||||
|
func (c *lclient) postFile(base, endpoint, fileType string, file, thumbnail InputFile, vals url.Values, v APIResponse) error {
|
||||||
|
u, err := joinURL(base, endpoint, vals)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.dispatch(vals.Get("chat_id"), func() ([]byte, error) {
|
||||||
|
return c.sendFile(file, thumbnail, u, fileType)
|
||||||
|
}, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// postMedia calls a Telegram API endpoint that sends a media group or edits a single media item.
|
||||||
|
// editSingle serialises only the first element instead of the full array.
|
||||||
|
func (c *lclient) postMedia(base, endpoint string, editSingle bool, vals url.Values, v APIResponse, files ...InputMedia) error {
|
||||||
|
u, err := joinURL(base, endpoint, vals)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.dispatch(vals.Get("chat_id"), func() ([]byte, error) {
|
||||||
|
return c.sendMediaFiles(u, editSingle, files...)
|
||||||
|
}, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// postStickers calls a Telegram API endpoint that sends one or more stickers.
|
||||||
|
func (c *lclient) postStickers(base, endpoint string, vals url.Values, v APIResponse, stickers ...InputSticker) error {
|
||||||
|
u, err := joinURL(base, endpoint, vals)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.dispatch(vals.Get("chat_id"), func() ([]byte, error) {
|
||||||
|
return c.sendStickers(u, stickers...)
|
||||||
|
}, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendMediaFiles serialises the media group into JSON and uploads any local files via multipart.
|
||||||
|
func (c *lclient) sendMediaFiles(url string, editSingle bool, files ...InputMedia) (res []byte, err error) {
|
||||||
|
var (
|
||||||
|
med []mediaEnvelope
|
||||||
|
cnt []content
|
||||||
|
jsn []byte
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
var im mediaEnvelope
|
||||||
|
var cntArr []content
|
||||||
|
|
||||||
|
media := file.media()
|
||||||
|
thumbnail := file.thumbnail()
|
||||||
|
|
||||||
|
im, cntArr, err = processMedia(media, thumbnail)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
im.InputMedia = file
|
||||||
|
|
||||||
|
med = append(med, im)
|
||||||
|
cnt = append(cnt, cntArr...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// editSingle is set when editing a single media message; Telegram expects
|
||||||
|
// the object directly rather than wrapped in an array.
|
||||||
|
if editSingle {
|
||||||
|
jsn, err = json.Marshal(med[0])
|
||||||
|
} else {
|
||||||
|
jsn, err = json.Marshal(med)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
url = fmt.Sprintf("%s&media=%s", url, jsn)
|
||||||
|
|
||||||
|
if len(cnt) > 0 {
|
||||||
|
return c.doPost(url, cnt...)
|
||||||
|
}
|
||||||
|
return c.doGet(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
// postProfilePhoto calls a Telegram API endpoint that sets a profile photo.
|
||||||
|
func (c *lclient) postProfilePhoto(base, endpoint, param string, photo InputProfilePhoto, vals url.Values, v APIResponse) error {
|
||||||
|
u, err := joinURL(base, endpoint, vals)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.dispatch(vals.Get("chat_id"), func() ([]byte, error) {
|
||||||
|
return c.sendProfilePhotoFile(u, param, photo)
|
||||||
|
}, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendProfilePhotoFile serialises the profile photo and uploads it if it's a local file.
|
||||||
|
func (c *lclient) sendProfilePhotoFile(u, param string, photo InputProfilePhoto) ([]byte, error) {
|
||||||
|
env, cnt, err := processProfilePhoto(photo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
jsn, err := json.Marshal(env)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
u = fmt.Sprintf("%s&%s=%s", u, param, jsn)
|
||||||
|
|
||||||
|
if len(cnt) > 0 {
|
||||||
|
return c.doPost(u, cnt...)
|
||||||
|
}
|
||||||
|
return c.doGet(u)
|
||||||
|
}
|
||||||
|
|
||||||
|
// postStoryContent calls a Telegram API endpoint that posts or edits a story.
|
||||||
|
func (c *lclient) postStoryContent(base, endpoint string, sc InputStoryContent, vals url.Values, v APIResponse) error {
|
||||||
|
u, err := joinURL(base, endpoint, vals)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.dispatch(vals.Get("chat_id"), func() ([]byte, error) {
|
||||||
|
return c.sendStoryContentFile(u, sc)
|
||||||
|
}, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendStoryContentFile serialises the story content and uploads it if it's a local file.
|
||||||
|
func (c *lclient) sendStoryContentFile(u string, sc InputStoryContent) ([]byte, error) {
|
||||||
|
env, cnt, err := processStoryContent(sc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
jsn, err := json.Marshal(env)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
u = fmt.Sprintf("%s&content=%s", u, jsn)
|
||||||
|
|
||||||
|
if len(cnt) > 0 {
|
||||||
|
return c.doPost(u, cnt...)
|
||||||
|
}
|
||||||
|
return c.doGet(u)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendStickers serialises the stickers and uploads any local files via multipart.
|
||||||
|
// A single sticker uses the "sticker" parameter; multiple use "stickers".
|
||||||
|
func (c *lclient) sendStickers(url string, stickers ...InputSticker) (res []byte, err error) {
|
||||||
|
var (
|
||||||
|
sti []stickerEnvelope
|
||||||
|
cnt []content
|
||||||
|
jsn []byte
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, s := range stickers {
|
||||||
|
var se stickerEnvelope
|
||||||
|
var cntArr []content
|
||||||
|
|
||||||
|
se, cntArr, err = processSticker(s.Sticker)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
se.InputSticker = s
|
||||||
|
|
||||||
|
sti = append(sti, se)
|
||||||
|
cnt = append(cnt, cntArr...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Telegram uses different parameter names for one vs many stickers.
|
||||||
|
if len(sti) == 1 {
|
||||||
|
jsn, _ = json.Marshal(sti[0])
|
||||||
|
url = fmt.Sprintf("%s&sticker=%s", url, jsn)
|
||||||
|
} else {
|
||||||
|
jsn, _ = json.Marshal(sti)
|
||||||
|
url = fmt.Sprintf("%s&stickers=%s", url, jsn)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cnt) > 0 {
|
||||||
|
return c.doPost(url, cnt...)
|
||||||
|
}
|
||||||
|
return c.doGet(url)
|
||||||
|
}
|
||||||
1007
vendor/github.com/NicoNex/echotron/v3/options.go
generated
vendored
Normal file
1007
vendor/github.com/NicoNex/echotron/v3/options.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
228
vendor/github.com/NicoNex/echotron/v3/passport.go
generated
vendored
Normal file
228
vendor/github.com/NicoNex/echotron/v3/passport.go
generated
vendored
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
package echotron
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PassportData contains information about Telegram Passport data shared with the bot by the user.
|
||||||
|
type PassportData struct {
|
||||||
|
Credentials EncryptedCredentials `json:"encrypted_credentials"`
|
||||||
|
Data []EncryptedPassportElement `json:"encrypted_passport_element"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PassportFile represents a file uploaded to Telegram Passport.
|
||||||
|
// Currently all Telegram Passport files are in JPEG format when decrypted and don't exceed 10MB.
|
||||||
|
type PassportFile struct {
|
||||||
|
FileID string `json:"file_id"`
|
||||||
|
FileUniqueID string `json:"file_unique_id"`
|
||||||
|
FileSize int64 `json:"file_size"`
|
||||||
|
FileDate int64 `json:"file_date"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncryptedPassportElementType is a custom type for the various possible options used as Type in EncryptedPassportElement.
|
||||||
|
type EncryptedPassportElementType string
|
||||||
|
|
||||||
|
// These are all the possible options that can be used as Type in EncryptedPassportElement.
|
||||||
|
const (
|
||||||
|
TypePersonalDetails EncryptedPassportElementType = "personal_details"
|
||||||
|
TypePassport = "passport"
|
||||||
|
TypeDriverLicense = "driver_license"
|
||||||
|
TypeIdentityCard = "identity_card"
|
||||||
|
TypeInternalPassport = "internal_passport"
|
||||||
|
TypeAddress = "address"
|
||||||
|
TypeUtilityBill = "utility_bill"
|
||||||
|
TypeBankStatement = "bank_statement"
|
||||||
|
TypeRentalAgreement = "rental_agreement"
|
||||||
|
TypePassportRegistration = "passport_registration"
|
||||||
|
TypeTemporaryRegistration = "temporary_registration"
|
||||||
|
TypePhoneNumber = "phone_number"
|
||||||
|
TypeEmail = "email"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EncryptedPassportElement contains information about documents or other Telegram Passport elements shared with the bot by the user.
|
||||||
|
type EncryptedPassportElement struct {
|
||||||
|
Type EncryptedPassportElementType `json:"type"`
|
||||||
|
Data string `json:"data,omitempty"`
|
||||||
|
PhoneNumber string `json:"phone_number,omitempty"`
|
||||||
|
Email string `json:"email,omitempty"`
|
||||||
|
Files *[]PassportFile `json:"files,omitempty"`
|
||||||
|
FrontSide *PassportFile `json:"front_side,omitempty"`
|
||||||
|
ReverseSide *PassportFile `json:"reverse_side,omitempty"`
|
||||||
|
Selfie *PassportFile `json:"selfie,omitempty"`
|
||||||
|
Translation *[]PassportFile `json:"translation,omitempty"`
|
||||||
|
Hash string `json:"hash"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncryptedCredentials contains data required for decrypting and authenticating EncryptedPassportElement.
|
||||||
|
// See the Telegram Passport Documentation for a complete description of the data decryption and authentication processes.
|
||||||
|
// https://core.telegram.org/passport#receiving-information
|
||||||
|
type EncryptedCredentials struct {
|
||||||
|
Data string `json:"data"`
|
||||||
|
Hash string `json:"hash"`
|
||||||
|
Secret string `json:"secret"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PassportElementErrorSource is a custom type for the various possible options used as Source in PassportElementSource.
|
||||||
|
type PassportElementErrorSource string
|
||||||
|
|
||||||
|
// These are all the possible options that can be used as Source in PassportElementSource.
|
||||||
|
const (
|
||||||
|
SourceData PassportElementErrorSource = "data"
|
||||||
|
SourceFrontSide = "front_side"
|
||||||
|
SourceReverseSide = "reverse_side"
|
||||||
|
SourceSelfie = "selfie"
|
||||||
|
SourceFile = "file"
|
||||||
|
SourceFiles = "files"
|
||||||
|
SourceTranslationFile = "translation_file"
|
||||||
|
SourceTranslationFiles = "translation_files"
|
||||||
|
SourceUnspecified = "unspecified"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PassportElementError is an interface for the various PassportElementError types.
|
||||||
|
type PassportElementError interface {
|
||||||
|
ImplementsPassportElementError()
|
||||||
|
}
|
||||||
|
|
||||||
|
// PassportElementErrorDataField represents an issue in one of the data fields that was provided by the user.
|
||||||
|
// The error is considered resolved when the field's value changes.
|
||||||
|
// Source MUST BE SourceData.
|
||||||
|
// Type MUST BE one of TypePersonalDetails, TypePassport, TypeDriverLicense, TypeIdentityCard, TypeInternalPassport and TypeAddress.
|
||||||
|
type PassportElementErrorDataField struct {
|
||||||
|
Source PassportElementErrorSource `json:"source"`
|
||||||
|
Type EncryptedPassportElementType `json:"type"`
|
||||||
|
FieldName string `json:"field_name"`
|
||||||
|
DataHash string `json:"data_hash"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsPassportElementError us a dummy method which exists to implement the interface PassportElementError.
|
||||||
|
func (p PassportElementErrorDataField) ImplementsPassportElementError() {}
|
||||||
|
|
||||||
|
// PassportElementErrorFrontSide represents an issue with the front side of a document.
|
||||||
|
// The error is considered resolved when the file with the front side of the document changes.
|
||||||
|
// Source MUST BE SourceFrontSide.
|
||||||
|
// Type MUST BE one of TypeDriverLicense and TypeIdentityCard.
|
||||||
|
type PassportElementErrorFrontSide struct {
|
||||||
|
Source PassportElementErrorSource `json:"source"`
|
||||||
|
Type EncryptedPassportElementType `json:"type"`
|
||||||
|
FileHash string `json:"file_hash"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsPassportElementError us a dummy method which exists to implement the interface PassportElementError.
|
||||||
|
func (p PassportElementErrorFrontSide) ImplementsPassportElementError() {}
|
||||||
|
|
||||||
|
// PassportElementErrorReverseSide represents an issue with the reverse side of a document.
|
||||||
|
// The error is considered resolved when the file with the reverse side of the document changes.
|
||||||
|
// Source MUST BE SourceReverseSide.
|
||||||
|
// Type MUST BE one of TypeDriverLicense and TypeIdentityCard.
|
||||||
|
type PassportElementErrorReverseSide struct {
|
||||||
|
Source PassportElementErrorSource `json:"source"`
|
||||||
|
Type EncryptedPassportElementType `json:"type"`
|
||||||
|
FileHash string `json:"file_hash"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsPassportElementError us a dummy method which exists to implement the interface PassportElementError.
|
||||||
|
func (p PassportElementErrorReverseSide) ImplementsPassportElementError() {}
|
||||||
|
|
||||||
|
// PassportElementErrorSelfie represents an issue with the selfie with a document.
|
||||||
|
// The error is considered resolved when the file with the selfie changes.
|
||||||
|
// Source MUST BE SourceSelfie.
|
||||||
|
// Type MUST BE one of TypePassport, TypeDriverLicense, TypeIdentityCard and TypeIdentityPassport.
|
||||||
|
type PassportElementErrorSelfie struct {
|
||||||
|
Source PassportElementErrorSource `json:"source"`
|
||||||
|
Type EncryptedPassportElementType `json:"type"`
|
||||||
|
FileHash string `json:"file_hash"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsPassportElementError us a dummy method which exists to implement the interface PassportElementError.
|
||||||
|
func (p PassportElementErrorSelfie) ImplementsPassportElementError() {}
|
||||||
|
|
||||||
|
// PassportElementErrorFile represents an issue with the document scan.
|
||||||
|
// The error is considered resolved when the file with the document scan changes.
|
||||||
|
// Source MUST BE SourceFile.
|
||||||
|
// Type MUST BE one of TypePassport, TypeDriverLicense, TypeIdentityCard and TypeIdentityPassport.
|
||||||
|
type PassportElementErrorFile struct {
|
||||||
|
Source PassportElementErrorSource `json:"source"`
|
||||||
|
Type EncryptedPassportElementType `json:"type"`
|
||||||
|
FileHash string `json:"file_hash"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsPassportElementError us a dummy method which exists to implement the interface PassportElementError.
|
||||||
|
func (p PassportElementErrorFile) ImplementsPassportElementError() {}
|
||||||
|
|
||||||
|
// PassportElementErrorFiles represents an issue with a list of scans.
|
||||||
|
// The error is considered resolved when the list of files containing the scans changes.
|
||||||
|
// Source MUST BE SourceFiles.
|
||||||
|
// Type MUST BE one of TypeUtilityBill, TypeBankStatement, TypeRentalAgreement, TypePassportRegistration and TypeTemporaryRegistration.
|
||||||
|
type PassportElementErrorFiles struct {
|
||||||
|
Source PassportElementErrorSource `json:"source"`
|
||||||
|
Type EncryptedPassportElementType `json:"type"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
FileHashes []string `json:"file_hashes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsPassportElementError us a dummy method which exists to implement the interface PassportElementError.
|
||||||
|
func (p PassportElementErrorFiles) ImplementsPassportElementError() {}
|
||||||
|
|
||||||
|
// PassportElementErrorTranslationFile represents an issue with one of the files that constitute the translation of the document.
|
||||||
|
// The error is considered resolved when the file changes.
|
||||||
|
// Source MUST BE SourceTranslationFile.
|
||||||
|
// Type MUST BE one of TypePassport, TypeDriverLicense, TypeIdentityCard, TypeInternalPassport, TypeUtilityBill, TypeBankStatement,
|
||||||
|
// TypeRentalAgreement, TypePassportRegistration and TypeTemporaryRegistration.
|
||||||
|
type PassportElementErrorTranslationFile struct {
|
||||||
|
Source PassportElementErrorSource `json:"source"`
|
||||||
|
Type EncryptedPassportElementType `json:"type"`
|
||||||
|
FileHash string `json:"file_hash"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsPassportElementError us a dummy method which exists to implement the interface PassportElementError.
|
||||||
|
func (p PassportElementErrorTranslationFile) ImplementsPassportElementError() {}
|
||||||
|
|
||||||
|
// PassportElementErrorTranslationFiles represents an issue with the translated version of a document.
|
||||||
|
// The error is considered resolved when a file with the document translation changes.
|
||||||
|
// Source MUST BE SourceTranslationFiles.
|
||||||
|
// Type MUST BE one of TypePassport, TypeDriverLicense, TypeIdentityCard, TypeInternalPassport, TypeUtilityBill, TypeBankStatement,
|
||||||
|
// TypeRentalAgreement, TypePassportRegistration and TypeTemporaryRegistration.
|
||||||
|
type PassportElementErrorTranslationFiles struct {
|
||||||
|
Source PassportElementErrorSource `json:"source"`
|
||||||
|
Type EncryptedPassportElementType `json:"type"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
FileHashes []string `json:"file_hashes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsPassportElementError us a dummy method which exists to implement the interface PassportElementError.
|
||||||
|
func (p PassportElementErrorTranslationFiles) ImplementsPassportElementError() {}
|
||||||
|
|
||||||
|
// PassportElementErrorUnspecified represents an issue in an unspecified place.
|
||||||
|
// The error is considered resolved when new data is added.
|
||||||
|
type PassportElementErrorUnspecified struct {
|
||||||
|
Source PassportElementErrorSource `json:"source"`
|
||||||
|
Type EncryptedPassportElementType `json:"type"`
|
||||||
|
ElementHash string `json:"element_hash"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsPassportElementError us a dummy method which exists to implement the interface PassportElementError.
|
||||||
|
func (p PassportElementErrorUnspecified) ImplementsPassportElementError() {}
|
||||||
|
|
||||||
|
// SetPassportDataErrors Informs a user that some of the Telegram Passport elements they provided contains errors.
|
||||||
|
// The user will not be able to re-submit their Passport to you until the errors are fixed.
|
||||||
|
// The contents of the field for which you returned the error must change.
|
||||||
|
func (a API) SetPassportDataErrors(userID int64, errors []PassportElementError) (res APIResponseBool, err error) {
|
||||||
|
var vals = make(url.Values)
|
||||||
|
|
||||||
|
errorsArr, err := json.Marshal(errors)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
vals.Set("user_id", itoa(userID))
|
||||||
|
vals.Set("errors", string(errorsArr))
|
||||||
|
return res, a.lclient.get(a.base, "setPassportDataErrors", vals, &res)
|
||||||
|
}
|
||||||
335
vendor/github.com/NicoNex/echotron/v3/payments.go
generated
vendored
Normal file
335
vendor/github.com/NicoNex/echotron/v3/payments.go
generated
vendored
Normal file
@@ -0,0 +1,335 @@
|
|||||||
|
/*
|
||||||
|
* Echotron
|
||||||
|
* Copyright (C) 2018 The Echotron Contributors
|
||||||
|
*
|
||||||
|
* Echotron is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Echotron is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package echotron
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LabeledPrice represents a portion of the price for goods or services.
|
||||||
|
type LabeledPrice struct {
|
||||||
|
Label string `json:"label"`
|
||||||
|
// Price of the product in the smallest units of the currency (integer, not float/double).
|
||||||
|
// For example, for a price of US$ 1.45 pass amount = 145.
|
||||||
|
// See the exp parameter in currencies.json, it shows the number of digits
|
||||||
|
// past the decimal point for each currency (2 for the majority of currencies).
|
||||||
|
Amount int `json:"amount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoice contains basic information about an invoice.
|
||||||
|
type Invoice struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
StartParameter string `json:"start_parameter"`
|
||||||
|
// Three-letter ISO 4217 currency code.
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
// Total amount in the smallest units of the currency (integer, not float/double).
|
||||||
|
// For example, for a price of US$ 1.45 pass amount = 145.
|
||||||
|
// See the exp parameter in currencies.json, it shows the number of digits
|
||||||
|
// past the decimal point for each currency (2 for the majority of currencies).
|
||||||
|
TotalAmount int `json:"total_amount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShippingAddress represents a shipping address.
|
||||||
|
type ShippingAddress struct {
|
||||||
|
// ISO 3166-1 alpha-2 country code.
|
||||||
|
CountryCode string `json:"country_code"`
|
||||||
|
State string `json:"state"`
|
||||||
|
City string `json:"city"`
|
||||||
|
StreetLine1 string `json:"street_line1"`
|
||||||
|
StreetLine2 string `json:"street_line2"`
|
||||||
|
PostCode string `json:"post_code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrderInfo represents information about an order.
|
||||||
|
type OrderInfo struct {
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
PhoneNumber string `json:"phone_number,omitempty"`
|
||||||
|
Email string `json:"email,omitempty"`
|
||||||
|
ShippingAddress ShippingAddress `json:"shipping_address,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SuccessfulPayment contains basic information about a successful payment.
|
||||||
|
type SuccessfulPayment struct {
|
||||||
|
OrderInfo OrderInfo `json:"order_info"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
InvoicePayload string `json:"invoice_payload"`
|
||||||
|
ShippingOptionID string `json:"shipping_option_id"`
|
||||||
|
TelegramPaymentChargeID string `json:"telegram_payment_charge_id"`
|
||||||
|
ProviderPaymentChargeID string `json:"provider_payment_charge_id"`
|
||||||
|
TotalAmount int `json:"total_amount"`
|
||||||
|
SubscriptionExpirationDate int `json:"subscription_expiration_date,omitempty"`
|
||||||
|
IsRecurring bool `json:"is_recurring,omitempty"`
|
||||||
|
IsFirstRecurring bool `json:"is_first_recurring,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RefundedPayment contains basic information about a refunded payment.
|
||||||
|
type RefundedPayment struct {
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
InvoicePayload string `json:"invoice_payload"`
|
||||||
|
TelegramPaymentChargeID string `json:"telegram_payment_charge_id"`
|
||||||
|
ProviderPaymentChargeID string `json:"provider_payment_charge_id,omitempty"`
|
||||||
|
TotalAmount int `json:"total_amount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShippingQuery contains information about an incoming shipping query.
|
||||||
|
type ShippingQuery struct {
|
||||||
|
ShippingAddress ShippingAddress `json:"shipping_address"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
InvoicePayload string `json:"invoice_payload"`
|
||||||
|
From User `json:"from"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PreCheckoutQuery contains information about an incoming pre-checkout query.
|
||||||
|
type PreCheckoutQuery struct {
|
||||||
|
OrderInfo OrderInfo `json:"order_info,omitempty"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
InvoicePayload string `json:"invoice_payload"`
|
||||||
|
ShippingOptionID string `json:"shipping_option_id,omitempty"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
From User `json:"from"`
|
||||||
|
TotalAmount int `json:"total_amount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PaidMediaPurchased contains information about a paid media purchase.
|
||||||
|
type PaidMediaPurchased struct {
|
||||||
|
PaidMediaPayload string `json:"paid_media_payload"`
|
||||||
|
From User `json:"from"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RevenueWithdrawalState describes the state of a revenue withdrawal operation.
|
||||||
|
type RevenueWithdrawalState interface {
|
||||||
|
ImplementsRevenueWithdrawalState()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RevenueWithdrawalStatePending describes the state of a withdrawal in progress.
|
||||||
|
type RevenueWithdrawalStatePending struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsRevenueWithdrawalState is used to implement the RevenueWithdrawalState interface.
|
||||||
|
func (r RevenueWithdrawalStatePending) ImplementsRevenueWithdrawalState() {}
|
||||||
|
|
||||||
|
// RevenueWithdrawalStateSucceeded describes the state of a succeeded withdrawal.
|
||||||
|
type RevenueWithdrawalStateSucceeded struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
Date int `json:"date"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsRevenueWithdrawalState is used to implement the RevenueWithdrawalState interface.
|
||||||
|
func (r RevenueWithdrawalStateSucceeded) ImplementsRevenueWithdrawalState() {}
|
||||||
|
|
||||||
|
// RevenueWithdrawalStateFailed describes the state of a failed withdrawal, in which the transaction was refunded.
|
||||||
|
type RevenueWithdrawalStateFailed struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsRevenueWithdrawalState is used to implement the RevenueWithdrawalState interface.
|
||||||
|
func (r RevenueWithdrawalStateFailed) ImplementsRevenueWithdrawalState() {}
|
||||||
|
|
||||||
|
// AffiliateInfo
|
||||||
|
type AffiliateInfo struct {
|
||||||
|
AffiliateUser *User `json:"affiliate_user,omitempty"`
|
||||||
|
AffiliateChat *Chat `json:"affiliate_chat,omitempty"`
|
||||||
|
CommissionPerMille int `json:"commission_per_mille"`
|
||||||
|
Amount int `json:"amount"`
|
||||||
|
NanostarAmount int `json:"nanostar_amount,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransactionPartner describes the source of a transaction, or its recipient for outgoing transactions.
|
||||||
|
type TransactionPartner interface {
|
||||||
|
ImplementsTransactionPartner()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransactionPartnerAffiliateProgram describes the affiliate program that issued the affiliate commission received via this transaction.
|
||||||
|
// Type MUST be "affiliate_program".
|
||||||
|
type TransactionPartnerAffiliateProgram struct {
|
||||||
|
SponsorUser *User `json:"sponsor_user,omitempty"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
CommissionPerMille int `json:"commission_per_mille,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsTransactionPartner is used to implement the TransactionPartner interface.
|
||||||
|
func (t TransactionPartnerAffiliateProgram) ImplementsTransactionPartner() {}
|
||||||
|
|
||||||
|
// TransactionPartnerFragment describes a withdrawal transaction with Fragment.
|
||||||
|
// Type MUST be "fragment".
|
||||||
|
type TransactionPartnerFragment struct {
|
||||||
|
WithdrawalState RevenueWithdrawalState `json:"withdrawal_state"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsTransactionPartner is used to implement the TransactionPartner interface.
|
||||||
|
func (t TransactionPartnerFragment) ImplementsTransactionPartner() {}
|
||||||
|
|
||||||
|
// TransactionPartnerUser describes a transaction with a user.
|
||||||
|
// Type MUST be "user".
|
||||||
|
type TransactionPartnerUser struct {
|
||||||
|
Gift Gift `json:"gift,omitempty"`
|
||||||
|
PaidMedia *[]PaidMedia `json:"paid_media,omitempty"`
|
||||||
|
Affiliate *AffiliateInfo `json:"affiliate,omitempty"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
InvoicePayload string `json:"invoice_payload,omitempty"`
|
||||||
|
PaidMediaPayload string `json:"paid_media_payload,omitempty"`
|
||||||
|
User User `json:"user"`
|
||||||
|
SubscriptionPeriod int `json:"subscription_period,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransactionPartnerChat describes a transaction with a chat.
|
||||||
|
type TransactionPartnerChat struct {
|
||||||
|
Gift Gift `json:"gift,omitempty"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Chat Chat `json:"chat"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsTransactionPartner is used to implement the TransactionPartner interface.
|
||||||
|
func (t TransactionPartnerUser) ImplementsTransactionPartner() {}
|
||||||
|
|
||||||
|
// TransactionPartnerTelegramAds describes a withdrawal transaction to the Telegram Ads platform.
|
||||||
|
// Type MUST be "telegram_ads".
|
||||||
|
type TransactionPartnerTelegramAds struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsTransactionPartner is used to implement the TransactionPartner interface.
|
||||||
|
func (t TransactionPartnerTelegramAds) ImplementsTransactionPartner() {}
|
||||||
|
|
||||||
|
// TransactionPartnerTelegramApi describes a transaction with payment for paid broadcasting.
|
||||||
|
// Type MUST be "telegram_api".
|
||||||
|
type TransactionPartnerTelegramApi struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
RequestCount int `json:"request_count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsTransactionPartner is used to implement the TransactionPartner interface.
|
||||||
|
func (t TransactionPartnerTelegramApi) ImplementsTransactionPartner() {}
|
||||||
|
|
||||||
|
// TransactionPartnerOther describes a transaction with an unknown source or recipient.
|
||||||
|
// Type MUST be "other".
|
||||||
|
type TransactionPartnerOther struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsTransactionPartner is used to implement the TransactionPartner interface.
|
||||||
|
func (t TransactionPartnerOther) ImplementsTransactionPartner() {}
|
||||||
|
|
||||||
|
// StarTransaction describes a Telegram Star transaction.
|
||||||
|
type StarTransaction struct {
|
||||||
|
Source TransactionPartner `json:"source"`
|
||||||
|
Receiver TransactionPartner `json:"receiver"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
Amount int `json:"amount"`
|
||||||
|
NanostarAmount int `json:"nanostar_amount,omitempty"`
|
||||||
|
Date int `json:"date"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// StarTransactions contains a list of Telegram Star transactions.
|
||||||
|
type StarTransactions struct {
|
||||||
|
Transaction []StarTransaction `json:"transaction"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// StarTransactionsOptions contains the optional parameters used by the GetStarTransactions method.
|
||||||
|
type StarTransactionsOptions struct {
|
||||||
|
Offset int `query:"offset"`
|
||||||
|
Limit int `query:"limit"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendInvoice is used to send invoices.
|
||||||
|
func (a API) SendInvoice(chatID int64, title, description, payload, currency string, prices []LabeledPrice, opts *InvoiceOptions) (res APIResponseMessage, err error) {
|
||||||
|
var vals = make(url.Values)
|
||||||
|
|
||||||
|
p, err := json.Marshal(prices)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
vals.Set("chat_id", itoa(chatID))
|
||||||
|
vals.Set("title", title)
|
||||||
|
vals.Set("description", description)
|
||||||
|
vals.Set("payload", payload)
|
||||||
|
vals.Set("currency", currency)
|
||||||
|
vals.Set("prices", string(p))
|
||||||
|
return res, a.lclient.get(a.base, "sendInvoice", addValues(vals, opts), &res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateInvoiceLink creates a link for an invoice.
|
||||||
|
func (a API) CreateInvoiceLink(title, description, payload, currency string, prices []LabeledPrice, opts *CreateInvoiceLinkOptions) (res APIResponseBase, err error) {
|
||||||
|
var vals = make(url.Values)
|
||||||
|
|
||||||
|
p, err := json.Marshal(prices)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
vals.Set("title", title)
|
||||||
|
vals.Set("description", description)
|
||||||
|
vals.Set("payload", payload)
|
||||||
|
vals.Set("currency", currency)
|
||||||
|
vals.Set("prices", string(p))
|
||||||
|
return res, a.lclient.get(a.base, "createInvoiceLink", addValues(vals, opts), &res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnswerShippingQuery is used to reply to shipping queries.
|
||||||
|
// If you sent an invoice requesting a shipping address and the parameter is_flexible was specified,
|
||||||
|
// the Bot API will send an Update with a shipping_query field to the bot.
|
||||||
|
func (a API) AnswerShippingQuery(shippingQueryID string, ok bool, opts *ShippingQueryOptions) (res APIResponseBase, err error) {
|
||||||
|
var vals = make(url.Values)
|
||||||
|
|
||||||
|
vals.Set("shipping_query_id", shippingQueryID)
|
||||||
|
vals.Set("ok", btoa(ok))
|
||||||
|
return res, a.lclient.get(a.base, "answerShippingQuery", addValues(vals, opts), &res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnswerPreCheckoutQuery is used to respond to such pre-checkout queries.
|
||||||
|
// Once the user has confirmed their payment and shipping details,
|
||||||
|
// the Bot API sends the final confirmation in the form of an Update with the field pre_checkout_query.
|
||||||
|
// NOTE: The Bot API must receive an answer within 10 seconds after the pre-checkout query was sent.
|
||||||
|
func (a API) AnswerPreCheckoutQuery(preCheckoutQueryID string, ok bool, opts *PreCheckoutOptions) (res APIResponseBase, err error) {
|
||||||
|
var vals = make(url.Values)
|
||||||
|
|
||||||
|
vals.Set("pre_checkout_query_id", preCheckoutQueryID)
|
||||||
|
vals.Set("ok", btoa(ok))
|
||||||
|
return res, a.lclient.get(a.base, "answerPreCheckoutQuery", addValues(vals, opts), &res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStarTransactions returns the bot's Telegram Star transactions in chronological order.
|
||||||
|
func (a API) GetStarTransactions(opts *StarTransactionsOptions) (res APIResponseStarTransactions, err error) {
|
||||||
|
return res, a.lclient.get(a.base, "getStarTransactions", urlValues(opts), &res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RefundStarPayment refunds a successful payment in Telegram Stars.
|
||||||
|
func (a API) RefundStarPayment(userID int64, telegramPaymentChargeID string) (res APIResponseBool, err error) {
|
||||||
|
var vals = make(url.Values)
|
||||||
|
|
||||||
|
vals.Set("user_id", itoa(userID))
|
||||||
|
vals.Set("telegram_payment_charge_id", telegramPaymentChargeID)
|
||||||
|
return res, a.lclient.get(a.base, "refundStarPayment", vals, &res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EditUserStarSubscription allows the bot to cancel or re-enable extension of a subscription paid in Telegram Stars.
|
||||||
|
func (a API) EditUserStarSubscription(userID int64, telegramPaymentChargeID string, isCanceled bool) (res APIResponseBool, err error) {
|
||||||
|
var vals = make(url.Values)
|
||||||
|
|
||||||
|
vals.Set("user_id", itoa(userID))
|
||||||
|
vals.Set("telegram_payment_charge_id", telegramPaymentChargeID)
|
||||||
|
vals.Set("is_canceled", btoa(isCanceled))
|
||||||
|
return res, a.lclient.get(a.base, "editUserStarSubscription", vals, &res)
|
||||||
|
}
|
||||||
88
vendor/github.com/NicoNex/echotron/v3/querybuilder.go
generated
vendored
Normal file
88
vendor/github.com/NicoNex/echotron/v3/querybuilder.go
generated
vendored
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* Echotron
|
||||||
|
* Copyright (C) 2018 The Echotron Contributors
|
||||||
|
*
|
||||||
|
* Echotron is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Echotron is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package echotron
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func toString(v reflect.Value) string {
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
return v.String()
|
||||||
|
|
||||||
|
case reflect.Float64:
|
||||||
|
return strconv.FormatFloat(v.Float(), 'f', -1, 64)
|
||||||
|
|
||||||
|
case reflect.Int, reflect.Int64:
|
||||||
|
return strconv.FormatInt(v.Int(), 10)
|
||||||
|
|
||||||
|
case reflect.Bool:
|
||||||
|
return strconv.FormatBool(v.Bool())
|
||||||
|
|
||||||
|
case reflect.Struct, reflect.Interface, reflect.Slice, reflect.Array:
|
||||||
|
b, _ := json.Marshal(v.Interface())
|
||||||
|
return string(b)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func scan(i any, v url.Values) url.Values {
|
||||||
|
e := reflect.ValueOf(i)
|
||||||
|
|
||||||
|
if e.Kind() == reflect.Pointer {
|
||||||
|
e = e.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.Kind() == reflect.Invalid {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < e.NumField(); i++ {
|
||||||
|
fTag := e.Type().Field(i).Tag
|
||||||
|
|
||||||
|
if name := fTag.Get("query"); name != "" && !e.Field(i).IsZero() {
|
||||||
|
v.Set(name, toString(e.Field(i)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func urlValues(i any) url.Values {
|
||||||
|
if i == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return scan(i, url.Values{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func addValues(vals url.Values, i any) url.Values {
|
||||||
|
if i == nil {
|
||||||
|
return vals
|
||||||
|
}
|
||||||
|
if vals == nil {
|
||||||
|
vals = make(url.Values)
|
||||||
|
}
|
||||||
|
return scan(i, vals)
|
||||||
|
}
|
||||||
137
vendor/github.com/NicoNex/echotron/v3/simpledsp.go
generated
vendored
Normal file
137
vendor/github.com/NicoNex/echotron/v3/simpledsp.go
generated
vendored
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
/*
|
||||||
|
* Echotron
|
||||||
|
* Copyright (C) 2023 The Echotron Contributors
|
||||||
|
*
|
||||||
|
* Echotron is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Echotron is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package echotron
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PollingUpdates is a wrapper function for PollingUpdatesOptions.
|
||||||
|
func PollingUpdates(token string) <-chan *Update {
|
||||||
|
return PollingUpdatesOptions(token, true, UpdateOptions{Timeout: 120})
|
||||||
|
}
|
||||||
|
|
||||||
|
// PollingUpdatesOptions returns a read-only channel of incoming updates from the Telegram API.
|
||||||
|
func PollingUpdatesOptions(token string, dropPendingUpdates bool, opts UpdateOptions) <-chan *Update {
|
||||||
|
var updates = make(chan *Update)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer close(updates)
|
||||||
|
|
||||||
|
var (
|
||||||
|
api = NewAPI(token)
|
||||||
|
timeout = opts.Timeout
|
||||||
|
isFirstRun = true
|
||||||
|
)
|
||||||
|
|
||||||
|
// deletes webhook if present to run in long polling mode
|
||||||
|
if _, err := api.DeleteWebhook(dropPendingUpdates); err != nil {
|
||||||
|
log.Println("echotron.PollingUpdates", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
if isFirstRun {
|
||||||
|
opts.Timeout = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := api.GetUpdates(&opts)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("echotron.PollingUpdates", err)
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !dropPendingUpdates || !isFirstRun {
|
||||||
|
for _, u := range response.Result {
|
||||||
|
updates <- u
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if l := len(response.Result); l > 0 {
|
||||||
|
opts.Offset = response.Result[l-1].ID + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if isFirstRun {
|
||||||
|
isFirstRun = false
|
||||||
|
opts.Timeout = timeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return updates
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebhookUpdates is a wrapper function for WebhookUpdatesOptions.
|
||||||
|
func WebhookUpdates(url, token string) <-chan *Update {
|
||||||
|
return WebhookUpdatesOptions(url, token, false, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebhookUpdatesOptions returns a read-only channel of incoming updates from the Telegram API.
|
||||||
|
// The webhookUrl should be provided in the following format: '<hostname>:<port>/<path>',
|
||||||
|
// eg: 'https://example.com:443/bot_token'.
|
||||||
|
// WebhookUpdatesOptions will then proceed to communicate the webhook url '<hostname>/<path>'
|
||||||
|
// to Telegram and run a webserver that listens to ':<port>' and handles the path.
|
||||||
|
func WebhookUpdatesOptions(whURL, token string, dropPendingUpdates bool, opts *WebhookOptions) <-chan *Update {
|
||||||
|
u, err := url.Parse(whURL)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
wURL := u.Hostname() + u.EscapedPath()
|
||||||
|
api := NewAPI(token)
|
||||||
|
if _, err := api.SetWebhook(wURL, dropPendingUpdates, opts); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var updates = make(chan *Update)
|
||||||
|
http.HandleFunc(u.EscapedPath(), func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var update Update
|
||||||
|
|
||||||
|
jsn, err := readRequest(r)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("echotron.WebhookUpdates", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(jsn, &update); err != nil {
|
||||||
|
log.Println("echotron.WebhookUpdates", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
updates <- &update
|
||||||
|
})
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer close(updates)
|
||||||
|
port := fmt.Sprintf(":%s", u.Port())
|
||||||
|
for {
|
||||||
|
if err := http.ListenAndServe(port, nil); err != nil {
|
||||||
|
log.Println("echotron.WebhookUpdates", err)
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return updates
|
||||||
|
}
|
||||||
26
vendor/github.com/NicoNex/echotron/v3/smap.go
generated
vendored
Normal file
26
vendor/github.com/NicoNex/echotron/v3/smap.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package echotron
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
type smap[K, V any] sync.Map
|
||||||
|
|
||||||
|
func (s *smap[K, V]) load(key K) (val V, ok bool) {
|
||||||
|
v, ok := (*sync.Map)(s).Load(key)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return v.(V), ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *smap[K, V]) store(key K, val V) {
|
||||||
|
(*sync.Map)(s).Store(key, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *smap[K, V]) loadOrStore(key K, val V) (actual V, loaded bool) {
|
||||||
|
a, loaded := (*sync.Map)(s).LoadOrStore(key, val)
|
||||||
|
return a.(V), loaded
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *smap[K, V]) delete(key K) {
|
||||||
|
(*sync.Map)(s).Delete(key)
|
||||||
|
}
|
||||||
271
vendor/github.com/NicoNex/echotron/v3/stickers.go
generated
vendored
Normal file
271
vendor/github.com/NicoNex/echotron/v3/stickers.go
generated
vendored
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
/*
|
||||||
|
* Echotron
|
||||||
|
* Copyright (C) 2018 The Echotron Contributors
|
||||||
|
*
|
||||||
|
* Echotron is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Echotron is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package echotron
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Sticker represents a sticker.
|
||||||
|
type Sticker struct {
|
||||||
|
Thumbnail *PhotoSize `json:"thumbnail,omitempty"`
|
||||||
|
MaskPosition *MaskPosition `json:"mask_position,omitempty"`
|
||||||
|
Type StickerSetType `json:"type"`
|
||||||
|
FileUniqueID string `json:"file_unique_id"`
|
||||||
|
SetName string `json:"set_name,omitempty"`
|
||||||
|
FileID string `json:"file_id"`
|
||||||
|
Emoji string `json:"emoji,omitempty"`
|
||||||
|
CustomEmojiID string `json:"custom_emoji_id,omitempty"`
|
||||||
|
PremiumAnimation File `json:"premium_animation,omitempty"`
|
||||||
|
FileSize int `json:"file_size,omitempty"`
|
||||||
|
Width int `json:"width"`
|
||||||
|
Height int `json:"height"`
|
||||||
|
IsVideo bool `json:"is_video"`
|
||||||
|
IsAnimated bool `json:"is_animated"`
|
||||||
|
NeedsRepainting bool `json:"needs_repainting,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// StickerSet represents a sticker set.
|
||||||
|
type StickerSet struct {
|
||||||
|
Thumbnail *PhotoSize `json:"thumbnail,omitempty"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
StickerType StickerSetType `json:"sticker_type"`
|
||||||
|
Stickers []Sticker `json:"stickers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// StickerSetType represents the type of a sticker or of the entire set
|
||||||
|
type StickerSetType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
RegularStickerSet StickerSetType = "regular"
|
||||||
|
MaskStickerSet = "mask"
|
||||||
|
CustomEmojiStickerSet = "custom_emoji"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StickerFormat is a custom type for the various sticker formats.
|
||||||
|
type StickerFormat string
|
||||||
|
|
||||||
|
// These are all the possible sticker formats.
|
||||||
|
const (
|
||||||
|
StaticFormat StickerFormat = "static"
|
||||||
|
AnimatedFormat = "animated"
|
||||||
|
VideoFormat = "video"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MaskPosition describes the position on faces where a mask should be placed by default.
|
||||||
|
type MaskPosition struct {
|
||||||
|
Point MaskPoint `json:"point"`
|
||||||
|
XShift float32 `json:"x_shift"`
|
||||||
|
YShift float32 `json:"y_shift"`
|
||||||
|
Scale float32 `json:"scale"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaskPoint is a custom type for the various part of face where a mask should be placed.
|
||||||
|
type MaskPoint string
|
||||||
|
|
||||||
|
// These are all the possible parts of the face for a mask.
|
||||||
|
const (
|
||||||
|
ForeheadPoint MaskPoint = "forehead"
|
||||||
|
EyesPoint = "eyes"
|
||||||
|
MouthPoint = "mouth"
|
||||||
|
ChinPoint = "chin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewStickerSetOptions contains the optional parameters used in the CreateNewStickerSet method.
|
||||||
|
type NewStickerSetOptions struct {
|
||||||
|
StickerType StickerSetType `query:"sticker_type"`
|
||||||
|
NeedsRepainting bool `query:"needs_repainting"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// InputSticker is a struct which describes a sticker to be added to a sticker set.
|
||||||
|
type InputSticker struct {
|
||||||
|
MaskPosition *MaskPosition `json:"mask_position,omitempty"`
|
||||||
|
Keywords *[]string `json:"keywords,omitempty"`
|
||||||
|
Format StickerFormat `json:"format"`
|
||||||
|
Sticker InputFile `json:"-"`
|
||||||
|
EmojiList []string `json:"emoji_list"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// stickerEnvelope is a generic struct for all the various structs under the InputSticker interface.
|
||||||
|
type stickerEnvelope struct {
|
||||||
|
Sticker string `json:"sticker"`
|
||||||
|
InputSticker
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendSticker is used to send static .WEBP or animated .TGS stickers.
|
||||||
|
func (a API) SendSticker(stickerID string, chatID int64, opts *StickerOptions) (res APIResponseMessage, err error) {
|
||||||
|
var vals = make(url.Values)
|
||||||
|
|
||||||
|
vals.Set("sticker", stickerID)
|
||||||
|
vals.Set("chat_id", itoa(chatID))
|
||||||
|
return res, a.lclient.get(a.base, "sendSticker", addValues(vals, opts), &res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStickerSet is used to get a sticker set.
|
||||||
|
func (a API) GetStickerSet(name string) (res APIResponseStickerSet, err error) {
|
||||||
|
var vals = make(url.Values)
|
||||||
|
|
||||||
|
vals.Set("name", name)
|
||||||
|
return res, a.lclient.get(a.base, "getStickerSet", vals, &res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCustomEmojiStickers is used to get information about custom emoji stickers by their identifiers.
|
||||||
|
func (a API) GetCustomEmojiStickers(customEmojiIDs ...string) (res APIResponseStickers, err error) {
|
||||||
|
var vals = make(url.Values)
|
||||||
|
|
||||||
|
jsn, _ := json.Marshal(customEmojiIDs)
|
||||||
|
vals.Set("custom_emoji_ids", string(jsn))
|
||||||
|
return res, a.lclient.get(a.base, "getCustomEmojiStickers", vals, &res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UploadStickerFile is used to upload a .PNG file with a sticker for later use in
|
||||||
|
// CreateNewStickerSet and AddStickerToSet methods (can be used multiple times).
|
||||||
|
func (a API) UploadStickerFile(userID int64, sticker InputFile, format StickerFormat) (res APIResponseFile, err error) {
|
||||||
|
var vals = make(url.Values)
|
||||||
|
|
||||||
|
vals.Set("user_id", itoa(userID))
|
||||||
|
vals.Set("sticker_format", string(format))
|
||||||
|
return res, a.lclient.postFile(a.base, "uploadStickerFile", "sticker", sticker, InputFile{}, vals, &res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateNewStickerSet is used to create a new sticker set owned by a user.
|
||||||
|
func (a API) CreateNewStickerSet(userID int64, name, title string, stickers []InputSticker, opts *NewStickerSetOptions) (res APIResponseBool, err error) {
|
||||||
|
var vals = make(url.Values)
|
||||||
|
|
||||||
|
vals.Set("user_id", itoa(userID))
|
||||||
|
vals.Set("name", name)
|
||||||
|
vals.Set("title", title)
|
||||||
|
return res, a.lclient.postStickers(a.base, "createNewStickerSet", addValues(vals, opts), &res, stickers...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddStickerToSet is used to add a new sticker to a set created by the bot.
|
||||||
|
func (a API) AddStickerToSet(userID int64, name string, sticker InputSticker) (res APIResponseBool, err error) {
|
||||||
|
var vals = make(url.Values)
|
||||||
|
|
||||||
|
vals.Set("user_id", itoa(userID))
|
||||||
|
vals.Set("name", name)
|
||||||
|
return res, a.lclient.postStickers(a.base, "addStickerToSet", vals, &res, sticker)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStickerPositionInSet is used to move a sticker in a set created by the bot to a specific position.
|
||||||
|
func (a API) SetStickerPositionInSet(sticker string, position int) (res APIResponseBase, err error) {
|
||||||
|
var vals = make(url.Values)
|
||||||
|
|
||||||
|
vals.Set("sticker", sticker)
|
||||||
|
vals.Set("position", itoa(int64(position)))
|
||||||
|
return res, a.lclient.get(a.base, "setStickerPositionInSet", vals, &res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteStickerFromSet is used to delete a sticker from a set created by the bot.
|
||||||
|
func (a API) DeleteStickerFromSet(sticker string) (res APIResponseBase, err error) {
|
||||||
|
var vals = make(url.Values)
|
||||||
|
|
||||||
|
vals.Set("sticker", sticker)
|
||||||
|
return res, a.lclient.get(a.base, "deleteStickerFromSet", vals, &res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplaceStickerInSet is used to replace an existing sticker in a sticker set with a new one.
|
||||||
|
// The method is equivalent to calling DeleteStickerFromSet, then AddStickerToSet, then SetStickerPositionInSet.
|
||||||
|
func (a API) ReplaceStickerInSet(userID int64, name string, old_sticker string, sticker InputSticker) (res APIResponseBool, err error) {
|
||||||
|
var vals = make(url.Values)
|
||||||
|
|
||||||
|
vals.Set("user_id", itoa(userID))
|
||||||
|
vals.Set("name", name)
|
||||||
|
vals.Set("old_sticker", old_sticker)
|
||||||
|
return res, a.lclient.postStickers(a.base, "replaceStickerInSet", vals, &res, sticker)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStickerEmojiList is used to change the list of emoji assigned to a regular or custom emoji sticker.
|
||||||
|
// The sticker must belong to a sticker set created by the bot.
|
||||||
|
func (a API) SetStickerEmojiList(sticker string, emojis []string) (res APIResponseBool, err error) {
|
||||||
|
var vals = make(url.Values)
|
||||||
|
|
||||||
|
jsn, _ := json.Marshal(emojis)
|
||||||
|
|
||||||
|
vals.Set("sticker", sticker)
|
||||||
|
vals.Set("emoji_list", string(jsn))
|
||||||
|
return res, a.lclient.get(a.base, "setStickerEmojiList", vals, &res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStickerKeywords is used to change search keywords assigned to a regular or custom emoji sticker.
|
||||||
|
// The sticker must belong to a sticker set created by the bot.
|
||||||
|
func (a API) SetStickerKeywords(sticker string, keywords []string) (res APIResponseBool, err error) {
|
||||||
|
var vals = make(url.Values)
|
||||||
|
|
||||||
|
jsn, _ := json.Marshal(keywords)
|
||||||
|
|
||||||
|
vals.Set("sticker", sticker)
|
||||||
|
vals.Set("keywords", string(jsn))
|
||||||
|
return res, a.lclient.get(a.base, "setStickerKeywords", vals, &res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStickerMaskPosition is used to change the mask position of a mask sticker.
|
||||||
|
// The sticker must belong to a sticker set that was created by the bot.
|
||||||
|
func (a API) SetStickerMaskPosition(sticker string, mask MaskPosition) (res APIResponseBool, err error) {
|
||||||
|
var vals = make(url.Values)
|
||||||
|
|
||||||
|
jsn, _ := json.Marshal(mask)
|
||||||
|
|
||||||
|
vals.Set("sticker", sticker)
|
||||||
|
vals.Set("mask_position", string(jsn))
|
||||||
|
return res, a.lclient.get(a.base, "setStickerMaskPosition", vals, &res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStickerSetTitle is used to set the title of a created sticker set.
|
||||||
|
func (a API) SetStickerSetTitle(name, title string) (res APIResponseBool, err error) {
|
||||||
|
var vals = make(url.Values)
|
||||||
|
|
||||||
|
vals.Set("name", name)
|
||||||
|
vals.Set("title", title)
|
||||||
|
return res, a.lclient.get(a.base, "setStickerSetTitle", vals, &res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStickerSetThumbnail is used to set the thumbnail of a sticker set.
|
||||||
|
func (a API) SetStickerSetThumbnail(name string, userID int64, thumbnail InputFile, format StickerFormat) (res APIResponseBase, err error) {
|
||||||
|
var vals = make(url.Values)
|
||||||
|
|
||||||
|
vals.Set("name", name)
|
||||||
|
vals.Set("user_id", itoa(userID))
|
||||||
|
vals.Set("format", string(format))
|
||||||
|
return res, a.lclient.postFile(a.base, "setStickerSetThumbnail", "thumbnail", thumbnail, InputFile{}, vals, &res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCustomEmojiStickerSetThumbnail is used to set the thumbnail of a custom emoji sticker set.
|
||||||
|
func (a API) SetCustomEmojiStickerSetThumbnail(name, emojiID string) (res APIResponseBool, err error) {
|
||||||
|
var vals = make(url.Values)
|
||||||
|
|
||||||
|
vals.Set("name", name)
|
||||||
|
vals.Set("custom_emoji_id", emojiID)
|
||||||
|
return res, a.lclient.get(a.base, "setCustomEmojiStickerSetThumbnail", vals, &res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteStickerSet is used to delete a sticker set that was created by the bot.
|
||||||
|
func (a API) DeleteStickerSet(name string) (res APIResponseBool, err error) {
|
||||||
|
var vals = make(url.Values)
|
||||||
|
|
||||||
|
vals.Set("name", name)
|
||||||
|
return res, a.lclient.get(a.base, "DeleteStickerSet", vals, &res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetForumTopicIconStickers is used to get custom emoji stickers, which can be used as a forum topic icon by any user.
|
||||||
|
func (a API) GetForumTopicIconStickers() (res APIResponseStickers, err error) {
|
||||||
|
return res, a.lclient.get(a.base, "getForumTopicIconStickers", nil, &res)
|
||||||
|
}
|
||||||
2427
vendor/github.com/NicoNex/echotron/v3/types.go
generated
vendored
Normal file
2427
vendor/github.com/NicoNex/echotron/v3/types.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
57
vendor/github.com/NicoNex/echotron/v3/webapp.go
generated
vendored
Normal file
57
vendor/github.com/NicoNex/echotron/v3/webapp.go
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* Echotron
|
||||||
|
* Copyright (C) 2022 The Echotron Contributors
|
||||||
|
*
|
||||||
|
* Echotron is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Echotron is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package echotron
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WebAppInfo contains information about a Web App.
|
||||||
|
type WebAppInfo struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SentWebAppMessage contains information about an inline message sent
|
||||||
|
// by a Web App on behalf of a user.
|
||||||
|
type SentWebAppMessage struct {
|
||||||
|
InlineMessageID string `json:"inline_message_id,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebAppData contains data sent from a Web App to the bot.
|
||||||
|
type WebAppData struct {
|
||||||
|
Data string `json:"data"`
|
||||||
|
ButtonText string `json:"button_text"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnswerWebAppQuery is used to set the result of an interaction with a Web App
|
||||||
|
// and send a corresponding message on behalf of the user to the chat from which
|
||||||
|
// the query originated.
|
||||||
|
func (a API) AnswerWebAppQuery(webAppQueryID string, result InlineQueryResult) (res APIResponseSentWebAppMessage, err error) {
|
||||||
|
var vals = make(url.Values)
|
||||||
|
|
||||||
|
resultJson, err := json.Marshal(result)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
vals.Set("web_app_query_id", webAppQueryID)
|
||||||
|
vals.Set("result", string(resultJson))
|
||||||
|
return res, a.lclient.get(a.base, "answerWebAppQuery", vals, &res)
|
||||||
|
}
|
||||||
1
vendor/github.com/carlmjohnson/requests/.gitignore
generated
vendored
Normal file
1
vendor/github.com/carlmjohnson/requests/.gitignore
generated
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
img/
|
||||||
21
vendor/github.com/carlmjohnson/requests/LICENSE
generated
vendored
Normal file
21
vendor/github.com/carlmjohnson/requests/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2021 Carl Johnson
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
314
vendor/github.com/carlmjohnson/requests/README.md
generated
vendored
Normal file
314
vendor/github.com/carlmjohnson/requests/README.md
generated
vendored
Normal file
@@ -0,0 +1,314 @@
|
|||||||
|
# Requests [](https://godoc.org/github.com/carlmjohnson/requests) [](https://goreportcard.com/report/github.com/carlmjohnson/requests) [](https://coveralls.io/github/earthboundkid/requests) [](https://github.com/avelino/awesome-go)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## _HTTP requests for Gophers._
|
||||||
|
|
||||||
|
**The problem**: Go's net/http is powerful and versatile, but using it correctly for client requests can be extremely verbose.
|
||||||
|
|
||||||
|
**The solution**: The requests.Builder type is a convenient way to build, send, and handle HTTP requests. Builder has a fluent API with methods returning a pointer to the same struct, which allows for declaratively describing a request by method chaining.
|
||||||
|
|
||||||
|
Requests also comes with tools for building custom http transports, include a request recorder and replayer for testing.
|
||||||
|
|
||||||
|
*[See this note on the canonical project URL.](https://gist.github.com/earthboundkid/8915002ae0e531cecdfc58bc6453ac80)*
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Simplifies HTTP client usage compared to net/http
|
||||||
|
- Can't forget to close response body
|
||||||
|
- Checks status codes by default
|
||||||
|
- Supports context.Context
|
||||||
|
- JSON serialization and deserialization helpers
|
||||||
|
- Easily manipulate URLs and query parameters
|
||||||
|
- Request recording and replaying for tests
|
||||||
|
- Customizable transports and validators that are compatible with the standard library and third party libraries
|
||||||
|
- No third party dependencies
|
||||||
|
- Good test coverage
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
### Simple GET into a string
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th><strong>code with net/http</strong></th>
|
||||||
|
<th><strong>code with requests</strong></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
```go
|
||||||
|
req, err := http.NewRequestWithContext(ctx,
|
||||||
|
http.MethodGet, "http://example.com", nil)
|
||||||
|
if err != nil {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
res, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
b, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
s := string(b)
|
||||||
|
```
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
```go
|
||||||
|
var s string
|
||||||
|
err := requests.
|
||||||
|
URL("http://example.com").
|
||||||
|
ToString(&s).
|
||||||
|
Fetch(ctx)
|
||||||
|
```
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td>11+ lines</td><td>5 lines</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
### POST a raw body
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th><strong>code with net/http</strong></th>
|
||||||
|
<th><strong>code with requests</strong></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
```go
|
||||||
|
body := bytes.NewReader(([]byte(`hello, world`))
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost,
|
||||||
|
"https://postman-echo.com/post", body)
|
||||||
|
if err != nil {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "text/plain")
|
||||||
|
res, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
_, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
```go
|
||||||
|
err := requests.
|
||||||
|
URL("https://postman-echo.com/post").
|
||||||
|
BodyBytes([]byte(`hello, world`)).
|
||||||
|
ContentType("text/plain").
|
||||||
|
Fetch(ctx)
|
||||||
|
```
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td>12+ lines</td><td>5 lines</td></tr></tbody></table>
|
||||||
|
|
||||||
|
### GET a JSON object
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th><strong>code with net/http</strong></th>
|
||||||
|
<th><strong>code with requests</strong></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
```go
|
||||||
|
var post placeholder
|
||||||
|
u, err := url.Parse("https://jsonplaceholder.typicode.com")
|
||||||
|
if err != nil {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
u.Path = fmt.Sprintf("/posts/%d", 1)
|
||||||
|
req, err := http.NewRequestWithContext(ctx,
|
||||||
|
http.MethodGet, u.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
res, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
b, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
err := json.Unmarshal(b, &post)
|
||||||
|
if err != nil {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</td><td>
|
||||||
|
|
||||||
|
```go
|
||||||
|
var post placeholder
|
||||||
|
err := requests.
|
||||||
|
URL("https://jsonplaceholder.typicode.com").
|
||||||
|
Pathf("/posts/%d", 1).
|
||||||
|
ToJSON(&post).
|
||||||
|
Fetch(ctx)
|
||||||
|
```
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td>18+ lines</td><td>7 lines</td></tr></tbody></table>
|
||||||
|
|
||||||
|
### POST a JSON object and parse the response
|
||||||
|
|
||||||
|
```go
|
||||||
|
var res placeholder
|
||||||
|
req := placeholder{
|
||||||
|
Title: "foo",
|
||||||
|
Body: "baz",
|
||||||
|
UserID: 1,
|
||||||
|
}
|
||||||
|
err := requests.
|
||||||
|
URL("/posts").
|
||||||
|
Host("jsonplaceholder.typicode.com").
|
||||||
|
BodyJSON(&req).
|
||||||
|
ToJSON(&res).
|
||||||
|
Fetch(ctx)
|
||||||
|
// net/http equivalent left as an exercise for the reader
|
||||||
|
```
|
||||||
|
|
||||||
|
### Set custom headers for a request
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Set headers
|
||||||
|
var headers postman
|
||||||
|
err := requests.
|
||||||
|
URL("https://postman-echo.com/get").
|
||||||
|
UserAgent("bond/james-bond").
|
||||||
|
ContentType("secret").
|
||||||
|
Header("martini", "shaken").
|
||||||
|
Fetch(ctx)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Easily manipulate URLs and query parameters
|
||||||
|
|
||||||
|
```go
|
||||||
|
u, err := requests.
|
||||||
|
URL("https://prod.example.com/get?a=1&b=2").
|
||||||
|
Hostf("%s.example.com", "dev1").
|
||||||
|
Param("b", "3").
|
||||||
|
ParamInt("c", 4).
|
||||||
|
URL()
|
||||||
|
if err != nil { /* ... */ }
|
||||||
|
fmt.Println(u.String()) // https://dev1.example.com/get?a=1&b=3&c=4
|
||||||
|
```
|
||||||
|
|
||||||
|
### Record and replay responses
|
||||||
|
|
||||||
|
```go
|
||||||
|
// record a request to the file system
|
||||||
|
var s1, s2 string
|
||||||
|
err := requests.URL("http://example.com").
|
||||||
|
Transport(reqtest.Record(nil, "somedir")).
|
||||||
|
ToString(&s1).
|
||||||
|
Fetch(ctx)
|
||||||
|
check(err)
|
||||||
|
|
||||||
|
// now replay the request in tests
|
||||||
|
err = requests.URL("http://example.com").
|
||||||
|
Transport(reqtest.Replay("somedir")).
|
||||||
|
ToString(&s2).
|
||||||
|
Fetch(ctx)
|
||||||
|
check(err)
|
||||||
|
assert(s1 == s2) // true
|
||||||
|
```
|
||||||
|
|
||||||
|
## FAQs
|
||||||
|
|
||||||
|
[See wiki](https://github.com/earthboundkid/requests/wiki) for more details.
|
||||||
|
|
||||||
|
### Why not just use the standard library HTTP client?
|
||||||
|
|
||||||
|
Brad Fitzpatrick, long time maintainer of the net/http package, [wrote an extensive list of problems with the standard library HTTP client](https://github.com/bradfitz/exp-httpclient/blob/master/problems.md). His four main points (ignoring issues that can't be resolved by a wrapper around the standard library) are:
|
||||||
|
|
||||||
|
> - Too easy to not call Response.Body.Close.
|
||||||
|
> - Too easy to not check return status codes
|
||||||
|
> - Context support is oddly bolted on
|
||||||
|
> - Proper usage is too many lines of boilerplate
|
||||||
|
|
||||||
|
Requests solves these issues by always closing the response body, checking status codes by default, always requiring a `context.Context`, and simplifying the boilerplate with a descriptive UI based on fluent method chaining.
|
||||||
|
|
||||||
|
### Why requests and not some other helper library?
|
||||||
|
|
||||||
|
There are two major flaws in other libraries as I see it. One is that in other libraries support for `context.Context` tends to be bolted on if it exists at all. Two, many hide the underlying `http.Client` in such a way that it is difficult or impossible to replace or mock out. Beyond that, I believe that none have achieved the same core simplicity that the requests library has.
|
||||||
|
|
||||||
|
### How do I just get some JSON?
|
||||||
|
|
||||||
|
```go
|
||||||
|
var data SomeDataType
|
||||||
|
err := requests.
|
||||||
|
URL("https://example.com/my-json").
|
||||||
|
ToJSON(&data).
|
||||||
|
Fetch(ctx)
|
||||||
|
```
|
||||||
|
|
||||||
|
### How do I post JSON and read the response JSON?
|
||||||
|
|
||||||
|
```go
|
||||||
|
body := MyRequestType{}
|
||||||
|
var resp MyResponseType
|
||||||
|
err := requests.
|
||||||
|
URL("https://example.com/my-json").
|
||||||
|
BodyJSON(&body).
|
||||||
|
ToJSON(&resp).
|
||||||
|
Fetch(ctx)
|
||||||
|
```
|
||||||
|
|
||||||
|
### How do I just save a file to disk?
|
||||||
|
|
||||||
|
It depends on exactly what you need in terms of file atomicity and buffering, but this will work for most cases:
|
||||||
|
|
||||||
|
```go
|
||||||
|
err := requests.
|
||||||
|
URL("http://example.com").
|
||||||
|
ToFile("myfile.txt").
|
||||||
|
Fetch(ctx)
|
||||||
|
```
|
||||||
|
|
||||||
|
For more advanced use case, use `ToWriter`.
|
||||||
|
|
||||||
|
### How do I save a response to a string?
|
||||||
|
|
||||||
|
```go
|
||||||
|
var s string
|
||||||
|
err := requests.
|
||||||
|
URL("http://example.com").
|
||||||
|
ToString(&s).
|
||||||
|
Fetch(ctx)
|
||||||
|
```
|
||||||
|
|
||||||
|
### How do I validate the response status?
|
||||||
|
|
||||||
|
By default, if no other validators are added to a builder, requests will check that the response is in the 2XX range. If you add another validator, you can add `builder.CheckStatus(200)` or `builder.AddValidator(requests.DefaultValidator)` to the validation stack.
|
||||||
|
|
||||||
|
To disable all response validation, run `builder.AddValidator(nil)`.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Please [create a discussion](https://github.com/earthboundkid/requests/discussions) before submitting a pull request for a new feature.
|
||||||
9
vendor/github.com/carlmjohnson/requests/SECURITY.md
generated
vendored
Normal file
9
vendor/github.com/carlmjohnson/requests/SECURITY.md
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Supported Versions
|
||||||
|
|
||||||
|
Only the most recently tagged version of this repository is supported.
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
Email me@carlmjohnson.net with any security concerns or vulnerabilities.
|
||||||
77
vendor/github.com/carlmjohnson/requests/body.go
generated
vendored
Normal file
77
vendor/github.com/carlmjohnson/requests/body.go
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package requests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BodyGetter provides a Builder with a source for a request body.
|
||||||
|
type BodyGetter = func() (io.ReadCloser, error)
|
||||||
|
|
||||||
|
// BodyReader is a BodyGetter that returns an io.Reader.
|
||||||
|
func BodyReader(r io.Reader) BodyGetter {
|
||||||
|
return func() (io.ReadCloser, error) {
|
||||||
|
if rc, ok := r.(io.ReadCloser); ok {
|
||||||
|
return rc, nil
|
||||||
|
}
|
||||||
|
return rc(r), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BodyWriter is a BodyGetter that pipes writes into a request body.
|
||||||
|
func BodyWriter(f func(w io.Writer) error) BodyGetter {
|
||||||
|
return func() (io.ReadCloser, error) {
|
||||||
|
r, w := io.Pipe()
|
||||||
|
go func() {
|
||||||
|
var err error
|
||||||
|
defer func() {
|
||||||
|
w.CloseWithError(err)
|
||||||
|
}()
|
||||||
|
err = f(w)
|
||||||
|
}()
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BodyBytes is a BodyGetter that returns the provided raw bytes.
|
||||||
|
func BodyBytes(b []byte) BodyGetter {
|
||||||
|
return func() (io.ReadCloser, error) {
|
||||||
|
return rc(bytes.NewReader(b)), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BodySerializer is a BodyGetter
|
||||||
|
// that uses the provided [Serializer]
|
||||||
|
// to build the body of a request from v.
|
||||||
|
func BodySerializer(s Serializer, v any) BodyGetter {
|
||||||
|
return func() (io.ReadCloser, error) {
|
||||||
|
b, err := s(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return rc(bytes.NewReader(b)), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BodyJSON is a [BodySerializer]
|
||||||
|
// that uses [JSONSerializer] to marshal the object.
|
||||||
|
func BodyJSON(v any) BodyGetter {
|
||||||
|
return BodySerializer(JSONSerializer, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BodyForm is a BodyGetter that builds an encoded form body.
|
||||||
|
func BodyForm(data url.Values) BodyGetter {
|
||||||
|
return func() (r io.ReadCloser, err error) {
|
||||||
|
return rc(strings.NewReader(data.Encode())), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BodyFile is a BodyGetter that reads the provided file path.
|
||||||
|
func BodyFile(name string) BodyGetter {
|
||||||
|
return func() (r io.ReadCloser, err error) {
|
||||||
|
return os.Open(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
281
vendor/github.com/carlmjohnson/requests/builder_core.go
generated
vendored
Normal file
281
vendor/github.com/carlmjohnson/requests/builder_core.go
generated
vendored
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
package requests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cmp"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"github.com/carlmjohnson/requests/internal/minitrue"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Builder is a convenient way to build, send, and handle HTTP requests.
|
||||||
|
// Builder has a fluent API with methods returning a pointer to the same
|
||||||
|
// struct, which allows for declaratively describing a request by method chaining.
|
||||||
|
//
|
||||||
|
// Builder can build a url.URL,
|
||||||
|
// build an http.Request,
|
||||||
|
// or handle a full http.Client request and response with validation.
|
||||||
|
//
|
||||||
|
// # Build a url.URL with Builder.URL
|
||||||
|
//
|
||||||
|
// Set the base URL by creating a new Builder with [requests.URL]
|
||||||
|
// or by calling [Builder.BaseURL]
|
||||||
|
// then customize it with
|
||||||
|
// [Builder.Scheme], [Builder.Host], [Builder.Hostf], [Builder.Path],
|
||||||
|
// [Builder.Pathf], [Builder.Param], and [Builder.ParamInt].
|
||||||
|
// [Builder.ParamOptional] can be used to add a query parameter
|
||||||
|
// only if it has not been otherwise set.
|
||||||
|
//
|
||||||
|
// # Build an http.Request with Builder.Request
|
||||||
|
//
|
||||||
|
// Set the method for a request with [Builder.Method]
|
||||||
|
// or use the [Builder.Delete], [Builder.Head], [Builder.Patch], [Builder.Post], and [Builder.Put] methods.
|
||||||
|
// By default, requests without a body are GET,
|
||||||
|
// and those with a body are POST.
|
||||||
|
//
|
||||||
|
// Set headers with [Builder.Header]
|
||||||
|
// or set conventional header keys with
|
||||||
|
// [Builder.Accept], [Builder.BasicAuth], [Builder.Bearer], [Builder.CacheControl],
|
||||||
|
// [Builder.ContentType], [Builder.Cookie], and [Builder.UserAgent].
|
||||||
|
// [Builder.HeaderOptional] can be used to add a header
|
||||||
|
// only if it has not been otherwise set.
|
||||||
|
//
|
||||||
|
// Set the body of the request, if any, with [Builder.Body]
|
||||||
|
// or use built in [Builder.BodyBytes], [Builder.BodyFile], [Builder.BodyForm],
|
||||||
|
// [Builder.BodyJSON], [Builder.BodyReader], or [Builder.BodyWriter].
|
||||||
|
//
|
||||||
|
// # Handle a request and response with Builder.Do or Builder.Fetch
|
||||||
|
//
|
||||||
|
// Set the http.Client to use for a request with [Builder.Client]
|
||||||
|
// and/or set an http.RoundTripper with [Builder.Transport].
|
||||||
|
//
|
||||||
|
// Add a response validator to the Builder with [Builder.AddValidator]
|
||||||
|
// or use the built in [Builder.CheckStatus], [Builder.CheckContentType],
|
||||||
|
// [Builder.CheckPeek], [Builder.CopyHeaders], and [Builder.ErrorJSON].
|
||||||
|
// If no validator has been added, Builder will use [DefaultValidator].
|
||||||
|
//
|
||||||
|
// Set a handler for a response with [Builder.Handle]
|
||||||
|
// or use the built in [Builder.ToHeaders], [Builder.ToJSON], [Builder.ToString],
|
||||||
|
// [Builder.ToBytesBuffer], or [Builder.ToWriter].
|
||||||
|
//
|
||||||
|
// [Builder.Fetch] creates an http.Request with [Builder.Request]
|
||||||
|
// and validates and handles it with [Builder.Do].
|
||||||
|
//
|
||||||
|
// # Other methods
|
||||||
|
//
|
||||||
|
// [Builder.Config] can be used to set several options on a Builder at once.
|
||||||
|
// [New] creates a new Builder and applies [Config] options to it.
|
||||||
|
//
|
||||||
|
// In many cases, it will be possible to set most options for an API endpoint
|
||||||
|
// in a Builder at the package or struct level
|
||||||
|
// and then call [Builder.Clone] in a function
|
||||||
|
// to add request specific details for the URL, parameters, headers, body, or handler.
|
||||||
|
//
|
||||||
|
// Errors returned by Builder methods will have an [ErrorKind] indicating their origin.
|
||||||
|
//
|
||||||
|
// The zero value of Builder is usable.
|
||||||
|
type Builder struct {
|
||||||
|
ub urlBuilder
|
||||||
|
rb requestBuilder
|
||||||
|
cl *http.Client
|
||||||
|
rt http.RoundTripper
|
||||||
|
validators []ResponseHandler
|
||||||
|
handler ResponseHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
// BaseURL sets the base URL that other URL methods modify.
|
||||||
|
// It is usually more convenient to use [URL] instead.
|
||||||
|
func (rb *Builder) BaseURL(baseurl string) *Builder {
|
||||||
|
rb.ub.BaseURL(baseurl)
|
||||||
|
return rb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scheme sets the scheme for a Builder's URL.
|
||||||
|
// It overrides the scheme set by BaseURL.
|
||||||
|
func (rb *Builder) Scheme(scheme string) *Builder {
|
||||||
|
rb.ub.Scheme(scheme)
|
||||||
|
return rb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Host sets the host for a Builder's URL.
|
||||||
|
// It overrides the host set by BaseURL.
|
||||||
|
func (rb *Builder) Host(host string) *Builder {
|
||||||
|
rb.ub.Host(host)
|
||||||
|
return rb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path joins a path to a Builder's URL per the path joining rules of RFC 3986.
|
||||||
|
// If the path begins with /, it overrides any existing path.
|
||||||
|
// If the path begins with ./ or ../, the final path will be rewritten in its absolute form when creating a request.
|
||||||
|
func (rb *Builder) Path(path string) *Builder {
|
||||||
|
rb.ub.Path(path)
|
||||||
|
return rb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Param sets a query parameter on a Builder's URL.
|
||||||
|
// It overwrites the existing values of a key.
|
||||||
|
func (rb *Builder) Param(key string, values ...string) *Builder {
|
||||||
|
rb.ub.Param(key, values...)
|
||||||
|
return rb
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParamOptional sets a query parameter on a Builder's URL
|
||||||
|
// only if it is not set by some other call to Param or ParamOptional
|
||||||
|
// and one of the values is a non-blank string.
|
||||||
|
func (rb *Builder) ParamOptional(key string, values ...string) *Builder {
|
||||||
|
rb.ub.ParamOptional(key, values...)
|
||||||
|
return rb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Header sets a header on a request. It overwrites the existing values of a key.
|
||||||
|
func (rb *Builder) Header(key string, values ...string) *Builder {
|
||||||
|
rb.rb.Header(key, values...)
|
||||||
|
return rb
|
||||||
|
}
|
||||||
|
|
||||||
|
// HeaderOptional sets a header on a request
|
||||||
|
// only if it has not already been set by another call to Header or HeaderOptional
|
||||||
|
// and one of the values is a non-blank string.
|
||||||
|
func (rb *Builder) HeaderOptional(key string, values ...string) *Builder {
|
||||||
|
rb.rb.HeaderOptional(key, values...)
|
||||||
|
return rb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cookie adds a cookie to a request.
|
||||||
|
// Unlike other headers, adding a cookie does not overwrite existing values.
|
||||||
|
func (rb *Builder) Cookie(name, value string) *Builder {
|
||||||
|
rb.rb.Cookie(name, value)
|
||||||
|
return rb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method sets the HTTP method for a request.
|
||||||
|
// By default, requests without a body are GET,
|
||||||
|
// and those with a body are POST.
|
||||||
|
func (rb *Builder) Method(method string) *Builder {
|
||||||
|
rb.rb.Method(method)
|
||||||
|
return rb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Body sets the BodyGetter to use to build the body of a request.
|
||||||
|
// The provided BodyGetter is used as an http.Request.GetBody func.
|
||||||
|
// It implicitly sets method to POST.
|
||||||
|
func (rb *Builder) Body(src BodyGetter) *Builder {
|
||||||
|
rb.rb.Body(src)
|
||||||
|
return rb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client sets the http.Client to use for requests. If nil, it uses http.DefaultClient.
|
||||||
|
func (rb *Builder) Client(cl *http.Client) *Builder {
|
||||||
|
rb.cl = cl
|
||||||
|
return rb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transport sets the http.RoundTripper to use for requests.
|
||||||
|
// If set, it makes a shallow copy of the http.Client before modifying it.
|
||||||
|
func (rb *Builder) Transport(rt http.RoundTripper) *Builder {
|
||||||
|
rb.rt = rt
|
||||||
|
return rb
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddValidator adds a response validator to the Builder.
|
||||||
|
// Adding a validator disables DefaultValidator.
|
||||||
|
// To disable all validation, just add nil.
|
||||||
|
func (rb *Builder) AddValidator(h ResponseHandler) *Builder {
|
||||||
|
rb.validators = append(rb.validators, h)
|
||||||
|
return rb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle sets the response handler for a Builder.
|
||||||
|
// To use multiple handlers, use ChainHandlers.
|
||||||
|
func (rb *Builder) Handle(h ResponseHandler) *Builder {
|
||||||
|
rb.handler = h
|
||||||
|
return rb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config allows Builder to be extended by functions that set several options at once.
|
||||||
|
func (rb *Builder) Config(cfgs ...Config) *Builder {
|
||||||
|
for _, cfg := range cfgs {
|
||||||
|
cfg(rb)
|
||||||
|
}
|
||||||
|
return rb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone creates a new Builder suitable for independent mutation.
|
||||||
|
func (rb *Builder) Clone() *Builder {
|
||||||
|
rb2 := *rb
|
||||||
|
rb2.ub = *rb.ub.Clone()
|
||||||
|
rb2.rb = *rb.rb.Clone()
|
||||||
|
rb2.validators = slices.Clip(rb2.validators)
|
||||||
|
return &rb2
|
||||||
|
}
|
||||||
|
|
||||||
|
func joinerrs(a, b error) error {
|
||||||
|
return fmt.Errorf("%w: %w", a, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// URL builds a *url.URL from the base URL and options set on the Builder.
|
||||||
|
// If a valid url.URL cannot be built,
|
||||||
|
// URL() nevertheless returns a new url.URL,
|
||||||
|
// so it is always safe to call u.String().
|
||||||
|
func (rb *Builder) URL() (u *url.URL, err error) {
|
||||||
|
u, err = rb.ub.URL()
|
||||||
|
if err != nil {
|
||||||
|
return u, joinerrs(ErrURL, err)
|
||||||
|
}
|
||||||
|
return u, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request builds a new http.Request with its context set.
|
||||||
|
func (rb *Builder) Request(ctx context.Context) (req *http.Request, err error) {
|
||||||
|
u, err := rb.URL()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req, err = rb.rb.Request(ctx, u)
|
||||||
|
if err != nil {
|
||||||
|
return nil, joinerrs(ErrRequest, err)
|
||||||
|
}
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do calls the underlying http.Client and validates and handles any resulting response. The response body is closed after all validators and the handler run.
|
||||||
|
func (rb *Builder) Do(req *http.Request) (err error) {
|
||||||
|
cl := cmp.Or(rb.cl, http.DefaultClient)
|
||||||
|
if rb.rt != nil {
|
||||||
|
cl2 := *cl
|
||||||
|
cl2.Transport = rb.rt
|
||||||
|
cl = &cl2
|
||||||
|
}
|
||||||
|
validators := rb.validators
|
||||||
|
if len(validators) == 0 {
|
||||||
|
validators = []ResponseHandler{DefaultValidator}
|
||||||
|
}
|
||||||
|
h := minitrue.Cond(rb.handler != nil,
|
||||||
|
rb.handler,
|
||||||
|
consumeBody)
|
||||||
|
|
||||||
|
code, err := do(cl, req, validators, h)
|
||||||
|
switch code {
|
||||||
|
case doOK:
|
||||||
|
return nil
|
||||||
|
case doConnect:
|
||||||
|
err = joinerrs(ErrTransport, err)
|
||||||
|
case doValidate:
|
||||||
|
err = joinerrs(ErrValidator, err)
|
||||||
|
case doHandle:
|
||||||
|
err = joinerrs(ErrHandler, err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch builds a request, sends it, and handles the response.
|
||||||
|
func (rb *Builder) Fetch(ctx context.Context) (err error) {
|
||||||
|
req, err := rb.Request(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return rb.Do(req)
|
||||||
|
}
|
||||||
239
vendor/github.com/carlmjohnson/requests/builder_extras.go
generated
vendored
Normal file
239
vendor/github.com/carlmjohnson/requests/builder_extras.go
generated
vendored
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
package requests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// URL creates a new Builder suitable for method chaining.
|
||||||
|
// It is equivalent to calling BaseURL on an empty Builder.
|
||||||
|
func URL(baseurl string) *Builder {
|
||||||
|
return (&Builder{}).BaseURL(baseurl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new Builder suitable for method chaining by applying the specified Configs.
|
||||||
|
// It is equivalent to calling Config on an empty Builder.
|
||||||
|
// The zero value of Builder is usable,
|
||||||
|
// so it is not necessary to call New
|
||||||
|
// when you do not have any Configs to apply.
|
||||||
|
func New(cfgs ...Config) *Builder {
|
||||||
|
return (&Builder{}).Config(cfgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Head sets HTTP method to HEAD.
|
||||||
|
func (rb *Builder) Head() *Builder {
|
||||||
|
return rb.Method(http.MethodHead)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post sets HTTP method to POST.
|
||||||
|
//
|
||||||
|
// Note that setting a Body causes a request to be POST by default.
|
||||||
|
func (rb *Builder) Post() *Builder {
|
||||||
|
return rb.Method(http.MethodPost)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put sets HTTP method to PUT.
|
||||||
|
func (rb *Builder) Put() *Builder {
|
||||||
|
return rb.Method(http.MethodPut)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Patch sets HTTP method to PATCH.
|
||||||
|
func (rb *Builder) Patch() *Builder {
|
||||||
|
return rb.Method(http.MethodPatch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete sets HTTP method to DELETE.
|
||||||
|
func (rb *Builder) Delete() *Builder {
|
||||||
|
return rb.Method(http.MethodDelete)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hostf calls Host with fmt.Sprintf.
|
||||||
|
func (rb *Builder) Hostf(format string, a ...any) *Builder {
|
||||||
|
return rb.Host(fmt.Sprintf(format, a...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pathf calls Path with fmt.Sprintf.
|
||||||
|
//
|
||||||
|
// Note that for security reasons, you must not use %s
|
||||||
|
// with a user provided string!
|
||||||
|
func (rb *Builder) Pathf(format string, a ...any) *Builder {
|
||||||
|
return rb.Path(fmt.Sprintf(format, a...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParamInt converts value to a string and calls Param.
|
||||||
|
func (rb *Builder) ParamInt(key string, value int) *Builder {
|
||||||
|
return rb.Param(key, strconv.Itoa(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Params calls Param with all the members of m.
|
||||||
|
func (rb *Builder) Params(m map[string][]string) *Builder {
|
||||||
|
for k, vv := range m {
|
||||||
|
rb.Param(k, vv...)
|
||||||
|
}
|
||||||
|
return rb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Headers calls Header with all the members of m.
|
||||||
|
func (rb *Builder) Headers(m map[string][]string) *Builder {
|
||||||
|
for k, vv := range m {
|
||||||
|
rb.Header(k, vv...)
|
||||||
|
}
|
||||||
|
return rb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept sets the Accept header for a request.
|
||||||
|
func (rb *Builder) Accept(contentTypes string) *Builder {
|
||||||
|
return rb.Header("Accept", contentTypes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CacheControl sets the client-side Cache-Control directive for a request.
|
||||||
|
func (rb *Builder) CacheControl(directive string) *Builder {
|
||||||
|
return rb.Header("Cache-Control", directive)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContentType sets the Content-Type header on a request.
|
||||||
|
func (rb *Builder) ContentType(ct string) *Builder {
|
||||||
|
return rb.Header("Content-Type", ct)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserAgent sets the User-Agent header.
|
||||||
|
func (rb *Builder) UserAgent(s string) *Builder {
|
||||||
|
return rb.Header("User-Agent", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BasicAuth sets the Authorization header to a basic auth credential.
|
||||||
|
func (rb *Builder) BasicAuth(username, password string) *Builder {
|
||||||
|
auth := username + ":" + password
|
||||||
|
v := base64.StdEncoding.EncodeToString([]byte(auth))
|
||||||
|
return rb.Header("Authorization", "Basic "+v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bearer sets the Authorization header to a bearer token.
|
||||||
|
func (rb *Builder) Bearer(token string) *Builder {
|
||||||
|
return rb.Header("Authorization", "Bearer "+token)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BodyReader sets the Builder's request body to r.
|
||||||
|
func (rb *Builder) BodyReader(r io.Reader) *Builder {
|
||||||
|
return rb.Body(BodyReader(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BodyWriter pipes writes from w to the Builder's request body.
|
||||||
|
func (rb *Builder) BodyWriter(f func(w io.Writer) error) *Builder {
|
||||||
|
return rb.Body(BodyWriter(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BodyBytes sets the Builder's request body to b.
|
||||||
|
func (rb *Builder) BodyBytes(b []byte) *Builder {
|
||||||
|
return rb.Body(BodyBytes(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BodySerializer sets the Builder's request body
|
||||||
|
// to the serialized object.
|
||||||
|
func (rb *Builder) BodySerializer(s Serializer, v any) *Builder {
|
||||||
|
return rb.
|
||||||
|
Body(BodySerializer(s, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BodyJSON sets the Builder's request body to the marshaled JSON.
|
||||||
|
// It uses [JSONSerializer] to marshal the object.
|
||||||
|
// It also sets ContentType to "application/json"
|
||||||
|
// if it is not otherwise set.
|
||||||
|
func (rb *Builder) BodyJSON(v any) *Builder {
|
||||||
|
return rb.
|
||||||
|
Body(BodyJSON(v)).
|
||||||
|
HeaderOptional("Content-Type", "application/json")
|
||||||
|
}
|
||||||
|
|
||||||
|
// BodyForm sets the Builder's request body to the encoded form.
|
||||||
|
// It also sets the ContentType to "application/x-www-form-urlencoded".
|
||||||
|
func (rb *Builder) BodyForm(data url.Values) *Builder {
|
||||||
|
return rb.
|
||||||
|
Body(BodyForm(data)).
|
||||||
|
ContentType("application/x-www-form-urlencoded")
|
||||||
|
}
|
||||||
|
|
||||||
|
// BodyFile sets the Builder's request body to read from the given file path.
|
||||||
|
func (rb *Builder) BodyFile(name string) *Builder {
|
||||||
|
return rb.Body(BodyFile(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckStatus adds a validator for status code of a response.
|
||||||
|
func (rb *Builder) CheckStatus(acceptStatuses ...int) *Builder {
|
||||||
|
return rb.AddValidator(CheckStatus(acceptStatuses...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckContentType adds a validator for the content type header of a response.
|
||||||
|
func (rb *Builder) CheckContentType(cts ...string) *Builder {
|
||||||
|
return rb.AddValidator(CheckContentType(cts...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckPeek adds a validator that peeks at the first n bytes of a response body.
|
||||||
|
func (rb *Builder) CheckPeek(n int, f func([]byte) error) *Builder {
|
||||||
|
return rb.AddValidator(CheckPeek(n, f))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToDeserializer sets the Builder to decode a response into v
|
||||||
|
// using a [Deserializer].
|
||||||
|
func (rb *Builder) ToDeserializer(d Deserializer, v any) *Builder {
|
||||||
|
return rb.
|
||||||
|
Handle(ToDeserializer(d, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToJSON sets the Builder to decode a response as a JSON object.
|
||||||
|
//
|
||||||
|
// It uses [JSONDeserializer] to unmarshal the object.
|
||||||
|
func (rb *Builder) ToJSON(v any) *Builder {
|
||||||
|
return rb.Handle(ToJSON(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToString sets the Builder to write the response body to the provided string pointer.
|
||||||
|
func (rb *Builder) ToString(sp *string) *Builder {
|
||||||
|
return rb.Handle(ToString(sp))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToBytesBuffer sets the Builder to write the response body to the provided bytes.Buffer.
|
||||||
|
func (rb *Builder) ToBytesBuffer(buf *bytes.Buffer) *Builder {
|
||||||
|
return rb.Handle(ToBytesBuffer(buf))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToWriter sets the Builder to copy the response body into w.
|
||||||
|
func (rb *Builder) ToWriter(w io.Writer) *Builder {
|
||||||
|
return rb.Handle(ToWriter(w))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToFile sets the Builder to write the response body to the given file name.
|
||||||
|
// The file and its parent directories are created automatically.
|
||||||
|
// For more advanced use cases, use ToWriter.
|
||||||
|
func (rb *Builder) ToFile(name string) *Builder {
|
||||||
|
return rb.Handle(ToFile(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyHeaders adds a validator which copies the response headers to h.
|
||||||
|
// Note that because CopyHeaders adds a validator,
|
||||||
|
// the DefaultValidator is disabled and must be added back manually
|
||||||
|
// if status code validation is desired.
|
||||||
|
func (rb *Builder) CopyHeaders(h map[string][]string) *Builder {
|
||||||
|
return rb.
|
||||||
|
AddValidator(CopyHeaders(h))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToHeaders sets the method to HEAD and adds a handler which copies the response headers to h.
|
||||||
|
// To just copy headers, see Builder.CopyHeaders.
|
||||||
|
func (rb *Builder) ToHeaders(h map[string][]string) *Builder {
|
||||||
|
return rb.
|
||||||
|
Head().
|
||||||
|
Handle(ChainHandlers(CopyHeaders(h), consumeBody))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorJSON adds a validator that applies DefaultValidator
|
||||||
|
// and decodes the response as a JSON object
|
||||||
|
// if the DefaultValidator check fails.
|
||||||
|
func (rb *Builder) ErrorJSON(v any) *Builder {
|
||||||
|
return rb.AddValidator(ErrorJSON(v))
|
||||||
|
}
|
||||||
77
vendor/github.com/carlmjohnson/requests/config.go
generated
vendored
Normal file
77
vendor/github.com/carlmjohnson/requests/config.go
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package requests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"compress/gzip"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/http/httptest"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config allows Builder to be extended by setting several options at once.
|
||||||
|
// For example, a Config might set a Body and its ContentType.
|
||||||
|
type Config = func(rb *Builder)
|
||||||
|
|
||||||
|
// GzipConfig writes a gzip stream to its request body using a callback.
|
||||||
|
// It also sets the appropriate Content-Encoding header and automatically
|
||||||
|
// closes and the stream when the callback returns.
|
||||||
|
func GzipConfig(level int, h func(gw *gzip.Writer) error) Config {
|
||||||
|
return func(rb *Builder) {
|
||||||
|
rb.
|
||||||
|
Header("Content-Encoding", "gzip").
|
||||||
|
BodyWriter(func(w io.Writer) error {
|
||||||
|
gw, err := gzip.NewWriterLevel(w, level)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = h(gw); err != nil {
|
||||||
|
gw.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return gw.Close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestServerConfig returns a Config
|
||||||
|
// which sets the Builder's BaseURL to s.URL
|
||||||
|
// and the Builder's Client to s.Client().
|
||||||
|
//
|
||||||
|
// Deprecated: Use reqtest.Server.
|
||||||
|
func TestServerConfig(s *httptest.Server) Config {
|
||||||
|
return func(rb *Builder) {
|
||||||
|
rb.
|
||||||
|
BaseURL(s.URL).
|
||||||
|
Client(s.Client())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BodyMultipart returns a Config
|
||||||
|
// that uses a multipart.Writer for the request body.
|
||||||
|
// If boundary is "", a multipart boundary is chosen at random.
|
||||||
|
// The content type of the request is set to multipart/form-data
|
||||||
|
// with the correct boundary.
|
||||||
|
// The multipart.Writer is automatically closed if the callback succeeds.
|
||||||
|
func BodyMultipart(boundary string, h func(multi *multipart.Writer) error) Config {
|
||||||
|
return func(rb *Builder) {
|
||||||
|
if boundary == "" {
|
||||||
|
multi := multipart.NewWriter(nil)
|
||||||
|
boundary = multi.Boundary()
|
||||||
|
}
|
||||||
|
rb.
|
||||||
|
ContentType("multipart/form-data; boundary=" + boundary).
|
||||||
|
BodyWriter(func(w io.Writer) error {
|
||||||
|
multi := multipart.NewWriter(w)
|
||||||
|
if err := multi.SetBoundary(boundary); err != nil {
|
||||||
|
return fmt.Errorf("setting boundary: %w", err)
|
||||||
|
}
|
||||||
|
if err := h(multi); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := multi.Close(); err != nil {
|
||||||
|
return fmt.Errorf("closing multipart writer: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
18
vendor/github.com/carlmjohnson/requests/cookies.go
generated
vendored
Normal file
18
vendor/github.com/carlmjohnson/requests/cookies.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package requests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/cookiejar"
|
||||||
|
|
||||||
|
"golang.org/x/net/publicsuffix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewCookieJar returns a cookie jar using the standard public suffix list.
|
||||||
|
func NewCookieJar() http.CookieJar {
|
||||||
|
jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
|
||||||
|
// As of Go 1.16, cookiejar.New err is hardcoded nil
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return jar
|
||||||
|
}
|
||||||
36
vendor/github.com/carlmjohnson/requests/core_do.go
generated
vendored
Normal file
36
vendor/github.com/carlmjohnson/requests/core_do.go
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package requests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type doResponse int
|
||||||
|
|
||||||
|
const (
|
||||||
|
doOK doResponse = iota
|
||||||
|
doConnect
|
||||||
|
doValidate
|
||||||
|
doHandle
|
||||||
|
)
|
||||||
|
|
||||||
|
func do(cl *http.Client, req *http.Request, validators []ResponseHandler, h ResponseHandler) (doResponse, error) {
|
||||||
|
res, err := cl.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return doConnect, err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
for _, v := range validators {
|
||||||
|
if v == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err = v(res); err != nil {
|
||||||
|
return doValidate, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err = h(res); err != nil {
|
||||||
|
return doHandle, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return doOK, nil
|
||||||
|
}
|
||||||
106
vendor/github.com/carlmjohnson/requests/core_req.go
generated
vendored
Normal file
106
vendor/github.com/carlmjohnson/requests/core_req.go
generated
vendored
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
package requests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cmp"
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"github.com/carlmjohnson/requests/internal/minitrue"
|
||||||
|
)
|
||||||
|
|
||||||
|
// nopCloser is like io.NopCloser(),
|
||||||
|
// but it is a concrete type so we can strip it out
|
||||||
|
// before setting a body on a request.
|
||||||
|
// See https://github.com/carlmjohnson/requests/discussions/49
|
||||||
|
type nopCloser struct {
|
||||||
|
io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
func rc(r io.Reader) nopCloser {
|
||||||
|
return nopCloser{r}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nopCloser) Close() error { return nil }
|
||||||
|
|
||||||
|
var _ io.ReadCloser = nopCloser{}
|
||||||
|
|
||||||
|
type requestBuilder struct {
|
||||||
|
headers []multimap
|
||||||
|
cookies []kvpair
|
||||||
|
getBody BodyGetter
|
||||||
|
method string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rb *requestBuilder) Header(key string, values ...string) {
|
||||||
|
rb.headers = append(rb.headers, multimap{key, values, false})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rb *requestBuilder) HeaderOptional(key string, values ...string) {
|
||||||
|
rb.headers = append(rb.headers, multimap{key, values, true})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rb *requestBuilder) Cookie(name, value string) {
|
||||||
|
rb.cookies = append(rb.cookies, kvpair{name, value})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rb *requestBuilder) Method(method string) {
|
||||||
|
rb.method = method
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rb *requestBuilder) Body(src BodyGetter) {
|
||||||
|
rb.getBody = src
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone creates a new Builder suitable for independent mutation.
|
||||||
|
func (rb *requestBuilder) Clone() *requestBuilder {
|
||||||
|
rb2 := *rb
|
||||||
|
rb2.headers = slices.Clip(rb2.headers)
|
||||||
|
rb2.cookies = slices.Clip(rb2.cookies)
|
||||||
|
return &rb2
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request builds a new http.Request with its context set.
|
||||||
|
func (rb *requestBuilder) Request(ctx context.Context, u *url.URL) (req *http.Request, err error) {
|
||||||
|
var body io.Reader
|
||||||
|
if rb.getBody != nil {
|
||||||
|
if body, err = rb.getBody(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if nopper, ok := body.(nopCloser); ok {
|
||||||
|
body = nopper.Reader
|
||||||
|
}
|
||||||
|
}
|
||||||
|
method := cmp.Or(rb.method,
|
||||||
|
minitrue.Cond(rb.getBody == nil,
|
||||||
|
http.MethodGet,
|
||||||
|
http.MethodPost))
|
||||||
|
|
||||||
|
req, err = http.NewRequestWithContext(ctx, method, u.String(), body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.GetBody = rb.getBody
|
||||||
|
|
||||||
|
for _, kv := range rb.headers {
|
||||||
|
if !kv.optional {
|
||||||
|
req.Header[http.CanonicalHeaderKey(kv.key)] = kv.values
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, kv := range rb.headers {
|
||||||
|
if kv.optional &&
|
||||||
|
req.Header.Get(kv.key) == "" &&
|
||||||
|
cmp.Or(kv.values...) != "" {
|
||||||
|
req.Header[http.CanonicalHeaderKey(kv.key)] = kv.values
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, kv := range rb.cookies {
|
||||||
|
req.AddCookie(&http.Cookie{
|
||||||
|
Name: kv.key,
|
||||||
|
Value: kv.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
94
vendor/github.com/carlmjohnson/requests/core_url.go
generated
vendored
Normal file
94
vendor/github.com/carlmjohnson/requests/core_url.go
generated
vendored
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
package requests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cmp"
|
||||||
|
"net/url"
|
||||||
|
"slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
type multimap struct {
|
||||||
|
key string
|
||||||
|
values []string
|
||||||
|
optional bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type kvpair struct {
|
||||||
|
key, value string
|
||||||
|
}
|
||||||
|
|
||||||
|
type urlBuilder struct {
|
||||||
|
baseurl string
|
||||||
|
scheme, host string
|
||||||
|
paths []string
|
||||||
|
params []multimap
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ub *urlBuilder) BaseURL(baseurl string) {
|
||||||
|
ub.baseurl = baseurl
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ub *urlBuilder) Scheme(scheme string) {
|
||||||
|
ub.scheme = scheme
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ub *urlBuilder) Host(host string) {
|
||||||
|
ub.host = host
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ub *urlBuilder) Path(path string) {
|
||||||
|
ub.paths = append(ub.paths, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ub *urlBuilder) Param(key string, values ...string) {
|
||||||
|
ub.params = append(ub.params, multimap{key, values, false})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ub *urlBuilder) ParamOptional(key string, values ...string) {
|
||||||
|
ub.params = append(ub.params, multimap{key, values, true})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ub *urlBuilder) Clone() *urlBuilder {
|
||||||
|
ub2 := *ub
|
||||||
|
ub2.paths = slices.Clip(ub2.paths)
|
||||||
|
ub2.params = slices.Clip(ub2.params)
|
||||||
|
return &ub2
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ub *urlBuilder) URL() (u *url.URL, err error) {
|
||||||
|
u, err = url.Parse(ub.baseurl)
|
||||||
|
if err != nil {
|
||||||
|
return new(url.URL), err
|
||||||
|
}
|
||||||
|
u.Scheme = cmp.Or(
|
||||||
|
ub.scheme,
|
||||||
|
u.Scheme,
|
||||||
|
"https",
|
||||||
|
)
|
||||||
|
u.Host = cmp.Or(ub.host, u.Host)
|
||||||
|
for _, p := range ub.paths {
|
||||||
|
u.Path = u.ResolveReference(&url.URL{Path: p}).Path
|
||||||
|
}
|
||||||
|
if len(ub.params) > 0 {
|
||||||
|
q := u.Query()
|
||||||
|
for _, kv := range ub.params {
|
||||||
|
if !kv.optional {
|
||||||
|
q[kv.key] = kv.values
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, kv := range ub.params {
|
||||||
|
if kv.optional &&
|
||||||
|
q.Get(kv.key) == "" &&
|
||||||
|
cmp.Or(kv.values...) != "" {
|
||||||
|
q[kv.key] = kv.values
|
||||||
|
}
|
||||||
|
}
|
||||||
|
u.RawQuery = q.Encode()
|
||||||
|
}
|
||||||
|
// Reparsing, in case the path rewriting broke the URL
|
||||||
|
u, err = url.Parse(u.String())
|
||||||
|
if err != nil {
|
||||||
|
return new(url.URL), err
|
||||||
|
}
|
||||||
|
return u, nil
|
||||||
|
}
|
||||||
3
vendor/github.com/carlmjohnson/requests/doc.go
generated
vendored
Normal file
3
vendor/github.com/carlmjohnson/requests/doc.go
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// Package requests is a convenience wrapper around net/http to make it faster
|
||||||
|
// and easier to build requests and custom transports.
|
||||||
|
package requests
|
||||||
20
vendor/github.com/carlmjohnson/requests/errorkind.go
generated
vendored
Normal file
20
vendor/github.com/carlmjohnson/requests/errorkind.go
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package requests
|
||||||
|
|
||||||
|
// ErrorKind indicates where an error was returned in the process of building, validating, and handling a request.
|
||||||
|
// Errors returned by Builder can be tested for their ErrorKind using errors.Is or errors.As.
|
||||||
|
type ErrorKind int8
|
||||||
|
|
||||||
|
//go:generate stringer -type=ErrorKind
|
||||||
|
|
||||||
|
// Enum values for type ErrorKind
|
||||||
|
const (
|
||||||
|
ErrURL ErrorKind = iota // error building URL
|
||||||
|
ErrRequest // error building the request
|
||||||
|
ErrTransport // error connecting
|
||||||
|
ErrValidator // validator error
|
||||||
|
ErrHandler // handler error
|
||||||
|
)
|
||||||
|
|
||||||
|
func (ek ErrorKind) Error() string {
|
||||||
|
return ek.String()
|
||||||
|
}
|
||||||
27
vendor/github.com/carlmjohnson/requests/errorkind_string.go
generated
vendored
Normal file
27
vendor/github.com/carlmjohnson/requests/errorkind_string.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// Code generated by "stringer -type=ErrorKind"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package requests
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||||
|
// Re-run the stringer command to generate them again.
|
||||||
|
var x [1]struct{}
|
||||||
|
_ = x[ErrURL-0]
|
||||||
|
_ = x[ErrRequest-1]
|
||||||
|
_ = x[ErrTransport-2]
|
||||||
|
_ = x[ErrValidator-3]
|
||||||
|
_ = x[ErrHandler-4]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _ErrorKind_name = "ErrURLErrRequestErrTransportErrValidatorErrHandler"
|
||||||
|
|
||||||
|
var _ErrorKind_index = [...]uint8{0, 6, 16, 28, 40, 50}
|
||||||
|
|
||||||
|
func (i ErrorKind) String() string {
|
||||||
|
if i < 0 || i >= ErrorKind(len(_ErrorKind_index)-1) {
|
||||||
|
return "ErrorKind(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _ErrorKind_name[_ErrorKind_index[i]:_ErrorKind_index[i+1]]
|
||||||
|
}
|
||||||
139
vendor/github.com/carlmjohnson/requests/handler.go
generated
vendored
Normal file
139
vendor/github.com/carlmjohnson/requests/handler.go
generated
vendored
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
package requests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/net/html"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ResponseHandler is used to validate or handle the response to a request.
|
||||||
|
type ResponseHandler = func(*http.Response) error
|
||||||
|
|
||||||
|
// ChainHandlers allows for the composing of validators or response handlers.
|
||||||
|
func ChainHandlers(handlers ...ResponseHandler) ResponseHandler {
|
||||||
|
return func(r *http.Response) error {
|
||||||
|
for _, h := range handlers {
|
||||||
|
if h == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := h(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func consumeBody(res *http.Response) (err error) {
|
||||||
|
const maxDiscardSize = 640 * 1 << 10
|
||||||
|
if _, err = io.CopyN(io.Discard, res.Body, maxDiscardSize); err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToDeserializer decodes a response into v using a [Deserializer].
|
||||||
|
func ToDeserializer(d Deserializer, v any) ResponseHandler {
|
||||||
|
return func(res *http.Response) error {
|
||||||
|
data, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = d(data, v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToJSON decodes a response as a JSON object.
|
||||||
|
//
|
||||||
|
// It uses [JSONDeserializer] to unmarshal the object.
|
||||||
|
func ToJSON(v any) ResponseHandler {
|
||||||
|
return ToDeserializer(JSONDeserializer, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToString writes the response body to the provided string pointer.
|
||||||
|
func ToString(sp *string) ResponseHandler {
|
||||||
|
return func(res *http.Response) error {
|
||||||
|
var buf strings.Builder
|
||||||
|
_, err := io.Copy(&buf, res.Body)
|
||||||
|
if err == nil {
|
||||||
|
*sp = buf.String()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToBytesBuffer writes the response body to the provided bytes.Buffer.
|
||||||
|
func ToBytesBuffer(buf *bytes.Buffer) ResponseHandler {
|
||||||
|
return func(res *http.Response) error {
|
||||||
|
_, err := io.Copy(buf, res.Body)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToBufioReader takes a callback which wraps the response body in a bufio.Reader.
|
||||||
|
func ToBufioReader(f func(r *bufio.Reader) error) ResponseHandler {
|
||||||
|
return func(res *http.Response) error {
|
||||||
|
return f(bufio.NewReader(res.Body))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToBufioScanner takes a callback which wraps the response body in a bufio.Scanner.
|
||||||
|
func ToBufioScanner(f func(r *bufio.Scanner) error) ResponseHandler {
|
||||||
|
return func(res *http.Response) error {
|
||||||
|
return f(bufio.NewScanner(res.Body))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToHTML parses the page with x/net/html.Parse.
|
||||||
|
//
|
||||||
|
// Deprecated: Use reqhtml.To.
|
||||||
|
func ToHTML(n *html.Node) ResponseHandler {
|
||||||
|
return ToBufioReader(func(r *bufio.Reader) error {
|
||||||
|
n2, err := html.Parse(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*n = *n2
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToWriter copies the response body to w.
|
||||||
|
func ToWriter(w io.Writer) ResponseHandler {
|
||||||
|
return ToBufioReader(func(r *bufio.Reader) error {
|
||||||
|
_, err := io.Copy(w, r)
|
||||||
|
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToFile writes the response body at the provided file path.
|
||||||
|
// The file and its parent directories are created automatically.
|
||||||
|
func ToFile(name string) ResponseHandler {
|
||||||
|
return func(res *http.Response) error {
|
||||||
|
_ = os.MkdirAll(filepath.Dir(name), 0777)
|
||||||
|
|
||||||
|
f, err := os.Create(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(f, res.Body)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToHeaders is an alias for backwards compatibility.
|
||||||
|
//
|
||||||
|
// Deprecated: Use CopyHeaders
|
||||||
|
var ToHeaders = CopyHeaders
|
||||||
9
vendor/github.com/carlmjohnson/requests/internal/minitrue/minitrue.go
generated
vendored
Normal file
9
vendor/github.com/carlmjohnson/requests/internal/minitrue/minitrue.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
// Package minitrue - Whatever the Package holds to be the truth, is truth.
|
||||||
|
package minitrue
|
||||||
|
|
||||||
|
func Cond[T any](val bool, a, b T) T {
|
||||||
|
if val {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
131
vendor/github.com/carlmjohnson/requests/recorder.go
generated
vendored
Normal file
131
vendor/github.com/carlmjohnson/requests/recorder.go
generated
vendored
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
package requests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Record returns an http.RoundTripper that writes out its
|
||||||
|
// requests and their responses to text files in basepath.
|
||||||
|
// Requests are named according to a hash of their contents.
|
||||||
|
// Responses are named according to the request that made them.
|
||||||
|
//
|
||||||
|
// Deprecated: Use reqtest.Record.
|
||||||
|
func Record(rt http.RoundTripper, basepath string) Transport {
|
||||||
|
if rt == nil {
|
||||||
|
rt = http.DefaultTransport
|
||||||
|
}
|
||||||
|
return RoundTripFunc(func(req *http.Request) (res *http.Response, err error) {
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("problem while recording transport: %w", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
_ = os.MkdirAll(basepath, 0755)
|
||||||
|
b, err := httputil.DumpRequest(req, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
reqname, resname := buildName(b)
|
||||||
|
name := filepath.Join(basepath, reqname)
|
||||||
|
if err = os.WriteFile(name, b, 0644); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if res, err = rt.RoundTrip(req); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b, err = httputil.DumpResponse(res, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
name = filepath.Join(basepath, resname)
|
||||||
|
if err = os.WriteFile(name, b, 0644); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replay returns an http.RoundTripper that reads its
|
||||||
|
// responses from text files in basepath.
|
||||||
|
// Responses are looked up according to a hash of the request.
|
||||||
|
//
|
||||||
|
// Deprecated: Use reqtest.Replay.
|
||||||
|
func Replay(basepath string) Transport {
|
||||||
|
return ReplayFS(os.DirFS(basepath))
|
||||||
|
}
|
||||||
|
|
||||||
|
var errNotFound = errors.New("response not found")
|
||||||
|
|
||||||
|
// ReplayFS returns an http.RoundTripper that reads its
|
||||||
|
// responses from text files in the fs.FS.
|
||||||
|
// Responses are looked up according to a hash of the request.
|
||||||
|
// Response file names may optionally be prefixed with comments for better human organization.
|
||||||
|
//
|
||||||
|
// Deprecated: Use reqtest.ReplayFS.
|
||||||
|
func ReplayFS(fsys fs.FS) Transport {
|
||||||
|
return RoundTripFunc(func(req *http.Request) (res *http.Response, err error) {
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("problem while replaying transport: %w", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
b, err := httputil.DumpRequest(req, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, name := buildName(b)
|
||||||
|
glob := "*" + name
|
||||||
|
matches, err := fs.Glob(fsys, glob)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(matches) == 0 {
|
||||||
|
return nil, fmt.Errorf("%w: no replay file matches %q", errNotFound, glob)
|
||||||
|
}
|
||||||
|
if len(matches) > 1 {
|
||||||
|
return nil, fmt.Errorf("ambiguous response: multiple replay files match %q", glob)
|
||||||
|
}
|
||||||
|
b, err = fs.ReadFile(fsys, matches[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r := bufio.NewReader(bytes.NewReader(b))
|
||||||
|
return http.ReadResponse(r, req)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildName(b []byte) (reqname, resname string) {
|
||||||
|
h := md5.New()
|
||||||
|
h.Write(b)
|
||||||
|
s := base64.URLEncoding.EncodeToString(h.Sum(nil))
|
||||||
|
return s[:8] + ".req.txt", s[:8] + ".res.txt"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Caching returns an http.RoundTripper that attempts to read its
|
||||||
|
// responses from text files in basepath. If the response is absent,
|
||||||
|
// it caches the result of issuing the request with rt in basepath.
|
||||||
|
// Requests are named according to a hash of their contents.
|
||||||
|
// Responses are named according to the request that made them.
|
||||||
|
//
|
||||||
|
// Deprecated: Use reqtest.Caching.
|
||||||
|
func Caching(rt http.RoundTripper, basepath string) Transport {
|
||||||
|
replay := Replay(basepath).RoundTrip
|
||||||
|
record := Record(rt, basepath).RoundTrip
|
||||||
|
return RoundTripFunc(func(req *http.Request) (res *http.Response, err error) {
|
||||||
|
res, err = replay(req)
|
||||||
|
if errors.Is(err, errNotFound) {
|
||||||
|
res, err = record(req)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
})
|
||||||
|
}
|
||||||
19
vendor/github.com/carlmjohnson/requests/redirects.go
generated
vendored
Normal file
19
vendor/github.com/carlmjohnson/requests/redirects.go
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package requests
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
// CheckRedirectPolicy is a function suitable for use as CheckRedirect on an http.Client.
|
||||||
|
type CheckRedirectPolicy = func(req *http.Request, via []*http.Request) error
|
||||||
|
|
||||||
|
// MaxFollow returns a CheckRedirectPolicy that follows a maximum of n redirects.
|
||||||
|
func MaxFollow(n int) CheckRedirectPolicy {
|
||||||
|
return func(req *http.Request, via []*http.Request) error {
|
||||||
|
if len(via) > n {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NoFollow is a CheckRedirectPolicy that does not follow redirects.
|
||||||
|
var NoFollow CheckRedirectPolicy = MaxFollow(0)
|
||||||
24
vendor/github.com/carlmjohnson/requests/serializer.go
generated
vendored
Normal file
24
vendor/github.com/carlmjohnson/requests/serializer.go
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package requests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Serializer is a function
|
||||||
|
// that can convert arbitrary data
|
||||||
|
// to bytes in some format.
|
||||||
|
type Serializer = func(v any) ([]byte, error)
|
||||||
|
|
||||||
|
// Deserializer is a function
|
||||||
|
// that can read data in some format
|
||||||
|
// and store the result in v.
|
||||||
|
type Deserializer = func(data []byte, v any) error
|
||||||
|
|
||||||
|
var (
|
||||||
|
// JSONSerializer is used by BodyJSON and Builder.BodyJSON.
|
||||||
|
// The default serializer may be changed in a future version of requests.
|
||||||
|
JSONSerializer Serializer = json.Marshal
|
||||||
|
// JSONDeserializer is used by ToJSON and Builder.ToJSON.
|
||||||
|
// The default deserializer may be changed in a future version of requests.
|
||||||
|
JSONDeserializer Deserializer = json.Unmarshal
|
||||||
|
)
|
||||||
128
vendor/github.com/carlmjohnson/requests/transport.go
generated
vendored
Normal file
128
vendor/github.com/carlmjohnson/requests/transport.go
generated
vendored
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
package requests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Transport is an alias of http.RoundTripper for documentation purposes.
|
||||||
|
type Transport = http.RoundTripper
|
||||||
|
|
||||||
|
// RoundTripFunc is an adaptor to use a function as an http.RoundTripper.
|
||||||
|
type RoundTripFunc func(req *http.Request) (res *http.Response, err error)
|
||||||
|
|
||||||
|
// RoundTrip implements http.RoundTripper.
|
||||||
|
func (rtf RoundTripFunc) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||||
|
return rtf(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Transport = RoundTripFunc(nil)
|
||||||
|
|
||||||
|
// ReplayString returns an http.RoundTripper that always responds with a
|
||||||
|
// request built from rawResponse. It is intended for use in one-off tests.
|
||||||
|
//
|
||||||
|
// Deprecated: Use reqtest.ReplayString.
|
||||||
|
func ReplayString(rawResponse string) Transport {
|
||||||
|
return RoundTripFunc(func(req *http.Request) (res *http.Response, err error) {
|
||||||
|
r := bufio.NewReader(strings.NewReader(rawResponse))
|
||||||
|
res, err = http.ReadResponse(r, req)
|
||||||
|
return
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserAgentTransport returns a wrapped http.RoundTripper that sets the User-Agent header on requests to s.
|
||||||
|
func UserAgentTransport(rt http.RoundTripper, s string) Transport {
|
||||||
|
if rt == nil {
|
||||||
|
rt = http.DefaultTransport
|
||||||
|
}
|
||||||
|
return RoundTripFunc(func(req *http.Request) (res *http.Response, err error) {
|
||||||
|
r2 := *req
|
||||||
|
r2.Header = r2.Header.Clone()
|
||||||
|
r2.Header.Set("User-Agent", s)
|
||||||
|
return rt.RoundTrip(&r2)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// PermitURLTransport returns a wrapped http.RoundTripper that rejects any requests whose URL doesn't match the provided regular expression string.
|
||||||
|
//
|
||||||
|
// PermitURLTransport will panic if the regexp does not compile.
|
||||||
|
func PermitURLTransport(rt http.RoundTripper, regex string) Transport {
|
||||||
|
if rt == nil {
|
||||||
|
rt = http.DefaultTransport
|
||||||
|
}
|
||||||
|
re := regexp.MustCompile(regex)
|
||||||
|
reErr := fmt.Errorf("requested URL not permitted by regexp: %s", regex)
|
||||||
|
return RoundTripFunc(func(req *http.Request) (res *http.Response, err error) {
|
||||||
|
if u := req.URL.String(); !re.MatchString(u) {
|
||||||
|
return nil, reErr
|
||||||
|
}
|
||||||
|
return rt.RoundTrip(req)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogTransport returns a wrapped http.RoundTripper
|
||||||
|
// that calls fn with details when a response has finished.
|
||||||
|
// A response is considered finished
|
||||||
|
// when the wrapper http.RoundTripper returns an error
|
||||||
|
// or the Response.Body is closed,
|
||||||
|
// whichever comes first.
|
||||||
|
// To simplify logging code,
|
||||||
|
// a nil *http.Response is replaced with a new http.Response.
|
||||||
|
func LogTransport(rt http.RoundTripper, fn func(req *http.Request, res *http.Response, err error, duration time.Duration)) Transport {
|
||||||
|
if rt == nil {
|
||||||
|
rt = http.DefaultTransport
|
||||||
|
}
|
||||||
|
return RoundTripFunc(func(req *http.Request) (res *http.Response, err error) {
|
||||||
|
start := time.Now()
|
||||||
|
res, err = rt.RoundTrip(req)
|
||||||
|
if err != nil {
|
||||||
|
res2 := res
|
||||||
|
if res == nil {
|
||||||
|
res2 = new(http.Response)
|
||||||
|
}
|
||||||
|
fn(req, res2, err, time.Since(start))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Body = closeLogger{res.Body, func() {
|
||||||
|
fn(req, res, err, time.Since(start))
|
||||||
|
}}
|
||||||
|
return
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type closeLogger struct {
|
||||||
|
io.ReadCloser
|
||||||
|
fn func()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl closeLogger) Close() error {
|
||||||
|
cl.fn()
|
||||||
|
return cl.ReadCloser.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DoerTransport converts a Doer into a Transport.
|
||||||
|
// It exists for compatibility with other libraries.
|
||||||
|
// A Doer is an interface with a Do method.
|
||||||
|
// Users should prefer Transport,
|
||||||
|
// because Do is the interface of http.Client
|
||||||
|
// which has higher level concerns.
|
||||||
|
func DoerTransport(cl interface {
|
||||||
|
Do(req *http.Request) (*http.Response, error)
|
||||||
|
}) Transport {
|
||||||
|
return RoundTripFunc(cl.Do)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorTransport always returns the specified error instead of connecting.
|
||||||
|
// It is intended for use in testing
|
||||||
|
// or to prevent accidental use of http.DefaultClient.
|
||||||
|
func ErrorTransport(err error) Transport {
|
||||||
|
return RoundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
|
return nil, err
|
||||||
|
})
|
||||||
|
}
|
||||||
102
vendor/github.com/carlmjohnson/requests/validator.go
generated
vendored
Normal file
102
vendor/github.com/carlmjohnson/requests/validator.go
generated
vendored
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
package requests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"maps"
|
||||||
|
"mime"
|
||||||
|
"net/http"
|
||||||
|
"slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultValidator is the validator applied by Builder unless otherwise specified.
|
||||||
|
var DefaultValidator ResponseHandler = CheckStatus(
|
||||||
|
http.StatusOK,
|
||||||
|
http.StatusCreated,
|
||||||
|
http.StatusAccepted,
|
||||||
|
http.StatusNonAuthoritativeInfo,
|
||||||
|
http.StatusNoContent,
|
||||||
|
)
|
||||||
|
|
||||||
|
// ResponseError is the error type produced by CheckStatus and CheckContentType.
|
||||||
|
type ResponseError http.Response
|
||||||
|
|
||||||
|
// Error fulfills the error interface.
|
||||||
|
func (se *ResponseError) Error() string {
|
||||||
|
return fmt.Sprintf("response error for %s", se.Request.URL.Redacted())
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckStatus validates the response has an acceptable status code.
|
||||||
|
func CheckStatus(acceptStatuses ...int) ResponseHandler {
|
||||||
|
return func(res *http.Response) error {
|
||||||
|
if slices.Contains(acceptStatuses, res.StatusCode) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("%w: unexpected status: %d",
|
||||||
|
(*ResponseError)(res), res.StatusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasStatusErr returns true if err is a ResponseError caused by any of the codes given.
|
||||||
|
func HasStatusErr(err error, codes ...int) bool {
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if se := new(ResponseError); errors.As(err, &se) {
|
||||||
|
return slices.Contains(codes, se.StatusCode)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckContentType validates that a response has one of the given content type headers.
|
||||||
|
func CheckContentType(cts ...string) ResponseHandler {
|
||||||
|
return func(res *http.Response) error {
|
||||||
|
mt, _, err := mime.ParseMediaType(res.Header.Get("Content-Type"))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%w: problem matching Content-Type",
|
||||||
|
(*ResponseError)(res))
|
||||||
|
}
|
||||||
|
if slices.Contains(cts, mt) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("%w: unexpected Content-Type: %s",
|
||||||
|
(*ResponseError)(res), mt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type bufioCloser struct {
|
||||||
|
*bufio.Reader
|
||||||
|
io.Closer
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckPeek wraps the body of a response in a bufio.Reader and
|
||||||
|
// gives f a peek at the first n bytes for validation.
|
||||||
|
func CheckPeek(n int, f func([]byte) error) ResponseHandler {
|
||||||
|
return func(res *http.Response) error {
|
||||||
|
// ensure buffer is at least minimum size
|
||||||
|
buf := bufio.NewReader(res.Body)
|
||||||
|
// ensure large peeks will fit in the buffer
|
||||||
|
buf = bufio.NewReaderSize(buf, n)
|
||||||
|
res.Body = &bufioCloser{
|
||||||
|
buf,
|
||||||
|
res.Body,
|
||||||
|
}
|
||||||
|
b, err := buf.Peek(n)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return f(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyHeaders copies the response headers to h.
|
||||||
|
func CopyHeaders(h map[string][]string) ResponseHandler {
|
||||||
|
return func(res *http.Response) error {
|
||||||
|
maps.Copy(h, res.Header)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
34
vendor/github.com/carlmjohnson/requests/validator_handler.go
generated
vendored
Normal file
34
vendor/github.com/carlmjohnson/requests/validator_handler.go
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package requests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ValidatorHandler composes a Validator and a Handler.
|
||||||
|
// If the validation check fails, it triggers the handler.
|
||||||
|
// Any errors from validator or handler will be joined to the error returned.
|
||||||
|
// If the handler succeeds, the error will matching ErrInvalidHandled.
|
||||||
|
func ValidatorHandler(v, h ResponseHandler) ResponseHandler {
|
||||||
|
return func(res *http.Response) error {
|
||||||
|
err1 := v(res)
|
||||||
|
if err1 == nil { // passes validation
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err2 := h(res)
|
||||||
|
if err2 == nil { // successfully handled
|
||||||
|
return fmt.Errorf("%w: %w", ErrInvalidHandled, err1)
|
||||||
|
}
|
||||||
|
return errors.Join(err1, err2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrInvalidHandled = errors.New("handled recovery from invalid response")
|
||||||
|
|
||||||
|
// ErrorJSON is a ValidatorHandler that applies DefaultValidator
|
||||||
|
// and decodes the response as a JSON object
|
||||||
|
// if the DefaultValidator check fails.
|
||||||
|
func ErrorJSON(v any) ResponseHandler {
|
||||||
|
return ValidatorHandler(DefaultValidator, ToJSON(v))
|
||||||
|
}
|
||||||
0
vendor/github.com/hjson/hjson-go/.gitignore
generated
vendored
Normal file
0
vendor/github.com/hjson/hjson-go/.gitignore
generated
vendored
Normal file
21
vendor/github.com/hjson/hjson-go/LICENSE
generated
vendored
Normal file
21
vendor/github.com/hjson/hjson-go/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2016, 2017 Christian Zangl
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
181
vendor/github.com/hjson/hjson-go/README.md
generated
vendored
Normal file
181
vendor/github.com/hjson/hjson-go/README.md
generated
vendored
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
# hjson-go
|
||||||
|
|
||||||
|
[](https://github.com/hjson/hjson-go/actions)
|
||||||
|
[](https://github.com/hjson/hjson-go/releases)
|
||||||
|
[](https://goreportcard.com/report/github.com/hjson/hjson-go)
|
||||||
|
[](http://gocover.io/github.com/hjson/hjson-go/)
|
||||||
|
[](https://godoc.org/github.com/hjson/hjson-go)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
# specify rate in requests/second (because comments are helpful!)
|
||||||
|
rate: 1000
|
||||||
|
|
||||||
|
// prefer c-style comments?
|
||||||
|
/* feeling old fashioned? */
|
||||||
|
|
||||||
|
# did you notice that rate doesn't need quotes?
|
||||||
|
hey: look ma, no quotes for strings either!
|
||||||
|
|
||||||
|
# best of all
|
||||||
|
notice: []
|
||||||
|
anything: ?
|
||||||
|
|
||||||
|
# yes, commas are optional!
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The Go implementation of Hjson is based on [hjson-js](https://github.com/hjson/hjson-js). For other platforms see [hjson.github.io](https://hjson.github.io).
|
||||||
|
|
||||||
|
# Install
|
||||||
|
|
||||||
|
Make sure you have a working Go environment. See the [install instructions](http://golang.org/doc/install.html).
|
||||||
|
|
||||||
|
1. Get the sources
|
||||||
|
```bash
|
||||||
|
$ go get -u github.com/hjson/hjson-go
|
||||||
|
```
|
||||||
|
2. Build the **hjson-cli** commandline tool (optional)
|
||||||
|
```bash
|
||||||
|
$ cd $(go env GOPATH)/src/github.com/hjson/hjson-go/hjson-cli && go install
|
||||||
|
$ hjson-cli --version
|
||||||
|
```
|
||||||
|
# Usage as command line tool
|
||||||
|
```
|
||||||
|
usage: hjson-cli [OPTIONS] [INPUT]
|
||||||
|
hjson can be used to convert JSON from/to Hjson.
|
||||||
|
|
||||||
|
hjson will read the given JSON/Hjson input file or read from stdin.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-allowMinusZero
|
||||||
|
Allow -0.
|
||||||
|
-bracesSameLine
|
||||||
|
Print braces on the same line.
|
||||||
|
-c Output as JSON.
|
||||||
|
-h Show this screen.
|
||||||
|
-indentBy string
|
||||||
|
The indent string. (default " ")
|
||||||
|
-j Output as formatted JSON.
|
||||||
|
-omitRootBraces
|
||||||
|
Omit braces at the root.
|
||||||
|
-quoteAlways
|
||||||
|
Always quote string values.
|
||||||
|
```
|
||||||
|
|
||||||
|
Sample:
|
||||||
|
- run `hjson-cli test.json > test.hjson` to convert to Hjson
|
||||||
|
- run `hjson-cli -j test.hjson > test.json` to convert to JSON
|
||||||
|
|
||||||
|
# Usage as a GO library
|
||||||
|
|
||||||
|
```go
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hjson/hjson-go"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
// Now let's look at decoding Hjson data into Go
|
||||||
|
// values.
|
||||||
|
sampleText := []byte(`
|
||||||
|
{
|
||||||
|
# specify rate in requests/second
|
||||||
|
rate: 1000
|
||||||
|
array:
|
||||||
|
[
|
||||||
|
foo
|
||||||
|
bar
|
||||||
|
]
|
||||||
|
}`)
|
||||||
|
|
||||||
|
// We need to provide a variable where Hjson
|
||||||
|
// can put the decoded data.
|
||||||
|
var dat map[string]interface{}
|
||||||
|
|
||||||
|
// Decode and a check for errors.
|
||||||
|
if err := hjson.Unmarshal(sampleText, &dat); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Println(dat)
|
||||||
|
|
||||||
|
// In order to use the values in the decoded map,
|
||||||
|
// we'll need to cast them to their appropriate type.
|
||||||
|
|
||||||
|
rate := dat["rate"].(float64)
|
||||||
|
fmt.Println(rate)
|
||||||
|
|
||||||
|
array := dat["array"].([]interface{})
|
||||||
|
str1 := array[0].(string)
|
||||||
|
fmt.Println(str1)
|
||||||
|
|
||||||
|
|
||||||
|
// To encode to Hjson with default options:
|
||||||
|
sampleMap := map[string]int{"apple": 5, "lettuce": 7}
|
||||||
|
hjson, _ := hjson.Marshal(sampleMap)
|
||||||
|
// this is short for:
|
||||||
|
// options := hjson.DefaultOptions()
|
||||||
|
// hjson, _ := hjson.MarshalWithOptions(sampleMap, options)
|
||||||
|
fmt.Println(string(hjson))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If you prefer, you can also unmarshal to Go objects by converting to JSON:
|
||||||
|
|
||||||
|
```go
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hjson/hjson-go"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Sample struct {
|
||||||
|
Rate int
|
||||||
|
Array []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
sampleText := []byte(`
|
||||||
|
{
|
||||||
|
# specify rate in requests/second
|
||||||
|
rate: 1000
|
||||||
|
array:
|
||||||
|
[
|
||||||
|
foo
|
||||||
|
bar
|
||||||
|
]
|
||||||
|
}`)
|
||||||
|
|
||||||
|
// read Hjson
|
||||||
|
var dat map[string]interface{}
|
||||||
|
hjson.Unmarshal(sampleText, &dat)
|
||||||
|
|
||||||
|
// convert to JSON
|
||||||
|
b, _ := json.Marshal(dat)
|
||||||
|
|
||||||
|
// unmarshal
|
||||||
|
var sample Sample
|
||||||
|
json.Unmarshal(b, &sample)
|
||||||
|
|
||||||
|
fmt.Println(sample.Rate)
|
||||||
|
fmt.Println(sample.Array)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# API
|
||||||
|
|
||||||
|
[](http://godoc.org/github.com/hjson/hjson-go)
|
||||||
|
|
||||||
|
# History
|
||||||
|
|
||||||
|
[see releases](https://github.com/hjson/hjson-go/releases)
|
||||||
58
vendor/github.com/hjson/hjson-go/build_release.sh
generated
vendored
Normal file
58
vendor/github.com/hjson/hjson-go/build_release.sh
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
cd `dirname $0`
|
||||||
|
ROOT=$PWD/_dist
|
||||||
|
|
||||||
|
if [ -d "$ROOT" ]; then rm -rf $ROOT; fi
|
||||||
|
|
||||||
|
mkdir $ROOT
|
||||||
|
|
||||||
|
function build() {
|
||||||
|
export GOOS=$1
|
||||||
|
export GOARCH=$2
|
||||||
|
|
||||||
|
echo build $GOOS $GOARCH
|
||||||
|
OUT=$ROOT/${GOOS}_${GOARCH}
|
||||||
|
mkdir $OUT
|
||||||
|
cd $OUT
|
||||||
|
go build github.com/hjson/hjson-go/hjson-cli
|
||||||
|
if [[ $3 == "zip" ]]; then
|
||||||
|
mv $OUT/hjson-cli.exe $OUT/hjson.exe
|
||||||
|
zip -j ${OUT}.zip $OUT/*
|
||||||
|
else
|
||||||
|
mv $OUT/hjson-cli $OUT/hjson
|
||||||
|
tar -czf ${OUT}.tar.gz -C $OUT .
|
||||||
|
fi
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
# not all targets can be built on travis
|
||||||
|
|
||||||
|
# build android arm
|
||||||
|
build darwin 386
|
||||||
|
build darwin amd64
|
||||||
|
# build darwin arm
|
||||||
|
# build darwin arm64
|
||||||
|
build dragonfly amd64
|
||||||
|
build freebsd 386
|
||||||
|
build freebsd amd64
|
||||||
|
build freebsd arm
|
||||||
|
build linux 386
|
||||||
|
build linux amd64
|
||||||
|
build linux arm
|
||||||
|
build linux arm64
|
||||||
|
build linux mips64
|
||||||
|
build linux mips64le
|
||||||
|
build linux ppc64
|
||||||
|
build linux ppc64le
|
||||||
|
build netbsd 386
|
||||||
|
build netbsd amd64
|
||||||
|
build netbsd arm
|
||||||
|
build openbsd 386
|
||||||
|
build openbsd amd64
|
||||||
|
build openbsd arm
|
||||||
|
build plan9 386
|
||||||
|
build plan9 amd64
|
||||||
|
build solaris amd64
|
||||||
|
build windows 386 zip
|
||||||
|
build windows amd64 zip
|
||||||
480
vendor/github.com/hjson/hjson-go/decode.go
generated
vendored
Normal file
480
vendor/github.com/hjson/hjson-go/decode.go
generated
vendored
Normal file
@@ -0,0 +1,480 @@
|
|||||||
|
package hjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type hjsonParser struct {
|
||||||
|
data []byte
|
||||||
|
at int // The index of the current character
|
||||||
|
ch byte // The current character
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) resetAt() {
|
||||||
|
p.at = 0
|
||||||
|
p.ch = ' '
|
||||||
|
}
|
||||||
|
|
||||||
|
func isPunctuatorChar(c byte) bool {
|
||||||
|
return c == '{' || c == '}' || c == '[' || c == ']' || c == ',' || c == ':'
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) errAt(message string) error {
|
||||||
|
var i int
|
||||||
|
col := 0
|
||||||
|
line := 1
|
||||||
|
for i = p.at - 1; i > 0 && p.data[i] != '\n'; i-- {
|
||||||
|
col++
|
||||||
|
}
|
||||||
|
for ; i > 0; i-- {
|
||||||
|
if p.data[i] == '\n' {
|
||||||
|
line++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
samEnd := p.at - col + 20
|
||||||
|
if samEnd > len(p.data) {
|
||||||
|
samEnd = len(p.data)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("%s at line %d,%d >>> %s", message, line, col, string(p.data[p.at-col:samEnd]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) next() bool {
|
||||||
|
// get the next character.
|
||||||
|
if p.at < len(p.data) {
|
||||||
|
p.ch = p.data[p.at]
|
||||||
|
p.at++
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
p.ch = 0
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) peek(offs int) byte {
|
||||||
|
pos := p.at + offs
|
||||||
|
if pos >= 0 && pos < len(p.data) {
|
||||||
|
return p.data[p.at+offs]
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var escapee = map[byte]byte{
|
||||||
|
'"': '"',
|
||||||
|
'\'': '\'',
|
||||||
|
'\\': '\\',
|
||||||
|
'/': '/',
|
||||||
|
'b': '\b',
|
||||||
|
'f': '\f',
|
||||||
|
'n': '\n',
|
||||||
|
'r': '\r',
|
||||||
|
't': '\t',
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) readString(allowML bool) (string, error) {
|
||||||
|
|
||||||
|
// Parse a string value.
|
||||||
|
res := new(bytes.Buffer)
|
||||||
|
|
||||||
|
// callers make sure that (ch === '"' || ch === "'")
|
||||||
|
// When parsing for string values, we must look for " and \ characters.
|
||||||
|
exitCh := p.ch
|
||||||
|
for p.next() {
|
||||||
|
if p.ch == exitCh {
|
||||||
|
p.next()
|
||||||
|
if allowML && exitCh == '\'' && p.ch == '\'' && res.Len() == 0 {
|
||||||
|
// ''' indicates a multiline string
|
||||||
|
p.next()
|
||||||
|
return p.readMLString()
|
||||||
|
} else {
|
||||||
|
return res.String(), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.ch == '\\' {
|
||||||
|
p.next()
|
||||||
|
if p.ch == 'u' {
|
||||||
|
uffff := 0
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
p.next()
|
||||||
|
var hex int
|
||||||
|
if p.ch >= '0' && p.ch <= '9' {
|
||||||
|
hex = int(p.ch - '0')
|
||||||
|
} else if p.ch >= 'a' && p.ch <= 'f' {
|
||||||
|
hex = int(p.ch - 'a' + 0xa)
|
||||||
|
} else if p.ch >= 'A' && p.ch <= 'F' {
|
||||||
|
hex = int(p.ch - 'A' + 0xa)
|
||||||
|
} else {
|
||||||
|
return "", p.errAt("Bad \\u char " + string(p.ch))
|
||||||
|
}
|
||||||
|
uffff = uffff*16 + hex
|
||||||
|
}
|
||||||
|
res.WriteRune(rune(uffff))
|
||||||
|
} else if ech, ok := escapee[p.ch]; ok {
|
||||||
|
res.WriteByte(ech)
|
||||||
|
} else {
|
||||||
|
return "", p.errAt("Bad escape \\" + string(p.ch))
|
||||||
|
}
|
||||||
|
} else if p.ch == '\n' || p.ch == '\r' {
|
||||||
|
return "", p.errAt("Bad string containing newline")
|
||||||
|
} else {
|
||||||
|
res.WriteByte(p.ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", p.errAt("Bad string")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) readMLString() (value string, err error) {
|
||||||
|
|
||||||
|
// Parse a multiline string value.
|
||||||
|
res := new(bytes.Buffer)
|
||||||
|
triple := 0
|
||||||
|
|
||||||
|
// we are at ''' +1 - get indent
|
||||||
|
indent := 0
|
||||||
|
for {
|
||||||
|
c := p.peek(-indent - 5)
|
||||||
|
if c == 0 || c == '\n' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
indent++
|
||||||
|
}
|
||||||
|
|
||||||
|
skipIndent := func() {
|
||||||
|
skip := indent
|
||||||
|
for p.ch > 0 && p.ch <= ' ' && p.ch != '\n' && skip > 0 {
|
||||||
|
skip--
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip white/to (newline)
|
||||||
|
for p.ch > 0 && p.ch <= ' ' && p.ch != '\n' {
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
if p.ch == '\n' {
|
||||||
|
p.next()
|
||||||
|
skipIndent()
|
||||||
|
}
|
||||||
|
|
||||||
|
// When parsing multiline string values, we must look for ' characters.
|
||||||
|
lastLf := false
|
||||||
|
for {
|
||||||
|
if p.ch == 0 {
|
||||||
|
return "", p.errAt("Bad multiline string")
|
||||||
|
} else if p.ch == '\'' {
|
||||||
|
triple++
|
||||||
|
p.next()
|
||||||
|
if triple == 3 {
|
||||||
|
sres := res.Bytes()
|
||||||
|
if lastLf {
|
||||||
|
return string(sres[0 : len(sres)-1]), nil // remove last EOL
|
||||||
|
}
|
||||||
|
return string(sres), nil
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
for triple > 0 {
|
||||||
|
res.WriteByte('\'')
|
||||||
|
triple--
|
||||||
|
lastLf = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.ch == '\n' {
|
||||||
|
res.WriteByte('\n')
|
||||||
|
lastLf = true
|
||||||
|
p.next()
|
||||||
|
skipIndent()
|
||||||
|
} else {
|
||||||
|
if p.ch != '\r' {
|
||||||
|
res.WriteByte(p.ch)
|
||||||
|
lastLf = false
|
||||||
|
}
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) readKeyname() (string, error) {
|
||||||
|
|
||||||
|
// quotes for keys are optional in Hjson
|
||||||
|
// unless they include {}[],: or whitespace.
|
||||||
|
|
||||||
|
if p.ch == '"' || p.ch == '\'' {
|
||||||
|
return p.readString(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
name := new(bytes.Buffer)
|
||||||
|
start := p.at
|
||||||
|
space := -1
|
||||||
|
for {
|
||||||
|
if p.ch == ':' {
|
||||||
|
if name.Len() == 0 {
|
||||||
|
return "", p.errAt("Found ':' but no key name (for an empty key name use quotes)")
|
||||||
|
} else if space >= 0 && space != name.Len() {
|
||||||
|
p.at = start + space
|
||||||
|
return "", p.errAt("Found whitespace in your key name (use quotes to include)")
|
||||||
|
}
|
||||||
|
return name.String(), nil
|
||||||
|
} else if p.ch <= ' ' {
|
||||||
|
if p.ch == 0 {
|
||||||
|
return "", p.errAt("Found EOF while looking for a key name (check your syntax)")
|
||||||
|
}
|
||||||
|
if space < 0 {
|
||||||
|
space = name.Len()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if isPunctuatorChar(p.ch) {
|
||||||
|
return "", p.errAt("Found '" + string(p.ch) + "' where a key name was expected (check your syntax or use quotes if the key name includes {}[],: or whitespace)")
|
||||||
|
}
|
||||||
|
name.WriteByte(p.ch)
|
||||||
|
}
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) white() {
|
||||||
|
for p.ch > 0 {
|
||||||
|
// Skip whitespace.
|
||||||
|
for p.ch > 0 && p.ch <= ' ' {
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
// Hjson allows comments
|
||||||
|
if p.ch == '#' || p.ch == '/' && p.peek(0) == '/' {
|
||||||
|
for p.ch > 0 && p.ch != '\n' {
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
} else if p.ch == '/' && p.peek(0) == '*' {
|
||||||
|
p.next()
|
||||||
|
p.next()
|
||||||
|
for p.ch > 0 && !(p.ch == '*' && p.peek(0) == '/') {
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
if p.ch > 0 {
|
||||||
|
p.next()
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) readTfnns() (interface{}, error) {
|
||||||
|
|
||||||
|
// Hjson strings can be quoteless
|
||||||
|
// returns string, true, false, or null.
|
||||||
|
|
||||||
|
if isPunctuatorChar(p.ch) {
|
||||||
|
return nil, p.errAt("Found a punctuator character '" + string(p.ch) + "' when expecting a quoteless string (check your syntax)")
|
||||||
|
}
|
||||||
|
chf := p.ch
|
||||||
|
value := new(bytes.Buffer)
|
||||||
|
value.WriteByte(p.ch)
|
||||||
|
|
||||||
|
for {
|
||||||
|
p.next()
|
||||||
|
isEol := p.ch == '\r' || p.ch == '\n' || p.ch == 0
|
||||||
|
if isEol ||
|
||||||
|
p.ch == ',' || p.ch == '}' || p.ch == ']' ||
|
||||||
|
p.ch == '#' ||
|
||||||
|
p.ch == '/' && (p.peek(0) == '/' || p.peek(0) == '*') {
|
||||||
|
switch chf {
|
||||||
|
case 'f':
|
||||||
|
if strings.TrimSpace(value.String()) == "false" {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
case 'n':
|
||||||
|
if strings.TrimSpace(value.String()) == "null" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
case 't':
|
||||||
|
if strings.TrimSpace(value.String()) == "true" {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if chf == '-' || chf >= '0' && chf <= '9' {
|
||||||
|
if n, err := tryParseNumber(value.Bytes(), false); err == nil {
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isEol {
|
||||||
|
// remove any whitespace at the end (ignored in quoteless strings)
|
||||||
|
return strings.TrimSpace(value.String()), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value.WriteByte(p.ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) readArray() (value interface{}, err error) {
|
||||||
|
|
||||||
|
// Parse an array value.
|
||||||
|
// assuming ch == '['
|
||||||
|
|
||||||
|
array := make([]interface{}, 0, 1)
|
||||||
|
|
||||||
|
p.next()
|
||||||
|
p.white()
|
||||||
|
|
||||||
|
if p.ch == ']' {
|
||||||
|
p.next()
|
||||||
|
return array, nil // empty array
|
||||||
|
}
|
||||||
|
|
||||||
|
for p.ch > 0 {
|
||||||
|
var val interface{}
|
||||||
|
if val, err = p.readValue(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
array = append(array, val)
|
||||||
|
p.white()
|
||||||
|
// in Hjson the comma is optional and trailing commas are allowed
|
||||||
|
if p.ch == ',' {
|
||||||
|
p.next()
|
||||||
|
p.white()
|
||||||
|
}
|
||||||
|
if p.ch == ']' {
|
||||||
|
p.next()
|
||||||
|
return array, nil
|
||||||
|
}
|
||||||
|
p.white()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, p.errAt("End of input while parsing an array (did you forget a closing ']'?)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) readObject(withoutBraces bool) (value interface{}, err error) {
|
||||||
|
// Parse an object value.
|
||||||
|
|
||||||
|
object := make(map[string]interface{})
|
||||||
|
|
||||||
|
if !withoutBraces {
|
||||||
|
// assuming ch == '{'
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
p.white()
|
||||||
|
if p.ch == '}' && !withoutBraces {
|
||||||
|
p.next()
|
||||||
|
return object, nil // empty object
|
||||||
|
}
|
||||||
|
for p.ch > 0 {
|
||||||
|
var key string
|
||||||
|
if key, err = p.readKeyname(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p.white()
|
||||||
|
if p.ch != ':' {
|
||||||
|
return nil, p.errAt("Expected ':' instead of '" + string(p.ch) + "'")
|
||||||
|
}
|
||||||
|
p.next()
|
||||||
|
// duplicate keys overwrite the previous value
|
||||||
|
var val interface{}
|
||||||
|
if val, err = p.readValue(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
object[key] = val
|
||||||
|
p.white()
|
||||||
|
// in Hjson the comma is optional and trailing commas are allowed
|
||||||
|
if p.ch == ',' {
|
||||||
|
p.next()
|
||||||
|
p.white()
|
||||||
|
}
|
||||||
|
if p.ch == '}' && !withoutBraces {
|
||||||
|
p.next()
|
||||||
|
return object, nil
|
||||||
|
}
|
||||||
|
p.white()
|
||||||
|
}
|
||||||
|
|
||||||
|
if withoutBraces {
|
||||||
|
return object, nil
|
||||||
|
}
|
||||||
|
return nil, p.errAt("End of input while parsing an object (did you forget a closing '}'?)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) readValue() (interface{}, error) {
|
||||||
|
|
||||||
|
// Parse a Hjson value. It could be an object, an array, a string, a number or a word.
|
||||||
|
|
||||||
|
p.white()
|
||||||
|
switch p.ch {
|
||||||
|
case '{':
|
||||||
|
return p.readObject(false)
|
||||||
|
case '[':
|
||||||
|
return p.readArray()
|
||||||
|
case '"', '\'':
|
||||||
|
return p.readString(true)
|
||||||
|
default:
|
||||||
|
return p.readTfnns()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) rootValue() (interface{}, error) {
|
||||||
|
// Braces for the root object are optional
|
||||||
|
|
||||||
|
p.white()
|
||||||
|
switch p.ch {
|
||||||
|
case '{':
|
||||||
|
return p.checkTrailing(p.readObject(false))
|
||||||
|
case '[':
|
||||||
|
return p.checkTrailing(p.readArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
// assume we have a root object without braces
|
||||||
|
res, err := p.checkTrailing(p.readObject(true))
|
||||||
|
if err == nil {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// test if we are dealing with a single JSON value instead (true/false/null/num/"")
|
||||||
|
p.resetAt()
|
||||||
|
if res2, err2 := p.checkTrailing(p.readValue()); err2 == nil {
|
||||||
|
return res2, nil
|
||||||
|
}
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) checkTrailing(v interface{}, err error) (interface{}, error) {
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p.white()
|
||||||
|
if p.ch > 0 {
|
||||||
|
return nil, p.errAt("Syntax error, found trailing characters")
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal parses the Hjson-encoded data and stores the result
|
||||||
|
// in the value pointed to by v.
|
||||||
|
//
|
||||||
|
// Unmarshal uses the inverse of the encodings that
|
||||||
|
// Marshal uses, allocating maps, slices, and pointers as necessary.
|
||||||
|
//
|
||||||
|
func Unmarshal(data []byte, v interface{}) (err error) {
|
||||||
|
var value interface{}
|
||||||
|
parser := &hjsonParser{data, 0, ' '}
|
||||||
|
parser.resetAt()
|
||||||
|
value, err = parser.rootValue()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rv := reflect.ValueOf(v)
|
||||||
|
if rv.Kind() != reflect.Ptr || rv.IsNil() {
|
||||||
|
return fmt.Errorf("non-pointer %v", reflect.TypeOf(v))
|
||||||
|
}
|
||||||
|
for rv.Kind() == reflect.Ptr {
|
||||||
|
rv = rv.Elem()
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if e := recover(); e != nil {
|
||||||
|
err = fmt.Errorf("%v", e)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
rv.Set(reflect.ValueOf(value))
|
||||||
|
return err
|
||||||
|
}
|
||||||
492
vendor/github.com/hjson/hjson-go/encode.go
generated
vendored
Normal file
492
vendor/github.com/hjson/hjson-go/encode.go
generated
vendored
Normal file
@@ -0,0 +1,492 @@
|
|||||||
|
package hjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EncoderOptions defines options for encoding to Hjson.
|
||||||
|
type EncoderOptions struct {
|
||||||
|
// End of line, should be either \n or \r\n
|
||||||
|
Eol string
|
||||||
|
// Place braces on the same line
|
||||||
|
BracesSameLine bool
|
||||||
|
// Emit braces for the root object
|
||||||
|
EmitRootBraces bool
|
||||||
|
// Always place string in quotes
|
||||||
|
QuoteAlways bool
|
||||||
|
// Place string in quotes if it could otherwise be a number, boolean or null
|
||||||
|
QuoteAmbiguousStrings bool
|
||||||
|
// Indent string
|
||||||
|
IndentBy string
|
||||||
|
// Base indentation string
|
||||||
|
BaseIndentation string
|
||||||
|
// Allow the -0 value (unlike ES6)
|
||||||
|
AllowMinusZero bool
|
||||||
|
// Encode unknown values as 'null'
|
||||||
|
UnknownAsNull bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultOptions returns the default encoding options.
|
||||||
|
func DefaultOptions() EncoderOptions {
|
||||||
|
opt := EncoderOptions{}
|
||||||
|
opt.Eol = "\n"
|
||||||
|
opt.BracesSameLine = false
|
||||||
|
opt.EmitRootBraces = true
|
||||||
|
opt.QuoteAlways = false
|
||||||
|
opt.QuoteAmbiguousStrings = true
|
||||||
|
opt.IndentBy = " "
|
||||||
|
opt.BaseIndentation = ""
|
||||||
|
opt.AllowMinusZero = false
|
||||||
|
opt.UnknownAsNull = false
|
||||||
|
return opt
|
||||||
|
}
|
||||||
|
|
||||||
|
type hjsonEncoder struct {
|
||||||
|
bytes.Buffer // output
|
||||||
|
EncoderOptions
|
||||||
|
indent int
|
||||||
|
}
|
||||||
|
|
||||||
|
var needsEscape, needsQuotes, needsEscapeML, startsWithKeyword, needsEscapeName *regexp.Regexp
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var commonRange = `\x7f-\x9f\x{00ad}\x{0600}-\x{0604}\x{070f}\x{17b4}\x{17b5}\x{200c}-\x{200f}\x{2028}-\x{202f}\x{2060}-\x{206f}\x{feff}\x{fff0}-\x{ffff}`
|
||||||
|
// needsEscape tests if the string can be written without escapes
|
||||||
|
needsEscape = regexp.MustCompile(`[\\\"\x00-\x1f` + commonRange + `]`)
|
||||||
|
// needsQuotes tests if the string can be written as a quoteless string (includes needsEscape but without \\ and \")
|
||||||
|
needsQuotes = regexp.MustCompile(`^\s|^"|^'|^#|^/\*|^//|^\{|^\}|^\[|^\]|^:|^,|\s$|[\x00-\x1f\x7f-\x9f\x{00ad}\x{0600}-\x{0604}\x{070f}\x{17b4}\x{17b5}\x{200c}-\x{200f}\x{2028}-\x{202f}\x{2060}-\x{206f}\x{feff}\x{fff0}-\x{ffff}]`)
|
||||||
|
// needsEscapeML tests if the string can be written as a multiline string (like needsEscape but without \n, \r, \\, \", \t)
|
||||||
|
var x08Or9 = `\x08` // `\x09` for the old behavior
|
||||||
|
needsEscapeML = regexp.MustCompile(`'''|^[\s]+$|[\x00-` + x08Or9 + `\x0b\x0c\x0e-\x1f` + commonRange + `]`)
|
||||||
|
// starts with a keyword and optionally is followed by a comment
|
||||||
|
startsWithKeyword = regexp.MustCompile(`^(true|false|null)\s*((,|\]|\}|#|//|/\*).*)?$`)
|
||||||
|
needsEscapeName = regexp.MustCompile(`[,\{\[\}\]\s:#"']|//|/\*`)
|
||||||
|
}
|
||||||
|
|
||||||
|
var meta = map[byte][]byte{
|
||||||
|
// table of character substitutions
|
||||||
|
'\b': []byte("\\b"),
|
||||||
|
'\t': []byte("\\t"),
|
||||||
|
'\n': []byte("\\n"),
|
||||||
|
'\f': []byte("\\f"),
|
||||||
|
'\r': []byte("\\r"),
|
||||||
|
'"': []byte("\\\""),
|
||||||
|
'\\': []byte("\\\\"),
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *hjsonEncoder) quoteReplace(text string) string {
|
||||||
|
return string(needsEscape.ReplaceAllFunc([]byte(text), func(a []byte) []byte {
|
||||||
|
c := meta[a[0]]
|
||||||
|
if c != nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
r, _ := utf8.DecodeRune(a)
|
||||||
|
return []byte(fmt.Sprintf("\\u%04x", r))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *hjsonEncoder) quote(value string, separator string, isRootObject bool) {
|
||||||
|
|
||||||
|
// Check if we can insert this string without quotes
|
||||||
|
// see hjson syntax (must not parse as true, false, null or number)
|
||||||
|
|
||||||
|
if len(value) == 0 {
|
||||||
|
e.WriteString(separator + `""`)
|
||||||
|
} else if e.QuoteAlways ||
|
||||||
|
needsQuotes.MatchString(value) || (e.QuoteAmbiguousStrings && (startsWithNumber([]byte(value)) ||
|
||||||
|
startsWithKeyword.MatchString(value))) {
|
||||||
|
|
||||||
|
// If the string contains no control characters, no quote characters, and no
|
||||||
|
// backslash characters, then we can safely slap some quotes around it.
|
||||||
|
// Otherwise we first check if the string can be expressed in multiline
|
||||||
|
// format or we must replace the offending characters with safe escape
|
||||||
|
// sequences.
|
||||||
|
|
||||||
|
if !needsEscape.MatchString(value) {
|
||||||
|
e.WriteString(separator + `"` + value + `"`)
|
||||||
|
} else if !needsEscapeML.MatchString(value) && !isRootObject {
|
||||||
|
e.mlString(value, separator)
|
||||||
|
} else {
|
||||||
|
e.WriteString(separator + `"` + e.quoteReplace(value) + `"`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// return without quotes
|
||||||
|
e.WriteString(separator + value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *hjsonEncoder) mlString(value string, separator string) {
|
||||||
|
// wrap the string into the ''' (multiline) format
|
||||||
|
|
||||||
|
a := strings.Split(strings.Replace(value, "\r", "", -1), "\n")
|
||||||
|
|
||||||
|
if len(a) == 1 {
|
||||||
|
// The string contains only a single line. We still use the multiline
|
||||||
|
// format as it avoids escaping the \ character (e.g. when used in a
|
||||||
|
// regex).
|
||||||
|
e.WriteString(separator + "'''")
|
||||||
|
e.WriteString(a[0])
|
||||||
|
} else {
|
||||||
|
e.writeIndent(e.indent + 1)
|
||||||
|
e.WriteString("'''")
|
||||||
|
for _, v := range a {
|
||||||
|
indent := e.indent + 1
|
||||||
|
if len(v) == 0 {
|
||||||
|
indent = 0
|
||||||
|
}
|
||||||
|
e.writeIndent(indent)
|
||||||
|
e.WriteString(v)
|
||||||
|
}
|
||||||
|
e.writeIndent(e.indent + 1)
|
||||||
|
}
|
||||||
|
e.WriteString("'''")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *hjsonEncoder) quoteName(name string) string {
|
||||||
|
if len(name) == 0 {
|
||||||
|
return `""`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we can insert this name without quotes
|
||||||
|
|
||||||
|
if needsEscapeName.MatchString(name) {
|
||||||
|
if needsEscape.MatchString(name) {
|
||||||
|
name = e.quoteReplace(name)
|
||||||
|
}
|
||||||
|
return `"` + name + `"`
|
||||||
|
}
|
||||||
|
// without quotes
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
type sortAlpha []reflect.Value
|
||||||
|
|
||||||
|
func (s sortAlpha) Len() int {
|
||||||
|
return len(s)
|
||||||
|
}
|
||||||
|
func (s sortAlpha) Swap(i, j int) {
|
||||||
|
s[i], s[j] = s[j], s[i]
|
||||||
|
}
|
||||||
|
func (s sortAlpha) Less(i, j int) bool {
|
||||||
|
return s[i].String() < s[j].String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *hjsonEncoder) writeIndent(indent int) {
|
||||||
|
e.WriteString(e.Eol)
|
||||||
|
e.WriteString(e.BaseIndentation)
|
||||||
|
for i := 0; i < indent; i++ {
|
||||||
|
e.WriteString(e.IndentBy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *hjsonEncoder) useMarshaler(value reflect.Value, separator string) error {
|
||||||
|
b, err := value.Interface().(json.Marshaler).MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
e.WriteString(separator)
|
||||||
|
e.WriteString(string(b))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var marshaler = reflect.TypeOf((*json.Marshaler)(nil)).Elem()
|
||||||
|
|
||||||
|
func (e *hjsonEncoder) str(value reflect.Value, noIndent bool, separator string, isRootObject bool) error {
|
||||||
|
|
||||||
|
// Produce a string from value.
|
||||||
|
|
||||||
|
kind := value.Kind()
|
||||||
|
|
||||||
|
if kind == reflect.Interface || kind == reflect.Ptr {
|
||||||
|
if value.IsNil() {
|
||||||
|
e.WriteString(separator)
|
||||||
|
e.WriteString("null")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return e.str(value.Elem(),noIndent,separator,isRootObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
if value.Type().Implements(marshaler) {
|
||||||
|
return e.useMarshaler(value, separator)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch kind {
|
||||||
|
case reflect.String:
|
||||||
|
e.quote(value.String(), separator, isRootObject)
|
||||||
|
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
e.WriteString(separator)
|
||||||
|
e.WriteString(strconv.FormatInt(value.Int(), 10))
|
||||||
|
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
|
||||||
|
reflect.Uintptr:
|
||||||
|
e.WriteString(separator)
|
||||||
|
e.WriteString(strconv.FormatUint(value.Uint(), 10))
|
||||||
|
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
// JSON numbers must be finite. Encode non-finite numbers as null.
|
||||||
|
e.WriteString(separator)
|
||||||
|
number := value.Float()
|
||||||
|
if math.IsInf(number, 0) || math.IsNaN(number) {
|
||||||
|
e.WriteString("null")
|
||||||
|
} else if !e.AllowMinusZero && number == -0 {
|
||||||
|
e.WriteString("0")
|
||||||
|
} else {
|
||||||
|
// find shortest representation ('G' does not work)
|
||||||
|
val := strconv.FormatFloat(number, 'f', -1, 64)
|
||||||
|
exp := strconv.FormatFloat(number, 'E', -1, 64)
|
||||||
|
if len(exp) < len(val) {
|
||||||
|
val = strings.ToLower(exp)
|
||||||
|
}
|
||||||
|
e.WriteString(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Bool:
|
||||||
|
e.WriteString(separator)
|
||||||
|
if value.Bool() {
|
||||||
|
e.WriteString("true")
|
||||||
|
} else {
|
||||||
|
e.WriteString("false")
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Slice, reflect.Array:
|
||||||
|
|
||||||
|
len := value.Len()
|
||||||
|
if len == 0 {
|
||||||
|
e.WriteString(separator)
|
||||||
|
e.WriteString("[]")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
indent1 := e.indent
|
||||||
|
e.indent++
|
||||||
|
|
||||||
|
if !noIndent && !e.BracesSameLine {
|
||||||
|
e.writeIndent(indent1)
|
||||||
|
} else {
|
||||||
|
e.WriteString(separator)
|
||||||
|
}
|
||||||
|
e.WriteString("[")
|
||||||
|
|
||||||
|
// Join all of the element texts together, separated with newlines
|
||||||
|
for i := 0; i < len; i++ {
|
||||||
|
e.writeIndent(e.indent)
|
||||||
|
if err := e.str(value.Index(i), true, "", false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
e.writeIndent(indent1)
|
||||||
|
e.WriteString("]")
|
||||||
|
|
||||||
|
e.indent = indent1
|
||||||
|
|
||||||
|
case reflect.Map:
|
||||||
|
|
||||||
|
len := value.Len()
|
||||||
|
if len == 0 {
|
||||||
|
e.WriteString(separator)
|
||||||
|
e.WriteString("{}")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
indent1 := e.indent
|
||||||
|
if !isRootObject || e.EmitRootBraces {
|
||||||
|
if !noIndent && !e.BracesSameLine {
|
||||||
|
e.writeIndent(e.indent)
|
||||||
|
} else {
|
||||||
|
e.WriteString(separator)
|
||||||
|
}
|
||||||
|
|
||||||
|
e.indent++
|
||||||
|
e.WriteString("{")
|
||||||
|
}
|
||||||
|
|
||||||
|
keys := value.MapKeys()
|
||||||
|
sort.Sort(sortAlpha(keys))
|
||||||
|
|
||||||
|
// Join all of the member texts together, separated with newlines
|
||||||
|
for i := 0; i < len; i++ {
|
||||||
|
if i > 0 || !isRootObject || e.EmitRootBraces {
|
||||||
|
e.writeIndent(e.indent)
|
||||||
|
}
|
||||||
|
e.WriteString(e.quoteName(keys[i].String()))
|
||||||
|
e.WriteString(":")
|
||||||
|
if err := e.str(value.MapIndex(keys[i]), false, " ", false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isRootObject || e.EmitRootBraces {
|
||||||
|
e.writeIndent(indent1)
|
||||||
|
e.WriteString("}")
|
||||||
|
}
|
||||||
|
e.indent = indent1
|
||||||
|
|
||||||
|
case reflect.Struct:
|
||||||
|
|
||||||
|
l := value.NumField()
|
||||||
|
if l == 0 {
|
||||||
|
e.WriteString(separator)
|
||||||
|
e.WriteString("{}")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
indent1 := e.indent
|
||||||
|
if !isRootObject || e.EmitRootBraces {
|
||||||
|
if !noIndent && !e.BracesSameLine {
|
||||||
|
e.writeIndent(e.indent)
|
||||||
|
} else {
|
||||||
|
e.WriteString(separator)
|
||||||
|
}
|
||||||
|
|
||||||
|
e.indent++
|
||||||
|
e.WriteString("{")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join all of the member texts together, separated with newlines
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
curStructField := value.Type().Field(i)
|
||||||
|
curField := value.Field(i)
|
||||||
|
|
||||||
|
name := curStructField.Name
|
||||||
|
jsonTag := curStructField.Tag.Get("json")
|
||||||
|
jsonComment := curStructField.Tag.Get("comment")
|
||||||
|
omitEmpty := false
|
||||||
|
if jsonTag == "-" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
splits := strings.Split(jsonTag, ",")
|
||||||
|
if splits[0] != "" {
|
||||||
|
name = splits[0]
|
||||||
|
}
|
||||||
|
if len(splits) > 1 {
|
||||||
|
for _, opt := range splits[1:] {
|
||||||
|
if opt == "omitempty" {
|
||||||
|
omitEmpty = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if omitEmpty && isEmptyValue(curField) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(jsonComment) > 0 {
|
||||||
|
for _, line := range strings.Split(jsonComment, e.Eol) {
|
||||||
|
if i > 0 || !isRootObject || e.EmitRootBraces {
|
||||||
|
e.writeIndent(e.indent)
|
||||||
|
}
|
||||||
|
e.WriteString(fmt.Sprintf("# %s", line))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if i > 0 || !isRootObject || e.EmitRootBraces {
|
||||||
|
e.writeIndent(e.indent)
|
||||||
|
}
|
||||||
|
e.WriteString(e.quoteName(name))
|
||||||
|
e.WriteString(":")
|
||||||
|
if err := e.str(curField, false, " ", false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(jsonComment) > 0 && i < l-1 {
|
||||||
|
e.WriteString(e.Eol)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isRootObject || e.EmitRootBraces {
|
||||||
|
e.writeIndent(indent1)
|
||||||
|
e.WriteString("}")
|
||||||
|
}
|
||||||
|
|
||||||
|
e.indent = indent1
|
||||||
|
|
||||||
|
default:
|
||||||
|
if e.UnknownAsNull {
|
||||||
|
// Use null as a placeholder for non-JSON values.
|
||||||
|
e.WriteString("null")
|
||||||
|
} else {
|
||||||
|
return errors.New("Unsupported type " + value.Type().String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isEmptyValue(v reflect.Value) bool {
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||||
|
return v.Len() == 0
|
||||||
|
case reflect.Bool:
|
||||||
|
return !v.Bool()
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return v.Int() == 0
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
return v.Uint() == 0
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return v.Float() == 0
|
||||||
|
case reflect.Interface, reflect.Ptr:
|
||||||
|
return v.IsNil()
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal returns the Hjson encoding of v using
|
||||||
|
// default options.
|
||||||
|
//
|
||||||
|
// See MarshalWithOptions.
|
||||||
|
//
|
||||||
|
func Marshal(v interface{}) ([]byte, error) {
|
||||||
|
return MarshalWithOptions(v, DefaultOptions())
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalWithOptions returns the Hjson encoding of v.
|
||||||
|
//
|
||||||
|
// Marshal traverses the value v recursively.
|
||||||
|
//
|
||||||
|
// Boolean values encode as JSON booleans.
|
||||||
|
//
|
||||||
|
// Floating point, integer, and Number values encode as JSON numbers.
|
||||||
|
//
|
||||||
|
// String values encode as Hjson strings (quoteless, multiline or
|
||||||
|
// JSON).
|
||||||
|
//
|
||||||
|
// Array and slice values encode as JSON arrays.
|
||||||
|
//
|
||||||
|
// Map values encode as JSON objects. The map's key type must be a
|
||||||
|
// string. The map keys are sorted and used as JSON object keys.
|
||||||
|
//
|
||||||
|
// Pointer values encode as the value pointed to.
|
||||||
|
// A nil pointer encodes as the null JSON value.
|
||||||
|
//
|
||||||
|
// Interface values encode as the value contained in the interface.
|
||||||
|
// A nil interface value encodes as the null JSON value.
|
||||||
|
//
|
||||||
|
// JSON cannot represent cyclic data structures and Marshal does not
|
||||||
|
// handle them. Passing cyclic structures to Marshal will result in
|
||||||
|
// an infinite recursion.
|
||||||
|
//
|
||||||
|
func MarshalWithOptions(v interface{}, options EncoderOptions) ([]byte, error) {
|
||||||
|
e := &hjsonEncoder{}
|
||||||
|
e.indent = 0
|
||||||
|
e.Eol = options.Eol
|
||||||
|
e.BracesSameLine = options.BracesSameLine
|
||||||
|
e.EmitRootBraces = options.EmitRootBraces
|
||||||
|
e.QuoteAlways = options.QuoteAlways
|
||||||
|
e.QuoteAmbiguousStrings = options.QuoteAmbiguousStrings
|
||||||
|
e.IndentBy = options.IndentBy
|
||||||
|
e.BaseIndentation = options.BaseIndentation
|
||||||
|
|
||||||
|
err := e.str(reflect.ValueOf(v), true, e.BaseIndentation, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return e.Bytes(), nil
|
||||||
|
}
|
||||||
108
vendor/github.com/hjson/hjson-go/parseNumber.go
generated
vendored
Normal file
108
vendor/github.com/hjson/hjson-go/parseNumber.go
generated
vendored
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
package hjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type parseNumber struct {
|
||||||
|
data []byte
|
||||||
|
at int // The index of the current character
|
||||||
|
ch byte // The current character
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parseNumber) next() bool {
|
||||||
|
// get the next character.
|
||||||
|
len := len(p.data)
|
||||||
|
if p.at < len {
|
||||||
|
p.ch = p.data[p.at]
|
||||||
|
p.at++
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if p.at == len {
|
||||||
|
p.at++
|
||||||
|
p.ch = 0
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parseNumber) peek(offs int) byte {
|
||||||
|
pos := p.at + offs
|
||||||
|
if pos >= 0 && pos < len(p.data) {
|
||||||
|
return p.data[pos]
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func startsWithNumber(text []byte) bool {
|
||||||
|
if _, err := tryParseNumber(text, true); err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func tryParseNumber(text []byte, stopAtNext bool) (float64, error) {
|
||||||
|
// Parse a number value.
|
||||||
|
|
||||||
|
p := parseNumber{text, 0, ' '}
|
||||||
|
leadingZeros := 0
|
||||||
|
testLeading := true
|
||||||
|
p.next()
|
||||||
|
if p.ch == '-' {
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
for p.ch >= '0' && p.ch <= '9' {
|
||||||
|
if testLeading {
|
||||||
|
if p.ch == '0' {
|
||||||
|
leadingZeros++
|
||||||
|
} else {
|
||||||
|
testLeading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
if testLeading {
|
||||||
|
leadingZeros--
|
||||||
|
} // single 0 is allowed
|
||||||
|
if p.ch == '.' {
|
||||||
|
for p.next() && p.ch >= '0' && p.ch <= '9' {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.ch == 'e' || p.ch == 'E' {
|
||||||
|
p.next()
|
||||||
|
if p.ch == '-' || p.ch == '+' {
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
for p.ch >= '0' && p.ch <= '9' {
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
end := p.at
|
||||||
|
|
||||||
|
// skip white/to (newline)
|
||||||
|
for p.ch > 0 && p.ch <= ' ' {
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
if stopAtNext {
|
||||||
|
// end scan if we find a punctuator character like ,}] or a comment
|
||||||
|
if p.ch == ',' || p.ch == '}' || p.ch == ']' ||
|
||||||
|
p.ch == '#' || p.ch == '/' && (p.peek(0) == '/' || p.peek(0) == '*') {
|
||||||
|
p.ch = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.ch > 0 || leadingZeros != 0 {
|
||||||
|
return 0, errors.New("Invalid number")
|
||||||
|
}
|
||||||
|
number, err := strconv.ParseFloat(string(p.data[0:end-1]), 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if math.IsInf(number, 0) || math.IsNaN(number) {
|
||||||
|
return 0, errors.New("Invalid number")
|
||||||
|
}
|
||||||
|
return number, nil
|
||||||
|
}
|
||||||
22
vendor/github.com/robfig/cron/v3/.gitignore
generated
vendored
Normal file
22
vendor/github.com/robfig/cron/v3/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
||||||
1
vendor/github.com/robfig/cron/v3/.travis.yml
generated
vendored
Normal file
1
vendor/github.com/robfig/cron/v3/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
language: go
|
||||||
21
vendor/github.com/robfig/cron/v3/LICENSE
generated
vendored
Normal file
21
vendor/github.com/robfig/cron/v3/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
Copyright (C) 2012 Rob Figueiredo
|
||||||
|
All Rights Reserved.
|
||||||
|
|
||||||
|
MIT LICENSE
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
125
vendor/github.com/robfig/cron/v3/README.md
generated
vendored
Normal file
125
vendor/github.com/robfig/cron/v3/README.md
generated
vendored
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
[](http://godoc.org/github.com/robfig/cron)
|
||||||
|
[](https://travis-ci.org/robfig/cron)
|
||||||
|
|
||||||
|
# cron
|
||||||
|
|
||||||
|
Cron V3 has been released!
|
||||||
|
|
||||||
|
To download the specific tagged release, run:
|
||||||
|
|
||||||
|
go get github.com/robfig/cron/v3@v3.0.0
|
||||||
|
|
||||||
|
Import it in your program as:
|
||||||
|
|
||||||
|
import "github.com/robfig/cron/v3"
|
||||||
|
|
||||||
|
It requires Go 1.11 or later due to usage of Go Modules.
|
||||||
|
|
||||||
|
Refer to the documentation here:
|
||||||
|
http://godoc.org/github.com/robfig/cron
|
||||||
|
|
||||||
|
The rest of this document describes the the advances in v3 and a list of
|
||||||
|
breaking changes for users that wish to upgrade from an earlier version.
|
||||||
|
|
||||||
|
## Upgrading to v3 (June 2019)
|
||||||
|
|
||||||
|
cron v3 is a major upgrade to the library that addresses all outstanding bugs,
|
||||||
|
feature requests, and rough edges. It is based on a merge of master which
|
||||||
|
contains various fixes to issues found over the years and the v2 branch which
|
||||||
|
contains some backwards-incompatible features like the ability to remove cron
|
||||||
|
jobs. In addition, v3 adds support for Go Modules, cleans up rough edges like
|
||||||
|
the timezone support, and fixes a number of bugs.
|
||||||
|
|
||||||
|
New features:
|
||||||
|
|
||||||
|
- Support for Go modules. Callers must now import this library as
|
||||||
|
`github.com/robfig/cron/v3`, instead of `gopkg.in/...`
|
||||||
|
|
||||||
|
- Fixed bugs:
|
||||||
|
- 0f01e6b parser: fix combining of Dow and Dom (#70)
|
||||||
|
- dbf3220 adjust times when rolling the clock forward to handle non-existent midnight (#157)
|
||||||
|
- eeecf15 spec_test.go: ensure an error is returned on 0 increment (#144)
|
||||||
|
- 70971dc cron.Entries(): update request for snapshot to include a reply channel (#97)
|
||||||
|
- 1cba5e6 cron: fix: removing a job causes the next scheduled job to run too late (#206)
|
||||||
|
|
||||||
|
- Standard cron spec parsing by default (first field is "minute"), with an easy
|
||||||
|
way to opt into the seconds field (quartz-compatible). Although, note that the
|
||||||
|
year field (optional in Quartz) is not supported.
|
||||||
|
|
||||||
|
- Extensible, key/value logging via an interface that complies with
|
||||||
|
the https://github.com/go-logr/logr project.
|
||||||
|
|
||||||
|
- The new Chain & JobWrapper types allow you to install "interceptors" to add
|
||||||
|
cross-cutting behavior like the following:
|
||||||
|
- Recover any panics from jobs
|
||||||
|
- Delay a job's execution if the previous run hasn't completed yet
|
||||||
|
- Skip a job's execution if the previous run hasn't completed yet
|
||||||
|
- Log each job's invocations
|
||||||
|
- Notification when jobs are completed
|
||||||
|
|
||||||
|
It is backwards incompatible with both v1 and v2. These updates are required:
|
||||||
|
|
||||||
|
- The v1 branch accepted an optional seconds field at the beginning of the cron
|
||||||
|
spec. This is non-standard and has led to a lot of confusion. The new default
|
||||||
|
parser conforms to the standard as described by [the Cron wikipedia page].
|
||||||
|
|
||||||
|
UPDATING: To retain the old behavior, construct your Cron with a custom
|
||||||
|
parser:
|
||||||
|
|
||||||
|
// Seconds field, required
|
||||||
|
cron.New(cron.WithSeconds())
|
||||||
|
|
||||||
|
// Seconds field, optional
|
||||||
|
cron.New(
|
||||||
|
cron.WithParser(
|
||||||
|
cron.SecondOptional | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor))
|
||||||
|
|
||||||
|
- The Cron type now accepts functional options on construction rather than the
|
||||||
|
previous ad-hoc behavior modification mechanisms (setting a field, calling a setter).
|
||||||
|
|
||||||
|
UPDATING: Code that sets Cron.ErrorLogger or calls Cron.SetLocation must be
|
||||||
|
updated to provide those values on construction.
|
||||||
|
|
||||||
|
- CRON_TZ is now the recommended way to specify the timezone of a single
|
||||||
|
schedule, which is sanctioned by the specification. The legacy "TZ=" prefix
|
||||||
|
will continue to be supported since it is unambiguous and easy to do so.
|
||||||
|
|
||||||
|
UPDATING: No update is required.
|
||||||
|
|
||||||
|
- By default, cron will no longer recover panics in jobs that it runs.
|
||||||
|
Recovering can be surprising (see issue #192) and seems to be at odds with
|
||||||
|
typical behavior of libraries. Relatedly, the `cron.WithPanicLogger` option
|
||||||
|
has been removed to accommodate the more general JobWrapper type.
|
||||||
|
|
||||||
|
UPDATING: To opt into panic recovery and configure the panic logger:
|
||||||
|
|
||||||
|
cron.New(cron.WithChain(
|
||||||
|
cron.Recover(logger), // or use cron.DefaultLogger
|
||||||
|
))
|
||||||
|
|
||||||
|
- In adding support for https://github.com/go-logr/logr, `cron.WithVerboseLogger` was
|
||||||
|
removed, since it is duplicative with the leveled logging.
|
||||||
|
|
||||||
|
UPDATING: Callers should use `WithLogger` and specify a logger that does not
|
||||||
|
discard `Info` logs. For convenience, one is provided that wraps `*log.Logger`:
|
||||||
|
|
||||||
|
cron.New(
|
||||||
|
cron.WithLogger(cron.VerbosePrintfLogger(logger)))
|
||||||
|
|
||||||
|
|
||||||
|
### Background - Cron spec format
|
||||||
|
|
||||||
|
There are two cron spec formats in common usage:
|
||||||
|
|
||||||
|
- The "standard" cron format, described on [the Cron wikipedia page] and used by
|
||||||
|
the cron Linux system utility.
|
||||||
|
|
||||||
|
- The cron format used by [the Quartz Scheduler], commonly used for scheduled
|
||||||
|
jobs in Java software
|
||||||
|
|
||||||
|
[the Cron wikipedia page]: https://en.wikipedia.org/wiki/Cron
|
||||||
|
[the Quartz Scheduler]: http://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/tutorial-lesson-06.html
|
||||||
|
|
||||||
|
The original version of this package included an optional "seconds" field, which
|
||||||
|
made it incompatible with both of these formats. Now, the "standard" format is
|
||||||
|
the default format accepted, and the Quartz format is opt-in.
|
||||||
92
vendor/github.com/robfig/cron/v3/chain.go
generated
vendored
Normal file
92
vendor/github.com/robfig/cron/v3/chain.go
generated
vendored
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
package cron
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// JobWrapper decorates the given Job with some behavior.
|
||||||
|
type JobWrapper func(Job) Job
|
||||||
|
|
||||||
|
// Chain is a sequence of JobWrappers that decorates submitted jobs with
|
||||||
|
// cross-cutting behaviors like logging or synchronization.
|
||||||
|
type Chain struct {
|
||||||
|
wrappers []JobWrapper
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewChain returns a Chain consisting of the given JobWrappers.
|
||||||
|
func NewChain(c ...JobWrapper) Chain {
|
||||||
|
return Chain{c}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then decorates the given job with all JobWrappers in the chain.
|
||||||
|
//
|
||||||
|
// This:
|
||||||
|
// NewChain(m1, m2, m3).Then(job)
|
||||||
|
// is equivalent to:
|
||||||
|
// m1(m2(m3(job)))
|
||||||
|
func (c Chain) Then(j Job) Job {
|
||||||
|
for i := range c.wrappers {
|
||||||
|
j = c.wrappers[len(c.wrappers)-i-1](j)
|
||||||
|
}
|
||||||
|
return j
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recover panics in wrapped jobs and log them with the provided logger.
|
||||||
|
func Recover(logger Logger) JobWrapper {
|
||||||
|
return func(j Job) Job {
|
||||||
|
return FuncJob(func() {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
const size = 64 << 10
|
||||||
|
buf := make([]byte, size)
|
||||||
|
buf = buf[:runtime.Stack(buf, false)]
|
||||||
|
err, ok := r.(error)
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf("%v", r)
|
||||||
|
}
|
||||||
|
logger.Error(err, "panic", "stack", "...\n"+string(buf))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
j.Run()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DelayIfStillRunning serializes jobs, delaying subsequent runs until the
|
||||||
|
// previous one is complete. Jobs running after a delay of more than a minute
|
||||||
|
// have the delay logged at Info.
|
||||||
|
func DelayIfStillRunning(logger Logger) JobWrapper {
|
||||||
|
return func(j Job) Job {
|
||||||
|
var mu sync.Mutex
|
||||||
|
return FuncJob(func() {
|
||||||
|
start := time.Now()
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
if dur := time.Since(start); dur > time.Minute {
|
||||||
|
logger.Info("delay", "duration", dur)
|
||||||
|
}
|
||||||
|
j.Run()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SkipIfStillRunning skips an invocation of the Job if a previous invocation is
|
||||||
|
// still running. It logs skips to the given logger at Info level.
|
||||||
|
func SkipIfStillRunning(logger Logger) JobWrapper {
|
||||||
|
return func(j Job) Job {
|
||||||
|
var ch = make(chan struct{}, 1)
|
||||||
|
ch <- struct{}{}
|
||||||
|
return FuncJob(func() {
|
||||||
|
select {
|
||||||
|
case v := <-ch:
|
||||||
|
j.Run()
|
||||||
|
ch <- v
|
||||||
|
default:
|
||||||
|
logger.Info("skip")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
27
vendor/github.com/robfig/cron/v3/constantdelay.go
generated
vendored
Normal file
27
vendor/github.com/robfig/cron/v3/constantdelay.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package cron
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// ConstantDelaySchedule represents a simple recurring duty cycle, e.g. "Every 5 minutes".
|
||||||
|
// It does not support jobs more frequent than once a second.
|
||||||
|
type ConstantDelaySchedule struct {
|
||||||
|
Delay time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every returns a crontab Schedule that activates once every duration.
|
||||||
|
// Delays of less than a second are not supported (will round up to 1 second).
|
||||||
|
// Any fields less than a Second are truncated.
|
||||||
|
func Every(duration time.Duration) ConstantDelaySchedule {
|
||||||
|
if duration < time.Second {
|
||||||
|
duration = time.Second
|
||||||
|
}
|
||||||
|
return ConstantDelaySchedule{
|
||||||
|
Delay: duration - time.Duration(duration.Nanoseconds())%time.Second,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next returns the next time this should be run.
|
||||||
|
// This rounds so that the next activation time will be on the second.
|
||||||
|
func (schedule ConstantDelaySchedule) Next(t time.Time) time.Time {
|
||||||
|
return t.Add(schedule.Delay - time.Duration(t.Nanosecond())*time.Nanosecond)
|
||||||
|
}
|
||||||
355
vendor/github.com/robfig/cron/v3/cron.go
generated
vendored
Normal file
355
vendor/github.com/robfig/cron/v3/cron.go
generated
vendored
Normal file
@@ -0,0 +1,355 @@
|
|||||||
|
package cron
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cron keeps track of any number of entries, invoking the associated func as
|
||||||
|
// specified by the schedule. It may be started, stopped, and the entries may
|
||||||
|
// be inspected while running.
|
||||||
|
type Cron struct {
|
||||||
|
entries []*Entry
|
||||||
|
chain Chain
|
||||||
|
stop chan struct{}
|
||||||
|
add chan *Entry
|
||||||
|
remove chan EntryID
|
||||||
|
snapshot chan chan []Entry
|
||||||
|
running bool
|
||||||
|
logger Logger
|
||||||
|
runningMu sync.Mutex
|
||||||
|
location *time.Location
|
||||||
|
parser ScheduleParser
|
||||||
|
nextID EntryID
|
||||||
|
jobWaiter sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScheduleParser is an interface for schedule spec parsers that return a Schedule
|
||||||
|
type ScheduleParser interface {
|
||||||
|
Parse(spec string) (Schedule, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Job is an interface for submitted cron jobs.
|
||||||
|
type Job interface {
|
||||||
|
Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schedule describes a job's duty cycle.
|
||||||
|
type Schedule interface {
|
||||||
|
// Next returns the next activation time, later than the given time.
|
||||||
|
// Next is invoked initially, and then each time the job is run.
|
||||||
|
Next(time.Time) time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// EntryID identifies an entry within a Cron instance
|
||||||
|
type EntryID int
|
||||||
|
|
||||||
|
// Entry consists of a schedule and the func to execute on that schedule.
|
||||||
|
type Entry struct {
|
||||||
|
// ID is the cron-assigned ID of this entry, which may be used to look up a
|
||||||
|
// snapshot or remove it.
|
||||||
|
ID EntryID
|
||||||
|
|
||||||
|
// Schedule on which this job should be run.
|
||||||
|
Schedule Schedule
|
||||||
|
|
||||||
|
// Next time the job will run, or the zero time if Cron has not been
|
||||||
|
// started or this entry's schedule is unsatisfiable
|
||||||
|
Next time.Time
|
||||||
|
|
||||||
|
// Prev is the last time this job was run, or the zero time if never.
|
||||||
|
Prev time.Time
|
||||||
|
|
||||||
|
// WrappedJob is the thing to run when the Schedule is activated.
|
||||||
|
WrappedJob Job
|
||||||
|
|
||||||
|
// Job is the thing that was submitted to cron.
|
||||||
|
// It is kept around so that user code that needs to get at the job later,
|
||||||
|
// e.g. via Entries() can do so.
|
||||||
|
Job Job
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid returns true if this is not the zero entry.
|
||||||
|
func (e Entry) Valid() bool { return e.ID != 0 }
|
||||||
|
|
||||||
|
// byTime is a wrapper for sorting the entry array by time
|
||||||
|
// (with zero time at the end).
|
||||||
|
type byTime []*Entry
|
||||||
|
|
||||||
|
func (s byTime) Len() int { return len(s) }
|
||||||
|
func (s byTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s byTime) Less(i, j int) bool {
|
||||||
|
// Two zero times should return false.
|
||||||
|
// Otherwise, zero is "greater" than any other time.
|
||||||
|
// (To sort it at the end of the list.)
|
||||||
|
if s[i].Next.IsZero() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if s[j].Next.IsZero() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return s[i].Next.Before(s[j].Next)
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new Cron job runner, modified by the given options.
|
||||||
|
//
|
||||||
|
// Available Settings
|
||||||
|
//
|
||||||
|
// Time Zone
|
||||||
|
// Description: The time zone in which schedules are interpreted
|
||||||
|
// Default: time.Local
|
||||||
|
//
|
||||||
|
// Parser
|
||||||
|
// Description: Parser converts cron spec strings into cron.Schedules.
|
||||||
|
// Default: Accepts this spec: https://en.wikipedia.org/wiki/Cron
|
||||||
|
//
|
||||||
|
// Chain
|
||||||
|
// Description: Wrap submitted jobs to customize behavior.
|
||||||
|
// Default: A chain that recovers panics and logs them to stderr.
|
||||||
|
//
|
||||||
|
// See "cron.With*" to modify the default behavior.
|
||||||
|
func New(opts ...Option) *Cron {
|
||||||
|
c := &Cron{
|
||||||
|
entries: nil,
|
||||||
|
chain: NewChain(),
|
||||||
|
add: make(chan *Entry),
|
||||||
|
stop: make(chan struct{}),
|
||||||
|
snapshot: make(chan chan []Entry),
|
||||||
|
remove: make(chan EntryID),
|
||||||
|
running: false,
|
||||||
|
runningMu: sync.Mutex{},
|
||||||
|
logger: DefaultLogger,
|
||||||
|
location: time.Local,
|
||||||
|
parser: standardParser,
|
||||||
|
}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(c)
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// FuncJob is a wrapper that turns a func() into a cron.Job
|
||||||
|
type FuncJob func()
|
||||||
|
|
||||||
|
func (f FuncJob) Run() { f() }
|
||||||
|
|
||||||
|
// AddFunc adds a func to the Cron to be run on the given schedule.
|
||||||
|
// The spec is parsed using the time zone of this Cron instance as the default.
|
||||||
|
// An opaque ID is returned that can be used to later remove it.
|
||||||
|
func (c *Cron) AddFunc(spec string, cmd func()) (EntryID, error) {
|
||||||
|
return c.AddJob(spec, FuncJob(cmd))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddJob adds a Job to the Cron to be run on the given schedule.
|
||||||
|
// The spec is parsed using the time zone of this Cron instance as the default.
|
||||||
|
// An opaque ID is returned that can be used to later remove it.
|
||||||
|
func (c *Cron) AddJob(spec string, cmd Job) (EntryID, error) {
|
||||||
|
schedule, err := c.parser.Parse(spec)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return c.Schedule(schedule, cmd), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schedule adds a Job to the Cron to be run on the given schedule.
|
||||||
|
// The job is wrapped with the configured Chain.
|
||||||
|
func (c *Cron) Schedule(schedule Schedule, cmd Job) EntryID {
|
||||||
|
c.runningMu.Lock()
|
||||||
|
defer c.runningMu.Unlock()
|
||||||
|
c.nextID++
|
||||||
|
entry := &Entry{
|
||||||
|
ID: c.nextID,
|
||||||
|
Schedule: schedule,
|
||||||
|
WrappedJob: c.chain.Then(cmd),
|
||||||
|
Job: cmd,
|
||||||
|
}
|
||||||
|
if !c.running {
|
||||||
|
c.entries = append(c.entries, entry)
|
||||||
|
} else {
|
||||||
|
c.add <- entry
|
||||||
|
}
|
||||||
|
return entry.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entries returns a snapshot of the cron entries.
|
||||||
|
func (c *Cron) Entries() []Entry {
|
||||||
|
c.runningMu.Lock()
|
||||||
|
defer c.runningMu.Unlock()
|
||||||
|
if c.running {
|
||||||
|
replyChan := make(chan []Entry, 1)
|
||||||
|
c.snapshot <- replyChan
|
||||||
|
return <-replyChan
|
||||||
|
}
|
||||||
|
return c.entrySnapshot()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Location gets the time zone location
|
||||||
|
func (c *Cron) Location() *time.Location {
|
||||||
|
return c.location
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entry returns a snapshot of the given entry, or nil if it couldn't be found.
|
||||||
|
func (c *Cron) Entry(id EntryID) Entry {
|
||||||
|
for _, entry := range c.Entries() {
|
||||||
|
if id == entry.ID {
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Entry{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove an entry from being run in the future.
|
||||||
|
func (c *Cron) Remove(id EntryID) {
|
||||||
|
c.runningMu.Lock()
|
||||||
|
defer c.runningMu.Unlock()
|
||||||
|
if c.running {
|
||||||
|
c.remove <- id
|
||||||
|
} else {
|
||||||
|
c.removeEntry(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the cron scheduler in its own goroutine, or no-op if already started.
|
||||||
|
func (c *Cron) Start() {
|
||||||
|
c.runningMu.Lock()
|
||||||
|
defer c.runningMu.Unlock()
|
||||||
|
if c.running {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.running = true
|
||||||
|
go c.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the cron scheduler, or no-op if already running.
|
||||||
|
func (c *Cron) Run() {
|
||||||
|
c.runningMu.Lock()
|
||||||
|
if c.running {
|
||||||
|
c.runningMu.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.running = true
|
||||||
|
c.runningMu.Unlock()
|
||||||
|
c.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// run the scheduler.. this is private just due to the need to synchronize
|
||||||
|
// access to the 'running' state variable.
|
||||||
|
func (c *Cron) run() {
|
||||||
|
c.logger.Info("start")
|
||||||
|
|
||||||
|
// Figure out the next activation times for each entry.
|
||||||
|
now := c.now()
|
||||||
|
for _, entry := range c.entries {
|
||||||
|
entry.Next = entry.Schedule.Next(now)
|
||||||
|
c.logger.Info("schedule", "now", now, "entry", entry.ID, "next", entry.Next)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
// Determine the next entry to run.
|
||||||
|
sort.Sort(byTime(c.entries))
|
||||||
|
|
||||||
|
var timer *time.Timer
|
||||||
|
if len(c.entries) == 0 || c.entries[0].Next.IsZero() {
|
||||||
|
// If there are no entries yet, just sleep - it still handles new entries
|
||||||
|
// and stop requests.
|
||||||
|
timer = time.NewTimer(100000 * time.Hour)
|
||||||
|
} else {
|
||||||
|
timer = time.NewTimer(c.entries[0].Next.Sub(now))
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case now = <-timer.C:
|
||||||
|
now = now.In(c.location)
|
||||||
|
c.logger.Info("wake", "now", now)
|
||||||
|
|
||||||
|
// Run every entry whose next time was less than now
|
||||||
|
for _, e := range c.entries {
|
||||||
|
if e.Next.After(now) || e.Next.IsZero() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
c.startJob(e.WrappedJob)
|
||||||
|
e.Prev = e.Next
|
||||||
|
e.Next = e.Schedule.Next(now)
|
||||||
|
c.logger.Info("run", "now", now, "entry", e.ID, "next", e.Next)
|
||||||
|
}
|
||||||
|
|
||||||
|
case newEntry := <-c.add:
|
||||||
|
timer.Stop()
|
||||||
|
now = c.now()
|
||||||
|
newEntry.Next = newEntry.Schedule.Next(now)
|
||||||
|
c.entries = append(c.entries, newEntry)
|
||||||
|
c.logger.Info("added", "now", now, "entry", newEntry.ID, "next", newEntry.Next)
|
||||||
|
|
||||||
|
case replyChan := <-c.snapshot:
|
||||||
|
replyChan <- c.entrySnapshot()
|
||||||
|
continue
|
||||||
|
|
||||||
|
case <-c.stop:
|
||||||
|
timer.Stop()
|
||||||
|
c.logger.Info("stop")
|
||||||
|
return
|
||||||
|
|
||||||
|
case id := <-c.remove:
|
||||||
|
timer.Stop()
|
||||||
|
now = c.now()
|
||||||
|
c.removeEntry(id)
|
||||||
|
c.logger.Info("removed", "entry", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// startJob runs the given job in a new goroutine.
|
||||||
|
func (c *Cron) startJob(j Job) {
|
||||||
|
c.jobWaiter.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer c.jobWaiter.Done()
|
||||||
|
j.Run()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// now returns current time in c location
|
||||||
|
func (c *Cron) now() time.Time {
|
||||||
|
return time.Now().In(c.location)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop stops the cron scheduler if it is running; otherwise it does nothing.
|
||||||
|
// A context is returned so the caller can wait for running jobs to complete.
|
||||||
|
func (c *Cron) Stop() context.Context {
|
||||||
|
c.runningMu.Lock()
|
||||||
|
defer c.runningMu.Unlock()
|
||||||
|
if c.running {
|
||||||
|
c.stop <- struct{}{}
|
||||||
|
c.running = false
|
||||||
|
}
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
go func() {
|
||||||
|
c.jobWaiter.Wait()
|
||||||
|
cancel()
|
||||||
|
}()
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
// entrySnapshot returns a copy of the current cron entry list.
|
||||||
|
func (c *Cron) entrySnapshot() []Entry {
|
||||||
|
var entries = make([]Entry, len(c.entries))
|
||||||
|
for i, e := range c.entries {
|
||||||
|
entries[i] = *e
|
||||||
|
}
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cron) removeEntry(id EntryID) {
|
||||||
|
var entries []*Entry
|
||||||
|
for _, e := range c.entries {
|
||||||
|
if e.ID != id {
|
||||||
|
entries = append(entries, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.entries = entries
|
||||||
|
}
|
||||||
231
vendor/github.com/robfig/cron/v3/doc.go
generated
vendored
Normal file
231
vendor/github.com/robfig/cron/v3/doc.go
generated
vendored
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
/*
|
||||||
|
Package cron implements a cron spec parser and job runner.
|
||||||
|
|
||||||
|
Installation
|
||||||
|
|
||||||
|
To download the specific tagged release, run:
|
||||||
|
|
||||||
|
go get github.com/robfig/cron/v3@v3.0.0
|
||||||
|
|
||||||
|
Import it in your program as:
|
||||||
|
|
||||||
|
import "github.com/robfig/cron/v3"
|
||||||
|
|
||||||
|
It requires Go 1.11 or later due to usage of Go Modules.
|
||||||
|
|
||||||
|
Usage
|
||||||
|
|
||||||
|
Callers may register Funcs to be invoked on a given schedule. Cron will run
|
||||||
|
them in their own goroutines.
|
||||||
|
|
||||||
|
c := cron.New()
|
||||||
|
c.AddFunc("30 * * * *", func() { fmt.Println("Every hour on the half hour") })
|
||||||
|
c.AddFunc("30 3-6,20-23 * * *", func() { fmt.Println(".. in the range 3-6am, 8-11pm") })
|
||||||
|
c.AddFunc("CRON_TZ=Asia/Tokyo 30 04 * * *", func() { fmt.Println("Runs at 04:30 Tokyo time every day") })
|
||||||
|
c.AddFunc("@hourly", func() { fmt.Println("Every hour, starting an hour from now") })
|
||||||
|
c.AddFunc("@every 1h30m", func() { fmt.Println("Every hour thirty, starting an hour thirty from now") })
|
||||||
|
c.Start()
|
||||||
|
..
|
||||||
|
// Funcs are invoked in their own goroutine, asynchronously.
|
||||||
|
...
|
||||||
|
// Funcs may also be added to a running Cron
|
||||||
|
c.AddFunc("@daily", func() { fmt.Println("Every day") })
|
||||||
|
..
|
||||||
|
// Inspect the cron job entries' next and previous run times.
|
||||||
|
inspect(c.Entries())
|
||||||
|
..
|
||||||
|
c.Stop() // Stop the scheduler (does not stop any jobs already running).
|
||||||
|
|
||||||
|
CRON Expression Format
|
||||||
|
|
||||||
|
A cron expression represents a set of times, using 5 space-separated fields.
|
||||||
|
|
||||||
|
Field name | Mandatory? | Allowed values | Allowed special characters
|
||||||
|
---------- | ---------- | -------------- | --------------------------
|
||||||
|
Minutes | Yes | 0-59 | * / , -
|
||||||
|
Hours | Yes | 0-23 | * / , -
|
||||||
|
Day of month | Yes | 1-31 | * / , - ?
|
||||||
|
Month | Yes | 1-12 or JAN-DEC | * / , -
|
||||||
|
Day of week | Yes | 0-6 or SUN-SAT | * / , - ?
|
||||||
|
|
||||||
|
Month and Day-of-week field values are case insensitive. "SUN", "Sun", and
|
||||||
|
"sun" are equally accepted.
|
||||||
|
|
||||||
|
The specific interpretation of the format is based on the Cron Wikipedia page:
|
||||||
|
https://en.wikipedia.org/wiki/Cron
|
||||||
|
|
||||||
|
Alternative Formats
|
||||||
|
|
||||||
|
Alternative Cron expression formats support other fields like seconds. You can
|
||||||
|
implement that by creating a custom Parser as follows.
|
||||||
|
|
||||||
|
cron.New(
|
||||||
|
cron.WithParser(
|
||||||
|
cron.NewParser(
|
||||||
|
cron.SecondOptional | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor)))
|
||||||
|
|
||||||
|
Since adding Seconds is the most common modification to the standard cron spec,
|
||||||
|
cron provides a builtin function to do that, which is equivalent to the custom
|
||||||
|
parser you saw earlier, except that its seconds field is REQUIRED:
|
||||||
|
|
||||||
|
cron.New(cron.WithSeconds())
|
||||||
|
|
||||||
|
That emulates Quartz, the most popular alternative Cron schedule format:
|
||||||
|
http://www.quartz-scheduler.org/documentation/quartz-2.x/tutorials/crontrigger.html
|
||||||
|
|
||||||
|
Special Characters
|
||||||
|
|
||||||
|
Asterisk ( * )
|
||||||
|
|
||||||
|
The asterisk indicates that the cron expression will match for all values of the
|
||||||
|
field; e.g., using an asterisk in the 5th field (month) would indicate every
|
||||||
|
month.
|
||||||
|
|
||||||
|
Slash ( / )
|
||||||
|
|
||||||
|
Slashes are used to describe increments of ranges. For example 3-59/15 in the
|
||||||
|
1st field (minutes) would indicate the 3rd minute of the hour and every 15
|
||||||
|
minutes thereafter. The form "*\/..." is equivalent to the form "first-last/...",
|
||||||
|
that is, an increment over the largest possible range of the field. The form
|
||||||
|
"N/..." is accepted as meaning "N-MAX/...", that is, starting at N, use the
|
||||||
|
increment until the end of that specific range. It does not wrap around.
|
||||||
|
|
||||||
|
Comma ( , )
|
||||||
|
|
||||||
|
Commas are used to separate items of a list. For example, using "MON,WED,FRI" in
|
||||||
|
the 5th field (day of week) would mean Mondays, Wednesdays and Fridays.
|
||||||
|
|
||||||
|
Hyphen ( - )
|
||||||
|
|
||||||
|
Hyphens are used to define ranges. For example, 9-17 would indicate every
|
||||||
|
hour between 9am and 5pm inclusive.
|
||||||
|
|
||||||
|
Question mark ( ? )
|
||||||
|
|
||||||
|
Question mark may be used instead of '*' for leaving either day-of-month or
|
||||||
|
day-of-week blank.
|
||||||
|
|
||||||
|
Predefined schedules
|
||||||
|
|
||||||
|
You may use one of several pre-defined schedules in place of a cron expression.
|
||||||
|
|
||||||
|
Entry | Description | Equivalent To
|
||||||
|
----- | ----------- | -------------
|
||||||
|
@yearly (or @annually) | Run once a year, midnight, Jan. 1st | 0 0 1 1 *
|
||||||
|
@monthly | Run once a month, midnight, first of month | 0 0 1 * *
|
||||||
|
@weekly | Run once a week, midnight between Sat/Sun | 0 0 * * 0
|
||||||
|
@daily (or @midnight) | Run once a day, midnight | 0 0 * * *
|
||||||
|
@hourly | Run once an hour, beginning of hour | 0 * * * *
|
||||||
|
|
||||||
|
Intervals
|
||||||
|
|
||||||
|
You may also schedule a job to execute at fixed intervals, starting at the time it's added
|
||||||
|
or cron is run. This is supported by formatting the cron spec like this:
|
||||||
|
|
||||||
|
@every <duration>
|
||||||
|
|
||||||
|
where "duration" is a string accepted by time.ParseDuration
|
||||||
|
(http://golang.org/pkg/time/#ParseDuration).
|
||||||
|
|
||||||
|
For example, "@every 1h30m10s" would indicate a schedule that activates after
|
||||||
|
1 hour, 30 minutes, 10 seconds, and then every interval after that.
|
||||||
|
|
||||||
|
Note: The interval does not take the job runtime into account. For example,
|
||||||
|
if a job takes 3 minutes to run, and it is scheduled to run every 5 minutes,
|
||||||
|
it will have only 2 minutes of idle time between each run.
|
||||||
|
|
||||||
|
Time zones
|
||||||
|
|
||||||
|
By default, all interpretation and scheduling is done in the machine's local
|
||||||
|
time zone (time.Local). You can specify a different time zone on construction:
|
||||||
|
|
||||||
|
cron.New(
|
||||||
|
cron.WithLocation(time.UTC))
|
||||||
|
|
||||||
|
Individual cron schedules may also override the time zone they are to be
|
||||||
|
interpreted in by providing an additional space-separated field at the beginning
|
||||||
|
of the cron spec, of the form "CRON_TZ=Asia/Tokyo".
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
# Runs at 6am in time.Local
|
||||||
|
cron.New().AddFunc("0 6 * * ?", ...)
|
||||||
|
|
||||||
|
# Runs at 6am in America/New_York
|
||||||
|
nyc, _ := time.LoadLocation("America/New_York")
|
||||||
|
c := cron.New(cron.WithLocation(nyc))
|
||||||
|
c.AddFunc("0 6 * * ?", ...)
|
||||||
|
|
||||||
|
# Runs at 6am in Asia/Tokyo
|
||||||
|
cron.New().AddFunc("CRON_TZ=Asia/Tokyo 0 6 * * ?", ...)
|
||||||
|
|
||||||
|
# Runs at 6am in Asia/Tokyo
|
||||||
|
c := cron.New(cron.WithLocation(nyc))
|
||||||
|
c.SetLocation("America/New_York")
|
||||||
|
c.AddFunc("CRON_TZ=Asia/Tokyo 0 6 * * ?", ...)
|
||||||
|
|
||||||
|
The prefix "TZ=(TIME ZONE)" is also supported for legacy compatibility.
|
||||||
|
|
||||||
|
Be aware that jobs scheduled during daylight-savings leap-ahead transitions will
|
||||||
|
not be run!
|
||||||
|
|
||||||
|
Job Wrappers
|
||||||
|
|
||||||
|
A Cron runner may be configured with a chain of job wrappers to add
|
||||||
|
cross-cutting functionality to all submitted jobs. For example, they may be used
|
||||||
|
to achieve the following effects:
|
||||||
|
|
||||||
|
- Recover any panics from jobs (activated by default)
|
||||||
|
- Delay a job's execution if the previous run hasn't completed yet
|
||||||
|
- Skip a job's execution if the previous run hasn't completed yet
|
||||||
|
- Log each job's invocations
|
||||||
|
|
||||||
|
Install wrappers for all jobs added to a cron using the `cron.WithChain` option:
|
||||||
|
|
||||||
|
cron.New(cron.WithChain(
|
||||||
|
cron.SkipIfStillRunning(logger),
|
||||||
|
))
|
||||||
|
|
||||||
|
Install wrappers for individual jobs by explicitly wrapping them:
|
||||||
|
|
||||||
|
job = cron.NewChain(
|
||||||
|
cron.SkipIfStillRunning(logger),
|
||||||
|
).Then(job)
|
||||||
|
|
||||||
|
Thread safety
|
||||||
|
|
||||||
|
Since the Cron service runs concurrently with the calling code, some amount of
|
||||||
|
care must be taken to ensure proper synchronization.
|
||||||
|
|
||||||
|
All cron methods are designed to be correctly synchronized as long as the caller
|
||||||
|
ensures that invocations have a clear happens-before ordering between them.
|
||||||
|
|
||||||
|
Logging
|
||||||
|
|
||||||
|
Cron defines a Logger interface that is a subset of the one defined in
|
||||||
|
github.com/go-logr/logr. It has two logging levels (Info and Error), and
|
||||||
|
parameters are key/value pairs. This makes it possible for cron logging to plug
|
||||||
|
into structured logging systems. An adapter, [Verbose]PrintfLogger, is provided
|
||||||
|
to wrap the standard library *log.Logger.
|
||||||
|
|
||||||
|
For additional insight into Cron operations, verbose logging may be activated
|
||||||
|
which will record job runs, scheduling decisions, and added or removed jobs.
|
||||||
|
Activate it with a one-off logger as follows:
|
||||||
|
|
||||||
|
cron.New(
|
||||||
|
cron.WithLogger(
|
||||||
|
cron.VerbosePrintfLogger(log.New(os.Stdout, "cron: ", log.LstdFlags))))
|
||||||
|
|
||||||
|
|
||||||
|
Implementation
|
||||||
|
|
||||||
|
Cron entries are stored in an array, sorted by their next activation time. Cron
|
||||||
|
sleeps until the next job is due to be run.
|
||||||
|
|
||||||
|
Upon waking:
|
||||||
|
- it runs each entry that is active on that second
|
||||||
|
- it calculates the next run times for the jobs that were run
|
||||||
|
- it re-sorts the array of entries by next activation time.
|
||||||
|
- it goes to sleep until the soonest job.
|
||||||
|
*/
|
||||||
|
package cron
|
||||||
86
vendor/github.com/robfig/cron/v3/logger.go
generated
vendored
Normal file
86
vendor/github.com/robfig/cron/v3/logger.go
generated
vendored
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
package cron
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultLogger is used by Cron if none is specified.
|
||||||
|
var DefaultLogger Logger = PrintfLogger(log.New(os.Stdout, "cron: ", log.LstdFlags))
|
||||||
|
|
||||||
|
// DiscardLogger can be used by callers to discard all log messages.
|
||||||
|
var DiscardLogger Logger = PrintfLogger(log.New(ioutil.Discard, "", 0))
|
||||||
|
|
||||||
|
// Logger is the interface used in this package for logging, so that any backend
|
||||||
|
// can be plugged in. It is a subset of the github.com/go-logr/logr interface.
|
||||||
|
type Logger interface {
|
||||||
|
// Info logs routine messages about cron's operation.
|
||||||
|
Info(msg string, keysAndValues ...interface{})
|
||||||
|
// Error logs an error condition.
|
||||||
|
Error(err error, msg string, keysAndValues ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrintfLogger wraps a Printf-based logger (such as the standard library "log")
|
||||||
|
// into an implementation of the Logger interface which logs errors only.
|
||||||
|
func PrintfLogger(l interface{ Printf(string, ...interface{}) }) Logger {
|
||||||
|
return printfLogger{l, false}
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerbosePrintfLogger wraps a Printf-based logger (such as the standard library
|
||||||
|
// "log") into an implementation of the Logger interface which logs everything.
|
||||||
|
func VerbosePrintfLogger(l interface{ Printf(string, ...interface{}) }) Logger {
|
||||||
|
return printfLogger{l, true}
|
||||||
|
}
|
||||||
|
|
||||||
|
type printfLogger struct {
|
||||||
|
logger interface{ Printf(string, ...interface{}) }
|
||||||
|
logInfo bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pl printfLogger) Info(msg string, keysAndValues ...interface{}) {
|
||||||
|
if pl.logInfo {
|
||||||
|
keysAndValues = formatTimes(keysAndValues)
|
||||||
|
pl.logger.Printf(
|
||||||
|
formatString(len(keysAndValues)),
|
||||||
|
append([]interface{}{msg}, keysAndValues...)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pl printfLogger) Error(err error, msg string, keysAndValues ...interface{}) {
|
||||||
|
keysAndValues = formatTimes(keysAndValues)
|
||||||
|
pl.logger.Printf(
|
||||||
|
formatString(len(keysAndValues)+2),
|
||||||
|
append([]interface{}{msg, "error", err}, keysAndValues...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatString returns a logfmt-like format string for the number of
|
||||||
|
// key/values.
|
||||||
|
func formatString(numKeysAndValues int) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
sb.WriteString("%s")
|
||||||
|
if numKeysAndValues > 0 {
|
||||||
|
sb.WriteString(", ")
|
||||||
|
}
|
||||||
|
for i := 0; i < numKeysAndValues/2; i++ {
|
||||||
|
if i > 0 {
|
||||||
|
sb.WriteString(", ")
|
||||||
|
}
|
||||||
|
sb.WriteString("%v=%v")
|
||||||
|
}
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatTimes formats any time.Time values as RFC3339.
|
||||||
|
func formatTimes(keysAndValues []interface{}) []interface{} {
|
||||||
|
var formattedArgs []interface{}
|
||||||
|
for _, arg := range keysAndValues {
|
||||||
|
if t, ok := arg.(time.Time); ok {
|
||||||
|
arg = t.Format(time.RFC3339)
|
||||||
|
}
|
||||||
|
formattedArgs = append(formattedArgs, arg)
|
||||||
|
}
|
||||||
|
return formattedArgs
|
||||||
|
}
|
||||||
45
vendor/github.com/robfig/cron/v3/option.go
generated
vendored
Normal file
45
vendor/github.com/robfig/cron/v3/option.go
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package cron
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Option represents a modification to the default behavior of a Cron.
|
||||||
|
type Option func(*Cron)
|
||||||
|
|
||||||
|
// WithLocation overrides the timezone of the cron instance.
|
||||||
|
func WithLocation(loc *time.Location) Option {
|
||||||
|
return func(c *Cron) {
|
||||||
|
c.location = loc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithSeconds overrides the parser used for interpreting job schedules to
|
||||||
|
// include a seconds field as the first one.
|
||||||
|
func WithSeconds() Option {
|
||||||
|
return WithParser(NewParser(
|
||||||
|
Second | Minute | Hour | Dom | Month | Dow | Descriptor,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithParser overrides the parser used for interpreting job schedules.
|
||||||
|
func WithParser(p ScheduleParser) Option {
|
||||||
|
return func(c *Cron) {
|
||||||
|
c.parser = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithChain specifies Job wrappers to apply to all jobs added to this cron.
|
||||||
|
// Refer to the Chain* functions in this package for provided wrappers.
|
||||||
|
func WithChain(wrappers ...JobWrapper) Option {
|
||||||
|
return func(c *Cron) {
|
||||||
|
c.chain = NewChain(wrappers...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithLogger uses the provided logger.
|
||||||
|
func WithLogger(logger Logger) Option {
|
||||||
|
return func(c *Cron) {
|
||||||
|
c.logger = logger
|
||||||
|
}
|
||||||
|
}
|
||||||
434
vendor/github.com/robfig/cron/v3/parser.go
generated
vendored
Normal file
434
vendor/github.com/robfig/cron/v3/parser.go
generated
vendored
Normal file
@@ -0,0 +1,434 @@
|
|||||||
|
package cron
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Configuration options for creating a parser. Most options specify which
|
||||||
|
// fields should be included, while others enable features. If a field is not
|
||||||
|
// included the parser will assume a default value. These options do not change
|
||||||
|
// the order fields are parse in.
|
||||||
|
type ParseOption int
|
||||||
|
|
||||||
|
const (
|
||||||
|
Second ParseOption = 1 << iota // Seconds field, default 0
|
||||||
|
SecondOptional // Optional seconds field, default 0
|
||||||
|
Minute // Minutes field, default 0
|
||||||
|
Hour // Hours field, default 0
|
||||||
|
Dom // Day of month field, default *
|
||||||
|
Month // Month field, default *
|
||||||
|
Dow // Day of week field, default *
|
||||||
|
DowOptional // Optional day of week field, default *
|
||||||
|
Descriptor // Allow descriptors such as @monthly, @weekly, etc.
|
||||||
|
)
|
||||||
|
|
||||||
|
var places = []ParseOption{
|
||||||
|
Second,
|
||||||
|
Minute,
|
||||||
|
Hour,
|
||||||
|
Dom,
|
||||||
|
Month,
|
||||||
|
Dow,
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaults = []string{
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"*",
|
||||||
|
"*",
|
||||||
|
"*",
|
||||||
|
}
|
||||||
|
|
||||||
|
// A custom Parser that can be configured.
|
||||||
|
type Parser struct {
|
||||||
|
options ParseOption
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewParser creates a Parser with custom options.
|
||||||
|
//
|
||||||
|
// It panics if more than one Optional is given, since it would be impossible to
|
||||||
|
// correctly infer which optional is provided or missing in general.
|
||||||
|
//
|
||||||
|
// Examples
|
||||||
|
//
|
||||||
|
// // Standard parser without descriptors
|
||||||
|
// specParser := NewParser(Minute | Hour | Dom | Month | Dow)
|
||||||
|
// sched, err := specParser.Parse("0 0 15 */3 *")
|
||||||
|
//
|
||||||
|
// // Same as above, just excludes time fields
|
||||||
|
// subsParser := NewParser(Dom | Month | Dow)
|
||||||
|
// sched, err := specParser.Parse("15 */3 *")
|
||||||
|
//
|
||||||
|
// // Same as above, just makes Dow optional
|
||||||
|
// subsParser := NewParser(Dom | Month | DowOptional)
|
||||||
|
// sched, err := specParser.Parse("15 */3")
|
||||||
|
//
|
||||||
|
func NewParser(options ParseOption) Parser {
|
||||||
|
optionals := 0
|
||||||
|
if options&DowOptional > 0 {
|
||||||
|
optionals++
|
||||||
|
}
|
||||||
|
if options&SecondOptional > 0 {
|
||||||
|
optionals++
|
||||||
|
}
|
||||||
|
if optionals > 1 {
|
||||||
|
panic("multiple optionals may not be configured")
|
||||||
|
}
|
||||||
|
return Parser{options}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse returns a new crontab schedule representing the given spec.
|
||||||
|
// It returns a descriptive error if the spec is not valid.
|
||||||
|
// It accepts crontab specs and features configured by NewParser.
|
||||||
|
func (p Parser) Parse(spec string) (Schedule, error) {
|
||||||
|
if len(spec) == 0 {
|
||||||
|
return nil, fmt.Errorf("empty spec string")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract timezone if present
|
||||||
|
var loc = time.Local
|
||||||
|
if strings.HasPrefix(spec, "TZ=") || strings.HasPrefix(spec, "CRON_TZ=") {
|
||||||
|
var err error
|
||||||
|
i := strings.Index(spec, " ")
|
||||||
|
eq := strings.Index(spec, "=")
|
||||||
|
if loc, err = time.LoadLocation(spec[eq+1 : i]); err != nil {
|
||||||
|
return nil, fmt.Errorf("provided bad location %s: %v", spec[eq+1:i], err)
|
||||||
|
}
|
||||||
|
spec = strings.TrimSpace(spec[i:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle named schedules (descriptors), if configured
|
||||||
|
if strings.HasPrefix(spec, "@") {
|
||||||
|
if p.options&Descriptor == 0 {
|
||||||
|
return nil, fmt.Errorf("parser does not accept descriptors: %v", spec)
|
||||||
|
}
|
||||||
|
return parseDescriptor(spec, loc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split on whitespace.
|
||||||
|
fields := strings.Fields(spec)
|
||||||
|
|
||||||
|
// Validate & fill in any omitted or optional fields
|
||||||
|
var err error
|
||||||
|
fields, err = normalizeFields(fields, p.options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
field := func(field string, r bounds) uint64 {
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
var bits uint64
|
||||||
|
bits, err = getField(field, r)
|
||||||
|
return bits
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
second = field(fields[0], seconds)
|
||||||
|
minute = field(fields[1], minutes)
|
||||||
|
hour = field(fields[2], hours)
|
||||||
|
dayofmonth = field(fields[3], dom)
|
||||||
|
month = field(fields[4], months)
|
||||||
|
dayofweek = field(fields[5], dow)
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &SpecSchedule{
|
||||||
|
Second: second,
|
||||||
|
Minute: minute,
|
||||||
|
Hour: hour,
|
||||||
|
Dom: dayofmonth,
|
||||||
|
Month: month,
|
||||||
|
Dow: dayofweek,
|
||||||
|
Location: loc,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalizeFields takes a subset set of the time fields and returns the full set
|
||||||
|
// with defaults (zeroes) populated for unset fields.
|
||||||
|
//
|
||||||
|
// As part of performing this function, it also validates that the provided
|
||||||
|
// fields are compatible with the configured options.
|
||||||
|
func normalizeFields(fields []string, options ParseOption) ([]string, error) {
|
||||||
|
// Validate optionals & add their field to options
|
||||||
|
optionals := 0
|
||||||
|
if options&SecondOptional > 0 {
|
||||||
|
options |= Second
|
||||||
|
optionals++
|
||||||
|
}
|
||||||
|
if options&DowOptional > 0 {
|
||||||
|
options |= Dow
|
||||||
|
optionals++
|
||||||
|
}
|
||||||
|
if optionals > 1 {
|
||||||
|
return nil, fmt.Errorf("multiple optionals may not be configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Figure out how many fields we need
|
||||||
|
max := 0
|
||||||
|
for _, place := range places {
|
||||||
|
if options&place > 0 {
|
||||||
|
max++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
min := max - optionals
|
||||||
|
|
||||||
|
// Validate number of fields
|
||||||
|
if count := len(fields); count < min || count > max {
|
||||||
|
if min == max {
|
||||||
|
return nil, fmt.Errorf("expected exactly %d fields, found %d: %s", min, count, fields)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("expected %d to %d fields, found %d: %s", min, max, count, fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate the optional field if not provided
|
||||||
|
if min < max && len(fields) == min {
|
||||||
|
switch {
|
||||||
|
case options&DowOptional > 0:
|
||||||
|
fields = append(fields, defaults[5]) // TODO: improve access to default
|
||||||
|
case options&SecondOptional > 0:
|
||||||
|
fields = append([]string{defaults[0]}, fields...)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown optional field")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate all fields not part of options with their defaults
|
||||||
|
n := 0
|
||||||
|
expandedFields := make([]string, len(places))
|
||||||
|
copy(expandedFields, defaults)
|
||||||
|
for i, place := range places {
|
||||||
|
if options&place > 0 {
|
||||||
|
expandedFields[i] = fields[n]
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return expandedFields, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var standardParser = NewParser(
|
||||||
|
Minute | Hour | Dom | Month | Dow | Descriptor,
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseStandard returns a new crontab schedule representing the given
|
||||||
|
// standardSpec (https://en.wikipedia.org/wiki/Cron). It requires 5 entries
|
||||||
|
// representing: minute, hour, day of month, month and day of week, in that
|
||||||
|
// order. It returns a descriptive error if the spec is not valid.
|
||||||
|
//
|
||||||
|
// It accepts
|
||||||
|
// - Standard crontab specs, e.g. "* * * * ?"
|
||||||
|
// - Descriptors, e.g. "@midnight", "@every 1h30m"
|
||||||
|
func ParseStandard(standardSpec string) (Schedule, error) {
|
||||||
|
return standardParser.Parse(standardSpec)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getField returns an Int with the bits set representing all of the times that
|
||||||
|
// the field represents or error parsing field value. A "field" is a comma-separated
|
||||||
|
// list of "ranges".
|
||||||
|
func getField(field string, r bounds) (uint64, error) {
|
||||||
|
var bits uint64
|
||||||
|
ranges := strings.FieldsFunc(field, func(r rune) bool { return r == ',' })
|
||||||
|
for _, expr := range ranges {
|
||||||
|
bit, err := getRange(expr, r)
|
||||||
|
if err != nil {
|
||||||
|
return bits, err
|
||||||
|
}
|
||||||
|
bits |= bit
|
||||||
|
}
|
||||||
|
return bits, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getRange returns the bits indicated by the given expression:
|
||||||
|
// number | number "-" number [ "/" number ]
|
||||||
|
// or error parsing range.
|
||||||
|
func getRange(expr string, r bounds) (uint64, error) {
|
||||||
|
var (
|
||||||
|
start, end, step uint
|
||||||
|
rangeAndStep = strings.Split(expr, "/")
|
||||||
|
lowAndHigh = strings.Split(rangeAndStep[0], "-")
|
||||||
|
singleDigit = len(lowAndHigh) == 1
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
var extra uint64
|
||||||
|
if lowAndHigh[0] == "*" || lowAndHigh[0] == "?" {
|
||||||
|
start = r.min
|
||||||
|
end = r.max
|
||||||
|
extra = starBit
|
||||||
|
} else {
|
||||||
|
start, err = parseIntOrName(lowAndHigh[0], r.names)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
switch len(lowAndHigh) {
|
||||||
|
case 1:
|
||||||
|
end = start
|
||||||
|
case 2:
|
||||||
|
end, err = parseIntOrName(lowAndHigh[1], r.names)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("too many hyphens: %s", expr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch len(rangeAndStep) {
|
||||||
|
case 1:
|
||||||
|
step = 1
|
||||||
|
case 2:
|
||||||
|
step, err = mustParseInt(rangeAndStep[1])
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special handling: "N/step" means "N-max/step".
|
||||||
|
if singleDigit {
|
||||||
|
end = r.max
|
||||||
|
}
|
||||||
|
if step > 1 {
|
||||||
|
extra = 0
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("too many slashes: %s", expr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if start < r.min {
|
||||||
|
return 0, fmt.Errorf("beginning of range (%d) below minimum (%d): %s", start, r.min, expr)
|
||||||
|
}
|
||||||
|
if end > r.max {
|
||||||
|
return 0, fmt.Errorf("end of range (%d) above maximum (%d): %s", end, r.max, expr)
|
||||||
|
}
|
||||||
|
if start > end {
|
||||||
|
return 0, fmt.Errorf("beginning of range (%d) beyond end of range (%d): %s", start, end, expr)
|
||||||
|
}
|
||||||
|
if step == 0 {
|
||||||
|
return 0, fmt.Errorf("step of range should be a positive number: %s", expr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return getBits(start, end, step) | extra, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseIntOrName returns the (possibly-named) integer contained in expr.
|
||||||
|
func parseIntOrName(expr string, names map[string]uint) (uint, error) {
|
||||||
|
if names != nil {
|
||||||
|
if namedInt, ok := names[strings.ToLower(expr)]; ok {
|
||||||
|
return namedInt, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mustParseInt(expr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// mustParseInt parses the given expression as an int or returns an error.
|
||||||
|
func mustParseInt(expr string) (uint, error) {
|
||||||
|
num, err := strconv.Atoi(expr)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to parse int from %s: %s", expr, err)
|
||||||
|
}
|
||||||
|
if num < 0 {
|
||||||
|
return 0, fmt.Errorf("negative number (%d) not allowed: %s", num, expr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return uint(num), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getBits sets all bits in the range [min, max], modulo the given step size.
|
||||||
|
func getBits(min, max, step uint) uint64 {
|
||||||
|
var bits uint64
|
||||||
|
|
||||||
|
// If step is 1, use shifts.
|
||||||
|
if step == 1 {
|
||||||
|
return ^(math.MaxUint64 << (max + 1)) & (math.MaxUint64 << min)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Else, use a simple loop.
|
||||||
|
for i := min; i <= max; i += step {
|
||||||
|
bits |= 1 << i
|
||||||
|
}
|
||||||
|
return bits
|
||||||
|
}
|
||||||
|
|
||||||
|
// all returns all bits within the given bounds. (plus the star bit)
|
||||||
|
func all(r bounds) uint64 {
|
||||||
|
return getBits(r.min, r.max, 1) | starBit
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseDescriptor returns a predefined schedule for the expression, or error if none matches.
|
||||||
|
func parseDescriptor(descriptor string, loc *time.Location) (Schedule, error) {
|
||||||
|
switch descriptor {
|
||||||
|
case "@yearly", "@annually":
|
||||||
|
return &SpecSchedule{
|
||||||
|
Second: 1 << seconds.min,
|
||||||
|
Minute: 1 << minutes.min,
|
||||||
|
Hour: 1 << hours.min,
|
||||||
|
Dom: 1 << dom.min,
|
||||||
|
Month: 1 << months.min,
|
||||||
|
Dow: all(dow),
|
||||||
|
Location: loc,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
case "@monthly":
|
||||||
|
return &SpecSchedule{
|
||||||
|
Second: 1 << seconds.min,
|
||||||
|
Minute: 1 << minutes.min,
|
||||||
|
Hour: 1 << hours.min,
|
||||||
|
Dom: 1 << dom.min,
|
||||||
|
Month: all(months),
|
||||||
|
Dow: all(dow),
|
||||||
|
Location: loc,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
case "@weekly":
|
||||||
|
return &SpecSchedule{
|
||||||
|
Second: 1 << seconds.min,
|
||||||
|
Minute: 1 << minutes.min,
|
||||||
|
Hour: 1 << hours.min,
|
||||||
|
Dom: all(dom),
|
||||||
|
Month: all(months),
|
||||||
|
Dow: 1 << dow.min,
|
||||||
|
Location: loc,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
case "@daily", "@midnight":
|
||||||
|
return &SpecSchedule{
|
||||||
|
Second: 1 << seconds.min,
|
||||||
|
Minute: 1 << minutes.min,
|
||||||
|
Hour: 1 << hours.min,
|
||||||
|
Dom: all(dom),
|
||||||
|
Month: all(months),
|
||||||
|
Dow: all(dow),
|
||||||
|
Location: loc,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
case "@hourly":
|
||||||
|
return &SpecSchedule{
|
||||||
|
Second: 1 << seconds.min,
|
||||||
|
Minute: 1 << minutes.min,
|
||||||
|
Hour: all(hours),
|
||||||
|
Dom: all(dom),
|
||||||
|
Month: all(months),
|
||||||
|
Dow: all(dow),
|
||||||
|
Location: loc,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const every = "@every "
|
||||||
|
if strings.HasPrefix(descriptor, every) {
|
||||||
|
duration, err := time.ParseDuration(descriptor[len(every):])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse duration %s: %s", descriptor, err)
|
||||||
|
}
|
||||||
|
return Every(duration), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("unrecognized descriptor: %s", descriptor)
|
||||||
|
}
|
||||||
188
vendor/github.com/robfig/cron/v3/spec.go
generated
vendored
Normal file
188
vendor/github.com/robfig/cron/v3/spec.go
generated
vendored
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
package cron
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// SpecSchedule specifies a duty cycle (to the second granularity), based on a
|
||||||
|
// traditional crontab specification. It is computed initially and stored as bit sets.
|
||||||
|
type SpecSchedule struct {
|
||||||
|
Second, Minute, Hour, Dom, Month, Dow uint64
|
||||||
|
|
||||||
|
// Override location for this schedule.
|
||||||
|
Location *time.Location
|
||||||
|
}
|
||||||
|
|
||||||
|
// bounds provides a range of acceptable values (plus a map of name to value).
|
||||||
|
type bounds struct {
|
||||||
|
min, max uint
|
||||||
|
names map[string]uint
|
||||||
|
}
|
||||||
|
|
||||||
|
// The bounds for each field.
|
||||||
|
var (
|
||||||
|
seconds = bounds{0, 59, nil}
|
||||||
|
minutes = bounds{0, 59, nil}
|
||||||
|
hours = bounds{0, 23, nil}
|
||||||
|
dom = bounds{1, 31, nil}
|
||||||
|
months = bounds{1, 12, map[string]uint{
|
||||||
|
"jan": 1,
|
||||||
|
"feb": 2,
|
||||||
|
"mar": 3,
|
||||||
|
"apr": 4,
|
||||||
|
"may": 5,
|
||||||
|
"jun": 6,
|
||||||
|
"jul": 7,
|
||||||
|
"aug": 8,
|
||||||
|
"sep": 9,
|
||||||
|
"oct": 10,
|
||||||
|
"nov": 11,
|
||||||
|
"dec": 12,
|
||||||
|
}}
|
||||||
|
dow = bounds{0, 6, map[string]uint{
|
||||||
|
"sun": 0,
|
||||||
|
"mon": 1,
|
||||||
|
"tue": 2,
|
||||||
|
"wed": 3,
|
||||||
|
"thu": 4,
|
||||||
|
"fri": 5,
|
||||||
|
"sat": 6,
|
||||||
|
}}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Set the top bit if a star was included in the expression.
|
||||||
|
starBit = 1 << 63
|
||||||
|
)
|
||||||
|
|
||||||
|
// Next returns the next time this schedule is activated, greater than the given
|
||||||
|
// time. If no time can be found to satisfy the schedule, return the zero time.
|
||||||
|
func (s *SpecSchedule) Next(t time.Time) time.Time {
|
||||||
|
// General approach
|
||||||
|
//
|
||||||
|
// For Month, Day, Hour, Minute, Second:
|
||||||
|
// Check if the time value matches. If yes, continue to the next field.
|
||||||
|
// If the field doesn't match the schedule, then increment the field until it matches.
|
||||||
|
// While incrementing the field, a wrap-around brings it back to the beginning
|
||||||
|
// of the field list (since it is necessary to re-verify previous field
|
||||||
|
// values)
|
||||||
|
|
||||||
|
// Convert the given time into the schedule's timezone, if one is specified.
|
||||||
|
// Save the original timezone so we can convert back after we find a time.
|
||||||
|
// Note that schedules without a time zone specified (time.Local) are treated
|
||||||
|
// as local to the time provided.
|
||||||
|
origLocation := t.Location()
|
||||||
|
loc := s.Location
|
||||||
|
if loc == time.Local {
|
||||||
|
loc = t.Location()
|
||||||
|
}
|
||||||
|
if s.Location != time.Local {
|
||||||
|
t = t.In(s.Location)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start at the earliest possible time (the upcoming second).
|
||||||
|
t = t.Add(1*time.Second - time.Duration(t.Nanosecond())*time.Nanosecond)
|
||||||
|
|
||||||
|
// This flag indicates whether a field has been incremented.
|
||||||
|
added := false
|
||||||
|
|
||||||
|
// If no time is found within five years, return zero.
|
||||||
|
yearLimit := t.Year() + 5
|
||||||
|
|
||||||
|
WRAP:
|
||||||
|
if t.Year() > yearLimit {
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the first applicable month.
|
||||||
|
// If it's this month, then do nothing.
|
||||||
|
for 1<<uint(t.Month())&s.Month == 0 {
|
||||||
|
// If we have to add a month, reset the other parts to 0.
|
||||||
|
if !added {
|
||||||
|
added = true
|
||||||
|
// Otherwise, set the date at the beginning (since the current time is irrelevant).
|
||||||
|
t = time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, loc)
|
||||||
|
}
|
||||||
|
t = t.AddDate(0, 1, 0)
|
||||||
|
|
||||||
|
// Wrapped around.
|
||||||
|
if t.Month() == time.January {
|
||||||
|
goto WRAP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now get a day in that month.
|
||||||
|
//
|
||||||
|
// NOTE: This causes issues for daylight savings regimes where midnight does
|
||||||
|
// not exist. For example: Sao Paulo has DST that transforms midnight on
|
||||||
|
// 11/3 into 1am. Handle that by noticing when the Hour ends up != 0.
|
||||||
|
for !dayMatches(s, t) {
|
||||||
|
if !added {
|
||||||
|
added = true
|
||||||
|
t = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, loc)
|
||||||
|
}
|
||||||
|
t = t.AddDate(0, 0, 1)
|
||||||
|
// Notice if the hour is no longer midnight due to DST.
|
||||||
|
// Add an hour if it's 23, subtract an hour if it's 1.
|
||||||
|
if t.Hour() != 0 {
|
||||||
|
if t.Hour() > 12 {
|
||||||
|
t = t.Add(time.Duration(24-t.Hour()) * time.Hour)
|
||||||
|
} else {
|
||||||
|
t = t.Add(time.Duration(-t.Hour()) * time.Hour)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Day() == 1 {
|
||||||
|
goto WRAP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for 1<<uint(t.Hour())&s.Hour == 0 {
|
||||||
|
if !added {
|
||||||
|
added = true
|
||||||
|
t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), 0, 0, 0, loc)
|
||||||
|
}
|
||||||
|
t = t.Add(1 * time.Hour)
|
||||||
|
|
||||||
|
if t.Hour() == 0 {
|
||||||
|
goto WRAP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for 1<<uint(t.Minute())&s.Minute == 0 {
|
||||||
|
if !added {
|
||||||
|
added = true
|
||||||
|
t = t.Truncate(time.Minute)
|
||||||
|
}
|
||||||
|
t = t.Add(1 * time.Minute)
|
||||||
|
|
||||||
|
if t.Minute() == 0 {
|
||||||
|
goto WRAP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for 1<<uint(t.Second())&s.Second == 0 {
|
||||||
|
if !added {
|
||||||
|
added = true
|
||||||
|
t = t.Truncate(time.Second)
|
||||||
|
}
|
||||||
|
t = t.Add(1 * time.Second)
|
||||||
|
|
||||||
|
if t.Second() == 0 {
|
||||||
|
goto WRAP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return t.In(origLocation)
|
||||||
|
}
|
||||||
|
|
||||||
|
// dayMatches returns true if the schedule's day-of-week and day-of-month
|
||||||
|
// restrictions are satisfied by the given time.
|
||||||
|
func dayMatches(s *SpecSchedule, t time.Time) bool {
|
||||||
|
var (
|
||||||
|
domMatch bool = 1<<uint(t.Day())&s.Dom > 0
|
||||||
|
dowMatch bool = 1<<uint(t.Weekday())&s.Dow > 0
|
||||||
|
)
|
||||||
|
if s.Dom&starBit > 0 || s.Dow&starBit > 0 {
|
||||||
|
return domMatch && dowMatch
|
||||||
|
}
|
||||||
|
return domMatch || dowMatch
|
||||||
|
}
|
||||||
27
vendor/golang.org/x/net/LICENSE
generated
vendored
Normal file
27
vendor/golang.org/x/net/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
Copyright 2009 The Go Authors.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google LLC nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
22
vendor/golang.org/x/net/PATENTS
generated
vendored
Normal file
22
vendor/golang.org/x/net/PATENTS
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
Additional IP Rights Grant (Patents)
|
||||||
|
|
||||||
|
"This implementation" means the copyrightable works distributed by
|
||||||
|
Google as part of the Go project.
|
||||||
|
|
||||||
|
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||||
|
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||||
|
patent license to make, have made, use, offer to sell, sell, import,
|
||||||
|
transfer and otherwise run, modify and propagate the contents of this
|
||||||
|
implementation of Go, where such license applies only to those patent
|
||||||
|
claims, both currently owned or controlled by Google and acquired in
|
||||||
|
the future, licensable by Google that are necessarily infringed by this
|
||||||
|
implementation of Go. This grant does not include claims that would be
|
||||||
|
infringed only as a consequence of further modification of this
|
||||||
|
implementation. If you or your agent or exclusive licensee institute or
|
||||||
|
order or agree to the institution of patent litigation against any
|
||||||
|
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||||
|
that this implementation of Go or any code incorporated within this
|
||||||
|
implementation of Go constitutes direct or contributory patent
|
||||||
|
infringement, or inducement of patent infringement, then any patent
|
||||||
|
rights granted to you under this License for this implementation of Go
|
||||||
|
shall terminate as of the date such litigation is filed.
|
||||||
78
vendor/golang.org/x/net/html/atom/atom.go
generated
vendored
Normal file
78
vendor/golang.org/x/net/html/atom/atom.go
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package atom provides integer codes (also known as atoms) for a fixed set of
|
||||||
|
// frequently occurring HTML strings: tag names and attribute keys such as "p"
|
||||||
|
// and "id".
|
||||||
|
//
|
||||||
|
// Sharing an atom's name between all elements with the same tag can result in
|
||||||
|
// fewer string allocations when tokenizing and parsing HTML. Integer
|
||||||
|
// comparisons are also generally faster than string comparisons.
|
||||||
|
//
|
||||||
|
// The value of an atom's particular code is not guaranteed to stay the same
|
||||||
|
// between versions of this package. Neither is any ordering guaranteed:
|
||||||
|
// whether atom.H1 < atom.H2 may also change. The codes are not guaranteed to
|
||||||
|
// be dense. The only guarantees are that e.g. looking up "div" will yield
|
||||||
|
// atom.Div, calling atom.Div.String will return "div", and atom.Div != 0.
|
||||||
|
package atom // import "golang.org/x/net/html/atom"
|
||||||
|
|
||||||
|
// Atom is an integer code for a string. The zero value maps to "".
|
||||||
|
type Atom uint32
|
||||||
|
|
||||||
|
// String returns the atom's name.
|
||||||
|
func (a Atom) String() string {
|
||||||
|
start := uint32(a >> 8)
|
||||||
|
n := uint32(a & 0xff)
|
||||||
|
if start+n > uint32(len(atomText)) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return atomText[start : start+n]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Atom) string() string {
|
||||||
|
return atomText[a>>8 : a>>8+a&0xff]
|
||||||
|
}
|
||||||
|
|
||||||
|
// fnv computes the FNV hash with an arbitrary starting value h.
|
||||||
|
func fnv(h uint32, s []byte) uint32 {
|
||||||
|
for i := range s {
|
||||||
|
h ^= uint32(s[i])
|
||||||
|
h *= 16777619
|
||||||
|
}
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
func match(s string, t []byte) bool {
|
||||||
|
for i, c := range t {
|
||||||
|
if s[i] != c {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup returns the atom whose name is s. It returns zero if there is no
|
||||||
|
// such atom. The lookup is case sensitive.
|
||||||
|
func Lookup(s []byte) Atom {
|
||||||
|
if len(s) == 0 || len(s) > maxAtomLen {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
h := fnv(hash0, s)
|
||||||
|
if a := table[h&uint32(len(table)-1)]; int(a&0xff) == len(s) && match(a.string(), s) {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
if a := table[(h>>16)&uint32(len(table)-1)]; int(a&0xff) == len(s) && match(a.string(), s) {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string whose contents are equal to s. In that sense, it is
|
||||||
|
// equivalent to string(s) but may be more efficient.
|
||||||
|
func String(s []byte) string {
|
||||||
|
if a := Lookup(s); a != 0 {
|
||||||
|
return a.String()
|
||||||
|
}
|
||||||
|
return string(s)
|
||||||
|
}
|
||||||
785
vendor/golang.org/x/net/html/atom/table.go
generated
vendored
Normal file
785
vendor/golang.org/x/net/html/atom/table.go
generated
vendored
Normal file
@@ -0,0 +1,785 @@
|
|||||||
|
// Code generated by go generate gen.go; DO NOT EDIT.
|
||||||
|
|
||||||
|
//go:generate go run gen.go
|
||||||
|
|
||||||
|
package atom
|
||||||
|
|
||||||
|
const (
|
||||||
|
A Atom = 0x1
|
||||||
|
Abbr Atom = 0x4
|
||||||
|
Accept Atom = 0x1a06
|
||||||
|
AcceptCharset Atom = 0x1a0e
|
||||||
|
Accesskey Atom = 0x2c09
|
||||||
|
Acronym Atom = 0xaa07
|
||||||
|
Action Atom = 0x26506
|
||||||
|
Address Atom = 0x6f107
|
||||||
|
Align Atom = 0xb105
|
||||||
|
Allowfullscreen Atom = 0x3280f
|
||||||
|
Allowpaymentrequest Atom = 0xc113
|
||||||
|
Allowusermedia Atom = 0xdd0e
|
||||||
|
Alt Atom = 0xf303
|
||||||
|
Annotation Atom = 0x1c90a
|
||||||
|
AnnotationXml Atom = 0x1c90e
|
||||||
|
Applet Atom = 0x30806
|
||||||
|
Area Atom = 0x35004
|
||||||
|
Article Atom = 0x3f607
|
||||||
|
As Atom = 0x3c02
|
||||||
|
Aside Atom = 0x10705
|
||||||
|
Async Atom = 0xff05
|
||||||
|
Audio Atom = 0x11505
|
||||||
|
Autocomplete Atom = 0x26b0c
|
||||||
|
Autofocus Atom = 0x12109
|
||||||
|
Autoplay Atom = 0x13c08
|
||||||
|
B Atom = 0x101
|
||||||
|
Base Atom = 0x3b04
|
||||||
|
Basefont Atom = 0x3b08
|
||||||
|
Bdi Atom = 0xba03
|
||||||
|
Bdo Atom = 0x14b03
|
||||||
|
Bgsound Atom = 0x15e07
|
||||||
|
Big Atom = 0x17003
|
||||||
|
Blink Atom = 0x17305
|
||||||
|
Blockquote Atom = 0x1870a
|
||||||
|
Body Atom = 0x2804
|
||||||
|
Br Atom = 0x202
|
||||||
|
Button Atom = 0x19106
|
||||||
|
Canvas Atom = 0x10306
|
||||||
|
Caption Atom = 0x22407
|
||||||
|
Center Atom = 0x21306
|
||||||
|
Challenge Atom = 0x28e09
|
||||||
|
Charset Atom = 0x2107
|
||||||
|
Checked Atom = 0x5b507
|
||||||
|
Cite Atom = 0x19c04
|
||||||
|
Class Atom = 0x55805
|
||||||
|
Code Atom = 0x5ee04
|
||||||
|
Col Atom = 0x1ab03
|
||||||
|
Colgroup Atom = 0x1ab08
|
||||||
|
Color Atom = 0x1bf05
|
||||||
|
Cols Atom = 0x1c404
|
||||||
|
Colspan Atom = 0x1c407
|
||||||
|
Command Atom = 0x1d707
|
||||||
|
Content Atom = 0x57b07
|
||||||
|
Contenteditable Atom = 0x57b0f
|
||||||
|
Contextmenu Atom = 0x37a0b
|
||||||
|
Controls Atom = 0x1de08
|
||||||
|
Coords Atom = 0x1f006
|
||||||
|
Crossorigin Atom = 0x1fa0b
|
||||||
|
Data Atom = 0x49904
|
||||||
|
Datalist Atom = 0x49908
|
||||||
|
Datetime Atom = 0x2ab08
|
||||||
|
Dd Atom = 0x2bf02
|
||||||
|
Default Atom = 0x10a07
|
||||||
|
Defer Atom = 0x5f005
|
||||||
|
Del Atom = 0x44c03
|
||||||
|
Desc Atom = 0x55504
|
||||||
|
Details Atom = 0x7207
|
||||||
|
Dfn Atom = 0x8703
|
||||||
|
Dialog Atom = 0xbb06
|
||||||
|
Dir Atom = 0x9303
|
||||||
|
Dirname Atom = 0x9307
|
||||||
|
Disabled Atom = 0x16408
|
||||||
|
Div Atom = 0x16b03
|
||||||
|
Dl Atom = 0x5d602
|
||||||
|
Download Atom = 0x45d08
|
||||||
|
Draggable Atom = 0x17a09
|
||||||
|
Dropzone Atom = 0x3ff08
|
||||||
|
Dt Atom = 0x64002
|
||||||
|
Em Atom = 0x6e02
|
||||||
|
Embed Atom = 0x6e05
|
||||||
|
Enctype Atom = 0x28007
|
||||||
|
Face Atom = 0x21104
|
||||||
|
Fieldset Atom = 0x21908
|
||||||
|
Figcaption Atom = 0x2210a
|
||||||
|
Figure Atom = 0x23b06
|
||||||
|
Font Atom = 0x3f04
|
||||||
|
Footer Atom = 0xf606
|
||||||
|
For Atom = 0x24703
|
||||||
|
ForeignObject Atom = 0x2470d
|
||||||
|
Foreignobject Atom = 0x2540d
|
||||||
|
Form Atom = 0x26104
|
||||||
|
Formaction Atom = 0x2610a
|
||||||
|
Formenctype Atom = 0x27c0b
|
||||||
|
Formmethod Atom = 0x2970a
|
||||||
|
Formnovalidate Atom = 0x2a10e
|
||||||
|
Formtarget Atom = 0x2b30a
|
||||||
|
Frame Atom = 0x8b05
|
||||||
|
Frameset Atom = 0x8b08
|
||||||
|
H1 Atom = 0x15c02
|
||||||
|
H2 Atom = 0x56102
|
||||||
|
H3 Atom = 0x2cd02
|
||||||
|
H4 Atom = 0x2fc02
|
||||||
|
H5 Atom = 0x33f02
|
||||||
|
H6 Atom = 0x34902
|
||||||
|
Head Atom = 0x32004
|
||||||
|
Header Atom = 0x32006
|
||||||
|
Headers Atom = 0x32007
|
||||||
|
Height Atom = 0x5206
|
||||||
|
Hgroup Atom = 0x64206
|
||||||
|
Hidden Atom = 0x2bd06
|
||||||
|
High Atom = 0x2ca04
|
||||||
|
Hr Atom = 0x15702
|
||||||
|
Href Atom = 0x2cf04
|
||||||
|
Hreflang Atom = 0x2cf08
|
||||||
|
Html Atom = 0x5604
|
||||||
|
HttpEquiv Atom = 0x2d70a
|
||||||
|
I Atom = 0x601
|
||||||
|
Icon Atom = 0x57a04
|
||||||
|
Id Atom = 0x10902
|
||||||
|
Iframe Atom = 0x2eb06
|
||||||
|
Image Atom = 0x2f105
|
||||||
|
Img Atom = 0x2f603
|
||||||
|
Input Atom = 0x44505
|
||||||
|
Inputmode Atom = 0x44509
|
||||||
|
Ins Atom = 0x20303
|
||||||
|
Integrity Atom = 0x23209
|
||||||
|
Is Atom = 0x16502
|
||||||
|
Isindex Atom = 0x2fe07
|
||||||
|
Ismap Atom = 0x30505
|
||||||
|
Itemid Atom = 0x38506
|
||||||
|
Itemprop Atom = 0x19d08
|
||||||
|
Itemref Atom = 0x3c707
|
||||||
|
Itemscope Atom = 0x66f09
|
||||||
|
Itemtype Atom = 0x30e08
|
||||||
|
Kbd Atom = 0xb903
|
||||||
|
Keygen Atom = 0x3206
|
||||||
|
Keytype Atom = 0xd607
|
||||||
|
Kind Atom = 0x17704
|
||||||
|
Label Atom = 0x5905
|
||||||
|
Lang Atom = 0x2d304
|
||||||
|
Legend Atom = 0x18106
|
||||||
|
Li Atom = 0xb202
|
||||||
|
Link Atom = 0x17404
|
||||||
|
List Atom = 0x49d04
|
||||||
|
Listing Atom = 0x49d07
|
||||||
|
Loop Atom = 0x5d04
|
||||||
|
Low Atom = 0xc303
|
||||||
|
Main Atom = 0x1004
|
||||||
|
Malignmark Atom = 0xb00a
|
||||||
|
Manifest Atom = 0x6d508
|
||||||
|
Map Atom = 0x30703
|
||||||
|
Mark Atom = 0xb604
|
||||||
|
Marquee Atom = 0x31607
|
||||||
|
Math Atom = 0x31d04
|
||||||
|
Max Atom = 0x33703
|
||||||
|
Maxlength Atom = 0x33709
|
||||||
|
Media Atom = 0xe605
|
||||||
|
Mediagroup Atom = 0xe60a
|
||||||
|
Menu Atom = 0x38104
|
||||||
|
Menuitem Atom = 0x38108
|
||||||
|
Meta Atom = 0x4ac04
|
||||||
|
Meter Atom = 0x9805
|
||||||
|
Method Atom = 0x29b06
|
||||||
|
Mglyph Atom = 0x2f706
|
||||||
|
Mi Atom = 0x34102
|
||||||
|
Min Atom = 0x34103
|
||||||
|
Minlength Atom = 0x34109
|
||||||
|
Mn Atom = 0x2a402
|
||||||
|
Mo Atom = 0xa402
|
||||||
|
Ms Atom = 0x67202
|
||||||
|
Mtext Atom = 0x34b05
|
||||||
|
Multiple Atom = 0x35908
|
||||||
|
Muted Atom = 0x36105
|
||||||
|
Name Atom = 0x9604
|
||||||
|
Nav Atom = 0x1303
|
||||||
|
Nobr Atom = 0x3704
|
||||||
|
Noembed Atom = 0x6c07
|
||||||
|
Noframes Atom = 0x8908
|
||||||
|
Nomodule Atom = 0xa208
|
||||||
|
Nonce Atom = 0x1a605
|
||||||
|
Noscript Atom = 0x2c208
|
||||||
|
Novalidate Atom = 0x2a50a
|
||||||
|
Object Atom = 0x25b06
|
||||||
|
Ol Atom = 0x13702
|
||||||
|
Onabort Atom = 0x19507
|
||||||
|
Onafterprint Atom = 0x2290c
|
||||||
|
Onautocomplete Atom = 0x2690e
|
||||||
|
Onautocompleteerror Atom = 0x26913
|
||||||
|
Onauxclick Atom = 0x6140a
|
||||||
|
Onbeforeprint Atom = 0x69c0d
|
||||||
|
Onbeforeunload Atom = 0x6e50e
|
||||||
|
Onblur Atom = 0x1ea06
|
||||||
|
Oncancel Atom = 0x11908
|
||||||
|
Oncanplay Atom = 0x14d09
|
||||||
|
Oncanplaythrough Atom = 0x14d10
|
||||||
|
Onchange Atom = 0x41508
|
||||||
|
Onclick Atom = 0x2e407
|
||||||
|
Onclose Atom = 0x36607
|
||||||
|
Oncontextmenu Atom = 0x3780d
|
||||||
|
Oncopy Atom = 0x38b06
|
||||||
|
Oncuechange Atom = 0x3910b
|
||||||
|
Oncut Atom = 0x39c05
|
||||||
|
Ondblclick Atom = 0x3a10a
|
||||||
|
Ondrag Atom = 0x3ab06
|
||||||
|
Ondragend Atom = 0x3ab09
|
||||||
|
Ondragenter Atom = 0x3b40b
|
||||||
|
Ondragexit Atom = 0x3bf0a
|
||||||
|
Ondragleave Atom = 0x3d90b
|
||||||
|
Ondragover Atom = 0x3e40a
|
||||||
|
Ondragstart Atom = 0x3ee0b
|
||||||
|
Ondrop Atom = 0x3fd06
|
||||||
|
Ondurationchange Atom = 0x40d10
|
||||||
|
Onemptied Atom = 0x40409
|
||||||
|
Onended Atom = 0x41d07
|
||||||
|
Onerror Atom = 0x42407
|
||||||
|
Onfocus Atom = 0x42b07
|
||||||
|
Onhashchange Atom = 0x4370c
|
||||||
|
Oninput Atom = 0x44307
|
||||||
|
Oninvalid Atom = 0x44f09
|
||||||
|
Onkeydown Atom = 0x45809
|
||||||
|
Onkeypress Atom = 0x4650a
|
||||||
|
Onkeyup Atom = 0x47407
|
||||||
|
Onlanguagechange Atom = 0x48110
|
||||||
|
Onload Atom = 0x49106
|
||||||
|
Onloadeddata Atom = 0x4910c
|
||||||
|
Onloadedmetadata Atom = 0x4a410
|
||||||
|
Onloadend Atom = 0x4ba09
|
||||||
|
Onloadstart Atom = 0x4c30b
|
||||||
|
Onmessage Atom = 0x4ce09
|
||||||
|
Onmessageerror Atom = 0x4ce0e
|
||||||
|
Onmousedown Atom = 0x4dc0b
|
||||||
|
Onmouseenter Atom = 0x4e70c
|
||||||
|
Onmouseleave Atom = 0x4f30c
|
||||||
|
Onmousemove Atom = 0x4ff0b
|
||||||
|
Onmouseout Atom = 0x50a0a
|
||||||
|
Onmouseover Atom = 0x5170b
|
||||||
|
Onmouseup Atom = 0x52209
|
||||||
|
Onmousewheel Atom = 0x5300c
|
||||||
|
Onoffline Atom = 0x53c09
|
||||||
|
Ononline Atom = 0x54508
|
||||||
|
Onpagehide Atom = 0x54d0a
|
||||||
|
Onpageshow Atom = 0x5630a
|
||||||
|
Onpaste Atom = 0x56f07
|
||||||
|
Onpause Atom = 0x58a07
|
||||||
|
Onplay Atom = 0x59406
|
||||||
|
Onplaying Atom = 0x59409
|
||||||
|
Onpopstate Atom = 0x59d0a
|
||||||
|
Onprogress Atom = 0x5a70a
|
||||||
|
Onratechange Atom = 0x5bc0c
|
||||||
|
Onrejectionhandled Atom = 0x5c812
|
||||||
|
Onreset Atom = 0x5da07
|
||||||
|
Onresize Atom = 0x5e108
|
||||||
|
Onscroll Atom = 0x5f508
|
||||||
|
Onsecuritypolicyviolation Atom = 0x5fd19
|
||||||
|
Onseeked Atom = 0x61e08
|
||||||
|
Onseeking Atom = 0x62609
|
||||||
|
Onselect Atom = 0x62f08
|
||||||
|
Onshow Atom = 0x63906
|
||||||
|
Onsort Atom = 0x64d06
|
||||||
|
Onstalled Atom = 0x65709
|
||||||
|
Onstorage Atom = 0x66009
|
||||||
|
Onsubmit Atom = 0x66908
|
||||||
|
Onsuspend Atom = 0x67909
|
||||||
|
Ontimeupdate Atom = 0x400c
|
||||||
|
Ontoggle Atom = 0x68208
|
||||||
|
Onunhandledrejection Atom = 0x68a14
|
||||||
|
Onunload Atom = 0x6a908
|
||||||
|
Onvolumechange Atom = 0x6b10e
|
||||||
|
Onwaiting Atom = 0x6bf09
|
||||||
|
Onwheel Atom = 0x6c807
|
||||||
|
Open Atom = 0x1a304
|
||||||
|
Optgroup Atom = 0x5f08
|
||||||
|
Optimum Atom = 0x6cf07
|
||||||
|
Option Atom = 0x6e106
|
||||||
|
Output Atom = 0x51106
|
||||||
|
P Atom = 0xc01
|
||||||
|
Param Atom = 0xc05
|
||||||
|
Pattern Atom = 0x6607
|
||||||
|
Picture Atom = 0x7b07
|
||||||
|
Ping Atom = 0xef04
|
||||||
|
Placeholder Atom = 0x1310b
|
||||||
|
Plaintext Atom = 0x1b209
|
||||||
|
Playsinline Atom = 0x1400b
|
||||||
|
Poster Atom = 0x64706
|
||||||
|
Pre Atom = 0x46a03
|
||||||
|
Preload Atom = 0x47a07
|
||||||
|
Progress Atom = 0x5a908
|
||||||
|
Prompt Atom = 0x52a06
|
||||||
|
Public Atom = 0x57606
|
||||||
|
Q Atom = 0xcf01
|
||||||
|
Radiogroup Atom = 0x30a
|
||||||
|
Rb Atom = 0x3a02
|
||||||
|
Readonly Atom = 0x35108
|
||||||
|
Referrerpolicy Atom = 0x3cb0e
|
||||||
|
Rel Atom = 0x47b03
|
||||||
|
Required Atom = 0x23f08
|
||||||
|
Reversed Atom = 0x8008
|
||||||
|
Rows Atom = 0x9c04
|
||||||
|
Rowspan Atom = 0x9c07
|
||||||
|
Rp Atom = 0x22f02
|
||||||
|
Rt Atom = 0x19a02
|
||||||
|
Rtc Atom = 0x19a03
|
||||||
|
Ruby Atom = 0xfb04
|
||||||
|
S Atom = 0x2501
|
||||||
|
Samp Atom = 0x7804
|
||||||
|
Sandbox Atom = 0x12907
|
||||||
|
Scope Atom = 0x67305
|
||||||
|
Scoped Atom = 0x67306
|
||||||
|
Script Atom = 0x2c406
|
||||||
|
Seamless Atom = 0x36b08
|
||||||
|
Search Atom = 0x55c06
|
||||||
|
Section Atom = 0x1e507
|
||||||
|
Select Atom = 0x63106
|
||||||
|
Selected Atom = 0x63108
|
||||||
|
Shape Atom = 0x1f505
|
||||||
|
Size Atom = 0x5e504
|
||||||
|
Sizes Atom = 0x5e505
|
||||||
|
Slot Atom = 0x20504
|
||||||
|
Small Atom = 0x32605
|
||||||
|
Sortable Atom = 0x64f08
|
||||||
|
Sorted Atom = 0x37206
|
||||||
|
Source Atom = 0x43106
|
||||||
|
Spacer Atom = 0x46e06
|
||||||
|
Span Atom = 0x9f04
|
||||||
|
Spellcheck Atom = 0x5b00a
|
||||||
|
Src Atom = 0x5e903
|
||||||
|
Srcdoc Atom = 0x5e906
|
||||||
|
Srclang Atom = 0x6f707
|
||||||
|
Srcset Atom = 0x6fe06
|
||||||
|
Start Atom = 0x3f405
|
||||||
|
Step Atom = 0x57304
|
||||||
|
Strike Atom = 0xd206
|
||||||
|
Strong Atom = 0x6db06
|
||||||
|
Style Atom = 0x70405
|
||||||
|
Sub Atom = 0x66b03
|
||||||
|
Summary Atom = 0x70907
|
||||||
|
Sup Atom = 0x71003
|
||||||
|
Svg Atom = 0x71303
|
||||||
|
System Atom = 0x71606
|
||||||
|
Tabindex Atom = 0x4b208
|
||||||
|
Table Atom = 0x58505
|
||||||
|
Target Atom = 0x2b706
|
||||||
|
Tbody Atom = 0x2705
|
||||||
|
Td Atom = 0x9202
|
||||||
|
Template Atom = 0x71908
|
||||||
|
Textarea Atom = 0x34c08
|
||||||
|
Tfoot Atom = 0xf505
|
||||||
|
Th Atom = 0x15602
|
||||||
|
Thead Atom = 0x31f05
|
||||||
|
Time Atom = 0x4204
|
||||||
|
Title Atom = 0x11005
|
||||||
|
Tr Atom = 0xcc02
|
||||||
|
Track Atom = 0x1ba05
|
||||||
|
Translate Atom = 0x20809
|
||||||
|
Tt Atom = 0x6802
|
||||||
|
Type Atom = 0xd904
|
||||||
|
Typemustmatch Atom = 0x2830d
|
||||||
|
U Atom = 0xb01
|
||||||
|
Ul Atom = 0xa702
|
||||||
|
Updateviacache Atom = 0x460e
|
||||||
|
Usemap Atom = 0x58e06
|
||||||
|
Value Atom = 0x1505
|
||||||
|
Var Atom = 0x16d03
|
||||||
|
Video Atom = 0x2e005
|
||||||
|
Wbr Atom = 0x56c03
|
||||||
|
Width Atom = 0x63e05
|
||||||
|
Workertype Atom = 0x7210a
|
||||||
|
Wrap Atom = 0x72b04
|
||||||
|
Xmp Atom = 0x12f03
|
||||||
|
)
|
||||||
|
|
||||||
|
const hash0 = 0x84f70e16
|
||||||
|
|
||||||
|
const maxAtomLen = 25
|
||||||
|
|
||||||
|
var table = [1 << 9]Atom{
|
||||||
|
0x1: 0x3ff08, // dropzone
|
||||||
|
0x2: 0x3b08, // basefont
|
||||||
|
0x3: 0x23209, // integrity
|
||||||
|
0x4: 0x43106, // source
|
||||||
|
0x5: 0x2c09, // accesskey
|
||||||
|
0x6: 0x1a06, // accept
|
||||||
|
0x7: 0x6c807, // onwheel
|
||||||
|
0xb: 0x47407, // onkeyup
|
||||||
|
0xc: 0x32007, // headers
|
||||||
|
0xd: 0x67306, // scoped
|
||||||
|
0xe: 0x67909, // onsuspend
|
||||||
|
0xf: 0x8908, // noframes
|
||||||
|
0x10: 0x1fa0b, // crossorigin
|
||||||
|
0x11: 0x2e407, // onclick
|
||||||
|
0x12: 0x3f405, // start
|
||||||
|
0x13: 0x37a0b, // contextmenu
|
||||||
|
0x14: 0x5e903, // src
|
||||||
|
0x15: 0x1c404, // cols
|
||||||
|
0x16: 0xbb06, // dialog
|
||||||
|
0x17: 0x47a07, // preload
|
||||||
|
0x18: 0x3c707, // itemref
|
||||||
|
0x1b: 0x2f105, // image
|
||||||
|
0x1d: 0x4ba09, // onloadend
|
||||||
|
0x1e: 0x45d08, // download
|
||||||
|
0x1f: 0x46a03, // pre
|
||||||
|
0x23: 0x2970a, // formmethod
|
||||||
|
0x24: 0x71303, // svg
|
||||||
|
0x25: 0xcf01, // q
|
||||||
|
0x26: 0x64002, // dt
|
||||||
|
0x27: 0x1de08, // controls
|
||||||
|
0x2a: 0x2804, // body
|
||||||
|
0x2b: 0xd206, // strike
|
||||||
|
0x2c: 0x3910b, // oncuechange
|
||||||
|
0x2d: 0x4c30b, // onloadstart
|
||||||
|
0x2e: 0x2fe07, // isindex
|
||||||
|
0x2f: 0xb202, // li
|
||||||
|
0x30: 0x1400b, // playsinline
|
||||||
|
0x31: 0x34102, // mi
|
||||||
|
0x32: 0x30806, // applet
|
||||||
|
0x33: 0x4ce09, // onmessage
|
||||||
|
0x35: 0x13702, // ol
|
||||||
|
0x36: 0x1a304, // open
|
||||||
|
0x39: 0x14d09, // oncanplay
|
||||||
|
0x3a: 0x6bf09, // onwaiting
|
||||||
|
0x3b: 0x11908, // oncancel
|
||||||
|
0x3c: 0x6a908, // onunload
|
||||||
|
0x3e: 0x53c09, // onoffline
|
||||||
|
0x3f: 0x1a0e, // accept-charset
|
||||||
|
0x40: 0x32004, // head
|
||||||
|
0x42: 0x3ab09, // ondragend
|
||||||
|
0x43: 0x1310b, // placeholder
|
||||||
|
0x44: 0x2b30a, // formtarget
|
||||||
|
0x45: 0x2540d, // foreignobject
|
||||||
|
0x47: 0x400c, // ontimeupdate
|
||||||
|
0x48: 0xdd0e, // allowusermedia
|
||||||
|
0x4a: 0x69c0d, // onbeforeprint
|
||||||
|
0x4b: 0x5604, // html
|
||||||
|
0x4c: 0x9f04, // span
|
||||||
|
0x4d: 0x64206, // hgroup
|
||||||
|
0x4e: 0x16408, // disabled
|
||||||
|
0x4f: 0x4204, // time
|
||||||
|
0x51: 0x42b07, // onfocus
|
||||||
|
0x53: 0xb00a, // malignmark
|
||||||
|
0x55: 0x4650a, // onkeypress
|
||||||
|
0x56: 0x55805, // class
|
||||||
|
0x57: 0x1ab08, // colgroup
|
||||||
|
0x58: 0x33709, // maxlength
|
||||||
|
0x59: 0x5a908, // progress
|
||||||
|
0x5b: 0x70405, // style
|
||||||
|
0x5c: 0x2a10e, // formnovalidate
|
||||||
|
0x5e: 0x38b06, // oncopy
|
||||||
|
0x60: 0x26104, // form
|
||||||
|
0x61: 0xf606, // footer
|
||||||
|
0x64: 0x30a, // radiogroup
|
||||||
|
0x66: 0xfb04, // ruby
|
||||||
|
0x67: 0x4ff0b, // onmousemove
|
||||||
|
0x68: 0x19d08, // itemprop
|
||||||
|
0x69: 0x2d70a, // http-equiv
|
||||||
|
0x6a: 0x15602, // th
|
||||||
|
0x6c: 0x6e02, // em
|
||||||
|
0x6d: 0x38108, // menuitem
|
||||||
|
0x6e: 0x63106, // select
|
||||||
|
0x6f: 0x48110, // onlanguagechange
|
||||||
|
0x70: 0x31f05, // thead
|
||||||
|
0x71: 0x15c02, // h1
|
||||||
|
0x72: 0x5e906, // srcdoc
|
||||||
|
0x75: 0x9604, // name
|
||||||
|
0x76: 0x19106, // button
|
||||||
|
0x77: 0x55504, // desc
|
||||||
|
0x78: 0x17704, // kind
|
||||||
|
0x79: 0x1bf05, // color
|
||||||
|
0x7c: 0x58e06, // usemap
|
||||||
|
0x7d: 0x30e08, // itemtype
|
||||||
|
0x7f: 0x6d508, // manifest
|
||||||
|
0x81: 0x5300c, // onmousewheel
|
||||||
|
0x82: 0x4dc0b, // onmousedown
|
||||||
|
0x84: 0xc05, // param
|
||||||
|
0x85: 0x2e005, // video
|
||||||
|
0x86: 0x4910c, // onloadeddata
|
||||||
|
0x87: 0x6f107, // address
|
||||||
|
0x8c: 0xef04, // ping
|
||||||
|
0x8d: 0x24703, // for
|
||||||
|
0x8f: 0x62f08, // onselect
|
||||||
|
0x90: 0x30703, // map
|
||||||
|
0x92: 0xc01, // p
|
||||||
|
0x93: 0x8008, // reversed
|
||||||
|
0x94: 0x54d0a, // onpagehide
|
||||||
|
0x95: 0x3206, // keygen
|
||||||
|
0x96: 0x34109, // minlength
|
||||||
|
0x97: 0x3e40a, // ondragover
|
||||||
|
0x98: 0x42407, // onerror
|
||||||
|
0x9a: 0x2107, // charset
|
||||||
|
0x9b: 0x29b06, // method
|
||||||
|
0x9c: 0x101, // b
|
||||||
|
0x9d: 0x68208, // ontoggle
|
||||||
|
0x9e: 0x2bd06, // hidden
|
||||||
|
0xa0: 0x3f607, // article
|
||||||
|
0xa2: 0x63906, // onshow
|
||||||
|
0xa3: 0x64d06, // onsort
|
||||||
|
0xa5: 0x57b0f, // contenteditable
|
||||||
|
0xa6: 0x66908, // onsubmit
|
||||||
|
0xa8: 0x44f09, // oninvalid
|
||||||
|
0xaa: 0x202, // br
|
||||||
|
0xab: 0x10902, // id
|
||||||
|
0xac: 0x5d04, // loop
|
||||||
|
0xad: 0x5630a, // onpageshow
|
||||||
|
0xb0: 0x2cf04, // href
|
||||||
|
0xb2: 0x2210a, // figcaption
|
||||||
|
0xb3: 0x2690e, // onautocomplete
|
||||||
|
0xb4: 0x49106, // onload
|
||||||
|
0xb6: 0x9c04, // rows
|
||||||
|
0xb7: 0x1a605, // nonce
|
||||||
|
0xb8: 0x68a14, // onunhandledrejection
|
||||||
|
0xbb: 0x21306, // center
|
||||||
|
0xbc: 0x59406, // onplay
|
||||||
|
0xbd: 0x33f02, // h5
|
||||||
|
0xbe: 0x49d07, // listing
|
||||||
|
0xbf: 0x57606, // public
|
||||||
|
0xc2: 0x23b06, // figure
|
||||||
|
0xc3: 0x57a04, // icon
|
||||||
|
0xc4: 0x1ab03, // col
|
||||||
|
0xc5: 0x47b03, // rel
|
||||||
|
0xc6: 0xe605, // media
|
||||||
|
0xc7: 0x12109, // autofocus
|
||||||
|
0xc8: 0x19a02, // rt
|
||||||
|
0xca: 0x2d304, // lang
|
||||||
|
0xcc: 0x49908, // datalist
|
||||||
|
0xce: 0x2eb06, // iframe
|
||||||
|
0xcf: 0x36105, // muted
|
||||||
|
0xd0: 0x6140a, // onauxclick
|
||||||
|
0xd2: 0x3c02, // as
|
||||||
|
0xd6: 0x3fd06, // ondrop
|
||||||
|
0xd7: 0x1c90a, // annotation
|
||||||
|
0xd8: 0x21908, // fieldset
|
||||||
|
0xdb: 0x2cf08, // hreflang
|
||||||
|
0xdc: 0x4e70c, // onmouseenter
|
||||||
|
0xdd: 0x2a402, // mn
|
||||||
|
0xde: 0xe60a, // mediagroup
|
||||||
|
0xdf: 0x9805, // meter
|
||||||
|
0xe0: 0x56c03, // wbr
|
||||||
|
0xe2: 0x63e05, // width
|
||||||
|
0xe3: 0x2290c, // onafterprint
|
||||||
|
0xe4: 0x30505, // ismap
|
||||||
|
0xe5: 0x1505, // value
|
||||||
|
0xe7: 0x1303, // nav
|
||||||
|
0xe8: 0x54508, // ononline
|
||||||
|
0xe9: 0xb604, // mark
|
||||||
|
0xea: 0xc303, // low
|
||||||
|
0xeb: 0x3ee0b, // ondragstart
|
||||||
|
0xef: 0x12f03, // xmp
|
||||||
|
0xf0: 0x22407, // caption
|
||||||
|
0xf1: 0xd904, // type
|
||||||
|
0xf2: 0x70907, // summary
|
||||||
|
0xf3: 0x6802, // tt
|
||||||
|
0xf4: 0x20809, // translate
|
||||||
|
0xf5: 0x1870a, // blockquote
|
||||||
|
0xf8: 0x15702, // hr
|
||||||
|
0xfa: 0x2705, // tbody
|
||||||
|
0xfc: 0x7b07, // picture
|
||||||
|
0xfd: 0x5206, // height
|
||||||
|
0xfe: 0x19c04, // cite
|
||||||
|
0xff: 0x2501, // s
|
||||||
|
0x101: 0xff05, // async
|
||||||
|
0x102: 0x56f07, // onpaste
|
||||||
|
0x103: 0x19507, // onabort
|
||||||
|
0x104: 0x2b706, // target
|
||||||
|
0x105: 0x14b03, // bdo
|
||||||
|
0x106: 0x1f006, // coords
|
||||||
|
0x107: 0x5e108, // onresize
|
||||||
|
0x108: 0x71908, // template
|
||||||
|
0x10a: 0x3a02, // rb
|
||||||
|
0x10b: 0x2a50a, // novalidate
|
||||||
|
0x10c: 0x460e, // updateviacache
|
||||||
|
0x10d: 0x71003, // sup
|
||||||
|
0x10e: 0x6c07, // noembed
|
||||||
|
0x10f: 0x16b03, // div
|
||||||
|
0x110: 0x6f707, // srclang
|
||||||
|
0x111: 0x17a09, // draggable
|
||||||
|
0x112: 0x67305, // scope
|
||||||
|
0x113: 0x5905, // label
|
||||||
|
0x114: 0x22f02, // rp
|
||||||
|
0x115: 0x23f08, // required
|
||||||
|
0x116: 0x3780d, // oncontextmenu
|
||||||
|
0x117: 0x5e504, // size
|
||||||
|
0x118: 0x5b00a, // spellcheck
|
||||||
|
0x119: 0x3f04, // font
|
||||||
|
0x11a: 0x9c07, // rowspan
|
||||||
|
0x11b: 0x10a07, // default
|
||||||
|
0x11d: 0x44307, // oninput
|
||||||
|
0x11e: 0x38506, // itemid
|
||||||
|
0x11f: 0x5ee04, // code
|
||||||
|
0x120: 0xaa07, // acronym
|
||||||
|
0x121: 0x3b04, // base
|
||||||
|
0x125: 0x2470d, // foreignObject
|
||||||
|
0x126: 0x2ca04, // high
|
||||||
|
0x127: 0x3cb0e, // referrerpolicy
|
||||||
|
0x128: 0x33703, // max
|
||||||
|
0x129: 0x59d0a, // onpopstate
|
||||||
|
0x12a: 0x2fc02, // h4
|
||||||
|
0x12b: 0x4ac04, // meta
|
||||||
|
0x12c: 0x17305, // blink
|
||||||
|
0x12e: 0x5f508, // onscroll
|
||||||
|
0x12f: 0x59409, // onplaying
|
||||||
|
0x130: 0xc113, // allowpaymentrequest
|
||||||
|
0x131: 0x19a03, // rtc
|
||||||
|
0x132: 0x72b04, // wrap
|
||||||
|
0x134: 0x8b08, // frameset
|
||||||
|
0x135: 0x32605, // small
|
||||||
|
0x137: 0x32006, // header
|
||||||
|
0x138: 0x40409, // onemptied
|
||||||
|
0x139: 0x34902, // h6
|
||||||
|
0x13a: 0x35908, // multiple
|
||||||
|
0x13c: 0x52a06, // prompt
|
||||||
|
0x13f: 0x28e09, // challenge
|
||||||
|
0x141: 0x4370c, // onhashchange
|
||||||
|
0x142: 0x57b07, // content
|
||||||
|
0x143: 0x1c90e, // annotation-xml
|
||||||
|
0x144: 0x36607, // onclose
|
||||||
|
0x145: 0x14d10, // oncanplaythrough
|
||||||
|
0x148: 0x5170b, // onmouseover
|
||||||
|
0x149: 0x64f08, // sortable
|
||||||
|
0x14a: 0xa402, // mo
|
||||||
|
0x14b: 0x2cd02, // h3
|
||||||
|
0x14c: 0x2c406, // script
|
||||||
|
0x14d: 0x41d07, // onended
|
||||||
|
0x14f: 0x64706, // poster
|
||||||
|
0x150: 0x7210a, // workertype
|
||||||
|
0x153: 0x1f505, // shape
|
||||||
|
0x154: 0x4, // abbr
|
||||||
|
0x155: 0x1, // a
|
||||||
|
0x156: 0x2bf02, // dd
|
||||||
|
0x157: 0x71606, // system
|
||||||
|
0x158: 0x4ce0e, // onmessageerror
|
||||||
|
0x159: 0x36b08, // seamless
|
||||||
|
0x15a: 0x2610a, // formaction
|
||||||
|
0x15b: 0x6e106, // option
|
||||||
|
0x15c: 0x31d04, // math
|
||||||
|
0x15d: 0x62609, // onseeking
|
||||||
|
0x15e: 0x39c05, // oncut
|
||||||
|
0x15f: 0x44c03, // del
|
||||||
|
0x160: 0x11005, // title
|
||||||
|
0x161: 0x11505, // audio
|
||||||
|
0x162: 0x63108, // selected
|
||||||
|
0x165: 0x3b40b, // ondragenter
|
||||||
|
0x166: 0x46e06, // spacer
|
||||||
|
0x167: 0x4a410, // onloadedmetadata
|
||||||
|
0x168: 0x44505, // input
|
||||||
|
0x16a: 0x58505, // table
|
||||||
|
0x16b: 0x41508, // onchange
|
||||||
|
0x16e: 0x5f005, // defer
|
||||||
|
0x171: 0x50a0a, // onmouseout
|
||||||
|
0x172: 0x20504, // slot
|
||||||
|
0x175: 0x3704, // nobr
|
||||||
|
0x177: 0x1d707, // command
|
||||||
|
0x17a: 0x7207, // details
|
||||||
|
0x17b: 0x38104, // menu
|
||||||
|
0x17c: 0xb903, // kbd
|
||||||
|
0x17d: 0x57304, // step
|
||||||
|
0x17e: 0x20303, // ins
|
||||||
|
0x17f: 0x13c08, // autoplay
|
||||||
|
0x182: 0x34103, // min
|
||||||
|
0x183: 0x17404, // link
|
||||||
|
0x185: 0x40d10, // ondurationchange
|
||||||
|
0x186: 0x9202, // td
|
||||||
|
0x187: 0x8b05, // frame
|
||||||
|
0x18a: 0x2ab08, // datetime
|
||||||
|
0x18b: 0x44509, // inputmode
|
||||||
|
0x18c: 0x35108, // readonly
|
||||||
|
0x18d: 0x21104, // face
|
||||||
|
0x18f: 0x5e505, // sizes
|
||||||
|
0x191: 0x4b208, // tabindex
|
||||||
|
0x192: 0x6db06, // strong
|
||||||
|
0x193: 0xba03, // bdi
|
||||||
|
0x194: 0x6fe06, // srcset
|
||||||
|
0x196: 0x67202, // ms
|
||||||
|
0x197: 0x5b507, // checked
|
||||||
|
0x198: 0xb105, // align
|
||||||
|
0x199: 0x1e507, // section
|
||||||
|
0x19b: 0x6e05, // embed
|
||||||
|
0x19d: 0x15e07, // bgsound
|
||||||
|
0x1a2: 0x49d04, // list
|
||||||
|
0x1a3: 0x61e08, // onseeked
|
||||||
|
0x1a4: 0x66009, // onstorage
|
||||||
|
0x1a5: 0x2f603, // img
|
||||||
|
0x1a6: 0xf505, // tfoot
|
||||||
|
0x1a9: 0x26913, // onautocompleteerror
|
||||||
|
0x1aa: 0x5fd19, // onsecuritypolicyviolation
|
||||||
|
0x1ad: 0x9303, // dir
|
||||||
|
0x1ae: 0x9307, // dirname
|
||||||
|
0x1b0: 0x5a70a, // onprogress
|
||||||
|
0x1b2: 0x65709, // onstalled
|
||||||
|
0x1b5: 0x66f09, // itemscope
|
||||||
|
0x1b6: 0x49904, // data
|
||||||
|
0x1b7: 0x3d90b, // ondragleave
|
||||||
|
0x1b8: 0x56102, // h2
|
||||||
|
0x1b9: 0x2f706, // mglyph
|
||||||
|
0x1ba: 0x16502, // is
|
||||||
|
0x1bb: 0x6e50e, // onbeforeunload
|
||||||
|
0x1bc: 0x2830d, // typemustmatch
|
||||||
|
0x1bd: 0x3ab06, // ondrag
|
||||||
|
0x1be: 0x5da07, // onreset
|
||||||
|
0x1c0: 0x51106, // output
|
||||||
|
0x1c1: 0x12907, // sandbox
|
||||||
|
0x1c2: 0x1b209, // plaintext
|
||||||
|
0x1c4: 0x34c08, // textarea
|
||||||
|
0x1c7: 0xd607, // keytype
|
||||||
|
0x1c8: 0x34b05, // mtext
|
||||||
|
0x1c9: 0x6b10e, // onvolumechange
|
||||||
|
0x1ca: 0x1ea06, // onblur
|
||||||
|
0x1cb: 0x58a07, // onpause
|
||||||
|
0x1cd: 0x5bc0c, // onratechange
|
||||||
|
0x1ce: 0x10705, // aside
|
||||||
|
0x1cf: 0x6cf07, // optimum
|
||||||
|
0x1d1: 0x45809, // onkeydown
|
||||||
|
0x1d2: 0x1c407, // colspan
|
||||||
|
0x1d3: 0x1004, // main
|
||||||
|
0x1d4: 0x66b03, // sub
|
||||||
|
0x1d5: 0x25b06, // object
|
||||||
|
0x1d6: 0x55c06, // search
|
||||||
|
0x1d7: 0x37206, // sorted
|
||||||
|
0x1d8: 0x17003, // big
|
||||||
|
0x1d9: 0xb01, // u
|
||||||
|
0x1db: 0x26b0c, // autocomplete
|
||||||
|
0x1dc: 0xcc02, // tr
|
||||||
|
0x1dd: 0xf303, // alt
|
||||||
|
0x1df: 0x7804, // samp
|
||||||
|
0x1e0: 0x5c812, // onrejectionhandled
|
||||||
|
0x1e1: 0x4f30c, // onmouseleave
|
||||||
|
0x1e2: 0x28007, // enctype
|
||||||
|
0x1e3: 0xa208, // nomodule
|
||||||
|
0x1e5: 0x3280f, // allowfullscreen
|
||||||
|
0x1e6: 0x5f08, // optgroup
|
||||||
|
0x1e8: 0x27c0b, // formenctype
|
||||||
|
0x1e9: 0x18106, // legend
|
||||||
|
0x1ea: 0x10306, // canvas
|
||||||
|
0x1eb: 0x6607, // pattern
|
||||||
|
0x1ec: 0x2c208, // noscript
|
||||||
|
0x1ed: 0x601, // i
|
||||||
|
0x1ee: 0x5d602, // dl
|
||||||
|
0x1ef: 0xa702, // ul
|
||||||
|
0x1f2: 0x52209, // onmouseup
|
||||||
|
0x1f4: 0x1ba05, // track
|
||||||
|
0x1f7: 0x3a10a, // ondblclick
|
||||||
|
0x1f8: 0x3bf0a, // ondragexit
|
||||||
|
0x1fa: 0x8703, // dfn
|
||||||
|
0x1fc: 0x26506, // action
|
||||||
|
0x1fd: 0x35004, // area
|
||||||
|
0x1fe: 0x31607, // marquee
|
||||||
|
0x1ff: 0x16d03, // var
|
||||||
|
}
|
||||||
|
|
||||||
|
const atomText = "abbradiogrouparamainavalueaccept-charsetbodyaccesskeygenobrb" +
|
||||||
|
"asefontimeupdateviacacheightmlabelooptgroupatternoembedetail" +
|
||||||
|
"sampictureversedfnoframesetdirnameterowspanomoduleacronymali" +
|
||||||
|
"gnmarkbdialogallowpaymentrequestrikeytypeallowusermediagroup" +
|
||||||
|
"ingaltfooterubyasyncanvasidefaultitleaudioncancelautofocusan" +
|
||||||
|
"dboxmplaceholderautoplaysinlinebdoncanplaythrough1bgsoundisa" +
|
||||||
|
"bledivarbigblinkindraggablegendblockquotebuttonabortcitempro" +
|
||||||
|
"penoncecolgrouplaintextrackcolorcolspannotation-xmlcommandco" +
|
||||||
|
"ntrolsectionblurcoordshapecrossoriginslotranslatefacenterfie" +
|
||||||
|
"ldsetfigcaptionafterprintegrityfigurequiredforeignObjectfore" +
|
||||||
|
"ignobjectformactionautocompleteerrorformenctypemustmatchalle" +
|
||||||
|
"ngeformmethodformnovalidatetimeformtargethiddenoscripthigh3h" +
|
||||||
|
"reflanghttp-equivideonclickiframeimageimglyph4isindexismappl" +
|
||||||
|
"etitemtypemarqueematheadersmallowfullscreenmaxlength5minleng" +
|
||||||
|
"th6mtextareadonlymultiplemutedoncloseamlessortedoncontextmen" +
|
||||||
|
"uitemidoncopyoncuechangeoncutondblclickondragendondragentero" +
|
||||||
|
"ndragexitemreferrerpolicyondragleaveondragoverondragstarticl" +
|
||||||
|
"eondropzonemptiedondurationchangeonendedonerroronfocusourceo" +
|
||||||
|
"nhashchangeoninputmodeloninvalidonkeydownloadonkeypresspacer" +
|
||||||
|
"onkeyupreloadonlanguagechangeonloadeddatalistingonloadedmeta" +
|
||||||
|
"databindexonloadendonloadstartonmessageerroronmousedownonmou" +
|
||||||
|
"seenteronmouseleaveonmousemoveonmouseoutputonmouseoveronmous" +
|
||||||
|
"eupromptonmousewheelonofflineononlineonpagehidesclassearch2o" +
|
||||||
|
"npageshowbronpastepublicontenteditableonpausemaponplayingonp" +
|
||||||
|
"opstateonprogresspellcheckedonratechangeonrejectionhandledon" +
|
||||||
|
"resetonresizesrcdocodeferonscrollonsecuritypolicyviolationau" +
|
||||||
|
"xclickonseekedonseekingonselectedonshowidthgrouposteronsorta" +
|
||||||
|
"bleonstalledonstorageonsubmitemscopedonsuspendontoggleonunha" +
|
||||||
|
"ndledrejectionbeforeprintonunloadonvolumechangeonwaitingonwh" +
|
||||||
|
"eeloptimumanifestrongoptionbeforeunloaddressrclangsrcsetstyl" +
|
||||||
|
"esummarysupsvgsystemplateworkertypewrap"
|
||||||
111
vendor/golang.org/x/net/html/const.go
generated
vendored
Normal file
111
vendor/golang.org/x/net/html/const.go
generated
vendored
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package html
|
||||||
|
|
||||||
|
// Section 12.2.4.2 of the HTML5 specification says "The following elements
|
||||||
|
// have varying levels of special parsing rules".
|
||||||
|
// https://html.spec.whatwg.org/multipage/syntax.html#the-stack-of-open-elements
|
||||||
|
var isSpecialElementMap = map[string]bool{
|
||||||
|
"address": true,
|
||||||
|
"applet": true,
|
||||||
|
"area": true,
|
||||||
|
"article": true,
|
||||||
|
"aside": true,
|
||||||
|
"base": true,
|
||||||
|
"basefont": true,
|
||||||
|
"bgsound": true,
|
||||||
|
"blockquote": true,
|
||||||
|
"body": true,
|
||||||
|
"br": true,
|
||||||
|
"button": true,
|
||||||
|
"caption": true,
|
||||||
|
"center": true,
|
||||||
|
"col": true,
|
||||||
|
"colgroup": true,
|
||||||
|
"dd": true,
|
||||||
|
"details": true,
|
||||||
|
"dir": true,
|
||||||
|
"div": true,
|
||||||
|
"dl": true,
|
||||||
|
"dt": true,
|
||||||
|
"embed": true,
|
||||||
|
"fieldset": true,
|
||||||
|
"figcaption": true,
|
||||||
|
"figure": true,
|
||||||
|
"footer": true,
|
||||||
|
"form": true,
|
||||||
|
"frame": true,
|
||||||
|
"frameset": true,
|
||||||
|
"h1": true,
|
||||||
|
"h2": true,
|
||||||
|
"h3": true,
|
||||||
|
"h4": true,
|
||||||
|
"h5": true,
|
||||||
|
"h6": true,
|
||||||
|
"head": true,
|
||||||
|
"header": true,
|
||||||
|
"hgroup": true,
|
||||||
|
"hr": true,
|
||||||
|
"html": true,
|
||||||
|
"iframe": true,
|
||||||
|
"img": true,
|
||||||
|
"input": true,
|
||||||
|
"keygen": true, // "keygen" has been removed from the spec, but are kept here for backwards compatibility.
|
||||||
|
"li": true,
|
||||||
|
"link": true,
|
||||||
|
"listing": true,
|
||||||
|
"main": true,
|
||||||
|
"marquee": true,
|
||||||
|
"menu": true,
|
||||||
|
"meta": true,
|
||||||
|
"nav": true,
|
||||||
|
"noembed": true,
|
||||||
|
"noframes": true,
|
||||||
|
"noscript": true,
|
||||||
|
"object": true,
|
||||||
|
"ol": true,
|
||||||
|
"p": true,
|
||||||
|
"param": true,
|
||||||
|
"plaintext": true,
|
||||||
|
"pre": true,
|
||||||
|
"script": true,
|
||||||
|
"section": true,
|
||||||
|
"select": true,
|
||||||
|
"source": true,
|
||||||
|
"style": true,
|
||||||
|
"summary": true,
|
||||||
|
"table": true,
|
||||||
|
"tbody": true,
|
||||||
|
"td": true,
|
||||||
|
"template": true,
|
||||||
|
"textarea": true,
|
||||||
|
"tfoot": true,
|
||||||
|
"th": true,
|
||||||
|
"thead": true,
|
||||||
|
"title": true,
|
||||||
|
"tr": true,
|
||||||
|
"track": true,
|
||||||
|
"ul": true,
|
||||||
|
"wbr": true,
|
||||||
|
"xmp": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
func isSpecialElement(element *Node) bool {
|
||||||
|
switch element.Namespace {
|
||||||
|
case "", "html":
|
||||||
|
return isSpecialElementMap[element.Data]
|
||||||
|
case "math":
|
||||||
|
switch element.Data {
|
||||||
|
case "mi", "mo", "mn", "ms", "mtext", "annotation-xml":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case "svg":
|
||||||
|
switch element.Data {
|
||||||
|
case "foreignObject", "desc", "title":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
122
vendor/golang.org/x/net/html/doc.go
generated
vendored
Normal file
122
vendor/golang.org/x/net/html/doc.go
generated
vendored
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package html implements an HTML5-compliant tokenizer and parser.
|
||||||
|
|
||||||
|
Tokenization is done by creating a Tokenizer for an io.Reader r. It is the
|
||||||
|
caller's responsibility to ensure that r provides UTF-8 encoded HTML.
|
||||||
|
|
||||||
|
z := html.NewTokenizer(r)
|
||||||
|
|
||||||
|
Given a Tokenizer z, the HTML is tokenized by repeatedly calling z.Next(),
|
||||||
|
which parses the next token and returns its type, or an error:
|
||||||
|
|
||||||
|
for {
|
||||||
|
tt := z.Next()
|
||||||
|
if tt == html.ErrorToken {
|
||||||
|
// ...
|
||||||
|
return ...
|
||||||
|
}
|
||||||
|
// Process the current token.
|
||||||
|
}
|
||||||
|
|
||||||
|
There are two APIs for retrieving the current token. The high-level API is to
|
||||||
|
call Token; the low-level API is to call Text or TagName / TagAttr. Both APIs
|
||||||
|
allow optionally calling Raw after Next but before Token, Text, TagName, or
|
||||||
|
TagAttr. In EBNF notation, the valid call sequence per token is:
|
||||||
|
|
||||||
|
Next {Raw} [ Token | Text | TagName {TagAttr} ]
|
||||||
|
|
||||||
|
Token returns an independent data structure that completely describes a token.
|
||||||
|
Entities (such as "<") are unescaped, tag names and attribute keys are
|
||||||
|
lower-cased, and attributes are collected into a []Attribute. For example:
|
||||||
|
|
||||||
|
for {
|
||||||
|
if z.Next() == html.ErrorToken {
|
||||||
|
// Returning io.EOF indicates success.
|
||||||
|
return z.Err()
|
||||||
|
}
|
||||||
|
emitToken(z.Token())
|
||||||
|
}
|
||||||
|
|
||||||
|
The low-level API performs fewer allocations and copies, but the contents of
|
||||||
|
the []byte values returned by Text, TagName and TagAttr may change on the next
|
||||||
|
call to Next. For example, to extract an HTML page's anchor text:
|
||||||
|
|
||||||
|
depth := 0
|
||||||
|
for {
|
||||||
|
tt := z.Next()
|
||||||
|
switch tt {
|
||||||
|
case html.ErrorToken:
|
||||||
|
return z.Err()
|
||||||
|
case html.TextToken:
|
||||||
|
if depth > 0 {
|
||||||
|
// emitBytes should copy the []byte it receives,
|
||||||
|
// if it doesn't process it immediately.
|
||||||
|
emitBytes(z.Text())
|
||||||
|
}
|
||||||
|
case html.StartTagToken, html.EndTagToken:
|
||||||
|
tn, _ := z.TagName()
|
||||||
|
if len(tn) == 1 && tn[0] == 'a' {
|
||||||
|
if tt == html.StartTagToken {
|
||||||
|
depth++
|
||||||
|
} else {
|
||||||
|
depth--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Parsing is done by calling Parse with an io.Reader, which returns the root of
|
||||||
|
the parse tree (the document element) as a *Node. It is the caller's
|
||||||
|
responsibility to ensure that the Reader provides UTF-8 encoded HTML. For
|
||||||
|
example, to process each anchor node in depth-first order:
|
||||||
|
|
||||||
|
doc, err := html.Parse(r)
|
||||||
|
if err != nil {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
for n := range doc.Descendants() {
|
||||||
|
if n.Type == html.ElementNode && n.Data == "a" {
|
||||||
|
// Do something with n...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
The relevant specifications include:
|
||||||
|
https://html.spec.whatwg.org/multipage/syntax.html and
|
||||||
|
https://html.spec.whatwg.org/multipage/syntax.html#tokenization
|
||||||
|
|
||||||
|
# Security Considerations
|
||||||
|
|
||||||
|
Care should be taken when parsing and interpreting HTML, whether full documents
|
||||||
|
or fragments, within the framework of the HTML specification, especially with
|
||||||
|
regard to untrusted inputs.
|
||||||
|
|
||||||
|
This package provides both a tokenizer and a parser, which implement the
|
||||||
|
tokenization, and tokenization and tree construction stages of the WHATWG HTML
|
||||||
|
parsing specification respectively. While the tokenizer parses and normalizes
|
||||||
|
individual HTML tokens, only the parser constructs the DOM tree from the
|
||||||
|
tokenized HTML, as described in the tree construction stage of the
|
||||||
|
specification, dynamically modifying or extending the document's DOM tree.
|
||||||
|
|
||||||
|
If your use case requires semantically well-formed HTML documents, as defined by
|
||||||
|
the WHATWG specification, the parser should be used rather than the tokenizer.
|
||||||
|
|
||||||
|
In security contexts, if trust decisions are being made using the tokenized or
|
||||||
|
parsed content, the input must be re-serialized (for instance by using Render or
|
||||||
|
Token.String) in order for those trust decisions to hold, as the process of
|
||||||
|
tokenization or parsing may alter the content.
|
||||||
|
*/
|
||||||
|
package html // import "golang.org/x/net/html"
|
||||||
|
|
||||||
|
// The tokenization algorithm implemented by this package is not a line-by-line
|
||||||
|
// transliteration of the relatively verbose state-machine in the WHATWG
|
||||||
|
// specification. A more direct approach is used instead, where the program
|
||||||
|
// counter implies the state, such as whether it is tokenizing a tag or a text
|
||||||
|
// node. Specification compliance is verified by checking expected and actual
|
||||||
|
// outputs over a test suite rather than aiming for algorithmic fidelity.
|
||||||
|
|
||||||
|
// TODO(nigeltao): Does a DOM API belong in this package or a separate one?
|
||||||
|
// TODO(nigeltao): How does parsing interact with a JavaScript engine?
|
||||||
156
vendor/golang.org/x/net/html/doctype.go
generated
vendored
Normal file
156
vendor/golang.org/x/net/html/doctype.go
generated
vendored
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package html
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// parseDoctype parses the data from a DoctypeToken into a name,
|
||||||
|
// public identifier, and system identifier. It returns a Node whose Type
|
||||||
|
// is DoctypeNode, whose Data is the name, and which has attributes
|
||||||
|
// named "system" and "public" for the two identifiers if they were present.
|
||||||
|
// quirks is whether the document should be parsed in "quirks mode".
|
||||||
|
func parseDoctype(s string) (n *Node, quirks bool) {
|
||||||
|
n = &Node{Type: DoctypeNode}
|
||||||
|
|
||||||
|
// Find the name.
|
||||||
|
space := strings.IndexAny(s, whitespace)
|
||||||
|
if space == -1 {
|
||||||
|
space = len(s)
|
||||||
|
}
|
||||||
|
n.Data = s[:space]
|
||||||
|
// The comparison to "html" is case-sensitive.
|
||||||
|
if n.Data != "html" {
|
||||||
|
quirks = true
|
||||||
|
}
|
||||||
|
n.Data = strings.ToLower(n.Data)
|
||||||
|
s = strings.TrimLeft(s[space:], whitespace)
|
||||||
|
|
||||||
|
if len(s) < 6 {
|
||||||
|
// It can't start with "PUBLIC" or "SYSTEM".
|
||||||
|
// Ignore the rest of the string.
|
||||||
|
return n, quirks || s != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
key := strings.ToLower(s[:6])
|
||||||
|
s = s[6:]
|
||||||
|
for key == "public" || key == "system" {
|
||||||
|
s = strings.TrimLeft(s, whitespace)
|
||||||
|
if s == "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
quote := s[0]
|
||||||
|
if quote != '"' && quote != '\'' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
s = s[1:]
|
||||||
|
q := strings.IndexRune(s, rune(quote))
|
||||||
|
var id string
|
||||||
|
if q == -1 {
|
||||||
|
id = s
|
||||||
|
s = ""
|
||||||
|
} else {
|
||||||
|
id = s[:q]
|
||||||
|
s = s[q+1:]
|
||||||
|
}
|
||||||
|
n.Attr = append(n.Attr, Attribute{Key: key, Val: id})
|
||||||
|
if key == "public" {
|
||||||
|
key = "system"
|
||||||
|
} else {
|
||||||
|
key = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if key != "" || s != "" {
|
||||||
|
quirks = true
|
||||||
|
} else if len(n.Attr) > 0 {
|
||||||
|
if n.Attr[0].Key == "public" {
|
||||||
|
public := strings.ToLower(n.Attr[0].Val)
|
||||||
|
switch public {
|
||||||
|
case "-//w3o//dtd w3 html strict 3.0//en//", "-/w3d/dtd html 4.0 transitional/en", "html":
|
||||||
|
quirks = true
|
||||||
|
default:
|
||||||
|
for _, q := range quirkyIDs {
|
||||||
|
if strings.HasPrefix(public, q) {
|
||||||
|
quirks = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The following two public IDs only cause quirks mode if there is no system ID.
|
||||||
|
if len(n.Attr) == 1 && (strings.HasPrefix(public, "-//w3c//dtd html 4.01 frameset//") ||
|
||||||
|
strings.HasPrefix(public, "-//w3c//dtd html 4.01 transitional//")) {
|
||||||
|
quirks = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if lastAttr := n.Attr[len(n.Attr)-1]; lastAttr.Key == "system" &&
|
||||||
|
strings.EqualFold(lastAttr.Val, "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd") {
|
||||||
|
quirks = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, quirks
|
||||||
|
}
|
||||||
|
|
||||||
|
// quirkyIDs is a list of public doctype identifiers that cause a document
|
||||||
|
// to be interpreted in quirks mode. The identifiers should be in lower case.
|
||||||
|
var quirkyIDs = []string{
|
||||||
|
"+//silmaril//dtd html pro v0r11 19970101//",
|
||||||
|
"-//advasoft ltd//dtd html 3.0 aswedit + extensions//",
|
||||||
|
"-//as//dtd html 3.0 aswedit + extensions//",
|
||||||
|
"-//ietf//dtd html 2.0 level 1//",
|
||||||
|
"-//ietf//dtd html 2.0 level 2//",
|
||||||
|
"-//ietf//dtd html 2.0 strict level 1//",
|
||||||
|
"-//ietf//dtd html 2.0 strict level 2//",
|
||||||
|
"-//ietf//dtd html 2.0 strict//",
|
||||||
|
"-//ietf//dtd html 2.0//",
|
||||||
|
"-//ietf//dtd html 2.1e//",
|
||||||
|
"-//ietf//dtd html 3.0//",
|
||||||
|
"-//ietf//dtd html 3.2 final//",
|
||||||
|
"-//ietf//dtd html 3.2//",
|
||||||
|
"-//ietf//dtd html 3//",
|
||||||
|
"-//ietf//dtd html level 0//",
|
||||||
|
"-//ietf//dtd html level 1//",
|
||||||
|
"-//ietf//dtd html level 2//",
|
||||||
|
"-//ietf//dtd html level 3//",
|
||||||
|
"-//ietf//dtd html strict level 0//",
|
||||||
|
"-//ietf//dtd html strict level 1//",
|
||||||
|
"-//ietf//dtd html strict level 2//",
|
||||||
|
"-//ietf//dtd html strict level 3//",
|
||||||
|
"-//ietf//dtd html strict//",
|
||||||
|
"-//ietf//dtd html//",
|
||||||
|
"-//metrius//dtd metrius presentational//",
|
||||||
|
"-//microsoft//dtd internet explorer 2.0 html strict//",
|
||||||
|
"-//microsoft//dtd internet explorer 2.0 html//",
|
||||||
|
"-//microsoft//dtd internet explorer 2.0 tables//",
|
||||||
|
"-//microsoft//dtd internet explorer 3.0 html strict//",
|
||||||
|
"-//microsoft//dtd internet explorer 3.0 html//",
|
||||||
|
"-//microsoft//dtd internet explorer 3.0 tables//",
|
||||||
|
"-//netscape comm. corp.//dtd html//",
|
||||||
|
"-//netscape comm. corp.//dtd strict html//",
|
||||||
|
"-//o'reilly and associates//dtd html 2.0//",
|
||||||
|
"-//o'reilly and associates//dtd html extended 1.0//",
|
||||||
|
"-//o'reilly and associates//dtd html extended relaxed 1.0//",
|
||||||
|
"-//softquad software//dtd hotmetal pro 6.0::19990601::extensions to html 4.0//",
|
||||||
|
"-//softquad//dtd hotmetal pro 4.0::19971010::extensions to html 4.0//",
|
||||||
|
"-//spyglass//dtd html 2.0 extended//",
|
||||||
|
"-//sq//dtd html 2.0 hotmetal + extensions//",
|
||||||
|
"-//sun microsystems corp.//dtd hotjava html//",
|
||||||
|
"-//sun microsystems corp.//dtd hotjava strict html//",
|
||||||
|
"-//w3c//dtd html 3 1995-03-24//",
|
||||||
|
"-//w3c//dtd html 3.2 draft//",
|
||||||
|
"-//w3c//dtd html 3.2 final//",
|
||||||
|
"-//w3c//dtd html 3.2//",
|
||||||
|
"-//w3c//dtd html 3.2s draft//",
|
||||||
|
"-//w3c//dtd html 4.0 frameset//",
|
||||||
|
"-//w3c//dtd html 4.0 transitional//",
|
||||||
|
"-//w3c//dtd html experimental 19960712//",
|
||||||
|
"-//w3c//dtd html experimental 970421//",
|
||||||
|
"-//w3c//dtd w3 html//",
|
||||||
|
"-//w3o//dtd w3 html 3.0//",
|
||||||
|
"-//webtechs//dtd mozilla html 2.0//",
|
||||||
|
"-//webtechs//dtd mozilla html//",
|
||||||
|
}
|
||||||
2253
vendor/golang.org/x/net/html/entity.go
generated
vendored
Normal file
2253
vendor/golang.org/x/net/html/entity.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
339
vendor/golang.org/x/net/html/escape.go
generated
vendored
Normal file
339
vendor/golang.org/x/net/html/escape.go
generated
vendored
Normal file
@@ -0,0 +1,339 @@
|
|||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package html
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// These replacements permit compatibility with old numeric entities that
|
||||||
|
// assumed Windows-1252 encoding.
|
||||||
|
// https://html.spec.whatwg.org/multipage/syntax.html#consume-a-character-reference
|
||||||
|
var replacementTable = [...]rune{
|
||||||
|
'\u20AC', // First entry is what 0x80 should be replaced with.
|
||||||
|
'\u0081',
|
||||||
|
'\u201A',
|
||||||
|
'\u0192',
|
||||||
|
'\u201E',
|
||||||
|
'\u2026',
|
||||||
|
'\u2020',
|
||||||
|
'\u2021',
|
||||||
|
'\u02C6',
|
||||||
|
'\u2030',
|
||||||
|
'\u0160',
|
||||||
|
'\u2039',
|
||||||
|
'\u0152',
|
||||||
|
'\u008D',
|
||||||
|
'\u017D',
|
||||||
|
'\u008F',
|
||||||
|
'\u0090',
|
||||||
|
'\u2018',
|
||||||
|
'\u2019',
|
||||||
|
'\u201C',
|
||||||
|
'\u201D',
|
||||||
|
'\u2022',
|
||||||
|
'\u2013',
|
||||||
|
'\u2014',
|
||||||
|
'\u02DC',
|
||||||
|
'\u2122',
|
||||||
|
'\u0161',
|
||||||
|
'\u203A',
|
||||||
|
'\u0153',
|
||||||
|
'\u009D',
|
||||||
|
'\u017E',
|
||||||
|
'\u0178', // Last entry is 0x9F.
|
||||||
|
// 0x00->'\uFFFD' is handled programmatically.
|
||||||
|
// 0x0D->'\u000D' is a no-op.
|
||||||
|
}
|
||||||
|
|
||||||
|
// unescapeEntity reads an entity like "<" from b[src:] and writes the
|
||||||
|
// corresponding "<" to b[dst:], returning the incremented dst and src cursors.
|
||||||
|
// Precondition: b[src] == '&' && dst <= src.
|
||||||
|
// attribute should be true if parsing an attribute value.
|
||||||
|
func unescapeEntity(b []byte, dst, src int, attribute bool) (dst1, src1 int) {
|
||||||
|
// https://html.spec.whatwg.org/multipage/syntax.html#consume-a-character-reference
|
||||||
|
|
||||||
|
// i starts at 1 because we already know that s[0] == '&'.
|
||||||
|
i, s := 1, b[src:]
|
||||||
|
|
||||||
|
if len(s) <= 1 {
|
||||||
|
b[dst] = b[src]
|
||||||
|
return dst + 1, src + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if s[i] == '#' {
|
||||||
|
if len(s) <= 3 { // We need to have at least "&#.".
|
||||||
|
b[dst] = b[src]
|
||||||
|
return dst + 1, src + 1
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
c := s[i]
|
||||||
|
hex := false
|
||||||
|
if c == 'x' || c == 'X' {
|
||||||
|
hex = true
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
x := '\x00'
|
||||||
|
for i < len(s) {
|
||||||
|
c = s[i]
|
||||||
|
i++
|
||||||
|
if hex {
|
||||||
|
if '0' <= c && c <= '9' {
|
||||||
|
x = 16*x + rune(c) - '0'
|
||||||
|
continue
|
||||||
|
} else if 'a' <= c && c <= 'f' {
|
||||||
|
x = 16*x + rune(c) - 'a' + 10
|
||||||
|
continue
|
||||||
|
} else if 'A' <= c && c <= 'F' {
|
||||||
|
x = 16*x + rune(c) - 'A' + 10
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else if '0' <= c && c <= '9' {
|
||||||
|
x = 10*x + rune(c) - '0'
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if c != ';' {
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if i <= 3 { // No characters matched.
|
||||||
|
b[dst] = b[src]
|
||||||
|
return dst + 1, src + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if 0x80 <= x && x <= 0x9F {
|
||||||
|
// Replace characters from Windows-1252 with UTF-8 equivalents.
|
||||||
|
x = replacementTable[x-0x80]
|
||||||
|
} else if x == 0 || (0xD800 <= x && x <= 0xDFFF) || x > 0x10FFFF {
|
||||||
|
// Replace invalid characters with the replacement character.
|
||||||
|
x = '\uFFFD'
|
||||||
|
}
|
||||||
|
|
||||||
|
return dst + utf8.EncodeRune(b[dst:], x), src + i
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume the maximum number of characters possible, with the
|
||||||
|
// consumed characters matching one of the named references.
|
||||||
|
|
||||||
|
for i < len(s) {
|
||||||
|
c := s[i]
|
||||||
|
i++
|
||||||
|
// Lower-cased characters are more common in entities, so we check for them first.
|
||||||
|
if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || '0' <= c && c <= '9' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if c != ';' {
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
entityName := string(s[1:i])
|
||||||
|
if entityName == "" {
|
||||||
|
// No-op.
|
||||||
|
} else if attribute && entityName[len(entityName)-1] != ';' && len(s) > i && s[i] == '=' {
|
||||||
|
// No-op.
|
||||||
|
} else if x := entity[entityName]; x != 0 {
|
||||||
|
return dst + utf8.EncodeRune(b[dst:], x), src + i
|
||||||
|
} else if x := entity2[entityName]; x[0] != 0 {
|
||||||
|
dst1 := dst + utf8.EncodeRune(b[dst:], x[0])
|
||||||
|
return dst1 + utf8.EncodeRune(b[dst1:], x[1]), src + i
|
||||||
|
} else if !attribute {
|
||||||
|
maxLen := len(entityName) - 1
|
||||||
|
if maxLen > longestEntityWithoutSemicolon {
|
||||||
|
maxLen = longestEntityWithoutSemicolon
|
||||||
|
}
|
||||||
|
for j := maxLen; j > 1; j-- {
|
||||||
|
if x := entity[entityName[:j]]; x != 0 {
|
||||||
|
return dst + utf8.EncodeRune(b[dst:], x), src + j + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dst1, src1 = dst+i, src+i
|
||||||
|
copy(b[dst:dst1], b[src:src1])
|
||||||
|
return dst1, src1
|
||||||
|
}
|
||||||
|
|
||||||
|
// unescape unescapes b's entities in-place, so that "a<b" becomes "a<b".
|
||||||
|
// attribute should be true if parsing an attribute value.
|
||||||
|
func unescape(b []byte, attribute bool) []byte {
|
||||||
|
for i, c := range b {
|
||||||
|
if c == '&' {
|
||||||
|
dst, src := unescapeEntity(b, i, i, attribute)
|
||||||
|
for src < len(b) {
|
||||||
|
c := b[src]
|
||||||
|
if c == '&' {
|
||||||
|
dst, src = unescapeEntity(b, dst, src, attribute)
|
||||||
|
} else {
|
||||||
|
b[dst] = c
|
||||||
|
dst, src = dst+1, src+1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b[0:dst]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// lower lower-cases the A-Z bytes in b in-place, so that "aBc" becomes "abc".
|
||||||
|
func lower(b []byte) []byte {
|
||||||
|
for i, c := range b {
|
||||||
|
if 'A' <= c && c <= 'Z' {
|
||||||
|
b[i] = c + 'a' - 'A'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// escapeComment is like func escape but escapes its input bytes less often.
|
||||||
|
// Per https://github.com/golang/go/issues/58246 some HTML comments are (1)
|
||||||
|
// meaningful and (2) contain angle brackets that we'd like to avoid escaping
|
||||||
|
// unless we have to.
|
||||||
|
//
|
||||||
|
// "We have to" includes the '&' byte, since that introduces other escapes.
|
||||||
|
//
|
||||||
|
// It also includes those bytes (not including EOF) that would otherwise end
|
||||||
|
// the comment. Per the summary table at the bottom of comment_test.go, this is
|
||||||
|
// the '>' byte that, per above, we'd like to avoid escaping unless we have to.
|
||||||
|
//
|
||||||
|
// Studying the summary table (and T actions in its '>' column) closely, we
|
||||||
|
// only need to escape in states 43, 44, 49, 51 and 52. State 43 is at the
|
||||||
|
// start of the comment data. State 52 is after a '!'. The other three states
|
||||||
|
// are after a '-'.
|
||||||
|
//
|
||||||
|
// Our algorithm is thus to escape every '&' and to escape '>' if and only if:
|
||||||
|
// - The '>' is after a '!' or '-' (in the unescaped data) or
|
||||||
|
// - The '>' is at the start of the comment data (after the opening "<!--").
|
||||||
|
func escapeComment(w writer, s string) error {
|
||||||
|
// When modifying this function, consider manually increasing the
|
||||||
|
// maxSuffixLen constant in func TestComments, from 6 to e.g. 9 or more.
|
||||||
|
// That increase should only be temporary, not committed, as it
|
||||||
|
// exponentially affects the test running time.
|
||||||
|
|
||||||
|
if len(s) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop:
|
||||||
|
// - Grow j such that s[i:j] does not need escaping.
|
||||||
|
// - If s[j] does need escaping, output s[i:j] and an escaped s[j],
|
||||||
|
// resetting i and j to point past that s[j] byte.
|
||||||
|
i := 0
|
||||||
|
for j := 0; j < len(s); j++ {
|
||||||
|
escaped := ""
|
||||||
|
switch s[j] {
|
||||||
|
case '&':
|
||||||
|
escaped = "&"
|
||||||
|
|
||||||
|
case '>':
|
||||||
|
if j > 0 {
|
||||||
|
if prev := s[j-1]; (prev != '!') && (prev != '-') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
escaped = ">"
|
||||||
|
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if i < j {
|
||||||
|
if _, err := w.WriteString(s[i:j]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, err := w.WriteString(escaped); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i = j + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if i < len(s) {
|
||||||
|
if _, err := w.WriteString(s[i:]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// escapeCommentString is to EscapeString as escapeComment is to escape.
|
||||||
|
func escapeCommentString(s string) string {
|
||||||
|
if strings.IndexAny(s, "&>") == -1 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
escapeComment(&buf, s)
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
const escapedChars = "&'<>\"\r"
|
||||||
|
|
||||||
|
func escape(w writer, s string) error {
|
||||||
|
i := strings.IndexAny(s, escapedChars)
|
||||||
|
for i != -1 {
|
||||||
|
if _, err := w.WriteString(s[:i]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var esc string
|
||||||
|
switch s[i] {
|
||||||
|
case '&':
|
||||||
|
esc = "&"
|
||||||
|
case '\'':
|
||||||
|
// "'" is shorter than "'" and apos was not in HTML until HTML5.
|
||||||
|
esc = "'"
|
||||||
|
case '<':
|
||||||
|
esc = "<"
|
||||||
|
case '>':
|
||||||
|
esc = ">"
|
||||||
|
case '"':
|
||||||
|
// """ is shorter than """.
|
||||||
|
esc = """
|
||||||
|
case '\r':
|
||||||
|
esc = " "
|
||||||
|
default:
|
||||||
|
panic("html: unrecognized escape character")
|
||||||
|
}
|
||||||
|
s = s[i+1:]
|
||||||
|
if _, err := w.WriteString(esc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i = strings.IndexAny(s, escapedChars)
|
||||||
|
}
|
||||||
|
_, err := w.WriteString(s)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// EscapeString escapes special characters like "<" to become "<". It
|
||||||
|
// escapes only five such characters: <, >, &, ' and ".
|
||||||
|
// UnescapeString(EscapeString(s)) == s always holds, but the converse isn't
|
||||||
|
// always true.
|
||||||
|
func EscapeString(s string) string {
|
||||||
|
if strings.IndexAny(s, escapedChars) == -1 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
escape(&buf, s)
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnescapeString unescapes entities like "<" to become "<". It unescapes a
|
||||||
|
// larger range of entities than EscapeString escapes. For example, "á"
|
||||||
|
// unescapes to "á", as does "á" and "&xE1;".
|
||||||
|
// UnescapeString(EscapeString(s)) == s always holds, but the converse isn't
|
||||||
|
// always true.
|
||||||
|
func UnescapeString(s string) string {
|
||||||
|
for _, c := range s {
|
||||||
|
if c == '&' {
|
||||||
|
return string(unescape([]byte(s), false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
221
vendor/golang.org/x/net/html/foreign.go
generated
vendored
Normal file
221
vendor/golang.org/x/net/html/foreign.go
generated
vendored
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package html
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func adjustAttributeNames(aa []Attribute, nameMap map[string]string) {
|
||||||
|
for i := range aa {
|
||||||
|
if newName, ok := nameMap[aa[i].Key]; ok {
|
||||||
|
aa[i].Key = newName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func adjustForeignAttributes(aa []Attribute) {
|
||||||
|
for i, a := range aa {
|
||||||
|
if a.Key == "" || a.Key[0] != 'x' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch a.Key {
|
||||||
|
case "xlink:actuate", "xlink:arcrole", "xlink:href", "xlink:role", "xlink:show",
|
||||||
|
"xlink:title", "xlink:type", "xml:base", "xml:lang", "xml:space", "xmlns:xlink":
|
||||||
|
j := strings.Index(a.Key, ":")
|
||||||
|
aa[i].Namespace = a.Key[:j]
|
||||||
|
aa[i].Key = a.Key[j+1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func htmlIntegrationPoint(n *Node) bool {
|
||||||
|
if n.Type != ElementNode {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
switch n.Namespace {
|
||||||
|
case "math":
|
||||||
|
if n.Data == "annotation-xml" {
|
||||||
|
for _, a := range n.Attr {
|
||||||
|
if a.Key == "encoding" {
|
||||||
|
if strings.EqualFold(a.Val, "text/html") || strings.EqualFold(a.Val, "application/xhtml+xml") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "svg":
|
||||||
|
switch n.Data {
|
||||||
|
case "desc", "foreignObject", "title":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func mathMLTextIntegrationPoint(n *Node) bool {
|
||||||
|
if n.Namespace != "math" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
switch n.Data {
|
||||||
|
case "mi", "mo", "mn", "ms", "mtext":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Section 12.2.6.5.
|
||||||
|
var breakout = map[string]bool{
|
||||||
|
"b": true,
|
||||||
|
"big": true,
|
||||||
|
"blockquote": true,
|
||||||
|
"body": true,
|
||||||
|
"br": true,
|
||||||
|
"center": true,
|
||||||
|
"code": true,
|
||||||
|
"dd": true,
|
||||||
|
"div": true,
|
||||||
|
"dl": true,
|
||||||
|
"dt": true,
|
||||||
|
"em": true,
|
||||||
|
"embed": true,
|
||||||
|
"h1": true,
|
||||||
|
"h2": true,
|
||||||
|
"h3": true,
|
||||||
|
"h4": true,
|
||||||
|
"h5": true,
|
||||||
|
"h6": true,
|
||||||
|
"head": true,
|
||||||
|
"hr": true,
|
||||||
|
"i": true,
|
||||||
|
"img": true,
|
||||||
|
"li": true,
|
||||||
|
"listing": true,
|
||||||
|
"menu": true,
|
||||||
|
"meta": true,
|
||||||
|
"nobr": true,
|
||||||
|
"ol": true,
|
||||||
|
"p": true,
|
||||||
|
"pre": true,
|
||||||
|
"ruby": true,
|
||||||
|
"s": true,
|
||||||
|
"small": true,
|
||||||
|
"span": true,
|
||||||
|
"strong": true,
|
||||||
|
"strike": true,
|
||||||
|
"sub": true,
|
||||||
|
"sup": true,
|
||||||
|
"table": true,
|
||||||
|
"tt": true,
|
||||||
|
"u": true,
|
||||||
|
"ul": true,
|
||||||
|
"var": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Section 12.2.6.5.
|
||||||
|
var svgTagNameAdjustments = map[string]string{
|
||||||
|
"altglyph": "altGlyph",
|
||||||
|
"altglyphdef": "altGlyphDef",
|
||||||
|
"altglyphitem": "altGlyphItem",
|
||||||
|
"animatecolor": "animateColor",
|
||||||
|
"animatemotion": "animateMotion",
|
||||||
|
"animatetransform": "animateTransform",
|
||||||
|
"clippath": "clipPath",
|
||||||
|
"feblend": "feBlend",
|
||||||
|
"fecolormatrix": "feColorMatrix",
|
||||||
|
"fecomponenttransfer": "feComponentTransfer",
|
||||||
|
"fecomposite": "feComposite",
|
||||||
|
"feconvolvematrix": "feConvolveMatrix",
|
||||||
|
"fediffuselighting": "feDiffuseLighting",
|
||||||
|
"fedisplacementmap": "feDisplacementMap",
|
||||||
|
"fedistantlight": "feDistantLight",
|
||||||
|
"feflood": "feFlood",
|
||||||
|
"fefunca": "feFuncA",
|
||||||
|
"fefuncb": "feFuncB",
|
||||||
|
"fefuncg": "feFuncG",
|
||||||
|
"fefuncr": "feFuncR",
|
||||||
|
"fegaussianblur": "feGaussianBlur",
|
||||||
|
"feimage": "feImage",
|
||||||
|
"femerge": "feMerge",
|
||||||
|
"femergenode": "feMergeNode",
|
||||||
|
"femorphology": "feMorphology",
|
||||||
|
"feoffset": "feOffset",
|
||||||
|
"fepointlight": "fePointLight",
|
||||||
|
"fespecularlighting": "feSpecularLighting",
|
||||||
|
"fespotlight": "feSpotLight",
|
||||||
|
"fetile": "feTile",
|
||||||
|
"feturbulence": "feTurbulence",
|
||||||
|
"foreignobject": "foreignObject",
|
||||||
|
"glyphref": "glyphRef",
|
||||||
|
"lineargradient": "linearGradient",
|
||||||
|
"radialgradient": "radialGradient",
|
||||||
|
"textpath": "textPath",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Section 12.2.6.1
|
||||||
|
var mathMLAttributeAdjustments = map[string]string{
|
||||||
|
"definitionurl": "definitionURL",
|
||||||
|
}
|
||||||
|
|
||||||
|
var svgAttributeAdjustments = map[string]string{
|
||||||
|
"attributename": "attributeName",
|
||||||
|
"attributetype": "attributeType",
|
||||||
|
"basefrequency": "baseFrequency",
|
||||||
|
"baseprofile": "baseProfile",
|
||||||
|
"calcmode": "calcMode",
|
||||||
|
"clippathunits": "clipPathUnits",
|
||||||
|
"diffuseconstant": "diffuseConstant",
|
||||||
|
"edgemode": "edgeMode",
|
||||||
|
"filterunits": "filterUnits",
|
||||||
|
"glyphref": "glyphRef",
|
||||||
|
"gradienttransform": "gradientTransform",
|
||||||
|
"gradientunits": "gradientUnits",
|
||||||
|
"kernelmatrix": "kernelMatrix",
|
||||||
|
"kernelunitlength": "kernelUnitLength",
|
||||||
|
"keypoints": "keyPoints",
|
||||||
|
"keysplines": "keySplines",
|
||||||
|
"keytimes": "keyTimes",
|
||||||
|
"lengthadjust": "lengthAdjust",
|
||||||
|
"limitingconeangle": "limitingConeAngle",
|
||||||
|
"markerheight": "markerHeight",
|
||||||
|
"markerunits": "markerUnits",
|
||||||
|
"markerwidth": "markerWidth",
|
||||||
|
"maskcontentunits": "maskContentUnits",
|
||||||
|
"maskunits": "maskUnits",
|
||||||
|
"numoctaves": "numOctaves",
|
||||||
|
"pathlength": "pathLength",
|
||||||
|
"patterncontentunits": "patternContentUnits",
|
||||||
|
"patterntransform": "patternTransform",
|
||||||
|
"patternunits": "patternUnits",
|
||||||
|
"pointsatx": "pointsAtX",
|
||||||
|
"pointsaty": "pointsAtY",
|
||||||
|
"pointsatz": "pointsAtZ",
|
||||||
|
"preservealpha": "preserveAlpha",
|
||||||
|
"preserveaspectratio": "preserveAspectRatio",
|
||||||
|
"primitiveunits": "primitiveUnits",
|
||||||
|
"refx": "refX",
|
||||||
|
"refy": "refY",
|
||||||
|
"repeatcount": "repeatCount",
|
||||||
|
"repeatdur": "repeatDur",
|
||||||
|
"requiredextensions": "requiredExtensions",
|
||||||
|
"requiredfeatures": "requiredFeatures",
|
||||||
|
"specularconstant": "specularConstant",
|
||||||
|
"specularexponent": "specularExponent",
|
||||||
|
"spreadmethod": "spreadMethod",
|
||||||
|
"startoffset": "startOffset",
|
||||||
|
"stddeviation": "stdDeviation",
|
||||||
|
"stitchtiles": "stitchTiles",
|
||||||
|
"surfacescale": "surfaceScale",
|
||||||
|
"systemlanguage": "systemLanguage",
|
||||||
|
"tablevalues": "tableValues",
|
||||||
|
"targetx": "targetX",
|
||||||
|
"targety": "targetY",
|
||||||
|
"textlength": "textLength",
|
||||||
|
"viewbox": "viewBox",
|
||||||
|
"viewtarget": "viewTarget",
|
||||||
|
"xchannelselector": "xChannelSelector",
|
||||||
|
"ychannelselector": "yChannelSelector",
|
||||||
|
"zoomandpan": "zoomAndPan",
|
||||||
|
}
|
||||||
54
vendor/golang.org/x/net/html/iter.go
generated
vendored
Normal file
54
vendor/golang.org/x/net/html/iter.go
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
// Copyright 2024 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package html
|
||||||
|
|
||||||
|
import "iter"
|
||||||
|
|
||||||
|
// Ancestors returns an iterator over the ancestors of n, starting with n.Parent.
|
||||||
|
//
|
||||||
|
// Mutating a Node or its parents while iterating may have unexpected results.
|
||||||
|
func (n *Node) Ancestors() iter.Seq[*Node] {
|
||||||
|
_ = n.Parent // eager nil check
|
||||||
|
|
||||||
|
return func(yield func(*Node) bool) {
|
||||||
|
for p := n.Parent; p != nil && yield(p); p = p.Parent {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChildNodes returns an iterator over the immediate children of n,
|
||||||
|
// starting with n.FirstChild.
|
||||||
|
//
|
||||||
|
// Mutating a Node or its children while iterating may have unexpected results.
|
||||||
|
func (n *Node) ChildNodes() iter.Seq[*Node] {
|
||||||
|
_ = n.FirstChild // eager nil check
|
||||||
|
|
||||||
|
return func(yield func(*Node) bool) {
|
||||||
|
for c := n.FirstChild; c != nil && yield(c); c = c.NextSibling {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Descendants returns an iterator over all nodes recursively beneath
|
||||||
|
// n, excluding n itself. Nodes are visited in depth-first preorder.
|
||||||
|
//
|
||||||
|
// Mutating a Node or its descendants while iterating may have unexpected results.
|
||||||
|
func (n *Node) Descendants() iter.Seq[*Node] {
|
||||||
|
_ = n.FirstChild // eager nil check
|
||||||
|
|
||||||
|
return func(yield func(*Node) bool) {
|
||||||
|
n.descendants(yield)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Node) descendants(yield func(*Node) bool) bool {
|
||||||
|
for c := range n.ChildNodes() {
|
||||||
|
if !yield(c) || !c.descendants(yield) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
230
vendor/golang.org/x/net/html/node.go
generated
vendored
Normal file
230
vendor/golang.org/x/net/html/node.go
generated
vendored
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package html
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/net/html/atom"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A NodeType is the type of a Node.
|
||||||
|
type NodeType uint32
|
||||||
|
|
||||||
|
//go:generate stringer -type NodeType
|
||||||
|
const (
|
||||||
|
ErrorNode NodeType = iota
|
||||||
|
TextNode
|
||||||
|
DocumentNode
|
||||||
|
ElementNode
|
||||||
|
CommentNode
|
||||||
|
DoctypeNode
|
||||||
|
// RawNode nodes are not returned by the parser, but can be part of the
|
||||||
|
// Node tree passed to func Render to insert raw HTML (without escaping).
|
||||||
|
// If so, this package makes no guarantee that the rendered HTML is secure
|
||||||
|
// (from e.g. Cross Site Scripting attacks) or well-formed.
|
||||||
|
RawNode
|
||||||
|
scopeMarkerNode
|
||||||
|
)
|
||||||
|
|
||||||
|
// Section 12.2.4.3 says "The markers are inserted when entering applet,
|
||||||
|
// object, marquee, template, td, th, and caption elements, and are used
|
||||||
|
// to prevent formatting from "leaking" into applet, object, marquee,
|
||||||
|
// template, td, th, and caption elements".
|
||||||
|
var scopeMarker = Node{Type: scopeMarkerNode}
|
||||||
|
|
||||||
|
// A Node consists of a NodeType and some Data (tag name for element nodes,
|
||||||
|
// content for text) and are part of a tree of Nodes. Element nodes may also
|
||||||
|
// have a Namespace and contain a slice of Attributes. Data is unescaped, so
|
||||||
|
// that it looks like "a<b" rather than "a<b". For element nodes, DataAtom
|
||||||
|
// is the atom for Data, or zero if Data is not a known tag name.
|
||||||
|
//
|
||||||
|
// Node trees may be navigated using the link fields (Parent,
|
||||||
|
// FirstChild, and so on) or a range loop over iterators such as
|
||||||
|
// [Node.Descendants].
|
||||||
|
//
|
||||||
|
// An empty Namespace implies a "http://www.w3.org/1999/xhtml" namespace.
|
||||||
|
// Similarly, "math" is short for "http://www.w3.org/1998/Math/MathML", and
|
||||||
|
// "svg" is short for "http://www.w3.org/2000/svg".
|
||||||
|
type Node struct {
|
||||||
|
Parent, FirstChild, LastChild, PrevSibling, NextSibling *Node
|
||||||
|
|
||||||
|
Type NodeType
|
||||||
|
DataAtom atom.Atom
|
||||||
|
Data string
|
||||||
|
Namespace string
|
||||||
|
Attr []Attribute
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertBefore inserts newChild as a child of n, immediately before oldChild
|
||||||
|
// in the sequence of n's children. oldChild may be nil, in which case newChild
|
||||||
|
// is appended to the end of n's children.
|
||||||
|
//
|
||||||
|
// It will panic if newChild already has a parent or siblings.
|
||||||
|
func (n *Node) InsertBefore(newChild, oldChild *Node) {
|
||||||
|
if newChild.Parent != nil || newChild.PrevSibling != nil || newChild.NextSibling != nil {
|
||||||
|
panic("html: InsertBefore called for an attached child Node")
|
||||||
|
}
|
||||||
|
var prev, next *Node
|
||||||
|
if oldChild != nil {
|
||||||
|
prev, next = oldChild.PrevSibling, oldChild
|
||||||
|
} else {
|
||||||
|
prev = n.LastChild
|
||||||
|
}
|
||||||
|
if prev != nil {
|
||||||
|
prev.NextSibling = newChild
|
||||||
|
} else {
|
||||||
|
n.FirstChild = newChild
|
||||||
|
}
|
||||||
|
if next != nil {
|
||||||
|
next.PrevSibling = newChild
|
||||||
|
} else {
|
||||||
|
n.LastChild = newChild
|
||||||
|
}
|
||||||
|
newChild.Parent = n
|
||||||
|
newChild.PrevSibling = prev
|
||||||
|
newChild.NextSibling = next
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendChild adds a node c as a child of n.
|
||||||
|
//
|
||||||
|
// It will panic if c already has a parent or siblings.
|
||||||
|
func (n *Node) AppendChild(c *Node) {
|
||||||
|
if c.Parent != nil || c.PrevSibling != nil || c.NextSibling != nil {
|
||||||
|
panic("html: AppendChild called for an attached child Node")
|
||||||
|
}
|
||||||
|
last := n.LastChild
|
||||||
|
if last != nil {
|
||||||
|
last.NextSibling = c
|
||||||
|
} else {
|
||||||
|
n.FirstChild = c
|
||||||
|
}
|
||||||
|
n.LastChild = c
|
||||||
|
c.Parent = n
|
||||||
|
c.PrevSibling = last
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveChild removes a node c that is a child of n. Afterwards, c will have
|
||||||
|
// no parent and no siblings.
|
||||||
|
//
|
||||||
|
// It will panic if c's parent is not n.
|
||||||
|
func (n *Node) RemoveChild(c *Node) {
|
||||||
|
if c.Parent != n {
|
||||||
|
panic("html: RemoveChild called for a non-child Node")
|
||||||
|
}
|
||||||
|
if n.FirstChild == c {
|
||||||
|
n.FirstChild = c.NextSibling
|
||||||
|
}
|
||||||
|
if c.NextSibling != nil {
|
||||||
|
c.NextSibling.PrevSibling = c.PrevSibling
|
||||||
|
}
|
||||||
|
if n.LastChild == c {
|
||||||
|
n.LastChild = c.PrevSibling
|
||||||
|
}
|
||||||
|
if c.PrevSibling != nil {
|
||||||
|
c.PrevSibling.NextSibling = c.NextSibling
|
||||||
|
}
|
||||||
|
c.Parent = nil
|
||||||
|
c.PrevSibling = nil
|
||||||
|
c.NextSibling = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// reparentChildren reparents all of src's child nodes to dst.
|
||||||
|
func reparentChildren(dst, src *Node) {
|
||||||
|
for {
|
||||||
|
child := src.FirstChild
|
||||||
|
if child == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
src.RemoveChild(child)
|
||||||
|
dst.AppendChild(child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// clone returns a new node with the same type, data and attributes.
|
||||||
|
// The clone has no parent, no siblings and no children.
|
||||||
|
func (n *Node) clone() *Node {
|
||||||
|
m := &Node{
|
||||||
|
Type: n.Type,
|
||||||
|
DataAtom: n.DataAtom,
|
||||||
|
Data: n.Data,
|
||||||
|
Attr: make([]Attribute, len(n.Attr)),
|
||||||
|
}
|
||||||
|
copy(m.Attr, n.Attr)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// nodeStack is a stack of nodes.
|
||||||
|
type nodeStack []*Node
|
||||||
|
|
||||||
|
// pop pops the stack. It will panic if s is empty.
|
||||||
|
func (s *nodeStack) pop() *Node {
|
||||||
|
i := len(*s)
|
||||||
|
n := (*s)[i-1]
|
||||||
|
*s = (*s)[:i-1]
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// top returns the most recently pushed node, or nil if s is empty.
|
||||||
|
func (s *nodeStack) top() *Node {
|
||||||
|
if i := len(*s); i > 0 {
|
||||||
|
return (*s)[i-1]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// index returns the index of the top-most occurrence of n in the stack, or -1
|
||||||
|
// if n is not present.
|
||||||
|
func (s *nodeStack) index(n *Node) int {
|
||||||
|
for i := len(*s) - 1; i >= 0; i-- {
|
||||||
|
if (*s)[i] == n {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// contains returns whether a is within s.
|
||||||
|
func (s *nodeStack) contains(a atom.Atom) bool {
|
||||||
|
for _, n := range *s {
|
||||||
|
if n.DataAtom == a && n.Namespace == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert inserts a node at the given index.
|
||||||
|
func (s *nodeStack) insert(i int, n *Node) {
|
||||||
|
(*s) = append(*s, nil)
|
||||||
|
copy((*s)[i+1:], (*s)[i:])
|
||||||
|
(*s)[i] = n
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove removes a node from the stack. It is a no-op if n is not present.
|
||||||
|
func (s *nodeStack) remove(n *Node) {
|
||||||
|
i := s.index(n)
|
||||||
|
if i == -1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
copy((*s)[i:], (*s)[i+1:])
|
||||||
|
j := len(*s) - 1
|
||||||
|
(*s)[j] = nil
|
||||||
|
*s = (*s)[:j]
|
||||||
|
}
|
||||||
|
|
||||||
|
type insertionModeStack []insertionMode
|
||||||
|
|
||||||
|
func (s *insertionModeStack) pop() (im insertionMode) {
|
||||||
|
i := len(*s)
|
||||||
|
im = (*s)[i-1]
|
||||||
|
*s = (*s)[:i-1]
|
||||||
|
return im
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *insertionModeStack) top() insertionMode {
|
||||||
|
if i := len(*s); i > 0 {
|
||||||
|
return (*s)[i-1]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
31
vendor/golang.org/x/net/html/nodetype_string.go
generated
vendored
Normal file
31
vendor/golang.org/x/net/html/nodetype_string.go
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// Code generated by "stringer -type NodeType"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package html
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||||
|
// Re-run the stringer command to generate them again.
|
||||||
|
var x [1]struct{}
|
||||||
|
_ = x[ErrorNode-0]
|
||||||
|
_ = x[TextNode-1]
|
||||||
|
_ = x[DocumentNode-2]
|
||||||
|
_ = x[ElementNode-3]
|
||||||
|
_ = x[CommentNode-4]
|
||||||
|
_ = x[DoctypeNode-5]
|
||||||
|
_ = x[RawNode-6]
|
||||||
|
_ = x[scopeMarkerNode-7]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _NodeType_name = "ErrorNodeTextNodeDocumentNodeElementNodeCommentNodeDoctypeNodeRawNodescopeMarkerNode"
|
||||||
|
|
||||||
|
var _NodeType_index = [...]uint8{0, 9, 17, 29, 40, 51, 62, 69, 84}
|
||||||
|
|
||||||
|
func (i NodeType) String() string {
|
||||||
|
idx := int(i) - 0
|
||||||
|
if i < 0 || idx >= len(_NodeType_index)-1 {
|
||||||
|
return "NodeType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _NodeType_name[_NodeType_index[idx]:_NodeType_index[idx+1]]
|
||||||
|
}
|
||||||
2489
vendor/golang.org/x/net/html/parse.go
generated
vendored
Normal file
2489
vendor/golang.org/x/net/html/parse.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
293
vendor/golang.org/x/net/html/render.go
generated
vendored
Normal file
293
vendor/golang.org/x/net/html/render.go
generated
vendored
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package html
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type writer interface {
|
||||||
|
io.Writer
|
||||||
|
io.ByteWriter
|
||||||
|
WriteString(string) (int, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render renders the parse tree n to the given writer.
|
||||||
|
//
|
||||||
|
// Rendering is done on a 'best effort' basis: calling Parse on the output of
|
||||||
|
// Render will always result in something similar to the original tree, but it
|
||||||
|
// is not necessarily an exact clone unless the original tree was 'well-formed'.
|
||||||
|
// 'Well-formed' is not easily specified; the HTML5 specification is
|
||||||
|
// complicated.
|
||||||
|
//
|
||||||
|
// Calling Parse on arbitrary input typically results in a 'well-formed' parse
|
||||||
|
// tree. However, it is possible for Parse to yield a 'badly-formed' parse tree.
|
||||||
|
// For example, in a 'well-formed' parse tree, no <a> element is a child of
|
||||||
|
// another <a> element: parsing "<a><a>" results in two sibling elements.
|
||||||
|
// Similarly, in a 'well-formed' parse tree, no <a> element is a child of a
|
||||||
|
// <table> element: parsing "<p><table><a>" results in a <p> with two sibling
|
||||||
|
// children; the <a> is reparented to the <table>'s parent. However, calling
|
||||||
|
// Parse on "<a><table><a>" does not return an error, but the result has an <a>
|
||||||
|
// element with an <a> child, and is therefore not 'well-formed'.
|
||||||
|
//
|
||||||
|
// Programmatically constructed trees are typically also 'well-formed', but it
|
||||||
|
// is possible to construct a tree that looks innocuous but, when rendered and
|
||||||
|
// re-parsed, results in a different tree. A simple example is that a solitary
|
||||||
|
// text node would become a tree containing <html>, <head> and <body> elements.
|
||||||
|
// Another example is that the programmatic equivalent of "a<head>b</head>c"
|
||||||
|
// becomes "<html><head><head/><body>abc</body></html>".
|
||||||
|
func Render(w io.Writer, n *Node) error {
|
||||||
|
if x, ok := w.(writer); ok {
|
||||||
|
return render(x, n)
|
||||||
|
}
|
||||||
|
buf := bufio.NewWriter(w)
|
||||||
|
if err := render(buf, n); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return buf.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// plaintextAbort is returned from render1 when a <plaintext> element
|
||||||
|
// has been rendered. No more end tags should be rendered after that.
|
||||||
|
var plaintextAbort = errors.New("html: internal error (plaintext abort)")
|
||||||
|
|
||||||
|
func render(w writer, n *Node) error {
|
||||||
|
err := render1(w, n)
|
||||||
|
if err == plaintextAbort {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func render1(w writer, n *Node) error {
|
||||||
|
// Render non-element nodes; these are the easy cases.
|
||||||
|
switch n.Type {
|
||||||
|
case ErrorNode:
|
||||||
|
return errors.New("html: cannot render an ErrorNode node")
|
||||||
|
case TextNode:
|
||||||
|
return escape(w, n.Data)
|
||||||
|
case DocumentNode:
|
||||||
|
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||||
|
if err := render1(w, c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case ElementNode:
|
||||||
|
// No-op.
|
||||||
|
case CommentNode:
|
||||||
|
if _, err := w.WriteString("<!--"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := escapeComment(w, n.Data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := w.WriteString("-->"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case DoctypeNode:
|
||||||
|
if _, err := w.WriteString("<!DOCTYPE "); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := escape(w, n.Data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if n.Attr != nil {
|
||||||
|
var p, s string
|
||||||
|
for _, a := range n.Attr {
|
||||||
|
switch a.Key {
|
||||||
|
case "public":
|
||||||
|
p = a.Val
|
||||||
|
case "system":
|
||||||
|
s = a.Val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p != "" {
|
||||||
|
if _, err := w.WriteString(" PUBLIC "); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := writeQuoted(w, p); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if s != "" {
|
||||||
|
if err := w.WriteByte(' '); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := writeQuoted(w, s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if s != "" {
|
||||||
|
if _, err := w.WriteString(" SYSTEM "); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := writeQuoted(w, s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return w.WriteByte('>')
|
||||||
|
case RawNode:
|
||||||
|
_, err := w.WriteString(n.Data)
|
||||||
|
return err
|
||||||
|
default:
|
||||||
|
return errors.New("html: unknown node type")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render the <xxx> opening tag.
|
||||||
|
if err := w.WriteByte('<'); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := w.WriteString(n.Data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, a := range n.Attr {
|
||||||
|
if err := w.WriteByte(' '); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if a.Namespace != "" {
|
||||||
|
if _, err := w.WriteString(a.Namespace); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := w.WriteByte(':'); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, err := w.WriteString(a.Key); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := w.WriteString(`="`); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := escape(w, a.Val); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := w.WriteByte('"'); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if voidElements[n.Data] {
|
||||||
|
if n.FirstChild != nil {
|
||||||
|
return fmt.Errorf("html: void element <%s> has child nodes", n.Data)
|
||||||
|
}
|
||||||
|
_, err := w.WriteString("/>")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := w.WriteByte('>'); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add initial newline where there is danger of a newline being ignored.
|
||||||
|
if c := n.FirstChild; c != nil && c.Type == TextNode && strings.HasPrefix(c.Data, "\n") {
|
||||||
|
switch n.Data {
|
||||||
|
case "pre", "listing", "textarea":
|
||||||
|
if err := w.WriteByte('\n'); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render any child nodes
|
||||||
|
if childTextNodesAreLiteral(n) {
|
||||||
|
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||||
|
if c.Type == TextNode {
|
||||||
|
if _, err := w.WriteString(c.Data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := render1(w, c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if n.Data == "plaintext" {
|
||||||
|
// Don't render anything else. <plaintext> must be the
|
||||||
|
// last element in the file, with no closing tag.
|
||||||
|
return plaintextAbort
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||||
|
if err := render1(w, c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render the </xxx> closing tag.
|
||||||
|
if _, err := w.WriteString("</"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := w.WriteString(n.Data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return w.WriteByte('>')
|
||||||
|
}
|
||||||
|
|
||||||
|
func childTextNodesAreLiteral(n *Node) bool {
|
||||||
|
// Per WHATWG HTML 13.3, if the parent of the current node is a style,
|
||||||
|
// script, xmp, iframe, noembed, noframes, or plaintext element, and the
|
||||||
|
// current node is a text node, append the value of the node's data
|
||||||
|
// literally. The specification is not explicit about it, but we only
|
||||||
|
// enforce this if we are in the HTML namespace (i.e. when the namespace is
|
||||||
|
// "").
|
||||||
|
// NOTE: we also always include noscript elements, although the
|
||||||
|
// specification states that they should only be rendered as such if
|
||||||
|
// scripting is enabled for the node (which is not something we track).
|
||||||
|
if n.Namespace != "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
switch n.Data {
|
||||||
|
case "iframe", "noembed", "noframes", "noscript", "plaintext", "script", "style", "xmp":
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeQuoted writes s to w surrounded by quotes. Normally it will use double
|
||||||
|
// quotes, but if s contains a double quote, it will use single quotes.
|
||||||
|
// It is used for writing the identifiers in a doctype declaration.
|
||||||
|
// In valid HTML, they can't contain both types of quotes.
|
||||||
|
func writeQuoted(w writer, s string) error {
|
||||||
|
var q byte = '"'
|
||||||
|
if strings.Contains(s, `"`) {
|
||||||
|
q = '\''
|
||||||
|
}
|
||||||
|
if err := w.WriteByte(q); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := w.WriteString(s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := w.WriteByte(q); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Section 12.1.2, "Elements", gives this list of void elements. Void elements
|
||||||
|
// are those that can't have any contents.
|
||||||
|
var voidElements = map[string]bool{
|
||||||
|
"area": true,
|
||||||
|
"base": true,
|
||||||
|
"br": true,
|
||||||
|
"col": true,
|
||||||
|
"embed": true,
|
||||||
|
"hr": true,
|
||||||
|
"img": true,
|
||||||
|
"input": true,
|
||||||
|
"keygen": true, // "keygen" has been removed from the spec, but are kept here for backwards compatibility.
|
||||||
|
"link": true,
|
||||||
|
"meta": true,
|
||||||
|
"param": true,
|
||||||
|
"source": true,
|
||||||
|
"track": true,
|
||||||
|
"wbr": true,
|
||||||
|
}
|
||||||
1286
vendor/golang.org/x/net/html/token.go
generated
vendored
Normal file
1286
vendor/golang.org/x/net/html/token.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
BIN
vendor/golang.org/x/net/publicsuffix/data/children
generated
vendored
Normal file
BIN
vendor/golang.org/x/net/publicsuffix/data/children
generated
vendored
Normal file
Binary file not shown.
BIN
vendor/golang.org/x/net/publicsuffix/data/nodes
generated
vendored
Normal file
BIN
vendor/golang.org/x/net/publicsuffix/data/nodes
generated
vendored
Normal file
Binary file not shown.
1
vendor/golang.org/x/net/publicsuffix/data/text
generated
vendored
Normal file
1
vendor/golang.org/x/net/publicsuffix/data/text
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
210
vendor/golang.org/x/net/publicsuffix/list.go
generated
vendored
Normal file
210
vendor/golang.org/x/net/publicsuffix/list.go
generated
vendored
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:generate go run gen.go
|
||||||
|
|
||||||
|
// Package publicsuffix provides a public suffix list based on data from
|
||||||
|
// https://publicsuffix.org/
|
||||||
|
//
|
||||||
|
// A public suffix is one under which Internet users can directly register
|
||||||
|
// names. It is related to, but different from, a TLD (top level domain).
|
||||||
|
//
|
||||||
|
// "com" is a TLD (top level domain). Top level means it has no dots.
|
||||||
|
//
|
||||||
|
// "com" is also a public suffix. Amazon and Google have registered different
|
||||||
|
// siblings under that domain: "amazon.com" and "google.com".
|
||||||
|
//
|
||||||
|
// "au" is another TLD, again because it has no dots. But it's not "amazon.au".
|
||||||
|
// Instead, it's "amazon.com.au".
|
||||||
|
//
|
||||||
|
// "com.au" isn't an actual TLD, because it's not at the top level (it has
|
||||||
|
// dots). But it is an eTLD (effective TLD), because that's the branching point
|
||||||
|
// for domain name registrars.
|
||||||
|
//
|
||||||
|
// Another name for "an eTLD" is "a public suffix". Often, what's more of
|
||||||
|
// interest is the eTLD+1, or one more label than the public suffix. For
|
||||||
|
// example, browsers partition read/write access to HTTP cookies according to
|
||||||
|
// the eTLD+1. Web pages served from "amazon.com.au" can't read cookies from
|
||||||
|
// "google.com.au", but web pages served from "maps.google.com" can share
|
||||||
|
// cookies from "www.google.com", so you don't have to sign into Google Maps
|
||||||
|
// separately from signing into Google Web Search. Note that all four of those
|
||||||
|
// domains have 3 labels and 2 dots. The first two domains are each an eTLD+1,
|
||||||
|
// the last two are not (but share the same eTLD+1: "google.com").
|
||||||
|
//
|
||||||
|
// All of these domains have the same eTLD+1:
|
||||||
|
// - "www.books.amazon.co.uk"
|
||||||
|
// - "books.amazon.co.uk"
|
||||||
|
// - "amazon.co.uk"
|
||||||
|
//
|
||||||
|
// Specifically, the eTLD+1 is "amazon.co.uk", because the eTLD is "co.uk".
|
||||||
|
//
|
||||||
|
// There is no closed form algorithm to calculate the eTLD of a domain.
|
||||||
|
// Instead, the calculation is data driven. This package provides a
|
||||||
|
// pre-compiled snapshot of Mozilla's PSL (Public Suffix List) data at
|
||||||
|
// https://publicsuffix.org/
|
||||||
|
package publicsuffix // import "golang.org/x/net/publicsuffix"
|
||||||
|
|
||||||
|
// TODO: specify case sensitivity and leading/trailing dot behavior for
|
||||||
|
// func PublicSuffix and func EffectiveTLDPlusOne.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http/cookiejar"
|
||||||
|
"net/netip"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// List implements the cookiejar.PublicSuffixList interface by calling the
|
||||||
|
// PublicSuffix function.
|
||||||
|
var List cookiejar.PublicSuffixList = list{}
|
||||||
|
|
||||||
|
type list struct{}
|
||||||
|
|
||||||
|
func (list) PublicSuffix(domain string) string {
|
||||||
|
ps, _ := PublicSuffix(domain)
|
||||||
|
return ps
|
||||||
|
}
|
||||||
|
|
||||||
|
func (list) String() string {
|
||||||
|
return version
|
||||||
|
}
|
||||||
|
|
||||||
|
// PublicSuffix returns the public suffix of the domain using a copy of the
|
||||||
|
// publicsuffix.org database compiled into the library.
|
||||||
|
//
|
||||||
|
// icann is whether the public suffix is managed by the Internet Corporation
|
||||||
|
// for Assigned Names and Numbers. If not, the public suffix is either a
|
||||||
|
// privately managed domain (and in practice, not a top level domain) or an
|
||||||
|
// unmanaged top level domain (and not explicitly mentioned in the
|
||||||
|
// publicsuffix.org list). For example, "foo.org" and "foo.co.uk" are ICANN
|
||||||
|
// domains, "foo.dyndns.org" is a private domain and
|
||||||
|
// "cromulent" is an unmanaged top level domain.
|
||||||
|
//
|
||||||
|
// Use cases for distinguishing ICANN domains like "foo.com" from private
|
||||||
|
// domains like "foo.appspot.com" can be found at
|
||||||
|
// https://wiki.mozilla.org/Public_Suffix_List/Use_Cases
|
||||||
|
func PublicSuffix(domain string) (publicSuffix string, icann bool) {
|
||||||
|
if _, err := netip.ParseAddr(domain); err == nil {
|
||||||
|
return domain, false
|
||||||
|
}
|
||||||
|
|
||||||
|
lo, hi := uint32(0), uint32(numTLD)
|
||||||
|
s, suffix, icannNode, wildcard := domain, len(domain), false, false
|
||||||
|
loop:
|
||||||
|
for {
|
||||||
|
dot := strings.LastIndexByte(s, '.')
|
||||||
|
if wildcard {
|
||||||
|
icann = icannNode
|
||||||
|
suffix = 1 + dot
|
||||||
|
}
|
||||||
|
if lo == hi {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
f := find(s[1+dot:], lo, hi)
|
||||||
|
if f == notFound {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
u := uint32(nodes.get(f) >> (nodesBitsTextOffset + nodesBitsTextLength))
|
||||||
|
icannNode = u&(1<<nodesBitsICANN-1) != 0
|
||||||
|
u >>= nodesBitsICANN
|
||||||
|
u = children.get(u & (1<<nodesBitsChildren - 1))
|
||||||
|
lo = u & (1<<childrenBitsLo - 1)
|
||||||
|
u >>= childrenBitsLo
|
||||||
|
hi = u & (1<<childrenBitsHi - 1)
|
||||||
|
u >>= childrenBitsHi
|
||||||
|
switch u & (1<<childrenBitsNodeType - 1) {
|
||||||
|
case nodeTypeNormal:
|
||||||
|
suffix = 1 + dot
|
||||||
|
case nodeTypeException:
|
||||||
|
suffix = 1 + len(s)
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
u >>= childrenBitsNodeType
|
||||||
|
wildcard = u&(1<<childrenBitsWildcard-1) != 0
|
||||||
|
if !wildcard {
|
||||||
|
icann = icannNode
|
||||||
|
}
|
||||||
|
|
||||||
|
if dot == -1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
s = s[:dot]
|
||||||
|
}
|
||||||
|
if suffix == len(domain) {
|
||||||
|
// If no rules match, the prevailing rule is "*".
|
||||||
|
return domain[1+strings.LastIndexByte(domain, '.'):], icann
|
||||||
|
}
|
||||||
|
return domain[suffix:], icann
|
||||||
|
}
|
||||||
|
|
||||||
|
const notFound uint32 = 1<<32 - 1
|
||||||
|
|
||||||
|
// find returns the index of the node in the range [lo, hi) whose label equals
|
||||||
|
// label, or notFound if there is no such node. The range is assumed to be in
|
||||||
|
// strictly increasing node label order.
|
||||||
|
func find(label string, lo, hi uint32) uint32 {
|
||||||
|
for lo < hi {
|
||||||
|
mid := lo + (hi-lo)/2
|
||||||
|
s := nodeLabel(mid)
|
||||||
|
if s < label {
|
||||||
|
lo = mid + 1
|
||||||
|
} else if s == label {
|
||||||
|
return mid
|
||||||
|
} else {
|
||||||
|
hi = mid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return notFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// nodeLabel returns the label for the i'th node.
|
||||||
|
func nodeLabel(i uint32) string {
|
||||||
|
x := nodes.get(i)
|
||||||
|
length := x & (1<<nodesBitsTextLength - 1)
|
||||||
|
x >>= nodesBitsTextLength
|
||||||
|
offset := x & (1<<nodesBitsTextOffset - 1)
|
||||||
|
return text[offset : offset+length]
|
||||||
|
}
|
||||||
|
|
||||||
|
// EffectiveTLDPlusOne returns the effective top level domain plus one more
|
||||||
|
// label. For example, the eTLD+1 for "foo.bar.golang.org" is "golang.org".
|
||||||
|
func EffectiveTLDPlusOne(domain string) (string, error) {
|
||||||
|
if strings.HasPrefix(domain, ".") || strings.HasSuffix(domain, ".") || strings.Contains(domain, "..") {
|
||||||
|
return "", fmt.Errorf("publicsuffix: empty label in domain %q", domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
suffix, _ := PublicSuffix(domain)
|
||||||
|
if len(domain) <= len(suffix) {
|
||||||
|
return "", fmt.Errorf("publicsuffix: cannot derive eTLD+1 for domain %q", domain)
|
||||||
|
}
|
||||||
|
i := len(domain) - len(suffix) - 1
|
||||||
|
if domain[i] != '.' {
|
||||||
|
return "", fmt.Errorf("publicsuffix: invalid public suffix %q for domain %q", suffix, domain)
|
||||||
|
}
|
||||||
|
return domain[1+strings.LastIndexByte(domain[:i], '.'):], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type uint32String string
|
||||||
|
|
||||||
|
func (u uint32String) get(i uint32) uint32 {
|
||||||
|
off := i * 4
|
||||||
|
u = u[off:] // help the compiler reduce bounds checks
|
||||||
|
return uint32(u[3]) |
|
||||||
|
uint32(u[2])<<8 |
|
||||||
|
uint32(u[1])<<16 |
|
||||||
|
uint32(u[0])<<24
|
||||||
|
}
|
||||||
|
|
||||||
|
type uint40String string
|
||||||
|
|
||||||
|
func (u uint40String) get(i uint32) uint64 {
|
||||||
|
off := uint64(i * (nodesBits / 8))
|
||||||
|
u = u[off:] // help the compiler reduce bounds checks
|
||||||
|
return uint64(u[4]) |
|
||||||
|
uint64(u[3])<<8 |
|
||||||
|
uint64(u[2])<<16 |
|
||||||
|
uint64(u[1])<<24 |
|
||||||
|
uint64(u[0])<<32
|
||||||
|
}
|
||||||
70
vendor/golang.org/x/net/publicsuffix/table.go
generated
vendored
Normal file
70
vendor/golang.org/x/net/publicsuffix/table.go
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
// generated by go run gen.go; DO NOT EDIT
|
||||||
|
|
||||||
|
package publicsuffix
|
||||||
|
|
||||||
|
import _ "embed"
|
||||||
|
|
||||||
|
const version = "publicsuffix.org's public_suffix_list.dat, git revision d6c92f1bbb7433e5db7b8405c25d4035fb8ff376 (2026-02-06T07:36:33Z)"
|
||||||
|
|
||||||
|
const (
|
||||||
|
nodesBits = 40
|
||||||
|
nodesBitsChildren = 10
|
||||||
|
nodesBitsICANN = 1
|
||||||
|
nodesBitsTextOffset = 16
|
||||||
|
nodesBitsTextLength = 6
|
||||||
|
|
||||||
|
childrenBitsWildcard = 1
|
||||||
|
childrenBitsNodeType = 2
|
||||||
|
childrenBitsHi = 14
|
||||||
|
childrenBitsLo = 14
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
nodeTypeNormal = 0
|
||||||
|
nodeTypeException = 1
|
||||||
|
nodeTypeParentOnly = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
// numTLD is the number of top level domains.
|
||||||
|
const numTLD = 1450
|
||||||
|
|
||||||
|
// text is the combined text of all labels.
|
||||||
|
//
|
||||||
|
//go:embed data/text
|
||||||
|
var text string
|
||||||
|
|
||||||
|
// nodes is the list of nodes. Each node is represented as a 40-bit integer,
|
||||||
|
// which encodes the node's children, wildcard bit and node type (as an index
|
||||||
|
// into the children array), ICANN bit and text.
|
||||||
|
//
|
||||||
|
// The layout within the node, from MSB to LSB, is:
|
||||||
|
//
|
||||||
|
// [ 7 bits] unused
|
||||||
|
// [10 bits] children index
|
||||||
|
// [ 1 bits] ICANN bit
|
||||||
|
// [16 bits] text index
|
||||||
|
// [ 6 bits] text length
|
||||||
|
//
|
||||||
|
//go:embed data/nodes
|
||||||
|
var nodes uint40String
|
||||||
|
|
||||||
|
// children is the list of nodes' children, the parent's wildcard bit and the
|
||||||
|
// parent's node type. If a node has no children then their children index
|
||||||
|
// will be in the range [0, 6), depending on the wildcard bit and node type.
|
||||||
|
//
|
||||||
|
// The layout within the uint32, from MSB to LSB, is:
|
||||||
|
//
|
||||||
|
// [ 1 bits] unused
|
||||||
|
// [ 1 bits] wildcard bit
|
||||||
|
// [ 2 bits] node type
|
||||||
|
// [14 bits] high nodes index (exclusive) of children
|
||||||
|
// [14 bits] low nodes index (inclusive) of children
|
||||||
|
//
|
||||||
|
//go:embed data/children
|
||||||
|
var children uint32String
|
||||||
|
|
||||||
|
// max children 935 (capacity 1023)
|
||||||
|
// max text offset 32332 (capacity 65535)
|
||||||
|
// max text length 31 (capacity 63)
|
||||||
|
// max hi 10533 (capacity 16383)
|
||||||
|
// max lo 10528 (capacity 16383)
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user