Initial commit

This commit is contained in:
eleksir
2026-03-14 22:51:43 +03:00
commit 0a73bca056
105 changed files with 24685 additions and 0 deletions

803
.golangci.yml Normal file
View 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
View 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
View 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
View 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: */

View 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
View 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
View 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
View 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=

View 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: */

View 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
View 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
View 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
View 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: */

View 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
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,339 @@
| <img src="assets/readme_banner.png" alt="logo" width="800"><br/><br/> [![Language](https://img.shields.io/badge/Language-Go-blue.svg)](https://golang.org/) [![PkgGoDev](https://pkg.go.dev/badge/github.com/NicoNex/echotron/v3)](https://pkg.go.dev/github.com/NicoNex/echotron/v3) [![Go Report Card](https://goreportcard.com/badge/github.com/NicoNex/echotron/v3)](https://goreportcard.com/report/github.com/NicoNex/echotron/v3) [![codecov](https://codecov.io/gh/NicoNex/echotron/graph/badge.svg?token=LVJGOEYL5M)](https://codecov.io/gh/NicoNex/echotron) [![License](http://img.shields.io/badge/license-LGPL3.0-orange.svg?style=flat)](https://github.com/NicoNex/echotron/blob/master/LICENSE) [![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go) [![Telegram](https://img.shields.io/badge/Echotron%20News-blue?logo=telegram&style=flat)](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

File diff suppressed because it is too large Load Diff

42
vendor/github.com/NicoNex/echotron/v3/apierror.go generated vendored Normal file
View 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)
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

228
vendor/github.com/NicoNex/echotron/v3/passport.go generated vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

57
vendor/github.com/NicoNex/echotron/v3/webapp.go generated vendored Normal file
View 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
View File

@@ -0,0 +1 @@
img/

21
vendor/github.com/carlmjohnson/requests/LICENSE generated vendored Normal file
View 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
View File

@@ -0,0 +1,314 @@
# Requests [![GoDoc](https://godoc.org/github.com/carlmjohnson/requests?status.svg)](https://godoc.org/github.com/carlmjohnson/requests) [![Go Report Card](https://goreportcard.com/badge/github.com/carlmjohnson/requests)](https://goreportcard.com/report/github.com/carlmjohnson/requests) [![Coverage Status](https://coveralls.io/repos/github/earthboundkid/requests/badge.svg)](https://coveralls.io/github/earthboundkid/requests) [![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go)
![Requests logo](https://github.com/earthboundkid/requests/assets/222245/e59a9f82-c6c7-46d8-96b2-ce2f7dd58dce)
## _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
View 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
View 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
View 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)
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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()
}

View 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
View 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

View 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
View 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
View 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
View 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
View 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
View 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
}
}

View 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
View File

21
vendor/github.com/hjson/hjson-go/LICENSE generated vendored Normal file
View 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
View File

@@ -0,0 +1,181 @@
# hjson-go
[![Build Status](https://github.com/hjson/hjson-go/workflows/test/badge.svg)](https://github.com/hjson/hjson-go/actions)
[![Go Pkg](https://img.shields.io/github/release/hjson/hjson-go.svg?style=flat-square&label=go-pkg)](https://github.com/hjson/hjson-go/releases)
[![Go Report Card](https://goreportcard.com/badge/github.com/hjson/hjson-go?style=flat-square)](https://goreportcard.com/report/github.com/hjson/hjson-go)
[![coverage](https://img.shields.io/badge/coverage-ok-brightgreen.svg?style=flat-square)](http://gocover.io/github.com/hjson/hjson-go/)
[![godoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](https://godoc.org/github.com/hjson/hjson-go)
![Hjson Intro](https://hjson.github.io/hjson1.gif)
```
{
# 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
[![godoc](https://godoc.org/github.com/hjson/hjson-go?status.svg)](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
View 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
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1 @@
language: go

21
vendor/github.com/robfig/cron/v3/LICENSE generated vendored Normal file
View 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
View File

@@ -0,0 +1,125 @@
[![GoDoc](http://godoc.org/github.com/robfig/cron?status.png)](http://godoc.org/github.com/robfig/cron)
[![Build Status](https://travis-ci.org/robfig/cron.svg?branch=master)](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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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 "&lt;") 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
View 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

File diff suppressed because it is too large Load Diff

339
vendor/golang.org/x/net/html/escape.go generated vendored Normal file
View 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 "&lt;" 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&lt;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 = "&amp;"
case '>':
if j > 0 {
if prev := s[j-1]; (prev != '!') && (prev != '-') {
continue
}
}
escaped = "&gt;"
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 = "&amp;"
case '\'':
// "&#39;" is shorter than "&apos;" and apos was not in HTML until HTML5.
esc = "&#39;"
case '<':
esc = "&lt;"
case '>':
esc = "&gt;"
case '"':
// "&#34;" is shorter than "&quot;".
esc = "&#34;"
case '\r':
esc = "&#13;"
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 "&lt;". 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 "&lt;" to become "<". It unescapes a
// larger range of entities than EscapeString escapes. For example, "&aacute;"
// unescapes to "á", as does "&#225;" 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
View 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
View 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
View 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&lt;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
View 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

File diff suppressed because it is too large Load Diff

293
vendor/golang.org/x/net/html/render.go generated vendored Normal file
View 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

File diff suppressed because it is too large Load Diff

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

Binary file not shown.

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
View 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
View 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