From 0a73bca056ea4cd22e9ba7e16d9d237e388ecdba Mon Sep 17 00:00:00 2001 From: eleksir Date: Sat, 14 Mar 2026 22:51:43 +0300 Subject: [PATCH] Initial commit --- .golangci.yml | 803 ++++++ Makefile | 76 + README.md | 55 + cmd/tg-holy-bot/main.go | 71 + contrib/tg-holy-bot.openrc | 21 + data/config.json | 51 + go.mod | 13 + go.sum | 12 + internal/holy/celebratoday.go | 110 + internal/holy/configLocations.go | 30 + internal/holy/cronTask.go | 24 + internal/holy/globals.go | 36 + internal/holy/readConfig.go | 116 + internal/holy/sigHandler.go | 32 + internal/holy/types.go | 37 + internal/log/log.go | 148 + .../NicoNex/echotron/v3/COPYING.LESSER | 165 ++ vendor/github.com/NicoNex/echotron/v3/LICENSE | 674 +++++ .../github.com/NicoNex/echotron/v3/README.md | 339 +++ vendor/github.com/NicoNex/echotron/v3/api.go | 1374 +++++++++ .../NicoNex/echotron/v3/apierror.go | 42 + .../NicoNex/echotron/v3/chatadminrights.go | 65 + .../NicoNex/echotron/v3/dispatcher.go | 207 ++ .../github.com/NicoNex/echotron/v3/games.go | 73 + .../github.com/NicoNex/echotron/v3/helpers.go | 214 ++ .../github.com/NicoNex/echotron/v3/inline.go | 601 ++++ .../NicoNex/echotron/v3/menubutton.go | 57 + .../github.com/NicoNex/echotron/v3/network.go | 442 +++ .../github.com/NicoNex/echotron/v3/options.go | 1007 +++++++ .../NicoNex/echotron/v3/passport.go | 228 ++ .../NicoNex/echotron/v3/payments.go | 335 +++ .../NicoNex/echotron/v3/querybuilder.go | 88 + .../NicoNex/echotron/v3/simpledsp.go | 137 + vendor/github.com/NicoNex/echotron/v3/smap.go | 26 + .../NicoNex/echotron/v3/stickers.go | 271 ++ .../github.com/NicoNex/echotron/v3/types.go | 2427 ++++++++++++++++ .../github.com/NicoNex/echotron/v3/webapp.go | 57 + .../carlmjohnson/requests/.gitignore | 1 + .../github.com/carlmjohnson/requests/LICENSE | 21 + .../carlmjohnson/requests/README.md | 314 +++ .../carlmjohnson/requests/SECURITY.md | 9 + .../github.com/carlmjohnson/requests/body.go | 77 + .../carlmjohnson/requests/builder_core.go | 281 ++ .../carlmjohnson/requests/builder_extras.go | 239 ++ .../carlmjohnson/requests/config.go | 77 + .../carlmjohnson/requests/cookies.go | 18 + .../carlmjohnson/requests/core_do.go | 36 + .../carlmjohnson/requests/core_req.go | 106 + .../carlmjohnson/requests/core_url.go | 94 + .../github.com/carlmjohnson/requests/doc.go | 3 + .../carlmjohnson/requests/errorkind.go | 20 + .../carlmjohnson/requests/errorkind_string.go | 27 + .../carlmjohnson/requests/handler.go | 139 + .../requests/internal/minitrue/minitrue.go | 9 + .../carlmjohnson/requests/recorder.go | 131 + .../carlmjohnson/requests/redirects.go | 19 + .../carlmjohnson/requests/serializer.go | 24 + .../carlmjohnson/requests/transport.go | 128 + .../carlmjohnson/requests/validator.go | 102 + .../requests/validator_handler.go | 34 + vendor/github.com/hjson/hjson-go/.gitignore | 0 vendor/github.com/hjson/hjson-go/LICENSE | 21 + vendor/github.com/hjson/hjson-go/README.md | 181 ++ .../hjson/hjson-go/build_release.sh | 58 + vendor/github.com/hjson/hjson-go/decode.go | 480 ++++ vendor/github.com/hjson/hjson-go/encode.go | 492 ++++ .../github.com/hjson/hjson-go/parseNumber.go | 108 + vendor/github.com/robfig/cron/v3/.gitignore | 22 + vendor/github.com/robfig/cron/v3/.travis.yml | 1 + vendor/github.com/robfig/cron/v3/LICENSE | 21 + vendor/github.com/robfig/cron/v3/README.md | 125 + vendor/github.com/robfig/cron/v3/chain.go | 92 + .../robfig/cron/v3/constantdelay.go | 27 + vendor/github.com/robfig/cron/v3/cron.go | 355 +++ vendor/github.com/robfig/cron/v3/doc.go | 231 ++ vendor/github.com/robfig/cron/v3/logger.go | 86 + vendor/github.com/robfig/cron/v3/option.go | 45 + vendor/github.com/robfig/cron/v3/parser.go | 434 +++ vendor/github.com/robfig/cron/v3/spec.go | 188 ++ vendor/golang.org/x/net/LICENSE | 27 + vendor/golang.org/x/net/PATENTS | 22 + vendor/golang.org/x/net/html/atom/atom.go | 78 + vendor/golang.org/x/net/html/atom/table.go | 785 ++++++ vendor/golang.org/x/net/html/const.go | 111 + vendor/golang.org/x/net/html/doc.go | 122 + vendor/golang.org/x/net/html/doctype.go | 156 ++ vendor/golang.org/x/net/html/entity.go | 2253 +++++++++++++++ vendor/golang.org/x/net/html/escape.go | 339 +++ vendor/golang.org/x/net/html/foreign.go | 221 ++ vendor/golang.org/x/net/html/iter.go | 54 + vendor/golang.org/x/net/html/node.go | 230 ++ .../golang.org/x/net/html/nodetype_string.go | 31 + vendor/golang.org/x/net/html/parse.go | 2489 +++++++++++++++++ vendor/golang.org/x/net/html/render.go | 293 ++ vendor/golang.org/x/net/html/token.go | 1286 +++++++++ .../x/net/publicsuffix/data/children | Bin 0 -> 3744 bytes .../golang.org/x/net/publicsuffix/data/nodes | Bin 0 -> 52665 bytes .../golang.org/x/net/publicsuffix/data/text | 1 + vendor/golang.org/x/net/publicsuffix/list.go | 210 ++ vendor/golang.org/x/net/publicsuffix/table.go | 70 + vendor/golang.org/x/time/LICENSE | 27 + vendor/golang.org/x/time/PATENTS | 22 + vendor/golang.org/x/time/rate/rate.go | 430 +++ vendor/golang.org/x/time/rate/sometimes.go | 67 + vendor/modules.txt | 21 + 105 files changed, 24685 insertions(+) create mode 100644 .golangci.yml create mode 100644 Makefile create mode 100644 README.md create mode 100644 cmd/tg-holy-bot/main.go create mode 100644 contrib/tg-holy-bot.openrc create mode 100644 data/config.json create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/holy/celebratoday.go create mode 100644 internal/holy/configLocations.go create mode 100644 internal/holy/cronTask.go create mode 100644 internal/holy/globals.go create mode 100644 internal/holy/readConfig.go create mode 100644 internal/holy/sigHandler.go create mode 100644 internal/holy/types.go create mode 100644 internal/log/log.go create mode 100644 vendor/github.com/NicoNex/echotron/v3/COPYING.LESSER create mode 100644 vendor/github.com/NicoNex/echotron/v3/LICENSE create mode 100644 vendor/github.com/NicoNex/echotron/v3/README.md create mode 100644 vendor/github.com/NicoNex/echotron/v3/api.go create mode 100644 vendor/github.com/NicoNex/echotron/v3/apierror.go create mode 100644 vendor/github.com/NicoNex/echotron/v3/chatadminrights.go create mode 100644 vendor/github.com/NicoNex/echotron/v3/dispatcher.go create mode 100644 vendor/github.com/NicoNex/echotron/v3/games.go create mode 100644 vendor/github.com/NicoNex/echotron/v3/helpers.go create mode 100644 vendor/github.com/NicoNex/echotron/v3/inline.go create mode 100644 vendor/github.com/NicoNex/echotron/v3/menubutton.go create mode 100644 vendor/github.com/NicoNex/echotron/v3/network.go create mode 100644 vendor/github.com/NicoNex/echotron/v3/options.go create mode 100644 vendor/github.com/NicoNex/echotron/v3/passport.go create mode 100644 vendor/github.com/NicoNex/echotron/v3/payments.go create mode 100644 vendor/github.com/NicoNex/echotron/v3/querybuilder.go create mode 100644 vendor/github.com/NicoNex/echotron/v3/simpledsp.go create mode 100644 vendor/github.com/NicoNex/echotron/v3/smap.go create mode 100644 vendor/github.com/NicoNex/echotron/v3/stickers.go create mode 100644 vendor/github.com/NicoNex/echotron/v3/types.go create mode 100644 vendor/github.com/NicoNex/echotron/v3/webapp.go create mode 100644 vendor/github.com/carlmjohnson/requests/.gitignore create mode 100644 vendor/github.com/carlmjohnson/requests/LICENSE create mode 100644 vendor/github.com/carlmjohnson/requests/README.md create mode 100644 vendor/github.com/carlmjohnson/requests/SECURITY.md create mode 100644 vendor/github.com/carlmjohnson/requests/body.go create mode 100644 vendor/github.com/carlmjohnson/requests/builder_core.go create mode 100644 vendor/github.com/carlmjohnson/requests/builder_extras.go create mode 100644 vendor/github.com/carlmjohnson/requests/config.go create mode 100644 vendor/github.com/carlmjohnson/requests/cookies.go create mode 100644 vendor/github.com/carlmjohnson/requests/core_do.go create mode 100644 vendor/github.com/carlmjohnson/requests/core_req.go create mode 100644 vendor/github.com/carlmjohnson/requests/core_url.go create mode 100644 vendor/github.com/carlmjohnson/requests/doc.go create mode 100644 vendor/github.com/carlmjohnson/requests/errorkind.go create mode 100644 vendor/github.com/carlmjohnson/requests/errorkind_string.go create mode 100644 vendor/github.com/carlmjohnson/requests/handler.go create mode 100644 vendor/github.com/carlmjohnson/requests/internal/minitrue/minitrue.go create mode 100644 vendor/github.com/carlmjohnson/requests/recorder.go create mode 100644 vendor/github.com/carlmjohnson/requests/redirects.go create mode 100644 vendor/github.com/carlmjohnson/requests/serializer.go create mode 100644 vendor/github.com/carlmjohnson/requests/transport.go create mode 100644 vendor/github.com/carlmjohnson/requests/validator.go create mode 100644 vendor/github.com/carlmjohnson/requests/validator_handler.go create mode 100644 vendor/github.com/hjson/hjson-go/.gitignore create mode 100644 vendor/github.com/hjson/hjson-go/LICENSE create mode 100644 vendor/github.com/hjson/hjson-go/README.md create mode 100644 vendor/github.com/hjson/hjson-go/build_release.sh create mode 100644 vendor/github.com/hjson/hjson-go/decode.go create mode 100644 vendor/github.com/hjson/hjson-go/encode.go create mode 100644 vendor/github.com/hjson/hjson-go/parseNumber.go create mode 100644 vendor/github.com/robfig/cron/v3/.gitignore create mode 100644 vendor/github.com/robfig/cron/v3/.travis.yml create mode 100644 vendor/github.com/robfig/cron/v3/LICENSE create mode 100644 vendor/github.com/robfig/cron/v3/README.md create mode 100644 vendor/github.com/robfig/cron/v3/chain.go create mode 100644 vendor/github.com/robfig/cron/v3/constantdelay.go create mode 100644 vendor/github.com/robfig/cron/v3/cron.go create mode 100644 vendor/github.com/robfig/cron/v3/doc.go create mode 100644 vendor/github.com/robfig/cron/v3/logger.go create mode 100644 vendor/github.com/robfig/cron/v3/option.go create mode 100644 vendor/github.com/robfig/cron/v3/parser.go create mode 100644 vendor/github.com/robfig/cron/v3/spec.go create mode 100644 vendor/golang.org/x/net/LICENSE create mode 100644 vendor/golang.org/x/net/PATENTS create mode 100644 vendor/golang.org/x/net/html/atom/atom.go create mode 100644 vendor/golang.org/x/net/html/atom/table.go create mode 100644 vendor/golang.org/x/net/html/const.go create mode 100644 vendor/golang.org/x/net/html/doc.go create mode 100644 vendor/golang.org/x/net/html/doctype.go create mode 100644 vendor/golang.org/x/net/html/entity.go create mode 100644 vendor/golang.org/x/net/html/escape.go create mode 100644 vendor/golang.org/x/net/html/foreign.go create mode 100644 vendor/golang.org/x/net/html/iter.go create mode 100644 vendor/golang.org/x/net/html/node.go create mode 100644 vendor/golang.org/x/net/html/nodetype_string.go create mode 100644 vendor/golang.org/x/net/html/parse.go create mode 100644 vendor/golang.org/x/net/html/render.go create mode 100644 vendor/golang.org/x/net/html/token.go create mode 100644 vendor/golang.org/x/net/publicsuffix/data/children create mode 100644 vendor/golang.org/x/net/publicsuffix/data/nodes create mode 100644 vendor/golang.org/x/net/publicsuffix/data/text create mode 100644 vendor/golang.org/x/net/publicsuffix/list.go create mode 100644 vendor/golang.org/x/net/publicsuffix/table.go create mode 100644 vendor/golang.org/x/time/LICENSE create mode 100644 vendor/golang.org/x/time/PATENTS create mode 100644 vendor/golang.org/x/time/rate/rate.go create mode 100644 vendor/golang.org/x/time/rate/sometimes.go create mode 100644 vendor/modules.txt diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..b3f8a03 --- /dev/null +++ b/.golangci.yml @@ -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$ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9ea41cf --- /dev/null +++ b/Makefile @@ -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: \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..bc16729 --- /dev/null +++ b/README.md @@ -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-сервисов. \ No newline at end of file diff --git a/cmd/tg-holy-bot/main.go b/cmd/tg-holy-bot/main.go new file mode 100644 index 0000000..9917821 --- /dev/null +++ b/cmd/tg-holy-bot/main.go @@ -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: */ diff --git a/contrib/tg-holy-bot.openrc b/contrib/tg-holy-bot.openrc new file mode 100644 index 0000000..91e055f --- /dev/null +++ b/contrib/tg-holy-bot.openrc @@ -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: \ No newline at end of file diff --git a/data/config.json b/data/config.json new file mode 100644 index 0000000..4bf539d --- /dev/null +++ b/data/config.json @@ -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" + ] +} \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..6931287 --- /dev/null +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..80b774b --- /dev/null +++ b/go.sum @@ -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= diff --git a/internal/holy/celebratoday.go b/internal/holy/celebratoday.go new file mode 100644 index 0000000..e3a5d30 --- /dev/null +++ b/internal/holy/celebratoday.go @@ -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 рекурсивная функция, ищущая " that closes the next token. If + // non-empty, the subsequent call to Next will return a raw or RCDATA text + // token: one that treats "

" as text instead of an element. + // rawTag's contents are lower-cased. + rawTag string + // textIsRaw is whether the current text token's data is not escaped. + textIsRaw bool + // convertNUL is whether NUL bytes in the current token's data should + // be converted into \ufffd replacement characters. + convertNUL bool + // allowCDATA is whether CDATA sections are allowed in the current context. + allowCDATA bool +} + +// AllowCDATA sets whether or not the tokenizer recognizes as +// the text "foo". The default value is false, which means to recognize it as +// a bogus comment "" instead. +// +// Strictly speaking, an HTML5 compliant tokenizer should allow CDATA if and +// only if tokenizing foreign content, such as MathML and SVG. However, +// tracking foreign-contentness is difficult to do purely in the tokenizer, +// as opposed to the parser, due to HTML integration points: an element +// can contain a that is foreign-to-SVG but not foreign-to- +// HTML. For strict compliance with the HTML5 tokenization algorithm, it is the +// responsibility of the user of a tokenizer to call AllowCDATA as appropriate. +// In practice, if using the tokenizer without caring whether MathML or SVG +// CDATA is text or comments, such as tokenizing HTML to find all the anchor +// text, it is acceptable to ignore this responsibility. +func (z *Tokenizer) AllowCDATA(allowCDATA bool) { + z.allowCDATA = allowCDATA +} + +// NextIsNotRawText instructs the tokenizer that the next token should not be +// considered as 'raw text'. Some elements, such as script and title elements, +// normally require the next token after the opening tag to be 'raw text' that +// has no child elements. For example, tokenizing "a<b>c</b>d" +// yields a start tag token for "", a text token for "a<b>c</b>d", and +// an end tag token for "". There are no distinct start tag or end tag +// tokens for the "" and "". +// +// This tokenizer implementation will generally look for raw text at the right +// times. Strictly speaking, an HTML5 compliant tokenizer should not look for +// raw text if in foreign content: generally needs raw text, but a +// <title> inside an <svg> does not. Another example is that a <textarea> +// generally needs raw text, but a <textarea> is not allowed as an immediate +// child of a <select>; in normal parsing, a <textarea> implies </select>, but +// one cannot close the implicit element when parsing a <select>'s InnerHTML. +// Similarly to AllowCDATA, tracking the correct moment to override raw-text- +// ness is difficult to do purely in the tokenizer, as opposed to the parser. +// For strict compliance with the HTML5 tokenization algorithm, it is the +// responsibility of the user of a tokenizer to call NextIsNotRawText as +// appropriate. In practice, like AllowCDATA, it is acceptable to ignore this +// responsibility for basic usage. +// +// Note that this 'raw text' concept is different from the one offered by the +// Tokenizer.Raw method. +func (z *Tokenizer) NextIsNotRawText() { + z.rawTag = "" +} + +// Err returns the error associated with the most recent ErrorToken token. +// This is typically io.EOF, meaning the end of tokenization. +func (z *Tokenizer) Err() error { + if z.tt != ErrorToken { + return nil + } + return z.err +} + +// readByte returns the next byte from the input stream, doing a buffered read +// from z.r into z.buf if necessary. z.buf[z.raw.start:z.raw.end] remains a contiguous byte +// slice that holds all the bytes read so far for the current token. +// It sets z.err if the underlying reader returns an error. +// Pre-condition: z.err == nil. +func (z *Tokenizer) readByte() byte { + if z.raw.end >= len(z.buf) { + // Our buffer is exhausted and we have to read from z.r. Check if the + // previous read resulted in an error. + if z.readErr != nil { + z.err = z.readErr + return 0 + } + // We copy z.buf[z.raw.start:z.raw.end] to the beginning of z.buf. If the length + // z.raw.end - z.raw.start is more than half the capacity of z.buf, then we + // allocate a new buffer before the copy. + c := cap(z.buf) + d := z.raw.end - z.raw.start + var buf1 []byte + if 2*d > c { + buf1 = make([]byte, d, 2*c) + } else { + buf1 = z.buf[:d] + } + copy(buf1, z.buf[z.raw.start:z.raw.end]) + if x := z.raw.start; x != 0 { + // Adjust the data/attr spans to refer to the same contents after the copy. + z.data.start -= x + z.data.end -= x + z.pendingAttr[0].start -= x + z.pendingAttr[0].end -= x + z.pendingAttr[1].start -= x + z.pendingAttr[1].end -= x + for i := range z.attr { + z.attr[i][0].start -= x + z.attr[i][0].end -= x + z.attr[i][1].start -= x + z.attr[i][1].end -= x + } + } + z.raw.start, z.raw.end, z.buf = 0, d, buf1[:d] + // Now that we have copied the live bytes to the start of the buffer, + // we read from z.r into the remainder. + var n int + n, z.readErr = readAtLeastOneByte(z.r, buf1[d:cap(buf1)]) + if n == 0 { + z.err = z.readErr + return 0 + } + z.buf = buf1[:d+n] + } + x := z.buf[z.raw.end] + z.raw.end++ + if z.maxBuf > 0 && z.raw.end-z.raw.start >= z.maxBuf { + z.err = ErrBufferExceeded + return 0 + } + return x +} + +// Buffered returns a slice containing data buffered but not yet tokenized. +func (z *Tokenizer) Buffered() []byte { + return z.buf[z.raw.end:] +} + +// readAtLeastOneByte wraps an io.Reader so that reading cannot return (0, nil). +// It returns io.ErrNoProgress if the underlying r.Read method returns (0, nil) +// too many times in succession. +func readAtLeastOneByte(r io.Reader, b []byte) (int, error) { + for i := 0; i < 100; i++ { + if n, err := r.Read(b); n != 0 || err != nil { + return n, err + } + } + return 0, io.ErrNoProgress +} + +// skipWhiteSpace skips past any white space. +func (z *Tokenizer) skipWhiteSpace() { + if z.err != nil { + return + } + for { + c := z.readByte() + if z.err != nil { + return + } + switch c { + case ' ', '\n', '\r', '\t', '\f': + // No-op. + default: + z.raw.end-- + return + } + } +} + +// readRawOrRCDATA reads until the next "</foo>", where "foo" is z.rawTag and +// is typically something like "script" or "textarea". +func (z *Tokenizer) readRawOrRCDATA() { + if z.rawTag == "script" { + z.readScript() + z.textIsRaw = true + z.rawTag = "" + return + } +loop: + for { + c := z.readByte() + if z.err != nil { + break loop + } + if c != '<' { + continue loop + } + c = z.readByte() + if z.err != nil { + break loop + } + if c != '/' { + z.raw.end-- + continue loop + } + if z.readRawEndTag() || z.err != nil { + break loop + } + } + z.data.end = z.raw.end + // A textarea's or title's RCDATA can contain escaped entities. + z.textIsRaw = z.rawTag != "textarea" && z.rawTag != "title" + z.rawTag = "" +} + +// readRawEndTag attempts to read a tag like "</foo>", where "foo" is z.rawTag. +// If it succeeds, it backs up the input position to reconsume the tag and +// returns true. Otherwise it returns false. The opening "</" has already been +// consumed. +func (z *Tokenizer) readRawEndTag() bool { + for i := 0; i < len(z.rawTag); i++ { + c := z.readByte() + if z.err != nil { + return false + } + if c != z.rawTag[i] && c != z.rawTag[i]-('a'-'A') { + z.raw.end-- + return false + } + } + c := z.readByte() + if z.err != nil { + return false + } + switch c { + case ' ', '\n', '\r', '\t', '\f', '/', '>': + // The 3 is 2 for the leading "</" plus 1 for the trailing character c. + z.raw.end -= 3 + len(z.rawTag) + return true + } + z.raw.end-- + return false +} + +// readScript reads until the next </script> tag, following the byzantine +// rules for escaping/hiding the closing tag. +func (z *Tokenizer) readScript() { + defer func() { + z.data.end = z.raw.end + }() + var c byte + +scriptData: + c = z.readByte() + if z.err != nil { + return + } + if c == '<' { + goto scriptDataLessThanSign + } + goto scriptData + +scriptDataLessThanSign: + c = z.readByte() + if z.err != nil { + return + } + switch c { + case '/': + goto scriptDataEndTagOpen + case '!': + goto scriptDataEscapeStart + } + z.raw.end-- + goto scriptData + +scriptDataEndTagOpen: + if z.readRawEndTag() || z.err != nil { + return + } + goto scriptData + +scriptDataEscapeStart: + c = z.readByte() + if z.err != nil { + return + } + if c == '-' { + goto scriptDataEscapeStartDash + } + z.raw.end-- + goto scriptData + +scriptDataEscapeStartDash: + c = z.readByte() + if z.err != nil { + return + } + if c == '-' { + goto scriptDataEscapedDashDash + } + z.raw.end-- + goto scriptData + +scriptDataEscaped: + c = z.readByte() + if z.err != nil { + return + } + switch c { + case '-': + goto scriptDataEscapedDash + case '<': + goto scriptDataEscapedLessThanSign + } + goto scriptDataEscaped + +scriptDataEscapedDash: + c = z.readByte() + if z.err != nil { + return + } + switch c { + case '-': + goto scriptDataEscapedDashDash + case '<': + goto scriptDataEscapedLessThanSign + } + goto scriptDataEscaped + +scriptDataEscapedDashDash: + c = z.readByte() + if z.err != nil { + return + } + switch c { + case '-': + goto scriptDataEscapedDashDash + case '<': + goto scriptDataEscapedLessThanSign + case '>': + goto scriptData + } + goto scriptDataEscaped + +scriptDataEscapedLessThanSign: + c = z.readByte() + if z.err != nil { + return + } + if c == '/' { + goto scriptDataEscapedEndTagOpen + } + if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' { + goto scriptDataDoubleEscapeStart + } + z.raw.end-- + goto scriptData + +scriptDataEscapedEndTagOpen: + if z.readRawEndTag() || z.err != nil { + return + } + goto scriptDataEscaped + +scriptDataDoubleEscapeStart: + z.raw.end-- + for i := 0; i < len("script"); i++ { + c = z.readByte() + if z.err != nil { + return + } + if c != "script"[i] && c != "SCRIPT"[i] { + z.raw.end-- + goto scriptDataEscaped + } + } + c = z.readByte() + if z.err != nil { + return + } + switch c { + case ' ', '\n', '\r', '\t', '\f', '/', '>': + goto scriptDataDoubleEscaped + } + z.raw.end-- + goto scriptDataEscaped + +scriptDataDoubleEscaped: + c = z.readByte() + if z.err != nil { + return + } + switch c { + case '-': + goto scriptDataDoubleEscapedDash + case '<': + goto scriptDataDoubleEscapedLessThanSign + } + goto scriptDataDoubleEscaped + +scriptDataDoubleEscapedDash: + c = z.readByte() + if z.err != nil { + return + } + switch c { + case '-': + goto scriptDataDoubleEscapedDashDash + case '<': + goto scriptDataDoubleEscapedLessThanSign + } + goto scriptDataDoubleEscaped + +scriptDataDoubleEscapedDashDash: + c = z.readByte() + if z.err != nil { + return + } + switch c { + case '-': + goto scriptDataDoubleEscapedDashDash + case '<': + goto scriptDataDoubleEscapedLessThanSign + case '>': + goto scriptData + } + goto scriptDataDoubleEscaped + +scriptDataDoubleEscapedLessThanSign: + c = z.readByte() + if z.err != nil { + return + } + if c == '/' { + goto scriptDataDoubleEscapeEnd + } + z.raw.end-- + goto scriptDataDoubleEscaped + +scriptDataDoubleEscapeEnd: + if z.readRawEndTag() { + z.raw.end += len("</script>") + goto scriptDataEscaped + } + if z.err != nil { + return + } + goto scriptDataDoubleEscaped +} + +// readComment reads the next comment token starting with "<!--". The opening +// "<!--" has already been consumed. +func (z *Tokenizer) readComment() { + // 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. + + z.data.start = z.raw.end + defer func() { + if z.data.end < z.data.start { + // It's a comment with no data, like <!-->. + z.data.end = z.data.start + } + }() + + var dashCount int + beginning := true + for { + c := z.readByte() + if z.err != nil { + z.data.end = z.calculateAbruptCommentDataEnd() + return + } + switch c { + case '-': + dashCount++ + continue + case '>': + if dashCount >= 2 || beginning { + z.data.end = z.raw.end - len("-->") + return + } + case '!': + if dashCount >= 2 { + c = z.readByte() + if z.err != nil { + z.data.end = z.calculateAbruptCommentDataEnd() + return + } else if c == '>' { + z.data.end = z.raw.end - len("--!>") + return + } else if c == '-' { + dashCount = 1 + beginning = false + continue + } + } + } + dashCount = 0 + beginning = false + } +} + +func (z *Tokenizer) calculateAbruptCommentDataEnd() int { + raw := z.Raw() + const prefixLen = len("<!--") + if len(raw) >= prefixLen { + raw = raw[prefixLen:] + if hasSuffix(raw, "--!") { + return z.raw.end - 3 + } else if hasSuffix(raw, "--") { + return z.raw.end - 2 + } else if hasSuffix(raw, "-") { + return z.raw.end - 1 + } + } + return z.raw.end +} + +func hasSuffix(b []byte, suffix string) bool { + if len(b) < len(suffix) { + return false + } + b = b[len(b)-len(suffix):] + for i := range b { + if b[i] != suffix[i] { + return false + } + } + return true +} + +// readUntilCloseAngle reads until the next ">". +func (z *Tokenizer) readUntilCloseAngle() { + z.data.start = z.raw.end + for { + c := z.readByte() + if z.err != nil { + z.data.end = z.raw.end + return + } + if c == '>' { + z.data.end = z.raw.end - len(">") + return + } + } +} + +// readMarkupDeclaration reads the next token starting with "<!". It might be +// a "<!--comment-->", a "<!DOCTYPE foo>", a "<![CDATA[section]]>" or +// "<!a bogus comment". The opening "<!" has already been consumed. +func (z *Tokenizer) readMarkupDeclaration() TokenType { + z.data.start = z.raw.end + var c [2]byte + for i := 0; i < 2; i++ { + c[i] = z.readByte() + if z.err != nil { + z.data.end = z.raw.end + return CommentToken + } + } + if c[0] == '-' && c[1] == '-' { + z.readComment() + return CommentToken + } + z.raw.end -= 2 + if z.readDoctype() { + return DoctypeToken + } + if z.allowCDATA && z.readCDATA() { + z.convertNUL = true + return TextToken + } + // It's a bogus comment. + z.readUntilCloseAngle() + return CommentToken +} + +// readDoctype attempts to read a doctype declaration and returns true if +// successful. The opening "<!" has already been consumed. +func (z *Tokenizer) readDoctype() bool { + const s = "DOCTYPE" + for i := 0; i < len(s); i++ { + c := z.readByte() + if z.err != nil { + z.data.end = z.raw.end + return false + } + if c != s[i] && c != s[i]+('a'-'A') { + // Back up to read the fragment of "DOCTYPE" again. + z.raw.end = z.data.start + return false + } + } + if z.skipWhiteSpace(); z.err != nil { + z.data.start = z.raw.end + z.data.end = z.raw.end + return true + } + z.readUntilCloseAngle() + return true +} + +// readCDATA attempts to read a CDATA section and returns true if +// successful. The opening "<!" has already been consumed. +func (z *Tokenizer) readCDATA() bool { + const s = "[CDATA[" + for i := 0; i < len(s); i++ { + c := z.readByte() + if z.err != nil { + z.data.end = z.raw.end + return false + } + if c != s[i] { + // Back up to read the fragment of "[CDATA[" again. + z.raw.end = z.data.start + return false + } + } + z.data.start = z.raw.end + brackets := 0 + for { + c := z.readByte() + if z.err != nil { + z.data.end = z.raw.end + return true + } + switch c { + case ']': + brackets++ + case '>': + if brackets >= 2 { + z.data.end = z.raw.end - len("]]>") + return true + } + brackets = 0 + default: + brackets = 0 + } + } +} + +// startTagIn returns whether the start tag in z.buf[z.data.start:z.data.end] +// case-insensitively matches any element of ss. +func (z *Tokenizer) startTagIn(ss ...string) bool { +loop: + for _, s := range ss { + if z.data.end-z.data.start != len(s) { + continue loop + } + for i := 0; i < len(s); i++ { + c := z.buf[z.data.start+i] + if 'A' <= c && c <= 'Z' { + c += 'a' - 'A' + } + if c != s[i] { + continue loop + } + } + return true + } + return false +} + +// readStartTag reads the next start tag token. The opening "<a" has already +// been consumed, where 'a' means anything in [A-Za-z]. +func (z *Tokenizer) readStartTag() TokenType { + z.readTag(true) + if z.err != nil { + return ErrorToken + } + // Several tags flag the tokenizer's next token as raw. + c, raw := z.buf[z.data.start], false + if 'A' <= c && c <= 'Z' { + c += 'a' - 'A' + } + switch c { + case 'i': + raw = z.startTagIn("iframe") + case 'n': + raw = z.startTagIn("noembed", "noframes", "noscript") + case 'p': + raw = z.startTagIn("plaintext") + case 's': + raw = z.startTagIn("script", "style") + case 't': + raw = z.startTagIn("textarea", "title") + case 'x': + raw = z.startTagIn("xmp") + } + if raw { + z.rawTag = strings.ToLower(string(z.buf[z.data.start:z.data.end])) + } + // Look for a self-closing token (e.g. <br/>). + // + // Originally, we did this by just checking that the last character of the + // tag (ignoring the closing bracket) was a solidus (/) character, but this + // is not always accurate. + // + // We need to be careful that we don't misinterpret a non-self-closing tag + // as self-closing, as can happen if the tag contains unquoted attribute + // values (i.e. <p a=/>). + // + // To avoid this, we check that the last non-bracket character of the tag + // (z.raw.end-2) isn't the same character as the last non-quote character of + // the last attribute of the tag (z.pendingAttr[1].end-1), if the tag has + // attributes. + nAttrs := len(z.attr) + if z.err == nil && z.buf[z.raw.end-2] == '/' && (nAttrs == 0 || z.raw.end-2 != z.attr[nAttrs-1][1].end-1) { + return SelfClosingTagToken + } + return StartTagToken +} + +// readTag reads the next tag token and its attributes. If saveAttr, those +// attributes are saved in z.attr, otherwise z.attr is set to an empty slice. +// The opening "<a" or "</a" has already been consumed, where 'a' means anything +// in [A-Za-z]. +func (z *Tokenizer) readTag(saveAttr bool) { + z.attr = z.attr[:0] + z.nAttrReturned = 0 + // Read the tag name and attribute key/value pairs. + z.readTagName() + if z.skipWhiteSpace(); z.err != nil { + return + } + for { + c := z.readByte() + if z.err != nil || c == '>' { + break + } + z.raw.end-- + z.readTagAttrKey() + z.readTagAttrVal() + // Save pendingAttr if saveAttr and that attribute has a non-empty key. + if saveAttr && z.pendingAttr[0].start != z.pendingAttr[0].end { + z.attr = append(z.attr, z.pendingAttr) + } + if z.skipWhiteSpace(); z.err != nil { + break + } + } +} + +// readTagName sets z.data to the "div" in "<div k=v>". The reader (z.raw.end) +// is positioned such that the first byte of the tag name (the "d" in "<div") +// has already been consumed. +func (z *Tokenizer) readTagName() { + z.data.start = z.raw.end - 1 + for { + c := z.readByte() + if z.err != nil { + z.data.end = z.raw.end + return + } + switch c { + case ' ', '\n', '\r', '\t', '\f': + z.data.end = z.raw.end - 1 + return + case '/', '>': + z.raw.end-- + z.data.end = z.raw.end + return + } + } +} + +// readTagAttrKey sets z.pendingAttr[0] to the "k" in "<div k=v>". +// Precondition: z.err == nil. +func (z *Tokenizer) readTagAttrKey() { + z.pendingAttr[0].start = z.raw.end + for { + c := z.readByte() + if z.err != nil { + z.pendingAttr[0].end = z.raw.end + return + } + switch c { + case '=': + if z.pendingAttr[0].start+1 == z.raw.end { + // WHATWG 13.2.5.32, if we see an equals sign before the attribute name + // begins, we treat it as a character in the attribute name and continue. + continue + } + fallthrough + case ' ', '\n', '\r', '\t', '\f', '/', '>': + // WHATWG 13.2.5.33 Attribute name state + // We need to reconsume the char in the after attribute name state to support the / character + z.raw.end-- + z.pendingAttr[0].end = z.raw.end + return + } + } +} + +// readTagAttrVal sets z.pendingAttr[1] to the "v" in "<div k=v>". +func (z *Tokenizer) readTagAttrVal() { + z.pendingAttr[1].start = z.raw.end + z.pendingAttr[1].end = z.raw.end + if z.skipWhiteSpace(); z.err != nil { + return + } + c := z.readByte() + if z.err != nil { + return + } + if c == '/' { + // WHATWG 13.2.5.34 After attribute name state + // U+002F SOLIDUS (/) - Switch to the self-closing start tag state. + return + } + if c != '=' { + z.raw.end-- + return + } + if z.skipWhiteSpace(); z.err != nil { + return + } + quote := z.readByte() + if z.err != nil { + return + } + switch quote { + case '>': + z.raw.end-- + return + + case '\'', '"': + z.pendingAttr[1].start = z.raw.end + for { + c := z.readByte() + if z.err != nil { + z.pendingAttr[1].end = z.raw.end + return + } + if c == quote { + z.pendingAttr[1].end = z.raw.end - 1 + return + } + } + + default: + z.pendingAttr[1].start = z.raw.end - 1 + for { + c := z.readByte() + if z.err != nil { + z.pendingAttr[1].end = z.raw.end + return + } + switch c { + case ' ', '\n', '\r', '\t', '\f': + z.pendingAttr[1].end = z.raw.end - 1 + return + case '>': + z.raw.end-- + z.pendingAttr[1].end = z.raw.end + return + } + } + } +} + +// Next scans the next token and returns its type. +func (z *Tokenizer) Next() TokenType { + z.raw.start = z.raw.end + z.data.start = z.raw.end + z.data.end = z.raw.end + if z.err != nil { + z.tt = ErrorToken + return z.tt + } + if z.rawTag != "" { + if z.rawTag == "plaintext" { + // Read everything up to EOF. + for z.err == nil { + z.readByte() + } + z.data.end = z.raw.end + z.textIsRaw = true + } else { + z.readRawOrRCDATA() + } + if z.data.end > z.data.start { + z.tt = TextToken + z.convertNUL = true + return z.tt + } + } + z.textIsRaw = false + z.convertNUL = false + +loop: + for { + c := z.readByte() + if z.err != nil { + break loop + } + if c != '<' { + continue loop + } + + // Check if the '<' we have just read is part of a tag, comment + // or doctype. If not, it's part of the accumulated text token. + c = z.readByte() + if z.err != nil { + break loop + } + var tokenType TokenType + switch { + case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z': + tokenType = StartTagToken + case c == '/': + tokenType = EndTagToken + case c == '!' || c == '?': + // We use CommentToken to mean any of "<!--actual comments-->", + // "<!DOCTYPE declarations>" and "<?xml processing instructions?>". + tokenType = CommentToken + default: + // Reconsume the current character. + z.raw.end-- + continue + } + + // We have a non-text token, but we might have accumulated some text + // before that. If so, we return the text first, and return the non- + // text token on the subsequent call to Next. + if x := z.raw.end - len("<a"); z.raw.start < x { + z.raw.end = x + z.data.end = x + z.tt = TextToken + return z.tt + } + switch tokenType { + case StartTagToken: + z.tt = z.readStartTag() + return z.tt + case EndTagToken: + c = z.readByte() + if z.err != nil { + break loop + } + if c == '>' { + // "</>" does not generate a token at all. Generate an empty comment + // to allow passthrough clients to pick up the data using Raw. + // Reset the tokenizer state and start again. + z.tt = CommentToken + return z.tt + } + if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' { + z.readTag(false) + if z.err != nil { + z.tt = ErrorToken + } else { + z.tt = EndTagToken + } + return z.tt + } + z.raw.end-- + z.readUntilCloseAngle() + z.tt = CommentToken + return z.tt + case CommentToken: + if c == '!' { + z.tt = z.readMarkupDeclaration() + return z.tt + } + z.raw.end-- + z.readUntilCloseAngle() + z.tt = CommentToken + return z.tt + } + } + if z.raw.start < z.raw.end { + z.data.end = z.raw.end + z.tt = TextToken + return z.tt + } + z.tt = ErrorToken + return z.tt +} + +// Raw returns the unmodified text of the current token. Calling Next, Token, +// Text, TagName or TagAttr may change the contents of the returned slice. +// +// The token stream's raw bytes partition the byte stream (up until an +// ErrorToken). There are no overlaps or gaps between two consecutive token's +// raw bytes. One implication is that the byte offset of the current token is +// the sum of the lengths of all previous tokens' raw bytes. +func (z *Tokenizer) Raw() []byte { + return z.buf[z.raw.start:z.raw.end] +} + +// convertNewlines converts "\r" and "\r\n" in s to "\n". +// The conversion happens in place, but the resulting slice may be shorter. +func convertNewlines(s []byte) []byte { + for i, c := range s { + if c != '\r' { + continue + } + + src := i + 1 + if src >= len(s) || s[src] != '\n' { + s[i] = '\n' + continue + } + + dst := i + for src < len(s) { + if s[src] == '\r' { + if src+1 < len(s) && s[src+1] == '\n' { + src++ + } + s[dst] = '\n' + } else { + s[dst] = s[src] + } + src++ + dst++ + } + return s[:dst] + } + return s +} + +var ( + nul = []byte("\x00") + replacement = []byte("\ufffd") +) + +// Text returns the unescaped text of a text, comment or doctype token. The +// contents of the returned slice may change on the next call to Next. +func (z *Tokenizer) Text() []byte { + switch z.tt { + case TextToken, CommentToken, DoctypeToken: + s := z.buf[z.data.start:z.data.end] + z.data.start = z.raw.end + z.data.end = z.raw.end + s = convertNewlines(s) + if (z.convertNUL || z.tt == CommentToken) && bytes.Contains(s, nul) { + s = bytes.Replace(s, nul, replacement, -1) + } + if !z.textIsRaw { + s = unescape(s, false) + } + return s + } + return nil +} + +// TagName returns the lower-cased name of a tag token (the `img` out of +// `<IMG SRC="foo">`) and whether the tag has attributes. +// The contents of the returned slice may change on the next call to Next. +func (z *Tokenizer) TagName() (name []byte, hasAttr bool) { + if z.data.start < z.data.end { + switch z.tt { + case StartTagToken, EndTagToken, SelfClosingTagToken: + s := z.buf[z.data.start:z.data.end] + z.data.start = z.raw.end + z.data.end = z.raw.end + return lower(s), z.nAttrReturned < len(z.attr) + } + } + return nil, false +} + +// TagAttr returns the lower-cased key and unescaped value of the next unparsed +// attribute for the current tag token and whether there are more attributes. +// The contents of the returned slices may change on the next call to Next. +func (z *Tokenizer) TagAttr() (key, val []byte, moreAttr bool) { + if z.nAttrReturned < len(z.attr) { + switch z.tt { + case StartTagToken, SelfClosingTagToken: + x := z.attr[z.nAttrReturned] + z.nAttrReturned++ + key = z.buf[x[0].start:x[0].end] + val = z.buf[x[1].start:x[1].end] + return lower(key), unescape(convertNewlines(val), true), z.nAttrReturned < len(z.attr) + } + } + return nil, nil, false +} + +// Token returns the current Token. The result's Data and Attr values remain +// valid after subsequent Next calls. +func (z *Tokenizer) Token() Token { + t := Token{Type: z.tt} + switch z.tt { + case TextToken, CommentToken, DoctypeToken: + t.Data = string(z.Text()) + case StartTagToken, SelfClosingTagToken, EndTagToken: + name, moreAttr := z.TagName() + for moreAttr { + var key, val []byte + key, val, moreAttr = z.TagAttr() + t.Attr = append(t.Attr, Attribute{"", atom.String(key), string(val)}) + } + if a := atom.Lookup(name); a != 0 { + t.DataAtom, t.Data = a, a.String() + } else { + t.DataAtom, t.Data = 0, string(name) + } + } + return t +} + +// SetMaxBuf sets a limit on the amount of data buffered during tokenization. +// A value of 0 means unlimited. +func (z *Tokenizer) SetMaxBuf(n int) { + z.maxBuf = n +} + +// NewTokenizer returns a new HTML Tokenizer for the given Reader. +// The input is assumed to be UTF-8 encoded. +func NewTokenizer(r io.Reader) *Tokenizer { + return NewTokenizerFragment(r, "") +} + +// NewTokenizerFragment returns a new HTML Tokenizer for the given Reader, for +// tokenizing an existing element's InnerHTML fragment. contextTag is that +// element's tag, such as "div" or "iframe". +// +// For example, how the InnerHTML "a<b" is tokenized depends on whether it is +// for a <p> tag or a <script> tag. +// +// The input is assumed to be UTF-8 encoded. +func NewTokenizerFragment(r io.Reader, contextTag string) *Tokenizer { + z := &Tokenizer{ + r: r, + buf: make([]byte, 0, 4096), + } + if contextTag != "" { + switch s := strings.ToLower(contextTag); s { + case "iframe", "noembed", "noframes", "noscript", "plaintext", "script", "style", "title", "textarea", "xmp": + z.rawTag = s + } + } + return z +} diff --git a/vendor/golang.org/x/net/publicsuffix/data/children b/vendor/golang.org/x/net/publicsuffix/data/children new file mode 100644 index 0000000000000000000000000000000000000000..1f16913084d9d0ce2b5012018c4e6c8fcd5e918b GIT binary patch literal 3744 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 literal 0 HcmV?d00001 diff --git a/vendor/golang.org/x/net/publicsuffix/data/nodes b/vendor/golang.org/x/net/publicsuffix/data/nodes new file mode 100644 index 0000000000000000000000000000000000000000..7ac74a1d4fe1efc0a3cdcab113a9ceb138f2f5d9 GIT binary patch literal 52665 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 literal 0 HcmV?d00001 diff --git a/vendor/golang.org/x/net/publicsuffix/data/text b/vendor/golang.org/x/net/publicsuffix/data/text new file mode 100644 index 0000000..38f06ea --- /dev/null +++ b/vendor/golang.org/x/net/publicsuffix/data/text @@ -0,0 +1 @@ +bonagasukeymachinebondigitaloceanspaces3-website-us-west-1bones3-website-us-west-2boomla1-plenitvedestrandiskstationcillair-traffic-controllagdenesnaaseinet-freaksakurastorageboschristmasakikuchikuseihicampinashikiminohostfoldiskussionsbereicheap-east-2bostik-serverrankoshigayachiyodaklakasamatsudoes-itjmaxxxn--12c1fe0brandisrechtrainingkpmgdbarclays3-fips-us-gov-west-1bostonakijinsekikogentlentapisa-geekarlsoyoriikarmoyoshiokanravoues3-eu-west-3botdashgabadaddjabbottjomelhus-northeast-1bouncemerckmsdsclouditchyouriparsakuratanishiwakinderoyurihonjournalistreaklinksakurawebredirectmelbourneboutiquebecologialaichaugianglassessmentsakyotanabellunoorepairbusanagochigasakishimabarakawagoeboutireserve-onlineboyfriendoftheinternetflixn--12cfi8ixb8lorenskogleezebozen-sudtirolovableprojectjxn--12co0c3b4evalleaostamayukuhashimokitayamaxarnetbankanzakiyosatokorozawap-southeast-7bozen-suedtirolovepopartindevsalangenissandoyusuharazurefdienbienishikatakayamatsushigemrstudio-prodoyolasitequipmentateshinanomachintaifun-dnshome-webservercellillesandefjordietateyamapartments3-ca-central-1bplacedogawarabikomaezakirunord-frontierepbodynathomebuiltwithdarklangevagrarmeniazurestaticappspaceusercontentproxy9guacuedaeguambulancechireadmyblogoip-dynamica-west-180recipescaracalculatorskeninjambylimanowarudaetnaamesjevuemielnogatabuseating-organicbcg123homepagexlimitedeltaitogliattips3-ap-northeast-3utilitiesmall-websozaibetsubamericanfamilydstcgroupperimo-siemenscaledekadena4ufcfaninohekinanporovnospamproxyokoteatonamidsundeportebetsukubank123kotisivultrobjectselinogradimo-i-ranamizuhobby-siteaches-yogano-ip-ddnsgurugbydgoszczecin-addrammenuorogerscblackbaudcdn-edgestackhero-networkinggroupowiat-band-campaignieznoboribetsubsc-paywhirlimodumemergencymruovatlassian-dev-buildereclaims3-ap-south-12hparasiteasypanelblagrigentobamaceiobbcn-north-123websitebuildersvp4lima-citychyattorneyagawafaicloudinedre-eiker2-deloitteastus2000123webseiteckidsmynascloudfrontendofinternet-dnsnasaarlandds3-ap-northeast-123sitewebcamauction-acornimsite164-balsan-suedtirolillyokosukanoyakage2balsfjorddnss3-accesspoint-fips3-ap-east-123paginawebadorsiteshikagamiishibechambagrice-labss3-123minsidaarborteamsterdamnserverbaniamallamazonwebservices-123miwebaccelastx4432-b-datacenterprisesakievennodebalancernfshostrowwlkpnftstorage123hjemmeside5brasiliadboxosascoli-picenord-odalovesickarpaczest-a-la-maisondre-landivtasvuodnakamurataiwanumatajimidorivnebravendbarefootballangenovarahkkeravjuh-ohtawaramotoineppueblockbusternikkoelnishikatsuragit-repostre-toteneiheijiitatebayashikaoizumizakitchenishikawazukamisatokonamerikawaueu-2bresciaogashimadachicappadovaapstecnologiazurewebsitests3-external-1bridgestonebrindisicilynxn--1ck2e1baremetalvdalipaynow-dnsdojobservablehqhaccaltanissettaikikugawaltervistablogivestbyglandroverhallaakesvuemielecceu-3broadwayusuitarumizusawabroke-itkmaxxn--1ctwolominamatargithubpreviewskrakowebview-assetsalatrobeneventochiokinoshimagentositempurlplfinancialpusercontentksatmalluccalvinklein-brb-hostingliwicebrokereportmpartsalon-1brothercules-developerauniteroirmeteorappartypo3serverevistathellebrumunddaluhanskartuzyuullensvanguardivttasvuotnakaniikawatanagurabrusselsaloonissayokoshibahikariyalibabacloudcsaltdalukoweddinglobodontexisteingeekaruizawabryanskierniewicebrynebwcloud-os-instancesaludixn--1lqs03nissedaluroyuzawabzhitomirhcloudiyclientozsdegreeclinicapitalonecliniquenoharaclothingdustdatadetectranbycngouv0cnpyatigorskiptveterinaireadymadethis-a-anarchistjordalshalsencntrani-andria-barletta-trani-andriacodespotenzagancoffeedbackanagawarszawashtenawsapprunnerdpoliticaarpharmaciensanjosoyrocommunity-prochowicecomochizukillvivanovoldacompanyantagonistockholmestrandurumisakimobetsumidanangodoesntexistmein-iservschulegallerycomparemarkerryhotelsannancomputercomsecretrosnubargainsureadthedocs-hosteditorxn--0trq7p7nnishimeraugustow-corp-staticblitzgierzgoraktyubinskaunicommuneencoreapiacenzabc01kapp-ionosegawadlugolekaascolipicenocelotennishiawakuracingheannakadomarineat-urlive-oninomiyakonojorpelandeus-canvasitebinatsukigatajiri234condoshiibabybluebitemasekd1conferenceconstruction-vaporcloudplatformshangriladeskjakamaiedge-stagingreaterconsuladobeio-static-accesscamdvrcampaniaconsultantraniandriabarlettatraniandriaconsultingrebedocapooguycontactivetrailwaycontagematsubaracontractorstababymilkashiwaraconvexecute-apictetcieszyncookingretakahatakaishimokawacooperativano-frankivskjervoyagecoprofesionalchikugodaddyn-o-saurealestatefarmerseinecorsicable-modemoneycosenzakopanecosidnsiskinkyowariasahikawasmercouchpotatofriesannoheliohostrodawaracouncil-central-1couponstackitagawassamukawatarikuzentakatairacozoracpservernamegataishinomakiloappsanokashiwazakiyosellsyourhomeftpharmacyonabaruminamiizukaminokawanishiaizubangecqldyndns-at-homedepotaruiocrankycrdyndns-at-workisboringsakershus-central-1creditcardyndns-blogsytecreditunion-webpaaskoyabenogiftsantamariakecremonasharissadistoloseyouriphdfcbankasserversembokutamakiyosunndalcrewp2cricketnedalcrimeast-kazakhstanangercrispmanagercrminamimakinfinitigooglecodebergrimstadyndns-freeboxosloisirsantoandrealtysnesanukinternationalcrotonecrowniphilipsaobernardovre-eikercrsaogoncanthoboleslawiecommerce-shopitsitecruisesaotomeldalcryptonomichiharacuiabacgiangiangrycuisinellahppictureshinordeste-idclkasukabeatsardegnarvikasumigaurayasudacuneocuritibackdropalermoarekembuchikumagayagawakkanaikawachinaganoharamcoacharitydalaheadjuegoshikibichuocutegirlfriendyndns-homednsardiniafedoraproject-studynaliasnesoddeno-stagingroks-thisayamanobearalvahkijoburgrayjayleagueschokokekscholarshipschoolbusinessebytomaridagawalmartransiphotographysiofeirafembetsukuintuitranslatefermockaszubytemarketingvollferraraferrarinuyamashinazawaferreroticahcesuolohmusashimurayamaizurunschuldockatowicefetsundyndns-remotewdyndns-iphonefossarlfgrongrossetouchijiwadediboxn--2m4a15efhvalerfilegear-sg-1filminamioguni5finalfinancefinnoyfirebaseapplinzinvestmentschulplattforminamisanrikubetsupersalevangerfirenetlibp2phutholdingsmartlabelingroundhandlingroznysaikisosakitahatakamatsukawafirenzefirestonefirmdaleilaocairtelebitbucketrzynh-servebeero-stageiseiroutingthecloudyndns-serverisignfishingokaseljeephuyenfitjarfitnessettsurugiminamitanefjalerflesbergrphxn--2scrj9caravanylvenetoeidsvollutrausercontentoyotsukaidownloadnpassenger-associationl-ams-1flickragerotikagaminordlandyndns-webhareidsbergriwataraindropikeflierneflirflogintohmalopolskanitransportefloppymntransurlfloraclegovcloudappschulserverflorencefloripadualstackatsushikabeautypedreamhosterschwarzgwesleyfloristanohatakahamalselveruminamiuonumatrixn--30rr7yflororoscrapper-sitefltrapanikolaeventscrappingrueflutterflowest1-us1-plenitravelersinsuranceflyfncarbonia-iglesias-carboniaiglesiascarboniafndyndns-wikindlegnicagliaricoharulezajskierval-d-aosta-valleyfoolfor-ourfor-somedusajscryptedyndns-worksarufutsunomiyawakasaikaitakokamikoaniikappudopaaskvolloanswatchesasayamattelemarkhangelskasuyakumodsasebofagefor-theaterfordeatnuniversitysvardoforexrotheshopwarezzoforgotdnscrysecuritytacticscwesteuropencraftravinhlonganforli-cesena-forlicesenaforlifestyleirfjordyndns1forsalesforceforsandasuolojcloud-ver-jpcargoboavistanbulsan-sudtirolutskarumaifminamifuranofortalfosneservehttpbincheonfotrdynnsassarintlon-2foxn--32vp30hachinoheavyfozfr-par-1fr-par-2franalytics-gatewayfredrikstadynservebbsaudafreedesktopazimuthaibinhphuocprapidynuddnsfreebox-osauheradyndns-mailovecollegefantasyleaguefreemyiphostyhostinguidedyn-berlincolnfreesitefreetlservehumourfreightrentin-sudtirolfrenchkisshikirkeneserveircarrdrayddns-ipatriafresenius-central-2friuli-v-giuliarafriuli-ve-giuliafriuli-vegiuliafriuli-venezia-giuliafriuli-veneziagiuliafriuli-vgiuliafriuliv-giuliafriulive-giuliafriulivegiuliafriulivenezia-giuliafriuliveneziagiuliafriulivgiuliafrlfroganserveminecraftrentin-sued-tirolfrognfrolandynuhosting-clusterfrom-akamaiorigin-staginguitarservemp3from-alfrom-arfrom-azureedgekey-stagingujaratmetacentrumbriafrom-callyfrom-cockpitrentin-suedtirolfrom-ctrentino-a-adigefrom-dcasacampinagrandebulsan-suedtiroluxenonconnectoyourafrom-debianfrom-flatangerfrom-gamvikatsuyamashikizunokuniminamiashigarafrom-hidnservep2pimientakazakinzais-a-bruinsfanfrom-iafrom-idynv6from-ilfrom-in-the-bandairtrafficplexus-2from-kservepicservequakefrom-kyfrom-lamericanexpresseljordyroyrvikingroceryfrom-malvikaufentigerfrom-mdfrom-meetrentino-aadigefrom-mifunefrom-mnfrom-modalenfrom-mservesarcasmolaquilarvikautokeinotionfrom-mtlservicebuskerudfrom-ncasertainairflowersalvadorfrom-ndfrom-nefrom-nhlfanfrom-njsevastopolitiendafrom-nminamiyamashirokawanabeepsongdalenviknagaraholtaleniwaizumiotsurugashimagazinefrom-nvalled-aostaobaolbia-tempio-olbiatempioolbialowiezachpomorskiengiangujohanamakinoharafrom-nyatomigrationidfrom-ohdancefrom-okegawatsonionjukujitawarafrom-orfrom-palmasfjordenfrom-praxihuanfrom-ris-a-bulls-fanfrom-schmidtre-gauldalfrom-sdfrom-tnfrom-txn--3bst00minanofrom-utsiracusagamiharafrom-val-daostavalleyfrom-vtrentino-alto-adigefrom-wafrom-wiardwebspace-hostorachampionshiptodayfrom-wvalledaostargetrentino-altoadigefrom-wyfrosinonefrostalowa-wolawafroyal-commissionporterfruskydivingulenfujiiderafujikawaguchikonefujiminokamoenais-a-candidatefujinomiyadatsunanjoetsulublindesnesevenassieradzfujiokazakirovogradoyfujisatoshoesewestus2fujisawafujishiroishidakabiratoridecafederation-ranchernigovallee-aosteroyfujitsuruokagoshimamurogawafujiyoshidattorelayfukayabeagleboardfukuchiyamadattoweberlevagangaviikanonjis-a-catererfukudomigawafukuis-a-celticsfanfukumitsubishigakiryuohkurafukuokakamigaharafukuroishikariwakunigamihamadavvenjargalsacefukusakisarazure-apigeefukuyamagatakaharunjargaularavellinodeobjectstoragefunabashiriuchinadavvesiidaknongunmaoris-a-chefarsundyndns-office-on-the-webflowtest-iservebloginlinefunagatakahashimamakishiwadazaifudaigoguovdageaidnunusualpersonfunahashikamiamakusatsumasendaisenergyeonggildeskaliszfundfunkfeuerfunnelsexyfuoiskujukuriyamandalfuosskodjeezfurubirafurudonordre-landfurukawaiishoppingushikamifuranore-og-uvdalfusodegaurafussagemakerfutabayamaguchinomihachimanagementrentino-s-tirolfutboldlygoingnowhere-for-more-og-romsdalfuttsurutashinais-a-conservativefsnoasakakinokiafuturecmsheezyfuturehostingxn--3ds443gzfuturemailingfvghakonehakubaclieu-1hakuis-a-cpaneliv-dnshimosuwalkis-a-cubicle-slaveroykenhakusandnessjoenhaldenhalfmoonscaleforcehalsaitamatsukuris-a-democratrentino-stirolham-radio-opocznortonkotsumomodelscapetownnews-staginghamburghammarfeastasiahamurakamigoris-a-designerhanamigawahanawahandahandcraftedugit-pages-researchedmarketplacehangglidinghangoutrentino-sud-tirolhannannestadhannoshiroomghanoipinbrowsersafetymarketshimotsukehanyuzenhappoumuginowaniihamatamakawajimangolffanshimotsumayfirstreamlitappinkddiamondshinichinanhasamazoncognito-idpdnshinjotelulucaniahasaminami-alpshinjukuleuvenicehashbanghasudahasura-appinokofuefukihaborovigoldpoint2thisamitsukehasvikfh-muensterhatenablogisticsxn--3e0b707ehatenadiaryhatinhachiojiyachtshellhatogayahabacninhbinhdinhktrentino-sudtirolhatoyamazakitakamiizumisanofidongthapmircloudnsupdaterhatsukaichikawamisatohokkaidonnakanotoddenhattfjelldalhayashimamotobusellfyis-a-doctoruncontainershinkamigotourshinshinotsupplyhazuminobushibuyahikobearblogsiteleaf-south-1helpgfoggiahelsinkitakatakanabeardubaioirasebastopoleapcellclstagehirnhemneshinshirohemsedalhepforgeblockshintokushimaheroyhetemlbfanheyflowhoswholidayhigashiagatsumagoianiahigashichichibuzentsujiiehigashihiroshimanehigashiizumozakitakyushunantankhakassiahigashikagawahigashikagurasoedahigashikawakitaaikitamiharunzenhigashikurumegurownproviderhigashimatsushimarcherkasykkylvenneslaskerrypropertieshintomikasaharahigashimatsuyamakitaakitadaitoigawahigashimurayamamotorcycleshinyoshitomiokamishihorohigashinarusells-for-lesshiojirishirifujiedahigashinehigashiomitamamurausukitamotosumy-routerhigashiosakasayamanakakogawahigashishirakawamatakanezawahigashisumiyoshikawaminamiaikitanakagusukumodenaklodzkobierzycehigashitsunotairesindevicenzamamihokksundhigashiurawa-mazowszexposeducationhercules-appioneerhigashiyamatokoriyamanashijonawatehigashiyodogawahigashiyoshinogaris-a-financialadvisor-aurdalhiphoplixn--3hcrj9cashorokanaiehippythonanywherealtorhiraizumisatokaizukakudamatsuehirakatashinagawahiranais-a-fullstackharkivallee-d-aostehirarahiratsukagawahirayahoooshikamagayaitakaokalmykiahitachiomiyakehitachiotaketakarazukaluganskharkovalleeaostehitradinghjartdalhjelmelandholyhomegoodshioyaltaketomisatoyakokonoehomeipippugliahomelinuxn--3pxu8khersonyhomesecuritymacaparecidahomesecuritypccwuozuerichardliguriahomesenseeringhomeskleppivohostinghomeunixn--41ahondahonjyoitakasagonohejis-a-geekhmelnitskiyamashikokuchuohornindalhorsells-for-usgovcloudapilottotalhortenkawahospitalhotelwithflightshirahamatonbetsupportrentino-sued-tirolhotmailhoyangerhoylandetakasakitashiobarahrsnillfjordhungyenhurdalhurumajis-a-goodyearhyllestadhyogoris-a-greenhypernodessaitokamachippubetsuikitaurahyugawarahyundaiwafuneis-not-certifiedis-savedis-slickhplayitrentinos-tirolis-uberleetrentinostirolis-very-badis-very-evillasalleitungsenis-very-goodis-very-niceis-very-sweetpepperugiais-with-thebandoomdnshisuifuettertdasnetzisk01isk02jenv-arubahcavuotnagahamaroygardengerdalp1jeonnamsosnowiecateringebumbleshrimperiajetztrentinosud-tiroljevnakerjewelryjlljls-sto1jls-sto2jls-sto365jmpiwatejnjdfirmalborkdaljouwwebhoptokigawajoyokaichibahccavuotnagaivuotnagaokakyotambabia-goraclecloudappssejny-2jozis-a-knightpointtokashikiwakuratejpmorgangwonjpncatfoodrivelandrobakamaihd-stagingloomy-gatewayjprshitaramakoseis-a-libertariankosherokuappizzakoshimizumakis-a-linux-useranishiaritabashikshacknetlifylkesbiblackfridaynightrentino-suedtirolkoshugheshizuokamitsuekosugekotohiradomainshoujis-a-llamarugame-hostrowieconomiasadogadobeioruntimedicinakanojogaszkolamdongnairlineedleasingkotourakouhokumakogenkounosunnydaykouyamassa-carrara-massacarraramassabuzzkouzushimassivegridkozagawakozakis-a-musiciankozowienkppspbarsycenterprisecloudbeesusercontentaveusercontentawktoyonakagyokutoyonezawauiusercontentdllive-websitebizenakasatsunairportashkentatamotors3-deprecatedgcaffeinehimejibxos3-eu-central-1krasnikahokutokyotangopensocialkrasnodarkredumbrellapykrelliankristiansandcatshowakristiansundkrodsheradkrokstadelvaldaostaticsigdalkropyvnytskyis-a-nascarfankrymisasaguris-a-nursells-itrentinoa-adigekumamotoyamasudakumanowtvaomoriguchiharag-cloud-charternopilawakayamafeloabatochigiehtavuoatnabudejjurkumatorinokumejimatlabgkumenanyokkaichirurgiens-dentistes-en-francekundenkunisakis-a-painterhostsolutionshiranukamisunagawakunitachiaraisaijolsterkunitomigusukukis-a-patsfankunneppubtlsiiitesilknx-serversicherungkuokgroupkomatsushimasoykurgankurobeebyteappenginekurogiminamiawajikis-a-personaltrainerkuroisoftwarendalenugkuromatsunais-a-photographermesserlikescandypoppdalkuronkurotakikawasakis-a-playershiftrentinoaadigekushirogawakustanais-a-republicanonoichinosekigaharakusupabaseoullensakerkutchanelkutnokuzumakis-a-rockstarachowicekvafjordkvalsundkvamfamplifyappchizipifony-1kvanangenkvinesdalkvinnheradkviteseidatingkvitsoykwpspdnsimple-urlmktgorymmvareservdmoliserniamombetsuppliesimplesitemonza-brianzapposirdalmonza-e-della-brianzaptomobegetmyipirangallocustomer-ocienciamonzabrianzaramonzaebrianzamonzaedellabrianzamordoviamorenarashinoharamoriyamatsumotofukemoriyoshiminamibosogndalmormonstermoroyamatsunomortgagemoscowiiheyaizuwakamatsubushikusakadogawamoseushimoichikuzenmosjoenmoskenesiskomaganemosslingmotegirlymoviemovimientonsbergmtnmtranaritakurashikis-a-socialistordalmuikaminoyamaxunison-serviceslupskomforbarrell-of-knowledgeu-central-2mukodairamunakatanemuosattemupl-wawsappspacehostedpicardmurmanskommunalforbundmurotorcraftrentinosued-tirolmusashinodesakatakatsukis-a-soxfanmuseumisawamusicampobassociateslzmutsuzawamutualmyactivedirectorymyaddrangedalmyamazeplaystation-cloudyclustersmushcdn77-sslgbtrentinosuedtirolmyasustor-elvdalmycloudnasushiobaramydattolocalcertificationmydbservermyddnskingmydissentrentinsud-tirolmydnsokamogawamydobissmarterthanyousrcfdmydsokndalmyeffectrentinsudtirolmyfastly-edgemyfirewalledreplittlestargardmyforumisconfusedmyfritzmyftpaccessolardalmyhome-servermyjinomykolaivencloud66mymailermymediapcatholicp1mynetnamegawamyokohamamatsudamypeplatter-applcube-serversusakis-a-studentalmypetsolundbeckommunemyphotoshibalena-devicesomamypigboatsomnaturalmypsxn--45br5cylmyrdbxn--45brj9caxiaskimitsubatamicrolightingloppennemysecuritycamerakermyshopblocksoowilliamhillmyshopifymyspreadshopselectrentinsued-tirolmysynologyeongnamdinhs-heilbronnoysundmytabitordermythic-beastsopotrentinsuedtirolmytis-a-bloggermytuleap-partnersor-odalmyvnchernovtsydneymywiredbladehostingpodhalepodlasiellakdnepropetrovskanlandpodzonepohlpoivronpokerpokrovskomonotteroypolkowicepoltavalle-aostavangerpolyspacepomorzeszowinbarsyonlinexus-3ponpesaro-urbino-pesarourbinopesaromasvuotnarusawapordenonepornporsangerporsangugeporsgrunnanpoznanprdprereleaserveftplockerprgmrprimeteleportrentoyookanazawaprincipenzaprivatelinkyard-cloudletsor-varangerprivatizehealthinsuranceprogressivegarsheiyufueliv-apiemontepromoldefinimaringatlangsondriobranconakamai-stagingpropertysfjordprotectionprotonettrevisohuissier-justiceprudentialpruszkowindowsservegame-serverprvcyou2-localtonetroandindependent-inquest-a-la-masionprvwineprzeworskogpunyukis-a-teacherkassyncloudpupulawypussycatanzarowinnersorfoldpvhachirogatakamoriokakegawapvtrogstadpwchiryukyuragifuchungbukharavennakaiwanairforceopzqotoyohashimotottoris-a-techietis-a-gurusgovcloudappnodeartheworkpcasinorddaluxuryqponiatowadaqsldqualifioapplumbingotembaixadaqualyhqpartnerqualyhqportalquangngais-a-therapistoiaquangninhthuanquangtritonoshonais-an-accountantshiraois-a-hard-workershirakolobrzegersundojin-dslattuminisitequickconnectroitskomorotsukamiminequicksytesorocabalestrandabergamobaragusabaerobaticketsorreisahayakawakamiichinomiyagitbookinghosteurovisionrenderquipelementsortlandquizzesorumishimatsumaebashimogosenqzzventurestaurantulaspeziavestfoldvestnesquaresinstagingvestre-slidrecifedexperts-comptablesrhtrustkaneyamazoevestre-totenris-an-anarchistorfjordvestvagoyvevelstadvfsrlvibo-valentiavibovalentiavideovinhphuchonanbungotakadaptableclercaobanglogowegroweiboliviajessheimmobilienisshingucciminamiechizeniyodogawavinnicanva-hosted-embedzin-buttervinnytsiavipsinaapplurinacionalvirginankokubunjis-an-artistorjdevcloudjiffyresdalvirtual-uservecounterstrikevirtualservervirtualuserveexchangevisakuholeckochikushinonsenasakuchinotsuchiurakawaviterboknowsitallvivianvivoryvixn--4dbgdty6choseikarugallupfizervkis-an-engineeringvlaanderenvladikavkazimierz-dolnyvladimirennesoyvlogvmitoyoakevolvologdanskonskowolayangroupixolinodeusercontentrentinosudtirolvolyngdalvoorlopervossevangenvotevotingvotoyosatoyonovpnplus-west-3vps-hostrynvusercontentunespritesoundcastripperwithgoogleapiszwithyoutubentrendhostingwiwatsukiyonotebook-fipstuff-4-salewixsitewixstudio-fipstufftoread-booksnesowawjgorawkzwloclawekonsulatinowruzhgorodwmcloudwmeloywmflabsurveyspectrumisugitolgap-north-1wnextdirectwpdevcloudwoodsideliveryworldworse-thanhphohochiminhackerwowiosrvrlessourcecraftromsakegawawpenginepoweredwphostedmailwpmucdn77-storagencywpmudevinappsusonowpsquaredwroclawsglobalacceleratorahimeshimagine-proxywtcp4wtfastly-terrariuminamiminowawwwitdkontogurawzmiuwajimaxn--54b7fta0cchoshichikashukudoyamalatvuopmicrosoftbankasaokamikitayamatsurindigenamsskoganeindustriaxn--55qw42gxn--55qx5dxn--5dbhl8dxn--5js045dxn--5rtp49chowderxn--5rtq34konyvelolipopmckinseyxn--5su34j936bgsgxn--5tzm5gxn--6btw5axn--6frz82gxn--6orx2rxn--6qq986b3xlxn--7t0a264choyodobashichinohealthcareersame-previeweirxn--80aaa0cvacationsuzakarpattiaaxn--80adxhksuzukananiimilanoticiassurgerydxn--80ao21axn--80aqecdr1axn--80asehdbasicserver-on-k3s3-me-south-1xn--80aswgxn--80audiopsysuzukis-an-actorxn--8dbq2axn--8ltr62koobindalxn--8pvr4uzhhorodxn--8y0a063axn--90a1affinitylotterybnikeeneticp0xn--90a3academiamibubbleappspotagerxn--90aeroportsinfolkebibleangaviikafjordpabianicentralus-1xn--90aishobaraoxn--90amcprequalifymeiwamizawaxn--90azhytomyradweblikes-piedmontunkoninfernovecorespeedpartnerxn--9dbq2axn--9et52uzsprytromsojampanasonichitachinakagawarmiastaplesame-appaviaxn--9krt00axn--9tfkyxn--andy-iraxn--aroport-byamembersvalbarduponthewifidelitypeformitourismilexn--asky-iraxn--aurskog-hland-jnbasilicataniaukraanghkeisenebakkeshibukawakeliwebhostingdyniakunemurorangecloudscalebookonlineustarostwodzislawdev-myqnapcloudflarecn-northwest-1xn--avery-yuasakuragawaxn--b-5gausdalxn--b4w605ferdxn--balsan-sdtirol-nsbasketballfinanzjaworznoticeableksvikapsiciliaurland-4-salernombrendlyngenflfanpachihayaakasakawaharaffleentrycloudflare-ipfstgstageorgeorgiap-southeast-4xn--bck1b9a5dre4chrome-central-1xn--bdddj-mrabdxn--bearalvhki-y4axn--berlevg-jxaxn--bhcavuotna-s4axn--bhccavuotna-k7axn--bidr-5nachikatsuuraxn--bievt-0qa2hosted-by-previderxn--bjddar-ptarnobrzegxn--blt-elabkhaziaxn--bmlo-grafana-developmentunnelmolexn--bod-2naturbruksgymnxn--bozen-sdtirol-2obihirosakikamijimatsuzakis-an-entertainerxn--brnny-wuacademy-firewall-gatewayxn--brnnysund-m8accident-investigation-aptibleadpagespeedmobilizeropschaefflerxn--brum-voagaturindalxn--btsfjord-9zaxn--bulsan-sdtirol-nsbatsfjordigickaracologneu-south-1xn--c1avgxn--c2br7gxn--c3s14mittwaldserverxn--cck2b3bauhauspostman-echofunatoriginstitutemp-dns3-object-lambda-urlolitapunkaragandaurskog-holandinggff5xn--cckwcxetdxn--cesena-forl-mcbnpparibashkiriaxn--cesenaforl-i8axn--cg4bkis-byklecznagatoromskoguchilloutsystemscloudsitevaksdalxn--ciqpnxn--clchc0ea0b2g2a9gcdxn--czr694beppublic-inquiryonagoyaustevollivingitlabbvieeemfakefurniturealtimedio-campidano-mediocampidanomediobninsk8s3-eu-north-1xn--czrs0t0xn--czru2dxn--d1acj3beskidyn-ip24xn--d1alfastlylbarrel-of-knowledgesuite-stagingivingjemnes3-globalatinabelementorayomitanobservereggio-emilia-romagnarutoolsztynsetatsunofficialivornomniwebspaceconfigma-governmentattoolforgeu-4xn--d1aturystykanieruchomoscientistreakusercontentrvarggatrysiljanewayxn--d5qv7z876chungnamdalseidfjordrrppgwangjulvikashibatakatorindustriesteinkjerxn--davvenjrga-y4axn--djrs72d6uyxn--djty4kooris-a-lawyerxn--dnna-graingerxn--drbak-wuaxn--dyry-iraxn--e1a4churchateblobanazawanggoupilefrakkestadtvsamegawaxn--eckvdtc9dxn--efvn9svchitosetogakushimotoganexn--efvy88hadanorth-kazakhstanxn--ehqz56nxn--elqq16hadselbuyshouseshimonitayanagitappwritesthisblogdnsfor-better-thanhhoamishirasatohnoshookuwanakatsugawaxn--eveni-0qa01gaxn--f6qx53axn--fct429kopervikmpspawnbaseminexn--fhbeiarnxn--finny-yuaxn--fiq228c5hsbciprianiigataipeigersundtwhitesnowflakeyword-onfabricafjsamnangerxn--fiq64bestbuyshoparenagareyamagicpatternsapporokunohealth-carereformemorialombardiademergentagents3-sa-east-1xn--fiqs8sveioxn--fiqz9svelvikongsvingerxn--fjord-lraxn--fjq720axn--fl-ziaxn--flor-jraxn--flw351exn--forl-cesena-fcbremangerxn--forlcesena-c8axn--fpcrj9c3dxn--frde-grajewolterskluwerxn--frna-woarais-certifiedxn--frya-hraxn--fzc2c9e2circleaninglugsjcbgmbhartinnxn--fzys8d69uvgmailxn--g2xx48ciscofreakadnsaliases121xn--gckr3f0fastvps-serveronakatombetsumitakagiizeaburxn--gecrj9cistrondheiminamiiseharaxn--ggaviika-8ya47haebaruericssonlanxesshimonosekikawaxn--gildeskl-g0axn--givuotna-8yanagawaxn--gjvik-wuaxn--gk3at1exn--gls-elacaixaxn--gmq050is-coolblogspotrentinoalto-adigexn--gmqw5axn--gnstigbestellen-zvbetaharanzanquangnamasteigenkainanaejrietiengiangjerdrumemsetaxiijimarnardalombardynamisches-dns3-us-east-2xn--gnstigliefern-wobiraxn--h-2failxn--h1ahnxn--h1alizxn--h2breg3evenesvn-reposphinxn--45q11cooldns-cloudflareglobalashovhackclubartowhmincommbankazoxn--h2brj9c8citadelhichisoctrangminakamichikaiseiyoichipsamparaglidingmodellingmx-central-1xn--h3cuzk1dielddanuorrittogojomediatechnologyeongbukoryokamikawanehonbetsuwanouchikuhokuryugasakis-a-liberalxn--hbmer-xqaxn--hcesuolo-7ya35bhzc66xn--hebda8bialystokkepnord-aurdalwaysdatabase44-sandboxfuseekarasjohkameyamatotakadaustrheimbamblebtimnetzgorzeleccocottemprendealstahaugesundereggio-calabriap-southeast-5xn--hery-iraxn--hgebostad-g3axn--hkkinen-5waxn--hmmrfeasta-s4accident-prevention-fleeklogesquare7xn--hnefoss-q1axn--hobl-iraxn--holtlen-hxaxn--hpmir-xqaxn--hxt814exn--hyanger-q1axn--hylandet-54axn--i1b6b1a6a2exn--imr513nxn--indery-fyanaizuxn--io0a7is-foundationxn--j1adpmnxn--j1aefauskedsmokorsetagayaseralingenoaiusercontentranoyxn--j1ael8bielawalbrzychaselfiparliamentayninhachijoinmcdireggiocalabriauth-fipsiqcxjavald-aostatichostreak-linkanumazuryokozempresashibetsukumiyamagasakinkobayashimofusagaeroclubmedecin-berlindasdaejeonbuk0emmafann-arborlanddl-o-g-i-nayoro0o0g0xn--j1amhagakhanhhoabinhduongxn--j6w193gxn--jlq480n2rgxn--jlster-byandexcloudxn--jrpeland-54axn--jvr189miuraxn--k7yn95exn--karmy-yuaxn--kbrq7oxn--kcrx77d1x4axn--kfjord-iuaxn--klbu-woaxn--klt787dxn--kltp7dxn--kltx9axn--klty5xn--4dbrk0cexn--koluokta-7ya57hagebostadxn--kprw13dxn--kpry57dxn--kput3is-gonexn--krager-gyaotsurnadalxn--kranghke-b0axn--krdsherad-m8axn--krehamn-dxaxn--krjohka-hwab49jejusgovtrafficmanagerxn--ksnes-uuaxn--kvfjord-nxaxn--kvitsy-fyasakaiminatoyotap-southeast-3xn--kvnangen-k0axn--l-1fairwindsurfbsbxn--1qqw23axn--l1accentureklamborghinikonantoshimatsusakahoginozawaonsennanmokurennebunkyonanaoshimamateramochausercontentuscanyxn--laheadju-7yasugithubusercontentushungryxn--langevg-jxaxn--lcvr32dxn--ldingen-q1axn--leagaviika-52biella-speziauthgear-stagingitpagemrappui-productions3-eu-west-1xn--lesund-huaxn--lgbbat1ad8jelasticbeanstalklabudhabikinokawabajddarvanedgecompute-1xn--lgrd-poacctfcloudflareanycastdlibestadultuvalle-daostakkomakis-an-actresshiraokamitondabayashiogamagoriziaxn--lhppi-xqaxn--linds-pratoyotomiyazakis-into-animeinforumzxn--loabt-0qaxn--lrdal-sraxn--lrenskog-54axn--lt-liaciticurus-4xn--lten-granexn--lury-iraxn--m3ch0j3axn--mely-iraxn--merker-kuaxn--mgb2ddeswidnicanva-appspjelkavikomvuxn--42c2d9axn--mgb9awbfbx-osaveincloudyndns-picsbsarpsborgripeeweeklylotteryxn--mgba3a3ejtuxfamilyxn--mgba3a4f16axn--mgba3a4fra1-dell-ogliastrapiappleyxn--mgba7c0bbn0axn--mgbaam7a8haibarakitahiroshimap-south-2xn--mgbab2bdxn--mgbah1a3hjkrdxn--mgbai9a5eva00bielskoczow-credentialless-staticblitzlgjerstadiscordsays3-us-gov-east-1xn--mgbai9azgqp6jelenia-goraxn--mgbayh7gparallelxn--mgbbh1a71exn--mgbc0a9azcgxn--mgbca7dzdoxn--mgbcpq6gpa1axn--mgberp4a5d4a87gxn--mgberp4a5d4arxn--mgbgu82axn--mgbi4ecexperimentswidnikitagatakinouexn--mgbpl2fhskosaigawaxn--mgbqly7c0a67fbcivilaviation-riopretogitsulidluyaniizaporizhzhiaxn--mgbqly7cvafricanvacode-builder-stg-builderxn--mgbt3dhdxn--mgbtf8fldrvaroyxn--mgbtx2bieszczadygeyachimataijiiyamanouchikujoinvilleirvikarasjoketokuyamarumorimachidauthgearapps-1and1xn--mgbx4cd0abogadobeaemcloud-ip6xn--mix082fbxosaves-the-whalessandria-trani-barletta-andriatranibarlettaandriaxn--mix891fedjeducatorprojectransfer-webapp-fipsavonatalxn--mjndalen-64axn--mk0axindependent-inquiryxn--mk1bu44clanbibaiduckdnsamsclubin-vpndnsamsungotsukisofukushimaniwamannordreisa-hockeynutwentertainmentoystre-slidrettozawaxn--mkru45is-into-carshiratakahagiangxn--mlatvuopmi-s4axn--mli-tlavagiskexn--mlselv-iuaxn--moreke-juaxn--mori-qsakurais-into-cartoonshishikuis-a-hunterxn--mosjen-eyasuokanmakiyokawaraxn--mot-tlavangenxn--mre-og-romsdal-qqbuserveboltuyenquangbinhthuanxn--msy-ula0haiduongxn--mtta-vrjjat-k7aflakstadaokayamazonaws-cloud9xn--muost-0qaxn--mxtq1miyazure-mobilexn--ngbc5azdxn--ngbe9e0axn--ngbrxn--4gbriminiserverxn--nit225kosakaerodromegadgets-itcouldbeworfashionstorebaseballooningroks-theatrentin-sud-tirolxn--nmesjevuemie-tcbalsan-sudtirolkuszczytnoopstmnxn--nnx388axn--nodellogliastraderxn--nqv7fs00emaxn--nry-yla5gxn--ntso0iqx3axn--ntsq17gxn--nttery-byaeservehalflifeinsurancexn--nvuotna-hwaxn--nyqy26axn--o1achernivtsienaharimakeupsunappgafanxn--o3cw4haiphongonnakayamangyshlakamaized-stagingxn--o3cyx2axn--od0algardxn--od0aq3bievathletajimabaria-vungtaudibleborkangereggioemiliaromagnarviikamiokameokamakurazakiwielunnerehabmereisenishinomiyashironomurauthordalandroidgnishiizunazukifr-1xn--ogbpf8flekkefjordxn--oppegrd-ixaxn--ostery-fyatsukannamimatakasugais-into-gamessinaplesknshisognexn--osyro-wuaxn--otu796dxn--p1acfolkswiebodzindependent-commissionxn--p1ais-leetrentinoaltoadigexn--pgbs0dhlxn--4it168dxn--porsgu-sta26fedorainfracloudfunctionsaxoxn--pssu33lxn--pssy2uxn--q7ce6axn--q9jyb4cldmail-boxn--1lqs71durbanamexnetgamersandvikcoromantovalle-d-aostavernxn--qcka1pmclerkstagexn--qqqt11miyotamanoxn--qxa6axn--qxamjondalenxn--rady-iraxn--rdal-poaxn--rde-ulazioxn--rdy-0nabaris-localplayerxn--rennesy-v1axn--rhkkervju-01afedorapeopleikangerxn--rholt-mragowoltlab-democraciaxn--rhqv96gxn--rht27zxn--rht3dxn--rht61exn--risa-5navigationxn--risr-iraxn--rland-uuaxn--rlingen-mxaxn--rmskog-byatsushiroxn--rny31hair-surveillancexn--rovu88bifukagawalesundiscordsezpisdnipropetrovskypecorindependent-paneliv-cdn77-securealmesswithdns3-us-gov-west-1xn--rros-granvindafjordxn--rskog-uuaxn--rst-0navois-lostrolekamaishimodatexn--rsta-framercanvaswinoujsciencexn--rvc1e0am3exn--ryken-vuaxn--ryrvik-byawaraxn--s-1faithainguyenxn--s9brj9clever-clouderavpagexn--sandnessjen-ogbizxn--sandy-yuaxn--sdtirol-n2axn--seral-lraxn--ses554gxn--sgne-graphicswisspockongsbergxn--skierv-utazurecontainerimakanegasakis-not-axn--skjervy-v1axn--skjk-soaxn--sknit-yqaxn--sknland-fxaxn--slat-5navuotnaroyxn--slt-elabrdns-dynamic-dnsabruzzombieidskogasawarackmazerbaijan-mayenbaidarchitectestingrok-freeddnsgeekgalaxyzxn--smla-hraxn--smna-gratangenxn--snase-nraxn--sndre-land-0cbigv-infolldalomodxn--11b4c3discountry-snowplowiczeladzw-staticblitzxn--snes-poaxn--snsa-roaxn--sr-aurdal-l8axn--sr-fron-q1axn--sr-odal-q1axn--sr-varanger-ggbiharstadotsubetsugaruhr-uni-bochumsochimkenthickarasuyamashikeu-south-2xn--srfold-byawatahamaxn--srreisa-q1axn--srum-gratis-a-bookkeepermarriottwmailxn--stfold-9xaxn--stjrdal-s1axn--stjrdalshalsen-sqbihoronobeokagakikiraraumaintenanceu1-plenittedalomzaporizhzhegurindependent-review3s3-us-west-1xn--stre-toten-zcbikedaemongolianishinoomotegoismailillehammerfeste-iparmatta-varjjathruherebungoonomutazas3-us-west-2xn--t60b56axn--tckwebthingsxn--tiq49xqyjellybeanxn--tjme-hraxn--tn0agrondarqtxn--tnsberg-q1axn--tor131oxn--trany-yuaxn--trentin-sd-tirol-rzbioxn--trentin-sdtirol-7vbirkenesoddtangentapps3-website-ap-northeast-1xn--trentino-sd-tirol-c3bittermezproxyonagunicloudiscourses3-website-ap-southeast-1xn--trentino-sdtirol-szbjerkreimdbarcelonagawakuyabukihokuizumocha-sandboxmitakeharaudnedalnishigorlicebinordkapparisor-fronishiharakrehamnishiazaibradescotaribeiraogakicks-assncf-ipfs3-ap-southeast-2ixboxeroxajuniperecreationirasakibigawaknoluoktachikawafflecellpagest-mon-blogueurodirumaceratagajobojibmdeuxfleurs3-ap-southeast-1337xn--trentinosd-tirol-rzbjugnishinoshimatsuurautoscanaryggeemrnotebooks-prodeobservableusercontentatarantoyokawap-southeast-6116-bambinagisobetsuldalpha-myqnapcloudaccess3-ap-northeast-2038xn--trentinosdtirol-7vbloombergentingjesdalondonetskaratsuginamikatagamimozaokinawashirosatobishimadridvagsoyereithuathienhueusc-de-east-1xn--trentinsd-tirol-6vblushakotanishiokoppegardiscoverdalondrinapolicevervaultjeldsundisharparochernihivgubarclaycards3-fips-us-gov-east-1xn--trentinsdtirol-nsbmoattachments3-website-ap-southeast-2xn--trgstad-r1axn--trna-woaxn--troms-zuaxn--tysvr-vraxn--uc0atvegaspydebergxn--uc0ay4axn--uist22hakatanorthflankazunotogawaxn--uisz3gxn--unjrga-rtarpitxn--unup4yxn--uuwu58axn--vads-jraxn--valle-aoste-ebbtxn--valle-d-aoste-ehboehringerikerxn--valleaoste-e7axn--valledaoste-ebbvadsoccerxn--vard-jraxn--vegrshei-c0axn--vermgensberater-ctb-hostingxn--vermgensberatung-pwbms3-website-eu-west-1xn--vestvgy-ixa6oxn--vg-yiabmwcloudnonproddagestangevje-og-hornnes3-website-sa-east-1xn--vgan-qoaxn--vgsy-qoa0j0xn--vgu402cleverappsangotpantheonsitexn--vhquvelvetuckerxn--vler-qoaxn--vre-eiker-k8axn--vrggt-xqadxn--vry-yla5gxn--vuq861bnrweatherchannelsdvrdns3-website-us-east-1xn--w4r85el8fhu5dnraxn--w4rs40lxn--wcvs22dxn--wgbh1clickrisinglesjaguarvodkafkashiharaxn--wgbl6axn--xhq521bolognagasakikonaircraftraeumtgeradealerdalcest-le-patron-forgerockyotobetsucks3-website-us-gov-west-1xn--xkc2al3hye2axn--xkc2dl3a5ee0hakodatexn--y9a3aquarellebesbyencowayxn--yer-znavyxn--yfro4i67oxn--ygarden-p1axn--ygbi2ammxn--4it797kontumintshizukuishimojis-a-landscaperspectakashimarshallstatebankhmelnytskyivalleedaostexn--ystre-slidre-ujbolzano-altoadigextraspace-to-rentalstomakomaibaravocats3-eu-west-2xn--zbx025dxn--zf0avxn--4pvxs4allxn--zfr164bomlodingenishitosashimizunaminamidaitomanaustdalopparachutingjovikareliancexnbayernxtooldevicexz \ No newline at end of file diff --git a/vendor/golang.org/x/net/publicsuffix/list.go b/vendor/golang.org/x/net/publicsuffix/list.go new file mode 100644 index 0000000..7ab8b3c --- /dev/null +++ b/vendor/golang.org/x/net/publicsuffix/list.go @@ -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 +} diff --git a/vendor/golang.org/x/net/publicsuffix/table.go b/vendor/golang.org/x/net/publicsuffix/table.go new file mode 100644 index 0000000..a376632 --- /dev/null +++ b/vendor/golang.org/x/net/publicsuffix/table.go @@ -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) diff --git a/vendor/golang.org/x/time/LICENSE b/vendor/golang.org/x/time/LICENSE new file mode 100644 index 0000000..6a66aea --- /dev/null +++ b/vendor/golang.org/x/time/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +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 Inc. 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. diff --git a/vendor/golang.org/x/time/PATENTS b/vendor/golang.org/x/time/PATENTS new file mode 100644 index 0000000..7330990 --- /dev/null +++ b/vendor/golang.org/x/time/PATENTS @@ -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. diff --git a/vendor/golang.org/x/time/rate/rate.go b/vendor/golang.org/x/time/rate/rate.go new file mode 100644 index 0000000..8f6c7f4 --- /dev/null +++ b/vendor/golang.org/x/time/rate/rate.go @@ -0,0 +1,430 @@ +// Copyright 2015 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 rate provides a rate limiter. +package rate + +import ( + "context" + "fmt" + "math" + "sync" + "time" +) + +// Limit defines the maximum frequency of some events. +// Limit is represented as number of events per second. +// A zero Limit allows no events. +type Limit float64 + +// Inf is the infinite rate limit; it allows all events (even if burst is zero). +const Inf = Limit(math.MaxFloat64) + +// Every converts a minimum time interval between events to a Limit. +func Every(interval time.Duration) Limit { + if interval <= 0 { + return Inf + } + return 1 / Limit(interval.Seconds()) +} + +// A Limiter controls how frequently events are allowed to happen. +// It implements a "token bucket" of size b, initially full and refilled +// at rate r tokens per second. +// Informally, in any large enough time interval, the Limiter limits the +// rate to r tokens per second, with a maximum burst size of b events. +// As a special case, if r == Inf (the infinite rate), b is ignored. +// See https://en.wikipedia.org/wiki/Token_bucket for more about token buckets. +// +// The zero value is a valid Limiter, but it will reject all events. +// Use NewLimiter to create non-zero Limiters. +// +// Limiter has three main methods, Allow, Reserve, and Wait. +// Most callers should use Wait. +// +// Each of the three methods consumes a single token. +// They differ in their behavior when no token is available. +// If no token is available, Allow returns false. +// If no token is available, Reserve returns a reservation for a future token +// and the amount of time the caller must wait before using it. +// If no token is available, Wait blocks until one can be obtained +// or its associated context.Context is canceled. +// +// The methods AllowN, ReserveN, and WaitN consume n tokens. +// +// Limiter is safe for simultaneous use by multiple goroutines. +type Limiter struct { + mu sync.Mutex + limit Limit + burst int + tokens float64 + // last is the last time the limiter's tokens field was updated + last time.Time + // lastEvent is the latest time of a rate-limited event (past or future) + lastEvent time.Time +} + +// Limit returns the maximum overall event rate. +func (lim *Limiter) Limit() Limit { + lim.mu.Lock() + defer lim.mu.Unlock() + return lim.limit +} + +// Burst returns the maximum burst size. Burst is the maximum number of tokens +// that can be consumed in a single call to Allow, Reserve, or Wait, so higher +// Burst values allow more events to happen at once. +// A zero Burst allows no events, unless limit == Inf. +func (lim *Limiter) Burst() int { + lim.mu.Lock() + defer lim.mu.Unlock() + return lim.burst +} + +// TokensAt returns the number of tokens available at time t. +func (lim *Limiter) TokensAt(t time.Time) float64 { + lim.mu.Lock() + _, tokens := lim.advance(t) // does not mutate lim + lim.mu.Unlock() + return tokens +} + +// Tokens returns the number of tokens available now. +func (lim *Limiter) Tokens() float64 { + return lim.TokensAt(time.Now()) +} + +// NewLimiter returns a new Limiter that allows events up to rate r and permits +// bursts of at most b tokens. +func NewLimiter(r Limit, b int) *Limiter { + return &Limiter{ + limit: r, + burst: b, + } +} + +// Allow reports whether an event may happen now. +func (lim *Limiter) Allow() bool { + return lim.AllowN(time.Now(), 1) +} + +// AllowN reports whether n events may happen at time t. +// Use this method if you intend to drop / skip events that exceed the rate limit. +// Otherwise use Reserve or Wait. +func (lim *Limiter) AllowN(t time.Time, n int) bool { + return lim.reserveN(t, n, 0).ok +} + +// A Reservation holds information about events that are permitted by a Limiter to happen after a delay. +// A Reservation may be canceled, which may enable the Limiter to permit additional events. +type Reservation struct { + ok bool + lim *Limiter + tokens int + timeToAct time.Time + // This is the Limit at reservation time, it can change later. + limit Limit +} + +// OK returns whether the limiter can provide the requested number of tokens +// within the maximum wait time. If OK is false, Delay returns InfDuration, and +// Cancel does nothing. +func (r *Reservation) OK() bool { + return r.ok +} + +// Delay is shorthand for DelayFrom(time.Now()). +func (r *Reservation) Delay() time.Duration { + return r.DelayFrom(time.Now()) +} + +// InfDuration is the duration returned by Delay when a Reservation is not OK. +const InfDuration = time.Duration(math.MaxInt64) + +// DelayFrom returns the duration for which the reservation holder must wait +// before taking the reserved action. Zero duration means act immediately. +// InfDuration means the limiter cannot grant the tokens requested in this +// Reservation within the maximum wait time. +func (r *Reservation) DelayFrom(t time.Time) time.Duration { + if !r.ok { + return InfDuration + } + delay := r.timeToAct.Sub(t) + if delay < 0 { + return 0 + } + return delay +} + +// Cancel is shorthand for CancelAt(time.Now()). +func (r *Reservation) Cancel() { + r.CancelAt(time.Now()) +} + +// CancelAt indicates that the reservation holder will not perform the reserved action +// and reverses the effects of this Reservation on the rate limit as much as possible, +// considering that other reservations may have already been made. +func (r *Reservation) CancelAt(t time.Time) { + if !r.ok { + return + } + + r.lim.mu.Lock() + defer r.lim.mu.Unlock() + + if r.lim.limit == Inf || r.tokens == 0 || r.timeToAct.Before(t) { + return + } + + // calculate tokens to restore + // The duration between lim.lastEvent and r.timeToAct tells us how many tokens were reserved + // after r was obtained. These tokens should not be restored. + restoreTokens := float64(r.tokens) - r.limit.tokensFromDuration(r.lim.lastEvent.Sub(r.timeToAct)) + if restoreTokens <= 0 { + return + } + // advance time to now + t, tokens := r.lim.advance(t) + // calculate new number of tokens + tokens += restoreTokens + if burst := float64(r.lim.burst); tokens > burst { + tokens = burst + } + // update state + r.lim.last = t + r.lim.tokens = tokens + if r.timeToAct == r.lim.lastEvent { + prevEvent := r.timeToAct.Add(r.limit.durationFromTokens(float64(-r.tokens))) + if !prevEvent.Before(t) { + r.lim.lastEvent = prevEvent + } + } +} + +// Reserve is shorthand for ReserveN(time.Now(), 1). +func (lim *Limiter) Reserve() *Reservation { + return lim.ReserveN(time.Now(), 1) +} + +// ReserveN returns a Reservation that indicates how long the caller must wait before n events happen. +// The Limiter takes this Reservation into account when allowing future events. +// The returned Reservation’s OK() method returns false if n exceeds the Limiter's burst size. +// Usage example: +// +// r := lim.ReserveN(time.Now(), 1) +// if !r.OK() { +// // Not allowed to act! Did you remember to set lim.burst to be > 0 ? +// return +// } +// time.Sleep(r.Delay()) +// Act() +// +// Use this method if you wish to wait and slow down in accordance with the rate limit without dropping events. +// If you need to respect a deadline or cancel the delay, use Wait instead. +// To drop or skip events exceeding rate limit, use Allow instead. +func (lim *Limiter) ReserveN(t time.Time, n int) *Reservation { + r := lim.reserveN(t, n, InfDuration) + return &r +} + +// Wait is shorthand for WaitN(ctx, 1). +func (lim *Limiter) Wait(ctx context.Context) (err error) { + return lim.WaitN(ctx, 1) +} + +// WaitN blocks until lim permits n events to happen. +// It returns an error if n exceeds the Limiter's burst size, the Context is +// canceled, or the expected wait time exceeds the Context's Deadline. +// The burst limit is ignored if the rate limit is Inf. +func (lim *Limiter) WaitN(ctx context.Context, n int) (err error) { + // The test code calls lim.wait with a fake timer generator. + // This is the real timer generator. + newTimer := func(d time.Duration) (<-chan time.Time, func() bool, func()) { + timer := time.NewTimer(d) + return timer.C, timer.Stop, func() {} + } + + return lim.wait(ctx, n, time.Now(), newTimer) +} + +// wait is the internal implementation of WaitN. +func (lim *Limiter) wait(ctx context.Context, n int, t time.Time, newTimer func(d time.Duration) (<-chan time.Time, func() bool, func())) error { + lim.mu.Lock() + burst := lim.burst + limit := lim.limit + lim.mu.Unlock() + + if n > burst && limit != Inf { + return fmt.Errorf("rate: Wait(n=%d) exceeds limiter's burst %d", n, burst) + } + // Check if ctx is already cancelled + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + // Determine wait limit + waitLimit := InfDuration + if deadline, ok := ctx.Deadline(); ok { + waitLimit = deadline.Sub(t) + } + // Reserve + r := lim.reserveN(t, n, waitLimit) + if !r.ok { + return fmt.Errorf("rate: Wait(n=%d) would exceed context deadline", n) + } + // Wait if necessary + delay := r.DelayFrom(t) + if delay == 0 { + return nil + } + ch, stop, advance := newTimer(delay) + defer stop() + advance() // only has an effect when testing + select { + case <-ch: + // We can proceed. + return nil + case <-ctx.Done(): + // Context was canceled before we could proceed. Cancel the + // reservation, which may permit other events to proceed sooner. + r.Cancel() + return ctx.Err() + } +} + +// SetLimit is shorthand for SetLimitAt(time.Now(), newLimit). +func (lim *Limiter) SetLimit(newLimit Limit) { + lim.SetLimitAt(time.Now(), newLimit) +} + +// SetLimitAt sets a new Limit for the limiter. The new Limit, and Burst, may be violated +// or underutilized by those which reserved (using Reserve or Wait) but did not yet act +// before SetLimitAt was called. +func (lim *Limiter) SetLimitAt(t time.Time, newLimit Limit) { + lim.mu.Lock() + defer lim.mu.Unlock() + + t, tokens := lim.advance(t) + + lim.last = t + lim.tokens = tokens + lim.limit = newLimit +} + +// SetBurst is shorthand for SetBurstAt(time.Now(), newBurst). +func (lim *Limiter) SetBurst(newBurst int) { + lim.SetBurstAt(time.Now(), newBurst) +} + +// SetBurstAt sets a new burst size for the limiter. +func (lim *Limiter) SetBurstAt(t time.Time, newBurst int) { + lim.mu.Lock() + defer lim.mu.Unlock() + + t, tokens := lim.advance(t) + + lim.last = t + lim.tokens = tokens + lim.burst = newBurst +} + +// reserveN is a helper method for AllowN, ReserveN, and WaitN. +// maxFutureReserve specifies the maximum reservation wait duration allowed. +// reserveN returns Reservation, not *Reservation, to avoid allocation in AllowN and WaitN. +func (lim *Limiter) reserveN(t time.Time, n int, maxFutureReserve time.Duration) Reservation { + lim.mu.Lock() + defer lim.mu.Unlock() + + if lim.limit == Inf { + return Reservation{ + ok: true, + lim: lim, + tokens: n, + timeToAct: t, + } + } else if lim.limit == 0 { + var ok bool + if lim.burst >= n { + ok = true + lim.burst -= n + } + return Reservation{ + ok: ok, + lim: lim, + tokens: lim.burst, + timeToAct: t, + } + } + + t, tokens := lim.advance(t) + + // Calculate the remaining number of tokens resulting from the request. + tokens -= float64(n) + + // Calculate the wait duration + var waitDuration time.Duration + if tokens < 0 { + waitDuration = lim.limit.durationFromTokens(-tokens) + } + + // Decide result + ok := n <= lim.burst && waitDuration <= maxFutureReserve + + // Prepare reservation + r := Reservation{ + ok: ok, + lim: lim, + limit: lim.limit, + } + if ok { + r.tokens = n + r.timeToAct = t.Add(waitDuration) + + // Update state + lim.last = t + lim.tokens = tokens + lim.lastEvent = r.timeToAct + } + + return r +} + +// advance calculates and returns an updated state for lim resulting from the passage of time. +// lim is not changed. +// advance requires that lim.mu is held. +func (lim *Limiter) advance(t time.Time) (newT time.Time, newTokens float64) { + last := lim.last + if t.Before(last) { + last = t + } + + // Calculate the new number of tokens, due to time that passed. + elapsed := t.Sub(last) + delta := lim.limit.tokensFromDuration(elapsed) + tokens := lim.tokens + delta + if burst := float64(lim.burst); tokens > burst { + tokens = burst + } + return t, tokens +} + +// durationFromTokens is a unit conversion function from the number of tokens to the duration +// of time it takes to accumulate them at a rate of limit tokens per second. +func (limit Limit) durationFromTokens(tokens float64) time.Duration { + if limit <= 0 { + return InfDuration + } + seconds := tokens / float64(limit) + return time.Duration(float64(time.Second) * seconds) +} + +// tokensFromDuration is a unit conversion function from a time duration to the number of tokens +// which could be accumulated during that duration at a rate of limit tokens per second. +func (limit Limit) tokensFromDuration(d time.Duration) float64 { + if limit <= 0 { + return 0 + } + return d.Seconds() * float64(limit) +} diff --git a/vendor/golang.org/x/time/rate/sometimes.go b/vendor/golang.org/x/time/rate/sometimes.go new file mode 100644 index 0000000..6ba99dd --- /dev/null +++ b/vendor/golang.org/x/time/rate/sometimes.go @@ -0,0 +1,67 @@ +// Copyright 2022 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 rate + +import ( + "sync" + "time" +) + +// Sometimes will perform an action occasionally. The First, Every, and +// Interval fields govern the behavior of Do, which performs the action. +// A zero Sometimes value will perform an action exactly once. +// +// # Example: logging with rate limiting +// +// var sometimes = rate.Sometimes{First: 3, Interval: 10*time.Second} +// func Spammy() { +// sometimes.Do(func() { log.Info("here I am!") }) +// } +type Sometimes struct { + First int // if non-zero, the first N calls to Do will run f. + Every int // if non-zero, every Nth call to Do will run f. + Interval time.Duration // if non-zero and Interval has elapsed since f's last run, Do will run f. + + mu sync.Mutex + count int // number of Do calls + last time.Time // last time f was run +} + +// Do runs the function f as allowed by First, Every, and Interval. +// +// The model is a union (not intersection) of filters. The first call to Do +// always runs f. Subsequent calls to Do run f if allowed by First or Every or +// Interval. +// +// A non-zero First:N causes the first N Do(f) calls to run f. +// +// A non-zero Every:M causes every Mth Do(f) call, starting with the first, to +// run f. +// +// A non-zero Interval causes Do(f) to run f if Interval has elapsed since +// Do last ran f. +// +// Specifying multiple filters produces the union of these execution streams. +// For example, specifying both First:N and Every:M causes the first N Do(f) +// calls and every Mth Do(f) call, starting with the first, to run f. See +// Examples for more. +// +// If Do is called multiple times simultaneously, the calls will block and run +// serially. Therefore, Do is intended for lightweight operations. +// +// Because a call to Do may block until f returns, if f causes Do to be called, +// it will deadlock. +func (s *Sometimes) Do(f func()) { + s.mu.Lock() + defer s.mu.Unlock() + if s.count == 0 || + (s.First > 0 && s.count < s.First) || + (s.Every > 0 && s.count%s.Every == 0) || + (s.Interval > 0 && time.Since(s.last) >= s.Interval) { + f() + s.last = time.Now() + } + s.count++ +} diff --git a/vendor/modules.txt b/vendor/modules.txt new file mode 100644 index 0000000..77eef06 --- /dev/null +++ b/vendor/modules.txt @@ -0,0 +1,21 @@ +# github.com/NicoNex/echotron/v3 v3.45.0 +## explicit; go 1.19 +github.com/NicoNex/echotron/v3 +# github.com/carlmjohnson/requests v0.25.1 +## explicit; go 1.23.0 +github.com/carlmjohnson/requests +github.com/carlmjohnson/requests/internal/minitrue +# github.com/hjson/hjson-go v3.3.0+incompatible +## explicit +github.com/hjson/hjson-go +# github.com/robfig/cron/v3 v3.0.1 +## explicit; go 1.12 +github.com/robfig/cron/v3 +# golang.org/x/net v0.51.0 +## explicit; go 1.25.0 +golang.org/x/net/html +golang.org/x/net/html/atom +golang.org/x/net/publicsuffix +# golang.org/x/time v0.5.0 +## explicit; go 1.18 +golang.org/x/time/rate