Update linter, fix linter errors
This commit is contained in:
747
.golangci.yml
747
.golangci.yml
@@ -1,29 +1,12 @@
|
|||||||
|
version: "2"
|
||||||
run:
|
run:
|
||||||
tests: false
|
go: "1.25"
|
||||||
modules-download-mode: readonly
|
modules-download-mode: readonly
|
||||||
|
tests: false
|
||||||
allow-parallel-runners: true
|
allow-parallel-runners: true
|
||||||
go: '1.24'
|
|
||||||
issues:
|
|
||||||
exclude-dirs:
|
|
||||||
- data
|
|
||||||
- vendor
|
|
||||||
output:
|
|
||||||
sort-results: true
|
|
||||||
linters-settings:
|
|
||||||
gosec:
|
|
||||||
excludes:
|
|
||||||
# Оно не используется для повышения безопасности или каких-то таких секъюрых вещей, оно используется для выборки
|
|
||||||
# произвольного элемента из массива, так что ИДИ НАХУЙ с этой проверкой и не еби мозги липовой безопасностью.
|
|
||||||
- G404 # Insecure random number source (rand)
|
|
||||||
linters:
|
linters:
|
||||||
disable-all: true
|
default: none
|
||||||
enable:
|
enable:
|
||||||
- errcheck
|
|
||||||
- gosimple
|
|
||||||
- govet
|
|
||||||
- ineffassign
|
|
||||||
- staticcheck
|
|
||||||
- unused
|
|
||||||
- asasalint
|
- asasalint
|
||||||
- asciicheck
|
- asciicheck
|
||||||
- bodyclose
|
- bodyclose
|
||||||
@@ -31,25 +14,30 @@ linters:
|
|||||||
- contextcheck
|
- contextcheck
|
||||||
- decorder
|
- decorder
|
||||||
- durationcheck
|
- durationcheck
|
||||||
|
- errcheck
|
||||||
- errchkjson
|
- errchkjson
|
||||||
- errorlint
|
- errorlint
|
||||||
- exhaustive
|
- exhaustive
|
||||||
- exhaustruct
|
- exptostd
|
||||||
- copyloopvar
|
|
||||||
- ginkgolinter
|
- ginkgolinter
|
||||||
- gocheckcompilerdirectives
|
- gocheckcompilerdirectives
|
||||||
- gocritic
|
- gocritic
|
||||||
|
- godoclint
|
||||||
- godot
|
- godot
|
||||||
- err113
|
- godox
|
||||||
- gofmt
|
- goprintffuncname
|
||||||
- goimports
|
|
||||||
- gosec
|
- gosec
|
||||||
- gosmopolitan
|
- gosmopolitan
|
||||||
|
- govet
|
||||||
- grouper
|
- grouper
|
||||||
- importas
|
- importas
|
||||||
|
- ineffassign
|
||||||
|
- intrange
|
||||||
- ireturn
|
- ireturn
|
||||||
- makezero
|
- makezero
|
||||||
- mirror
|
- mirror
|
||||||
|
- misspell
|
||||||
|
- modernize
|
||||||
- nakedret
|
- nakedret
|
||||||
- nilerr
|
- nilerr
|
||||||
- nilnil
|
- nilnil
|
||||||
@@ -57,13 +45,714 @@ linters:
|
|||||||
- noctx
|
- noctx
|
||||||
- nolintlint
|
- nolintlint
|
||||||
- nonamedreturns
|
- nonamedreturns
|
||||||
|
- perfsprint
|
||||||
- predeclared
|
- predeclared
|
||||||
- promlinter
|
- promlinter
|
||||||
- reassign
|
- reassign
|
||||||
- revive
|
- revive
|
||||||
- rowserrcheck
|
- rowserrcheck
|
||||||
- stylecheck
|
- staticcheck
|
||||||
- unconvert
|
- unconvert
|
||||||
|
- unused
|
||||||
|
- usestdlibvars
|
||||||
- wastedassign
|
- wastedassign
|
||||||
- whitespace
|
- wsl_v5
|
||||||
- wsl
|
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
|
||||||
|
- 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$
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// Package main содержит основную функцию для запуска логики бота.
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -18,7 +19,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
//go:embed version
|
//go:embed version
|
||||||
var version string
|
var version string //nolint: grouper
|
||||||
|
|
||||||
// main - фактичеcки, начало и основное тело программы.
|
// main - фактичеcки, начало и основное тело программы.
|
||||||
func main() {
|
func main() {
|
||||||
@@ -36,7 +37,7 @@ func main() {
|
|||||||
DisableLevelTruncation: false,
|
DisableLevelTruncation: false,
|
||||||
DisableColors: true,
|
DisableColors: true,
|
||||||
FullTimestamp: true,
|
FullTimestamp: true,
|
||||||
TimestampFormat: "2006-01-02 15:04:05",
|
TimestampFormat: time.DateTime,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err := j.ReadConfig(); err != nil {
|
if err := j.ReadConfig(); err != nil {
|
||||||
@@ -47,7 +48,6 @@ func main() {
|
|||||||
|
|
||||||
j.C.Version = version
|
j.C.Version = version
|
||||||
|
|
||||||
// no panic
|
|
||||||
switch j.C.Loglevel {
|
switch j.C.Loglevel {
|
||||||
case "fatal":
|
case "fatal":
|
||||||
log.SetLevel(log.FatalLevel)
|
log.SetLevel(log.FatalLevel)
|
||||||
|
|||||||
3
go.mod
3
go.mod
@@ -5,15 +5,14 @@ go 1.24.3
|
|||||||
require (
|
require (
|
||||||
github.com/davecgh/go-spew v1.1.1
|
github.com/davecgh/go-spew v1.1.1
|
||||||
github.com/eleksir/go-xmpp v1.6.0
|
github.com/eleksir/go-xmpp v1.6.0
|
||||||
|
github.com/google/uuid v1.6.0
|
||||||
github.com/hjson/hjson-go v3.3.0+incompatible
|
github.com/hjson/hjson-go v3.3.0+incompatible
|
||||||
github.com/jbrukh/bayesian v0.0.0-20231117143245-13ae6f916c7a
|
github.com/jbrukh/bayesian v0.0.0-20231117143245-13ae6f916c7a
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6
|
|
||||||
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637
|
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
|
||||||
golang.org/x/crypto v0.38.0 // indirect
|
golang.org/x/crypto v0.38.0 // indirect
|
||||||
golang.org/x/net v0.40.0 // indirect
|
golang.org/x/net v0.40.0 // indirect
|
||||||
golang.org/x/sys v0.33.0 // indirect
|
golang.org/x/sys v0.33.0 // indirect
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -18,8 +18,6 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc
|
|||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI=
|
|
||||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ=
|
|
||||||
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
||||||
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// Package jabber contains all buny jabber bot guts.
|
||||||
package jabber
|
package jabber
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -12,11 +13,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
goodDictionaryFile string = "./data/good_dictionary.txt"
|
// goodDictionaryFile путь до файла с заведомо хорошими фразами.
|
||||||
badDictionaryFile string = "./data/bad_dictionary.txt"
|
goodDictionaryFile string = "./data/good_dictionary.txt" //nolint: unused
|
||||||
dataFile string = "./data/data.bin"
|
// badDictionaryFile путь до файла с заведемо плохими фразами.
|
||||||
Bad bayesian.Class = "Bad"
|
badDictionaryFile string = "./data/bad_dictionary.txt" //nolint: unused
|
||||||
Good bayesian.Class = "Good"
|
// dataFile путь до файла с данными тренировки фильтра.
|
||||||
|
dataFile string = "./data/data.bin" //nolint: unused
|
||||||
|
// Bad bayessian class with bad items.
|
||||||
|
Bad bayesian.Class = "Bad"
|
||||||
|
// Good bayessian class with good items.
|
||||||
|
Good bayesian.Class = "Good"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Выдаёт "очки похожести" для данной фразы.
|
// Выдаёт "очки похожести" для данной фразы.
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ func (j *Jabber) ReadWhitelist() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
whitelistJSONPath := fmt.Sprintf("%s/data/whitelist.json", filepath.Dir(executablePath))
|
whitelistJSONPath := filepath.Dir(executablePath) + "/data/whitelist.json"
|
||||||
|
|
||||||
locations := []string{
|
locations := []string{
|
||||||
"~/.buny-jabber-bot-whitelist.json",
|
"~/.buny-jabber-bot-whitelist.json",
|
||||||
@@ -39,12 +39,12 @@ func (j *Jabber) ReadWhitelist() error {
|
|||||||
for _, location := range locations {
|
for _, location := range locations {
|
||||||
fileInfo, err := os.Stat(location)
|
fileInfo, err := os.Stat(location)
|
||||||
|
|
||||||
// Предполагаем, что файла либо нет, либо мы не можем его прочитать, второе надо бы логгировать, но пока забьём
|
// Предполагаем, что файла либо нет, либо мы не можем его прочитать, второе надо бы логгировать, но пока забьём.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Файл белого списка длинноват для белого списка, попробуем следующего кандидата
|
// Файл белого списка длинноват для белого списка, попробуем следующего кандидата.
|
||||||
if fileInfo.Size() > 2097152 {
|
if fileInfo.Size() > 2097152 {
|
||||||
log.Warnf("Whitelist file %s is too long for whitelist, skipping", location)
|
log.Warnf("Whitelist file %s is too long for whitelist, skipping", location)
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ func (j *Jabber) ReadWhitelist() error {
|
|||||||
|
|
||||||
buf, err := os.ReadFile(location)
|
buf, err := os.ReadFile(location)
|
||||||
|
|
||||||
// Не удалось прочитать, попробуем следующего кандидата
|
// Не удалось прочитать, попробуем следующего кандидата.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("Skip reading whitelist file %s: %s", location, err)
|
log.Warnf("Skip reading whitelist file %s: %s", location, err)
|
||||||
|
|
||||||
@@ -65,12 +65,12 @@ func (j *Jabber) ReadWhitelist() error {
|
|||||||
// сериализуем в json, а потом json превращаем в структурку. Не очень эффективно, но он и нечасто требуется.
|
// сериализуем в json, а потом json превращаем в структурку. Не очень эффективно, но он и нечасто требуется.
|
||||||
var (
|
var (
|
||||||
sampleWhitelist MyWhiteList
|
sampleWhitelist MyWhiteList
|
||||||
tmp map[string]interface{}
|
tmp map[string]any
|
||||||
)
|
)
|
||||||
|
|
||||||
err = hjson.Unmarshal(buf, &tmp)
|
err = hjson.Unmarshal(buf, &tmp)
|
||||||
|
|
||||||
// Не удалось распарсить - попробуем следующего кандидата
|
// Не удалось распарсить - попробуем следующего кандидата.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("Skip parsing whitelist file %s: %s", location, err)
|
log.Warnf("Skip parsing whitelist file %s: %s", location, err)
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ func (j *Jabber) ReadWhitelist() error {
|
|||||||
|
|
||||||
tmpJSON, err := json.Marshal(tmp)
|
tmpJSON, err := json.Marshal(tmp)
|
||||||
|
|
||||||
// Не удалось преобразовать map-ку в json
|
// Не удалось преобразовать map-ку в json.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("Skip parsing whitelist file %s: %s", location, err)
|
log.Warnf("Skip parsing whitelist file %s: %s", location, err)
|
||||||
|
|
||||||
@@ -123,7 +123,7 @@ func (j *Jabber) ReadBlacklist() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
whitelistJSONPath := fmt.Sprintf("%s/data/blacklist.json", filepath.Dir(executablePath))
|
whitelistJSONPath := filepath.Dir(executablePath) + "/data/blacklist.json"
|
||||||
|
|
||||||
locations := []string{
|
locations := []string{
|
||||||
"~/.bunyPresense-jabber-bot-blacklist.json",
|
"~/.bunyPresense-jabber-bot-blacklist.json",
|
||||||
@@ -135,12 +135,12 @@ func (j *Jabber) ReadBlacklist() error {
|
|||||||
for _, location := range locations {
|
for _, location := range locations {
|
||||||
fileInfo, err := os.Stat(location)
|
fileInfo, err := os.Stat(location)
|
||||||
|
|
||||||
// Предполагаем, что файла либо нет, либо мы не можем его прочитать, второе надо бы логгировать, но пока забьём
|
// Предполагаем, что файла либо нет, либо мы не можем его прочитать, второе надо бы логгировать, но пока забьём.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Файл чёрного списка длинноват для чёрного списка, попробуем следующего кандидата
|
// Файл чёрного списка длинноват для чёрного списка, попробуем следующего кандидата.
|
||||||
if fileInfo.Size() > 16777216 {
|
if fileInfo.Size() > 16777216 {
|
||||||
log.Warnf("Blacklist file %s is too long for blacklist, skipping", location)
|
log.Warnf("Blacklist file %s is too long for blacklist, skipping", location)
|
||||||
|
|
||||||
@@ -149,7 +149,7 @@ func (j *Jabber) ReadBlacklist() error {
|
|||||||
|
|
||||||
buf, err := os.ReadFile(location)
|
buf, err := os.ReadFile(location)
|
||||||
|
|
||||||
// Не удалось прочитать, попробуем следующего кандидата
|
// Не удалось прочитать, попробуем следующего кандидата.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("Skip reading blacklist file %s: %s", location, err)
|
log.Warnf("Skip reading blacklist file %s: %s", location, err)
|
||||||
|
|
||||||
@@ -161,12 +161,12 @@ func (j *Jabber) ReadBlacklist() error {
|
|||||||
// сериализуем в json, а потом json превращаем в структурку. Не очень эффективно, но он и нечасто требуется.
|
// сериализуем в json, а потом json превращаем в структурку. Не очень эффективно, но он и нечасто требуется.
|
||||||
var (
|
var (
|
||||||
sampleBlacklist MyBlackList
|
sampleBlacklist MyBlackList
|
||||||
tmp map[string]interface{}
|
tmp map[string]any
|
||||||
)
|
)
|
||||||
|
|
||||||
err = hjson.Unmarshal(buf, &tmp)
|
err = hjson.Unmarshal(buf, &tmp)
|
||||||
|
|
||||||
// Не удалось распарсить - попробуем следующего кандидата
|
// Не удалось распарсить - попробуем следующего кандидата.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("Skip parsing blacklist file %s: %s", location, err)
|
log.Warnf("Skip parsing blacklist file %s: %s", location, err)
|
||||||
|
|
||||||
@@ -175,7 +175,7 @@ func (j *Jabber) ReadBlacklist() error {
|
|||||||
|
|
||||||
tmpJSON, err := json.Marshal(tmp)
|
tmpJSON, err := json.Marshal(tmp)
|
||||||
|
|
||||||
// Не удалось преобразовать map-ку в json
|
// Не удалось преобразовать map-ку в json.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("Skip parsing blacklist file %s: %s", location, err)
|
log.Warnf("Skip parsing blacklist file %s: %s", location, err)
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
@@ -17,9 +18,9 @@ func (j *Jabber) BunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
|||||||
var err error
|
var err error
|
||||||
|
|
||||||
// Если у presence-а есть JID и presence из одной из комнат, в которой мы есть и если его домен в чёрном
|
// Если у presence-а есть JID и presence из одной из комнат, в которой мы есть и если его домен в чёрном
|
||||||
// списке, заносим пидора в список outcast-ов
|
// списке, заносим пидора в список outcast-ов.
|
||||||
if v.JID != "" {
|
if v.JID != "" {
|
||||||
// На всякий случай: себя никогда не баним, явным образом
|
// На всякий случай: себя никогда не баним, явным образом.
|
||||||
if v.JID == j.Talk.JID() {
|
if v.JID == j.Talk.JID() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -34,23 +35,19 @@ func (j *Jabber) BunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
|||||||
|
|
||||||
goodJid := strings.SplitN(v.JID, "/", 2)[0]
|
goodJid := strings.SplitN(v.JID, "/", 2)[0]
|
||||||
|
|
||||||
// Обрабатываем правила белого списка
|
// Обрабатываем правила белого списка.
|
||||||
for _, good := range j.WhiteList.Whitelist {
|
for _, good := range j.WhiteList.Whitelist {
|
||||||
// Глобальный белый список
|
// Глобальный белый список.
|
||||||
if good.RoomName == "" {
|
if good.RoomName == "" {
|
||||||
for _, person := range good.Jid {
|
if slices.Contains(good.Jid, goodJid) {
|
||||||
if person == goodJid {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Белый список для конкретных каналов
|
// Белый список для конкретных каналов.
|
||||||
if good.RoomName == room {
|
if good.RoomName == room {
|
||||||
for _, person := range good.Jid {
|
if slices.Contains(good.Jid, goodJid) {
|
||||||
if person == goodJid {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -74,16 +71,16 @@ func (j *Jabber) BunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
|||||||
evilNick := ""
|
evilNick := ""
|
||||||
evilNicks := strings.SplitN(v.From, "/", 2)
|
evilNicks := strings.SplitN(v.From, "/", 2)
|
||||||
|
|
||||||
// Давайте-ка похэндлим нонсенс - когда у нас в строке нету разделителя
|
// Давайте-ка похэндлим нонсенс - когда у нас в строке нету разделителя.
|
||||||
if len(evilNicks) > 1 {
|
if len(evilNicks) > 1 {
|
||||||
evilNick = evilNicks[1]
|
evilNick = evilNicks[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обрабатываем правила чёрного списка
|
// Обрабатываем правила чёрного списка.
|
||||||
for _, cRoom := range j.RoomsConnected {
|
for _, cRoom := range j.RoomsConnected {
|
||||||
if cRoom == room {
|
if cRoom == room {
|
||||||
for _, bEntry := range j.BlackList.Blacklist {
|
for _, bEntry := range j.BlackList.Blacklist {
|
||||||
// Обработаем правила глобального чёрного списка
|
// Обработаем правила глобального чёрного списка.
|
||||||
if bEntry.RoomName == "" {
|
if bEntry.RoomName == "" {
|
||||||
for _, jidRegexp := range bEntry.JidRe {
|
for _, jidRegexp := range bEntry.JidRe {
|
||||||
if jidRegexp == "" {
|
if jidRegexp == "" {
|
||||||
@@ -147,7 +144,7 @@ func (j *Jabber) BunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
|||||||
nickRegexp,
|
nickRegexp,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Баним именно jid
|
// Баним именно jid.
|
||||||
id, err := j.Squash(room, evilJid, bEntry.ReasonEnable, v.Type)
|
id, err := j.Squash(room, evilJid, bEntry.ReasonEnable, v.Type)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -167,7 +164,7 @@ func (j *Jabber) BunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обработаем правила конкретного канала, комнаты конференции
|
// Обработаем правила конкретного канала, комнаты конференции.
|
||||||
if bEntry.RoomName == room {
|
if bEntry.RoomName == room {
|
||||||
for _, jidRegexp := range bEntry.JidRe {
|
for _, jidRegexp := range bEntry.JidRe {
|
||||||
if jidRegexp == "" {
|
if jidRegexp == "" {
|
||||||
@@ -232,7 +229,7 @@ func (j *Jabber) BunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
|||||||
nickRegexp,
|
nickRegexp,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Баним именно jid
|
// Баним именно jid.
|
||||||
id, err := j.Squash(room, evilJid, bEntry.ReasonEnable, v.Type)
|
id, err := j.Squash(room, evilJid, bEntry.ReasonEnable, v.Type)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -265,8 +262,7 @@ func (j *Jabber) BunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
|||||||
func (j *Jabber) BunyChat(v xmpp.Chat) error {
|
func (j *Jabber) BunyChat(v xmpp.Chat) error {
|
||||||
var (
|
var (
|
||||||
room = (strings.SplitN(v.Remote, "/", 2))[0]
|
room = (strings.SplitN(v.Remote, "/", 2))[0]
|
||||||
// nick = (strings.SplitN(v.Remote, "/", 2))[1]
|
err error
|
||||||
err error
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Действовать мы можем только в рамках тех комнат, где явно присуствуем.
|
// Действовать мы можем только в рамках тех комнат, где явно присуствуем.
|
||||||
@@ -274,7 +270,7 @@ func (j *Jabber) BunyChat(v xmpp.Chat) error {
|
|||||||
if cRoom == room {
|
if cRoom == room {
|
||||||
// Перебирём правила чёрных списков.
|
// Перебирём правила чёрных списков.
|
||||||
for _, bEntry := range j.BlackList.Blacklist {
|
for _, bEntry := range j.BlackList.Blacklist {
|
||||||
// Обработаем правила глобального чёрного списка
|
// Обработаем правила глобального чёрного списка.
|
||||||
if bEntry.RoomName == "" {
|
if bEntry.RoomName == "" {
|
||||||
for _, phraseRegexp := range bEntry.PhraseRe {
|
for _, phraseRegexp := range bEntry.PhraseRe {
|
||||||
if phraseRegexp == "" {
|
if phraseRegexp == "" {
|
||||||
@@ -317,7 +313,7 @@ func (j *Jabber) BunyChat(v xmpp.Chat) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обработаем правила чёрного списка для конкретной комнаты
|
// Обработаем правила чёрного списка для конкретной комнаты.
|
||||||
if bEntry.RoomName == room {
|
if bEntry.RoomName == room {
|
||||||
for _, phraseRegexp := range bEntry.PhraseRe {
|
for _, phraseRegexp := range bEntry.PhraseRe {
|
||||||
if phraseRegexp == "" {
|
if phraseRegexp == "" {
|
||||||
@@ -364,10 +360,10 @@ func (j *Jabber) BunyChat(v xmpp.Chat) error {
|
|||||||
// Если включено, проверяем фразу на КАПС.
|
// Если включено, проверяем фразу на КАПС.
|
||||||
for _, channel := range j.C.Jabber.Channels {
|
for _, channel := range j.C.Jabber.Channels {
|
||||||
if channel.Name == room && channel.AllCaps.Enabled {
|
if channel.Name == room && channel.AllCaps.Enabled {
|
||||||
// Нормализуем строку и вырежем из неё пробелы
|
// Нормализуем строку и вырежем из неё пробелы.
|
||||||
normPhrase := strings.ReplaceAll(nString(v.Text), " ", "")
|
normPhrase := strings.ReplaceAll(nString(v.Text), " ", "")
|
||||||
|
|
||||||
// Проверяем согласно тому, что длина фразы более чем сколько-то символов
|
// Проверяем согласно тому, что длина фразы более чем сколько-то символов.
|
||||||
if len(normPhrase) >= channel.AllCaps.MinLength {
|
if len(normPhrase) >= channel.AllCaps.MinLength {
|
||||||
normPhraseUpper := strings.ReplaceAll(nStringUpper(v.Text), " ", "")
|
normPhraseUpper := strings.ReplaceAll(nStringUpper(v.Text), " ", "")
|
||||||
|
|
||||||
@@ -401,7 +397,7 @@ func (j *Jabber) BunySoftwareVersion(v xmpp.IQ, ver IqResultSoftwareVersion) err
|
|||||||
var err error
|
var err error
|
||||||
|
|
||||||
if v.From != "" {
|
if v.From != "" {
|
||||||
// На всякий случай: себя никогда не баним, явным образом
|
// На всякий случай: себя никогда не баним, явным образом.
|
||||||
if v.From == j.Talk.JID() {
|
if v.From == j.Talk.JID() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -420,6 +416,7 @@ func (j *Jabber) BunySoftwareVersion(v xmpp.IQ, ver IqResultSoftwareVersion) err
|
|||||||
|
|
||||||
for _, presenceJSONString := range presenceJSONStrings {
|
for _, presenceJSONString := range presenceJSONStrings {
|
||||||
var p xmpp.Presence
|
var p xmpp.Presence
|
||||||
|
|
||||||
_ = json.Unmarshal([]byte(presenceJSONString), &p)
|
_ = json.Unmarshal([]byte(presenceJSONString), &p)
|
||||||
|
|
||||||
if p.Affiliation == "admin" || p.Affiliation == "owner" {
|
if p.Affiliation == "admin" || p.Affiliation == "owner" {
|
||||||
@@ -442,7 +439,7 @@ func (j *Jabber) BunySoftwareVersion(v xmpp.IQ, ver IqResultSoftwareVersion) err
|
|||||||
for _, cRoom := range j.RoomsConnected {
|
for _, cRoom := range j.RoomsConnected {
|
||||||
if cRoom == room {
|
if cRoom == room {
|
||||||
for _, bEntry := range j.BlackList.Blacklist {
|
for _, bEntry := range j.BlackList.Blacklist {
|
||||||
// Обработаем правила глобального чёрного списка
|
// Обработаем правила глобального чёрного списка.
|
||||||
if bEntry.RoomName == "" {
|
if bEntry.RoomName == "" {
|
||||||
for _, useragent := range bEntry.UserAgent {
|
for _, useragent := range bEntry.UserAgent {
|
||||||
switch {
|
switch {
|
||||||
|
|||||||
@@ -4,16 +4,18 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Collection это sync.Map-ка, хранящая в себе произвольные итемы.
|
type (
|
||||||
type Collection struct {
|
// Collection это sync.Map-ка, хранящая в себе произвольные итемы.
|
||||||
items sync.Map
|
Collection struct {
|
||||||
close chan struct{}
|
close chan struct{}
|
||||||
}
|
items sync.Map
|
||||||
|
}
|
||||||
|
|
||||||
// Item представляет собой интерфейс для хранения произвольных данных.
|
// Item представляет собой интерфейс для хранения произвольных данных.
|
||||||
type item struct {
|
item struct {
|
||||||
data interface{}
|
data any
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// NewCollection создаёт инстанс коллекции.
|
// NewCollection создаёт инстанс коллекции.
|
||||||
func NewCollection() *Collection {
|
func NewCollection() *Collection {
|
||||||
@@ -25,7 +27,7 @@ func NewCollection() *Collection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get достаёт данные по заданному ключу из коллекции.
|
// Get достаёт данные по заданному ключу из коллекции.
|
||||||
func (collection *Collection) Get(key interface{}) (interface{}, bool) {
|
func (collection *Collection) Get(key any) (any, bool) {
|
||||||
obj, exists := collection.items.Load(key)
|
obj, exists := collection.items.Load(key)
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
@@ -38,15 +40,15 @@ func (collection *Collection) Get(key interface{}) (interface{}, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set сохраняет данные с заданным ключом в коллекцию.
|
// Set сохраняет данные с заданным ключом в коллекцию.
|
||||||
func (collection *Collection) Set(key interface{}, value interface{}) {
|
func (collection *Collection) Set(key any, value any) {
|
||||||
collection.items.Store(key, item{
|
collection.items.Store(key, item{
|
||||||
data: value,
|
data: value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Range применяет функцию f ко всем ключам в коллекции.
|
// Range применяет функцию f ко всем ключам в коллекции.
|
||||||
func (collection *Collection) Range(f func(key, value interface{}) bool) {
|
func (collection *Collection) Range(f func(key, value any) bool) {
|
||||||
fn := func(key, value interface{}) bool {
|
fn := func(key, value any) bool {
|
||||||
item := value.(item)
|
item := value.(item)
|
||||||
|
|
||||||
return f(key, item.data)
|
return f(key, item.data)
|
||||||
@@ -56,13 +58,14 @@ func (collection *Collection) Range(f func(key, value interface{}) bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delete удаляет ключ и значение из коллекции данных.
|
// Delete удаляет ключ и значение из коллекции данных.
|
||||||
func (collection *Collection) Delete(key interface{}) {
|
func (collection *Collection) Delete(key any) {
|
||||||
collection.items.Delete(key)
|
collection.items.Delete(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close очищает и высвобождает ресурсы, занятые коллекцией.
|
// Close очищает и высвобождает ресурсы, занятые коллекцией.
|
||||||
func (collection *Collection) Close() {
|
func (collection *Collection) Close() {
|
||||||
collection.close <- struct{}{}
|
collection.close <- struct{}{}
|
||||||
|
|
||||||
collection.items = sync.Map{}
|
collection.items = sync.Map{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ import (
|
|||||||
func (j *Jabber) Cmd(v xmpp.Chat) error {
|
func (j *Jabber) Cmd(v xmpp.Chat) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
switch {
|
switch v.Text {
|
||||||
case v.Text == fmt.Sprintf("%shelp", j.C.CSign) || v.Text == fmt.Sprintf("%sпомощь", j.C.CSign):
|
case j.C.CSign + "help", j.C.CSign + "помощь":
|
||||||
var (
|
var (
|
||||||
answer string
|
answer string
|
||||||
chosenOneTalks = false
|
chosenOneTalks = false
|
||||||
@@ -30,10 +30,11 @@ func (j *Jabber) Cmd(v xmpp.Chat) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if chosenOneTalks {
|
if chosenOneTalks {
|
||||||
answer = fmt.Sprintf("%sпомощь - этот список команд\n", j.C.CSign)
|
answer = j.C.CSign + "помощь - этот список команд\n"
|
||||||
answer += fmt.Sprintf("%shelp - this commands list\n", j.C.CSign)
|
|
||||||
answer += fmt.Sprintf("%srehash - reload white and black lists (available to bot admins only)\n", j.C.CSign)
|
answer += j.C.CSign + "help - this commands list\n"
|
||||||
answer += fmt.Sprintf("%sver|%sversion - prints version of software", j.C.CSign, j.C.CSign)
|
answer += j.C.CSign + "rehash - reload white and black lists (available to bot admins only)\n"
|
||||||
|
answer += j.C.CSign + "ver | " + j.C.CSign + "version - prints version of software"
|
||||||
} else {
|
} else {
|
||||||
answer = "Ничем помочь не могу. Луна не светит на тебя."
|
answer = "Ничем помочь не могу. Луна не светит на тебя."
|
||||||
}
|
}
|
||||||
@@ -56,15 +57,14 @@ func (j *Jabber) Cmd(v xmpp.Chat) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
case v.Text == fmt.Sprintf("%srehash", j.C.CSign): //nolint:wsl
|
case j.C.CSign + "rehash":
|
||||||
/* Эта команда может прилетать из приватной беседы с realjid-ом, из приватной беседы с chat-nick-ом, а также из
|
/* Эта команда может прилетать из приватной беседы с realjid-ом, из приватной беседы с chat-nick-ом, а также из
|
||||||
* чятика.
|
* чятика.
|
||||||
* Groupchat мы можем определить по v.Type (groupchat) и тогда понятно что делать.
|
* Groupchat мы можем определить по v.Type (groupchat) и тогда понятно что делать.
|
||||||
* А вот, например, приватные притязания определить несколько сложно-вато. Но можно попробовать, например,
|
* А вот, например, приватные притязания определить несколько сложно-вато. Но можно попробовать, например,
|
||||||
* пытаться по списку presence-ов находить персонажа и его real jid и уже из этого определять master он или нет.
|
* пытаться по списку presence-ов находить персонажа и его real jid и уже из этого определять master он или нет.
|
||||||
*/
|
*/
|
||||||
|
// Попробуем определить каков алгоритм наших действий, в зависимости от типа сообщения.
|
||||||
// Попробуем определить каков алгоритм наших действий, в зависимости от типа сообщения
|
|
||||||
switch v.Type {
|
switch v.Type {
|
||||||
case "groupchat":
|
case "groupchat":
|
||||||
// Проверим, является ли "командир" избранным или это самозванец?
|
// Проверим, является ли "командир" избранным или это самозванец?
|
||||||
@@ -90,6 +90,7 @@ func (j *Jabber) Cmd(v xmpp.Chat) error {
|
|||||||
)
|
)
|
||||||
|
|
||||||
var msg xmpp.Chat
|
var msg xmpp.Chat
|
||||||
|
|
||||||
msg.Remote = v.Remote
|
msg.Remote = v.Remote
|
||||||
msg.Type = v.Type
|
msg.Type = v.Type
|
||||||
msg.Text = "Ничем помочь не могу. Луна не светит на тебя."
|
msg.Text = "Ничем помочь не могу. Луна не светит на тебя."
|
||||||
@@ -103,8 +104,9 @@ func (j *Jabber) Cmd(v xmpp.Chat) error {
|
|||||||
listsLoaded = false
|
listsLoaded = false
|
||||||
|
|
||||||
var msg xmpp.Chat
|
var msg xmpp.Chat
|
||||||
|
|
||||||
msg.Remote = v.Remote
|
msg.Remote = v.Remote
|
||||||
msg.Type = "chat" // private message
|
msg.Type = "chat" // private message.
|
||||||
msg.Text = fmt.Sprint(err)
|
msg.Text = fmt.Sprint(err)
|
||||||
|
|
||||||
if _, err := j.Talk.Send(msg); err != nil {
|
if _, err := j.Talk.Send(msg); err != nil {
|
||||||
@@ -116,8 +118,9 @@ func (j *Jabber) Cmd(v xmpp.Chat) error {
|
|||||||
listsLoaded = false
|
listsLoaded = false
|
||||||
|
|
||||||
var msg xmpp.Chat
|
var msg xmpp.Chat
|
||||||
|
|
||||||
msg.Remote = v.Remote
|
msg.Remote = v.Remote
|
||||||
msg.Type = "chat" // private message
|
msg.Type = "chat" // private message.
|
||||||
msg.Text = fmt.Sprint(err)
|
msg.Text = fmt.Sprint(err)
|
||||||
|
|
||||||
if _, err := j.Talk.Send(msg); err != nil {
|
if _, err := j.Talk.Send(msg); err != nil {
|
||||||
@@ -129,6 +132,7 @@ func (j *Jabber) Cmd(v xmpp.Chat) error {
|
|||||||
room := (strings.SplitN(v.Remote, "/", 2))[0]
|
room := (strings.SplitN(v.Remote, "/", 2))[0]
|
||||||
|
|
||||||
var msg xmpp.Chat
|
var msg xmpp.Chat
|
||||||
|
|
||||||
msg.Remote = room
|
msg.Remote = room
|
||||||
msg.Type = v.Type
|
msg.Type = v.Type
|
||||||
msg.Text = "Сделано"
|
msg.Text = "Сделано"
|
||||||
@@ -137,17 +141,18 @@ func (j *Jabber) Cmd(v xmpp.Chat) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case "chat":
|
case "chat":
|
||||||
room := (strings.SplitN(v.Remote, "/", 2))[0]
|
room := (strings.SplitN(v.Remote, "/", 2))[0]
|
||||||
|
|
||||||
// Реагировать на приватную команду "из комнаты", только если бот в комнате
|
// Реагировать на приватную команду "из комнаты", только если бот в комнате.
|
||||||
if slices.Contains(j.RoomsConnected, room) {
|
if slices.Contains(j.RoomsConnected, room) {
|
||||||
var (
|
var (
|
||||||
chosenOneTalks = false
|
chosenOneTalks = false
|
||||||
listsLoaded = true
|
listsLoaded = true
|
||||||
)
|
)
|
||||||
|
|
||||||
// Реагировать на rehash только если собеседник является bot master-ом
|
// Реагировать на rehash только если собеседник является bot master-ом.
|
||||||
realJID := j.GetRealJIDfromNick(v.Remote)
|
realJID := j.GetRealJIDfromNick(v.Remote)
|
||||||
|
|
||||||
for _, master := range j.C.Jabber.BotMasters {
|
for _, master := range j.C.Jabber.BotMasters {
|
||||||
@@ -165,6 +170,7 @@ func (j *Jabber) Cmd(v xmpp.Chat) error {
|
|||||||
)
|
)
|
||||||
|
|
||||||
var msg xmpp.Chat
|
var msg xmpp.Chat
|
||||||
|
|
||||||
msg.Remote = v.Remote
|
msg.Remote = v.Remote
|
||||||
msg.Type = v.Type
|
msg.Type = v.Type
|
||||||
msg.Text = "Ничем помочь не могу. Луна не светит на тебя."
|
msg.Text = "Ничем помочь не могу. Луна не светит на тебя."
|
||||||
@@ -178,6 +184,7 @@ func (j *Jabber) Cmd(v xmpp.Chat) error {
|
|||||||
listsLoaded = false
|
listsLoaded = false
|
||||||
|
|
||||||
var msg xmpp.Chat
|
var msg xmpp.Chat
|
||||||
|
|
||||||
msg.Remote = v.Remote
|
msg.Remote = v.Remote
|
||||||
msg.Type = v.Type
|
msg.Type = v.Type
|
||||||
msg.Text = fmt.Sprint(err)
|
msg.Text = fmt.Sprint(err)
|
||||||
@@ -191,6 +198,7 @@ func (j *Jabber) Cmd(v xmpp.Chat) error {
|
|||||||
listsLoaded = false
|
listsLoaded = false
|
||||||
|
|
||||||
var msg xmpp.Chat
|
var msg xmpp.Chat
|
||||||
|
|
||||||
msg.Remote = v.Remote
|
msg.Remote = v.Remote
|
||||||
msg.Type = v.Type
|
msg.Type = v.Type
|
||||||
msg.Text = fmt.Sprint(err)
|
msg.Text = fmt.Sprint(err)
|
||||||
@@ -202,6 +210,7 @@ func (j *Jabber) Cmd(v xmpp.Chat) error {
|
|||||||
|
|
||||||
if listsLoaded {
|
if listsLoaded {
|
||||||
var msg xmpp.Chat
|
var msg xmpp.Chat
|
||||||
|
|
||||||
msg.Remote = v.Remote
|
msg.Remote = v.Remote
|
||||||
msg.Type = v.Type
|
msg.Type = v.Type
|
||||||
msg.Text = "Сделано"
|
msg.Text = "Сделано"
|
||||||
@@ -214,12 +223,14 @@ func (j *Jabber) Cmd(v xmpp.Chat) error {
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
log.Infof("Rehash command from outer space: %s", spew.Sdump(v))
|
log.Infof("Rehash command from outer space: %s", spew.Sdump(v))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
case v.Text == fmt.Sprintf("%sver", j.C.CSign) || v.Text == fmt.Sprintf("%sversion", j.C.CSign):
|
|
||||||
|
case j.C.CSign + "ver", j.C.CSign + "version":
|
||||||
var (
|
var (
|
||||||
answer = fmt.Sprintf("Version %s", j.C.Version)
|
answer = "Version " + j.C.Version
|
||||||
)
|
)
|
||||||
|
|
||||||
dest := v.Remote
|
dest := v.Remote
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -12,26 +13,24 @@ import (
|
|||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/eleksir/go-xmpp"
|
"github.com/eleksir/go-xmpp"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ParseEvent парсит ивенты, прилетающие из модуля xmpp (изменения presence, фразы участников, ошибки).
|
// ParseEvent парсит ивенты, прилетающие из модуля xmpp (изменения presence, фразы участников, ошибки).
|
||||||
func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
func (j *Jabber) ParseEvent(e any) { //nolint:maintidx,gocognit,gocyclo
|
||||||
j.LastServerActivity = time.Now().Unix()
|
j.LastServerActivity = time.Now().Unix()
|
||||||
|
|
||||||
switch v := e.(type) {
|
switch v := e.(type) {
|
||||||
// Сообщение в чяти
|
// Сообщение в чяти.
|
||||||
case xmpp.Chat:
|
case xmpp.Chat:
|
||||||
log.Debugf("Looks like message, ChatType: %s, From: %s, Subject: %s Text: %s",
|
log.Debugf("Looks like message, ChatType: %s, From: %s, Subject: %s Text: %s",
|
||||||
v.Type, v.Remote, v.Subject, v.Text)
|
v.Type, v.Remote, v.Subject, v.Text)
|
||||||
|
|
||||||
// Топик чятика присылается в виде сообщения с subject, но без text
|
// Топик чятика присылается в виде сообщения с subject, но без text.
|
||||||
// В то же время сообщения от людей приходят с пустым subject, но с заполненным text
|
// В то же время сообщения от людей приходят с пустым subject, но с заполненным text.
|
||||||
if v.Text != "" {
|
if v.Text != "" {
|
||||||
// Чятики бывают групповые и не групповые, от этого зависит Remote, куда направлять сообщение
|
// Чятики бывают групповые и не групповые, от этого зависит Remote, куда направлять сообщение.
|
||||||
switch v.Type { //nolint:wsl,whitespace
|
switch v.Type { //nolint:wsl,whitespace
|
||||||
|
// Групповой чятик.
|
||||||
// Групповой чятик
|
|
||||||
case "groupchat":
|
case "groupchat":
|
||||||
var (
|
var (
|
||||||
room = strings.SplitN(v.Remote, "/", 2)[0]
|
room = strings.SplitN(v.Remote, "/", 2)[0]
|
||||||
@@ -64,7 +63,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
j.LastMucActivity.Set(muc, j.LastServerActivity)
|
j.LastMucActivity.Set(muc, j.LastServerActivity)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Приватный чятик
|
// Приватный чятик.
|
||||||
case "chat":
|
case "chat":
|
||||||
// Здесь у нас может быть 2 вида From:
|
// Здесь у нас может быть 2 вида From:
|
||||||
// slackware-current@conference.jabber.ru/eleksir - это если сообщение из публичного чятика
|
// slackware-current@conference.jabber.ru/eleksir - это если сообщение из публичного чятика
|
||||||
@@ -80,7 +79,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
j.LastActivity = j.LastServerActivity
|
j.LastActivity = j.LastServerActivity
|
||||||
|
|
||||||
// Внезапно, ошибки. По идее они должны ассоциироваться с отправляемыми сообщениями, но по ходу это
|
// Внезапно, ошибки. По идее они должны ассоциироваться с отправляемыми сообщениями, но по ходу это
|
||||||
// не реализовано, поэтому мы получаем ошибки в форме отдельного чятика
|
// не реализовано, поэтому мы получаем ошибки в форме отдельного чятика.
|
||||||
case "error":
|
case "error":
|
||||||
// Это ошибка со стороны сервера, видимо, в ответ на наши действия и поскольку такие вещи происходят
|
// Это ошибка со стороны сервера, видимо, в ответ на наши действия и поскольку такие вещи происходят
|
||||||
// асинхронно, связать их с конкретными нашими действиями довольно сложно.
|
// асинхронно, связать их с конкретными нашими действиями довольно сложно.
|
||||||
@@ -90,7 +89,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
|
|
||||||
// IQ-сообщение - пинг, понг, что-то ещё...
|
// IQ-сообщение - пинг, понг, что-то ещё...
|
||||||
case xmpp.IQ:
|
case xmpp.IQ:
|
||||||
// По правилам IQ обязательно должна содержать ID, причём не пустой
|
// По правилам IQ обязательно должна содержать ID, причём не пустой.
|
||||||
if v.ID == "" {
|
if v.ID == "" {
|
||||||
log.Info("Got an IQ stanza with empty id, discarding")
|
log.Info("Got an IQ stanza with empty id, discarding")
|
||||||
|
|
||||||
@@ -114,8 +113,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
|
|
||||||
if err := xml.Unmarshal(v.Query, &iqStruct); err == nil {
|
if err := xml.Unmarshal(v.Query, &iqStruct); err == nil {
|
||||||
switch iqStruct.Xmlns { //nolint:wsl,whitespace
|
switch iqStruct.Xmlns { //nolint:wsl,whitespace
|
||||||
|
// Запрос номера версии приложения.
|
||||||
// Запрос номера версии приложения
|
|
||||||
case "jabber:iq:version":
|
case "jabber:iq:version":
|
||||||
log.Infof("Got IQ get request for version from %s", v.From)
|
log.Infof("Got IQ get request for version from %s", v.From)
|
||||||
|
|
||||||
@@ -138,7 +136,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
|
|
||||||
parseError = false
|
parseError = false
|
||||||
|
|
||||||
// Нам прислали попингуй
|
// Нам прислали попингуй.
|
||||||
case "urn:xmpp:ping":
|
case "urn:xmpp:ping":
|
||||||
log.Infof("Got IQ get request for pong from %s", v.From)
|
log.Infof("Got IQ get request for pong from %s", v.From)
|
||||||
|
|
||||||
@@ -175,11 +173,12 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
|
|
||||||
parseError = false
|
parseError = false
|
||||||
|
|
||||||
// Запрос на список поддерживаемых фич - версия, локальное время, итд
|
// Запрос на список поддерживаемых фич - версия, локальное время, итд.
|
||||||
case xmpp.XMPPNS_DISCO_INFO:
|
case xmpp.XMPPNS_DISCO_INFO:
|
||||||
log.Infof("Got IQ get disco#info request from %s", v.From)
|
log.Infof("Got IQ get disco#info request from %s", v.From)
|
||||||
|
|
||||||
answer := "<query xmlns=\"http://jabber.org/protocol/disco#info\">"
|
answer := "<query xmlns=\"http://jabber.org/protocol/disco#info\">"
|
||||||
|
|
||||||
answer += "<feature var=\"jabber:iq:version\" />"
|
answer += "<feature var=\"jabber:iq:version\" />"
|
||||||
answer += "<feature var=\"urn:xmpp:time\" />"
|
answer += "<feature var=\"urn:xmpp:time\" />"
|
||||||
answer += "<feature var=\"urn:xmpp:ping\" />"
|
answer += "<feature var=\"urn:xmpp:ping\" />"
|
||||||
@@ -209,7 +208,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
|
|
||||||
parseError = false
|
parseError = false
|
||||||
|
|
||||||
// Спрашивают список команд, чтобы поуправлять этим клиентом (xep-0030)
|
// Спрашивают список команд, чтобы поуправлять этим клиентом (xep-0030).
|
||||||
case xmpp.XMPPNS_DISCO_ITEMS:
|
case xmpp.XMPPNS_DISCO_ITEMS:
|
||||||
log.Infof("Got IQ get disco#items request from %s, answer service unavailable", v.From)
|
log.Infof("Got IQ get disco#items request from %s, answer service unavailable", v.From)
|
||||||
|
|
||||||
@@ -231,7 +230,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
|
|
||||||
parseError = false
|
parseError = false
|
||||||
|
|
||||||
// Запрашивают что-то, о чём мы не имеем представления
|
// Запрашивают что-то, о чём мы не имеем представления.
|
||||||
default:
|
default:
|
||||||
log.Info("Got an unknown IQ get request, discarding")
|
log.Info("Got an unknown IQ get request, discarding")
|
||||||
log.Info(spew.Sdump(e))
|
log.Info(spew.Sdump(e))
|
||||||
@@ -242,7 +241,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
parseError = true
|
parseError = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Попробуем распарсить входящий запрос как urn:xmpp:time
|
// Попробуем распарсить входящий запрос как urn:xmpp:time.
|
||||||
if parseError {
|
if parseError {
|
||||||
var iqStruct TimeIqGetQuery
|
var iqStruct TimeIqGetQuery
|
||||||
|
|
||||||
@@ -276,7 +275,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Попробуем распарсить входящий запрос как http://jabber.org/protocol/pubsub
|
// Попробуем распарсить входящий запрос как http://jabber.org/protocol/pubsub.
|
||||||
if parseError {
|
if parseError {
|
||||||
var iqStruct PubsubIQGetQuery
|
var iqStruct PubsubIQGetQuery
|
||||||
|
|
||||||
@@ -305,6 +304,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
default:
|
default:
|
||||||
log.Info("Got unknown IQ get pubsub something request, discarding")
|
log.Info("Got unknown IQ get pubsub something request, discarding")
|
||||||
log.Info(spew.Sdump(e))
|
log.Info(spew.Sdump(e))
|
||||||
|
|
||||||
parseError = true //nolint:wsl
|
parseError = true //nolint:wsl
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -312,7 +312,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Попробуем распарсить входящий запрос как urn:xmpp:ping он приходит при MUC Self-Ping (Schrödinger's Chat)
|
// Попробуем распарсить входящий запрос как urn:xmpp:ping он приходит при MUC Self-Ping (Schrödinger's Chat).
|
||||||
// Предполагается, что такие респонсы должны приходить только для пинга без серверной оптимизации.
|
// Предполагается, что такие респонсы должны приходить только для пинга без серверной оптимизации.
|
||||||
// Хотя идейно мы не поддерживаем работу без серверной оптимизации, но на пинг ответим, нам несложно.
|
// Хотя идейно мы не поддерживаем работу без серверной оптимизации, но на пинг ответим, нам несложно.
|
||||||
if parseError {
|
if parseError {
|
||||||
@@ -337,7 +337,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
parseError = false
|
parseError = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Не знаю, как парсить... залоггируем это дело и пойдём дальше
|
// Не знаю, как парсить... залоггируем это дело и пойдём дальше.
|
||||||
if parseError {
|
if parseError {
|
||||||
log.Infof("Does not look like parsable via iqStruct")
|
log.Infof("Does not look like parsable via iqStruct")
|
||||||
log.Info(spew.Sdump(e))
|
log.Info(spew.Sdump(e))
|
||||||
@@ -351,9 +351,10 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
// Похоже на pong от сервера (по стандарту в ответе нету query, но go-xmpp нам подсовывает это)
|
// Похоже на pong от сервера (по стандарту в ответе нету query, но go-xmpp нам подсовывает это).
|
||||||
case v.From == j.C.Jabber.Server && v.To == j.Talk.JID() && string(v.Query) == "<XMLElement></XMLElement>":
|
case v.From == j.C.Jabber.Server && v.To == j.Talk.JID() && string(v.Query) == "<XMLElement></XMLElement>":
|
||||||
log.Debugf("Got S2C pong answer from %s to %s", v.From, v.To)
|
log.Debugf("Got S2C pong answer from %s to %s", v.From, v.To)
|
||||||
|
|
||||||
j.ServerPingTimestampRx = time.Now().Unix() //nolint:wsl
|
j.ServerPingTimestampRx = time.Now().Unix() //nolint:wsl
|
||||||
|
|
||||||
// Похоже на понг второй стадии xep-0410 MUC-Ping-а, который у нас не реализован.
|
// Похоже на понг второй стадии xep-0410 MUC-Ping-а, который у нас не реализован.
|
||||||
@@ -378,13 +379,13 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
if mucNickMatch {
|
if mucNickMatch {
|
||||||
log.Debugf("Got 2-nd stage MUC pong answer (xep-0410) from %s to %s", v.From, v.To)
|
log.Debugf("Got 2-nd stage MUC pong answer (xep-0410) from %s to %s", v.From, v.To)
|
||||||
} else {
|
} else {
|
||||||
// Поскольку никакой логики у нас на этот счёт не предусмотрено, то просто пропускаем ответ
|
// Поскольку никакой логики у нас на этот счёт не предусмотрено, то просто пропускаем ответ.
|
||||||
log.Infof("Got unknown pong from %s to %s", v.From, v.To)
|
log.Infof("Got unknown pong from %s to %s", v.From, v.To)
|
||||||
log.Debug(spew.Sdump(e))
|
log.Debug(spew.Sdump(e))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Подтверждение бана
|
// Подтверждение бана.
|
||||||
case v.To == j.Talk.JID() && v.ID == "ban1":
|
case v.To == j.Talk.JID() && v.ID == "ban1":
|
||||||
mucNickMatch := false
|
mucNickMatch := false
|
||||||
|
|
||||||
@@ -402,15 +403,15 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Формально, ответ должен парситься как пустой result и совпадать с типом jabberSimpleIqGetQuery
|
// Формально, ответ должен парситься как пустой result и совпадать с типом jabberSimpleIqGetQuery.
|
||||||
// Ответ приходит с 2 xmlns, похоже, он парсится неправильно в go-xmpp.
|
// Ответ приходит с 2 xmlns, похоже, он парсится неправильно в go-xmpp.
|
||||||
// TODO: после исправления go-xmpp, надо исправить это тут
|
// TODO: после исправления go-xmpp, надо исправить это тут.
|
||||||
var iqStruct SimpleIqGetQuery
|
var iqStruct SimpleIqGetQuery
|
||||||
|
|
||||||
if err := xml.Unmarshal(v.Query, &iqStruct); err == nil {
|
if err := xml.Unmarshal(v.Query, &iqStruct); err == nil {
|
||||||
if iqStruct.Xmlns == "http://jabber.org/protocol/muc#admin" && iqStruct.Text == "" {
|
if iqStruct.Xmlns == "http://jabber.org/protocol/muc#admin" && iqStruct.Text == "" {
|
||||||
if mucNickMatch {
|
if mucNickMatch {
|
||||||
// Поскольку никакой логики у нас на этот счёт не предусмотрено, то просто пропускаем ответ
|
// Поскольку никакой логики у нас на этот счёт не предусмотрено, то просто пропускаем ответ.
|
||||||
log.Infof("Got ban successful from %s to %s", v.From, v.To)
|
log.Infof("Got ban successful from %s to %s", v.From, v.To)
|
||||||
} else {
|
} else {
|
||||||
log.Infof("Got ban successful from %s to %s, from room we are not in", v.From, v.To)
|
log.Infof("Got ban successful from %s to %s, from room we are not in", v.From, v.To)
|
||||||
@@ -465,7 +466,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
log.Debug(spew.Sdump(e))
|
log.Debug(spew.Sdump(e))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Этот бот не управляется со стороны сервера, поэтому все попытки порулить игнорируем
|
// Этот бот не управляется со стороны сервера, поэтому все попытки порулить игнорируем.
|
||||||
case xmpp.IQTypeSet:
|
case xmpp.IQTypeSet:
|
||||||
if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" {
|
if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" {
|
||||||
if slices.Contains(j.RoomsConnected, muc) {
|
if slices.Contains(j.RoomsConnected, muc) {
|
||||||
@@ -493,7 +494,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
|
|
||||||
log.Debug(spew.Sdump(e))
|
log.Debug(spew.Sdump(e))
|
||||||
|
|
||||||
// Нам прилетело сообщение об ошибке
|
// Нам прилетело сообщение об ошибке.
|
||||||
case xmpp.IQTypeError:
|
case xmpp.IQTypeError:
|
||||||
// Если сервер не хочет пинговаться и отвечает ошибкой на пинг, то наверно он не умеет в пинги,
|
// Если сервер не хочет пинговаться и отвечает ошибкой на пинг, то наверно он не умеет в пинги,
|
||||||
// хотя если мы его пингуем, значит он анонсировал такой capability. Вот, засранец!
|
// хотя если мы его пингуем, значит он анонсировал такой capability. Вот, засранец!
|
||||||
@@ -506,6 +507,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
if iqPingStruct.Xmlns == "urn:xmpp:ping" {
|
if iqPingStruct.Xmlns == "urn:xmpp:ping" {
|
||||||
if v.From == j.C.Jabber.Server {
|
if v.From == j.C.Jabber.Server {
|
||||||
msg := "Server announced that it can answer c2s ping, but gives us an error to such query, "
|
msg := "Server announced that it can answer c2s ping, but gives us an error to such query, "
|
||||||
|
|
||||||
msg += "fallback to keepalive whitespace pings"
|
msg += "fallback to keepalive whitespace pings"
|
||||||
log.Error(msg)
|
log.Error(msg)
|
||||||
|
|
||||||
@@ -521,7 +523,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
parseError = false
|
parseError = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Это у нас пинг xep-0410 и мы не в комнате, предполагается, что надо бы заджойниться
|
// Это у нас пинг xep-0410 и мы не в комнате, предполагается, что надо бы заджойниться.
|
||||||
if parseError {
|
if parseError {
|
||||||
var iqErrorCancelNotAcceptable IqErrorCancelNotAcceptable
|
var iqErrorCancelNotAcceptable IqErrorCancelNotAcceptable
|
||||||
|
|
||||||
@@ -572,13 +574,13 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
log.Error("Unhandled IQ Error message")
|
log.Error("Unhandled IQ Error message")
|
||||||
log.Error(spew.Sdump(e))
|
log.Error(spew.Sdump(e))
|
||||||
}
|
}
|
||||||
// Нам прилетело что-то неизвестное из семейства IQ stanza
|
// Нам прилетело что-то неизвестное из семейства IQ stanza.
|
||||||
default:
|
default:
|
||||||
log.Info("Got an unknown IQ request. Dunno how deal with it, discarding")
|
log.Info("Got an unknown IQ request. Dunno how deal with it, discarding")
|
||||||
log.Info(spew.Sdump(e))
|
log.Info(spew.Sdump(e))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Смена статуса участника
|
// Смена статуса участника.
|
||||||
case xmpp.Presence:
|
case xmpp.Presence:
|
||||||
if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" {
|
if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" {
|
||||||
if slices.Contains(j.RoomsConnected, muc) {
|
if slices.Contains(j.RoomsConnected, muc) {
|
||||||
@@ -593,13 +595,21 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
// клиента забанят из-за спама "вошёл-вышел".
|
// клиента забанят из-за спама "вошёл-вышел".
|
||||||
// Однако, это не значит, что такую ситуацию мы не должны корректным образом обрабатывать.
|
// Однако, это не значит, что такую ситуацию мы не должны корректным образом обрабатывать.
|
||||||
if v.Type == "unavailable" {
|
if v.Type == "unavailable" {
|
||||||
// Считаем, что мы больше не в комнате, поэтому не знаем, кто там есть
|
// Считаем, что мы больше не в комнате, поэтому не знаем, кто там есть.
|
||||||
j.RoomPresences.Delete(v.From)
|
j.RoomPresences.Delete(v.From)
|
||||||
|
|
||||||
log.Error("Presence notification - looks like another instance of client leaves room")
|
log.Error("Presence notification - looks like another instance of client leaves room")
|
||||||
|
|
||||||
if slices.Contains(j.RoomsConnected, v.From) {
|
if slices.Contains(j.RoomsConnected, v.From) {
|
||||||
go j.JoinMuc(v.From)
|
j.GTomb.Go(
|
||||||
|
func() error {
|
||||||
|
if err := j.JoinMuc(v.From); err != nil {
|
||||||
|
return fmt.Errorf("unable to join MUC %s: %w", v.From, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Errorf("Presence notification, Type: %s, From: %s, Show: %s, Status: %s",
|
log.Errorf("Presence notification, Type: %s, From: %s, Show: %s, Status: %s",
|
||||||
@@ -621,18 +631,19 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Это наш собственный Presence
|
// Это наш собственный Presence.
|
||||||
if v.Show == "" && v.Status == "" {
|
if v.Show == "" && v.Status == "" {
|
||||||
if nick == j.GetBotNickFromRoomConfig(room) {
|
if nick == j.GetBotNickFromRoomConfig(room) {
|
||||||
j.RoomsConnected = append(j.RoomsConnected, room)
|
j.RoomsConnected = append(j.RoomsConnected, room)
|
||||||
// На всякий случай дедуплицируем список комнат, к которым мы заджойнились.
|
// На всякий случай дедуплицируем список комнат, к которым мы заджойнились.
|
||||||
sort.Strings(j.RoomsConnected)
|
sort.Strings(j.RoomsConnected)
|
||||||
slices.Compact(j.RoomsConnected)
|
|
||||||
|
j.RoomsConnected = slices.Compact(j.RoomsConnected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch v.Role {
|
switch v.Role {
|
||||||
// Участник ушёл
|
// Участник ушёл.
|
||||||
case "none":
|
case "none":
|
||||||
if presenceJSONInterface, present := j.RoomPresences.Get(room); present {
|
if presenceJSONInterface, present := j.RoomPresences.Get(room); present {
|
||||||
presenceJSONStrings := InterfaceToStringSlice(presenceJSONInterface)
|
presenceJSONStrings := InterfaceToStringSlice(presenceJSONInterface)
|
||||||
@@ -641,6 +652,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
|
|
||||||
for _, presenceJSONstring := range presenceJSONStrings {
|
for _, presenceJSONstring := range presenceJSONStrings {
|
||||||
var p xmpp.Presence
|
var p xmpp.Presence
|
||||||
|
|
||||||
_ = json.Unmarshal([]byte(presenceJSONstring), &p)
|
_ = json.Unmarshal([]byte(presenceJSONstring), &p)
|
||||||
|
|
||||||
if p.From == v.From {
|
if p.From == v.From {
|
||||||
@@ -652,7 +664,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
|
|
||||||
j.RoomPresences.Set(room, newPresenceJSONStrings)
|
j.RoomPresences.Set(room, newPresenceJSONStrings)
|
||||||
}
|
}
|
||||||
// Участник пришёл
|
// Участник пришёл.
|
||||||
default:
|
default:
|
||||||
var (
|
var (
|
||||||
presenceJSONStrings []string
|
presenceJSONStrings []string
|
||||||
@@ -666,6 +678,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
|
|
||||||
for _, presenceJSONString := range presenceJSONStrings {
|
for _, presenceJSONString := range presenceJSONStrings {
|
||||||
var p xmpp.Presence
|
var p xmpp.Presence
|
||||||
|
|
||||||
_ = json.Unmarshal([]byte(presenceJSONString), &p)
|
_ = json.Unmarshal([]byte(presenceJSONString), &p)
|
||||||
|
|
||||||
// Если находим, что у нас уже есть клиент с таким же From, то есть полным nick-ом (для groupchat)
|
// Если находим, что у нас уже есть клиент с таким же From, то есть полным nick-ом (для groupchat)
|
||||||
@@ -696,7 +709,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ответ на запрос поддерживаемых фич, который "http://jabber.org/protocol/disco#info"
|
// Ответ на запрос поддерживаемых фич, который "http://jabber.org/protocol/disco#info".
|
||||||
case xmpp.DiscoResult:
|
case xmpp.DiscoResult:
|
||||||
if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" {
|
if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" {
|
||||||
if slices.Contains(j.RoomsConnected, muc) {
|
if slices.Contains(j.RoomsConnected, muc) {
|
||||||
@@ -704,12 +717,12 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Я видел 2 типа disco result и они отличались только []identities. Попробуем вытащить известный identity
|
// Я видел 2 типа disco result и они отличались только []identities. Попробуем вытащить известный identity.
|
||||||
// TODO: проверять адресата v.To
|
// TODO: проверять адресата v.To.
|
||||||
for _, ident := range v.Identities {
|
for _, ident := range v.Identities {
|
||||||
switch ident.Category {
|
switch ident.Category {
|
||||||
case "server":
|
case "server":
|
||||||
// Конкретно сейчас нас интересует только поддержка c2s ping
|
// Конкретно сейчас нас интересует только поддержка c2s ping.
|
||||||
for _, feature := range v.Features {
|
for _, feature := range v.Features {
|
||||||
log.Debugf("Server %s announced that it supports feature: %s", v.From, feature)
|
log.Debugf("Server %s announced that it supports feature: %s", v.From, feature)
|
||||||
j.ServerCapsList.Set(feature, true)
|
j.ServerCapsList.Set(feature, true)
|
||||||
@@ -722,6 +735,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
|
|
||||||
for _, feature := range v.Features {
|
for _, feature := range v.Features {
|
||||||
log.Debugf("MUC %s announced that it supports feature: %s", v.From, feature)
|
log.Debugf("MUC %s announced that it supports feature: %s", v.From, feature)
|
||||||
|
|
||||||
mucCaps[feature] = true //nolint:wsl
|
mucCaps[feature] = true //nolint:wsl
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -736,7 +750,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Это что-то неизвестное, подампим событие в лог
|
// Это что-то неизвестное, подампим событие в лог.
|
||||||
default:
|
default:
|
||||||
log.Info(spew.Sdump(e))
|
log.Info(spew.Sdump(e))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
// MyLoop - основной цикл программы.
|
// MyLoop - основной цикл программы.
|
||||||
func (j *Jabber) MyLoop() error {
|
func (j *Jabber) MyLoop() error {
|
||||||
for {
|
for {
|
||||||
// Зададим начальное значение глобальным переменным
|
// Зададим начальное значение глобальным переменным.
|
||||||
j.ServerPingTimestampRx = 0
|
j.ServerPingTimestampRx = 0
|
||||||
j.ServerPingTimestampTx = 0
|
j.ServerPingTimestampTx = 0
|
||||||
j.RoomsConnected = make([]string, 1)
|
j.RoomsConnected = make([]string, 1)
|
||||||
@@ -23,7 +23,7 @@ func (j *Jabber) MyLoop() error {
|
|||||||
j.ServerPingTimestampRx = 0
|
j.ServerPingTimestampRx = 0
|
||||||
j.RoomPresences = NewCollection()
|
j.RoomPresences = NewCollection()
|
||||||
|
|
||||||
// Установим коннект
|
// Установим коннект.
|
||||||
if err := j.EstablishConnection(); err != nil {
|
if err := j.EstablishConnection(); err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
|
|
||||||
@@ -48,14 +48,8 @@ func (j *Jabber) MyLoop() error {
|
|||||||
// j.GTomb.Go() тут зовётся внутри на каждую комнату, если у нас ошибка, эта горутинка рано или поздно выйдет.
|
// j.GTomb.Go() тут зовётся внутри на каждую комнату, если у нас ошибка, эта горутинка рано или поздно выйдет.
|
||||||
go j.ProbeMUCLiveness()
|
go j.ProbeMUCLiveness()
|
||||||
|
|
||||||
// Гребём ивенты...
|
// Гребём ивенты, пока стриггерилось завершение работы приложения, или соединение не порвалось.
|
||||||
for {
|
for !j.Shutdown {
|
||||||
// Стриггерилось завершение работы приложения, или соединение не установлено (порвалось, например)
|
|
||||||
// грести не надо
|
|
||||||
if j.Shutdown {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if !j.IsConnected {
|
if !j.IsConnected {
|
||||||
// Tight loop - это наверно не очень хорошо, думаю, ничего страшного не будет, если мы поспим 100мс.
|
// Tight loop - это наверно не очень хорошо, думаю, ничего страшного не будет, если мы поспим 100мс.
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
@@ -63,7 +57,7 @@ func (j *Jabber) MyLoop() error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Вынимаем ивент из "провода"
|
// Вынимаем ивент из "провода".
|
||||||
chat, err := j.Talk.Recv()
|
chat, err := j.Talk.Recv()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ func (j *Jabber) ReadConfig() error { //nolint:gocognit,gocyclo
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
configJSONPath := fmt.Sprintf("%s/data/config.json", filepath.Dir(executablePath))
|
configJSONPath := filepath.Dir(executablePath) + "/data/config.json"
|
||||||
|
|
||||||
locations := []string{
|
locations := []string{
|
||||||
"~/.buny-jabber-bot.json",
|
"~/.buny-jabber-bot.json",
|
||||||
@@ -66,7 +66,7 @@ func (j *Jabber) ReadConfig() error { //nolint:gocognit,gocyclo
|
|||||||
// сериализуем в json, а потом json превращаем в структурку. Не очень эффективно, но он и нечасто требуется.
|
// сериализуем в json, а потом json превращаем в структурку. Не очень эффективно, но он и нечасто требуется.
|
||||||
var (
|
var (
|
||||||
sampleConfig MyConfig
|
sampleConfig MyConfig
|
||||||
tmp map[string]interface{}
|
tmp map[string]any
|
||||||
)
|
)
|
||||||
|
|
||||||
err = hjson.Unmarshal(buf, &tmp)
|
err = hjson.Unmarshal(buf, &tmp)
|
||||||
@@ -80,7 +80,7 @@ func (j *Jabber) ReadConfig() error { //nolint:gocognit,gocyclo
|
|||||||
|
|
||||||
tmpJSON, err := json.Marshal(tmp)
|
tmpJSON, err := json.Marshal(tmp)
|
||||||
|
|
||||||
// Не удалось преобразовать map-ку в json
|
// Не удалось преобразовать map-ку в json.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("Skip parsing config file %s: %s", location, err)
|
log.Warnf("Skip parsing config file %s: %s", location, err)
|
||||||
|
|
||||||
@@ -93,9 +93,10 @@ func (j *Jabber) ReadConfig() error { //nolint:gocognit,gocyclo
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Значения для Jabber-клиента
|
// Значения для Jabber-клиента.
|
||||||
if sampleConfig.Jabber.Server == "" {
|
if sampleConfig.Jabber.Server == "" {
|
||||||
log.Error("Jabber server is not defined in config, using localhost")
|
log.Error("Jabber server is not defined in config, using localhost")
|
||||||
|
|
||||||
sampleConfig.Jabber.Server = "localhost" //nolint:wsl
|
sampleConfig.Jabber.Server = "localhost" //nolint:wsl
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,7 +122,7 @@ func (j *Jabber) ReadConfig() error { //nolint:gocognit,gocyclo
|
|||||||
sampleConfig.Jabber.SslVerify = false
|
sampleConfig.Jabber.SslVerify = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// sampleConfig.Jabber.InsecureAllowUnencryptedAuth = false, если не задан
|
// sampleConfig.Jabber.InsecureAllowUnencryptedAuth = false, если не задан.
|
||||||
|
|
||||||
if sampleConfig.Jabber.ConnectionTimeout == 0 {
|
if sampleConfig.Jabber.ConnectionTimeout == 0 {
|
||||||
sampleConfig.Jabber.ConnectionTimeout = 10
|
sampleConfig.Jabber.ConnectionTimeout = 10
|
||||||
@@ -175,14 +176,14 @@ func (j *Jabber) ReadConfig() error { //nolint:gocognit,gocyclo
|
|||||||
log.Infof("Jabber user not defined in config, guessing, it can be %s", sampleConfig.Jabber.User)
|
log.Infof("Jabber user not defined in config, guessing, it can be %s", sampleConfig.Jabber.User)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Если sampleConfig.Jabber.Password не задан, то авторизации не будет
|
// Если sampleConfig.Jabber.Password не задан, то авторизации не будет.
|
||||||
|
|
||||||
// Если не задано ни одного мастера, то бот сам себе мастер
|
// Если не задано ни одного мастера, то бот сам себе мастер.
|
||||||
if len(sampleConfig.Jabber.BotMasters) == 0 {
|
if len(sampleConfig.Jabber.BotMasters) == 0 {
|
||||||
sampleConfig.Jabber.BotMasters[0] = sampleConfig.Jabber.User
|
sampleConfig.Jabber.BotMasters[0] = sampleConfig.Jabber.User
|
||||||
}
|
}
|
||||||
|
|
||||||
// Нам бот нужен в каких-то чат-румах, а не "просто так"
|
// Нам бот нужен в каких-то чат-румах, а не "просто так".
|
||||||
if len(sampleConfig.Jabber.Channels) < 1 {
|
if len(sampleConfig.Jabber.Channels) < 1 {
|
||||||
return errors.New("no jabber channels/rooms defined in config, quitting") //nolint:goerr113
|
return errors.New("no jabber channels/rooms defined in config, quitting") //nolint:goerr113
|
||||||
}
|
}
|
||||||
@@ -196,8 +197,8 @@ func (j *Jabber) ReadConfig() error { //nolint:gocognit,gocyclo
|
|||||||
sampleConfig.Jabber.Channels[n].Nick = sampleConfig.Jabber.Nick
|
sampleConfig.Jabber.Channels[n].Nick = sampleConfig.Jabber.Nick
|
||||||
}
|
}
|
||||||
|
|
||||||
// channel.Password может быть пустым, тогда пароля нет
|
// channel.Password может быть пустым, тогда пароля нет.
|
||||||
// channel.Bayes.Enabled будет false, если не проставлен
|
// channel.Bayes.Enabled будет false, если не проставлен.
|
||||||
|
|
||||||
if channel.Bayes.MinLength < 40 {
|
if channel.Bayes.MinLength < 40 {
|
||||||
channel.Bayes.MinLength = 40
|
channel.Bayes.MinLength = 40
|
||||||
@@ -215,7 +216,7 @@ func (j *Jabber) ReadConfig() error { //nolint:gocognit,gocyclo
|
|||||||
channel.Bayes.DefaultAction = "log"
|
channel.Bayes.DefaultAction = "log"
|
||||||
}
|
}
|
||||||
|
|
||||||
// channel.AllCaps.Enabled будет false, если не указан
|
// channel.AllCaps.Enabled будет false, если не указан.
|
||||||
if channel.AllCaps.MinLength < 10 {
|
if channel.AllCaps.MinLength < 10 {
|
||||||
channel.AllCaps.MinLength = 10
|
channel.AllCaps.MinLength = 10
|
||||||
}
|
}
|
||||||
@@ -229,23 +230,23 @@ func (j *Jabber) ReadConfig() error { //nolint:gocognit,gocyclo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Если список фраз с которыми стартует бот пустой, вносим в него 1 запись с пустой строкой
|
// Если список фраз с которыми стартует бот пустой, вносим в него 1 запись с пустой строкой.
|
||||||
if len(sampleConfig.Jabber.StartupStatus) == 0 {
|
if len(sampleConfig.Jabber.StartupStatus) == 0 {
|
||||||
sampleConfig.Jabber.StartupStatus[0] = ""
|
sampleConfig.Jabber.StartupStatus[0] = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// Если список статусов, с которыми работает бот пустой, вносим в него 1 запись с пустой строкой
|
// Если список статусов, с которыми работает бот пустой, вносим в него 1 запись с пустой строкой.
|
||||||
if len(sampleConfig.Jabber.RuntimeStatus.Text) == 0 {
|
if len(sampleConfig.Jabber.RuntimeStatus.Text) == 0 {
|
||||||
sampleConfig.Jabber.RuntimeStatus.Text[0] = ""
|
sampleConfig.Jabber.RuntimeStatus.Text[0] = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// Если sampleConfig.Jabber.RuntimeStatus.RotationTime не задан, то он равен 0
|
// Если sampleConfig.Jabber.RuntimeStatus.RotationTime не задан, то он равен 0.
|
||||||
// Если sampleConfig.Jabber.RuntimeStatus.RotationSplayTime не задан, то он равен 0
|
// Если sampleConfig.Jabber.RuntimeStatus.RotationSplayTime не задан, то он равен 0.
|
||||||
|
|
||||||
// Если sampleConfig.Jabber.BanDelay не задан, то он равен 0
|
// Если sampleConfig.Jabber.BanDelay не задан, то он равен 0.
|
||||||
// Если sampleConfig.Jabber.BanPhrasesEnable не задан, то он false
|
// Если sampleConfig.Jabber.BanPhrasesEnable не задан, то он false.
|
||||||
|
|
||||||
// Если список фраз, с которыми банят пустой, то вносим в него одну позицию с пустой строкой
|
// Если список фраз, с которыми банят пустой, то вносим в него одну позицию с пустой строкой.
|
||||||
if len(sampleConfig.Jabber.BanPhrases) == 0 {
|
if len(sampleConfig.Jabber.BanPhrases) == 0 {
|
||||||
sampleConfig.Jabber.BanPhrases[0] = ""
|
sampleConfig.Jabber.BanPhrases[0] = ""
|
||||||
}
|
}
|
||||||
@@ -260,7 +261,7 @@ func (j *Jabber) ReadConfig() error { //nolint:gocognit,gocyclo
|
|||||||
log.Info("loglevel not defined in config, using info")
|
log.Info("loglevel not defined in config, using info")
|
||||||
}
|
}
|
||||||
|
|
||||||
// sampleConfig.Log = "" if not set
|
// sampleConfig.Log = "" if not set.
|
||||||
|
|
||||||
j.C = sampleConfig
|
j.C = sampleConfig
|
||||||
configLoaded = true
|
configLoaded = true
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// SigHandler срабатывает если происходит завершение программы, инициированное либо init-ом, либо пользователем.
|
||||||
|
// Обрабатвает сигналы INT, TERM, QUIT.
|
||||||
func (j *Jabber) SigHandler() error {
|
func (j *Jabber) SigHandler() error {
|
||||||
log.Debug("Installing signal handler")
|
log.Debug("Installing signal handler")
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ func (j *Jabber) Squash(room, jid string, reasonEnable bool, vType string) (stri
|
|||||||
|
|
||||||
if reasonEnable {
|
if reasonEnable {
|
||||||
var t = time.Now()
|
var t = time.Now()
|
||||||
|
|
||||||
ban += fmt.Sprintf(
|
ban += fmt.Sprintf(
|
||||||
"<reason>autoban at %04d.%02d.%02d %02d:%02d:%02d</reason>",
|
"<reason>autoban at %04d.%02d.%02d %02d:%02d:%02d</reason>",
|
||||||
t.Year(),
|
t.Year(),
|
||||||
|
|||||||
@@ -8,208 +8,209 @@ import (
|
|||||||
"gopkg.in/tomb.v2"
|
"gopkg.in/tomb.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MyConfig прототип структурки с конфигом.
|
type (
|
||||||
type MyConfig struct {
|
// MyConfig прототип структурки с конфигом.
|
||||||
|
MyConfig struct {
|
||||||
|
CSign string `json:"csign,omitempty"`
|
||||||
|
Loglevel string `json:"loglevel,omitempty"`
|
||||||
|
Log string `json:"log,omitempty"`
|
||||||
|
Version string `json:"version,omitempty"`
|
||||||
|
ExeName string `json:"exe_name,omitempty"`
|
||||||
|
Jabber struct {
|
||||||
|
Server string `json:"server,omitempty"`
|
||||||
|
Nick string `json:"nick,omitempty"`
|
||||||
|
Resource string `json:"resource,omitempty"`
|
||||||
|
User string `json:"user,omitempty"`
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
|
StartupStatus []string `json:"startup_status,omitempty"`
|
||||||
|
BotMasters []string `json:"bot_masters,omitempty"`
|
||||||
|
BanPhrases []string `json:"ban_phrases,omitempty"`
|
||||||
|
RuntimeStatus struct {
|
||||||
|
Text []string `json:"text,omitempty"`
|
||||||
|
RotationTime int64 `json:"rotation_time,omitempty"`
|
||||||
|
RotationSplayTime int64 `json:"rotation_splay_time,omitempty"`
|
||||||
|
} `json:"runtime_status"`
|
||||||
|
Channels []struct {
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Nick string `json:"nick,omitempty"`
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
|
AllCaps struct {
|
||||||
|
DefaultAction string `json:"default_action,omitempty"`
|
||||||
|
MinLength int `json:"min_length,omitempty"`
|
||||||
|
Enabled bool `json:"enabled,omitempty"`
|
||||||
|
} `json:"all_caps"`
|
||||||
|
Bayes struct {
|
||||||
|
DefaultAction string `json:"default_action,omitempty"`
|
||||||
|
MinWords int64 `json:"min_words,omitempty"`
|
||||||
|
MinLength int `json:"min_length,omitempty"`
|
||||||
|
Enabled bool `json:"enabled,omitempty"`
|
||||||
|
} `json:"bayes"`
|
||||||
|
} `json:"channels"`
|
||||||
|
ConnectionTimeout int64 `json:"connection_timeout,omitempty"`
|
||||||
|
ReconnectDelay int64 `json:"reconnect_delay,omitempty"`
|
||||||
|
ServerPingDelay int64 `json:"server_ping_delay,omitempty"`
|
||||||
|
MucPingDelay int64 `json:"muc_ping_delay,omitempty"`
|
||||||
|
MucRejoinDelay int64 `json:"muc_rejoin_delay,omitempty"`
|
||||||
|
PingSplayDelay int64 `json:"ping_splay_delay,omitempty"`
|
||||||
|
BanDelay int64 `json:"ban_delay,omitempty"`
|
||||||
|
Port int `json:"port,omitempty"`
|
||||||
|
Ssl bool `json:"ssl,omitempty"`
|
||||||
|
StartTLS bool `json:"starttls,omitempty"`
|
||||||
|
SslVerify bool `json:"ssl_verify,omitempty"`
|
||||||
|
InsecureAllowUnencryptedAuth bool `json:"insecureallowunencryptedauth,omitempty"`
|
||||||
|
BanPhrasesEnable bool `json:"ban_phrases_enable,omitempty"`
|
||||||
|
} `json:"jabber"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MyWhiteList прототип структурки с белым списком jid-ов.
|
||||||
|
MyWhiteList struct {
|
||||||
|
Whitelist []struct {
|
||||||
|
RoomName string `json:"room_name,omitempty"`
|
||||||
|
Jid []string `json:"jid,omitempty"`
|
||||||
|
WipeBans bool `json:"wipe_bans,omitempty"`
|
||||||
|
} `json:"whitelist,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MyBlackList прототип структурки с чёрным списком jid-ов.
|
||||||
|
MyBlackList struct {
|
||||||
|
Blacklist []struct {
|
||||||
|
RoomName string `json:"room_name,omitempty"`
|
||||||
|
JidRe []string `json:"jid_re,omitempty"`
|
||||||
|
NickRe []string `json:"nick_re,omitempty"`
|
||||||
|
PhraseRe []string `json:"phrase_re,omitempty"`
|
||||||
|
UserAgent []struct {
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Version string `json:"version,omitempty"`
|
||||||
|
Os string `json:"os,omitempty"`
|
||||||
|
} `json:"user_agent"`
|
||||||
|
ReasonEnable bool `json:"reason_enable,omitempty"`
|
||||||
|
} `json:"blacklist"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jabber основная структура-объект, содержащая стейты и проч.
|
||||||
Jabber struct {
|
Jabber struct {
|
||||||
Server string `json:"server,omitempty"`
|
// C - конфиг, как он распарсился из конфиг-файла.
|
||||||
Port int `json:"port,omitempty"`
|
C MyConfig
|
||||||
Ssl bool `json:"ssl,omitempty"`
|
|
||||||
StartTLS bool `json:"starttls,omitempty"`
|
|
||||||
SslVerify bool `json:"ssl_verify,omitempty"`
|
|
||||||
InsecureAllowUnencryptedAuth bool `json:"insecureallowunencryptedauth,omitempty"`
|
|
||||||
ConnectionTimeout int64 `json:"connection_timeout,omitempty"`
|
|
||||||
ReconnectDelay int64 `json:"reconnect_delay,omitempty"`
|
|
||||||
ServerPingDelay int64 `json:"server_ping_delay,omitempty"`
|
|
||||||
MucPingDelay int64 `json:"muc_ping_delay,omitempty"`
|
|
||||||
MucRejoinDelay int64 `json:"muc_rejoin_delay,omitempty"`
|
|
||||||
PingSplayDelay int64 `json:"ping_splay_delay,omitempty"`
|
|
||||||
Nick string `json:"nick,omitempty"`
|
|
||||||
Resource string `json:"resource,omitempty"`
|
|
||||||
User string `json:"user,omitempty"`
|
|
||||||
Password string `json:"password,omitempty"`
|
|
||||||
BotMasters []string `json:"bot_masters,omitempty"`
|
|
||||||
Channels []struct {
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
Nick string `json:"nick,omitempty"`
|
|
||||||
Password string `json:"password,omitempty"`
|
|
||||||
Bayes struct {
|
|
||||||
Enabled bool `json:"enabled,omitempty"`
|
|
||||||
MinWords int64 `json:"min_words,omitempty"`
|
|
||||||
MinLength int `json:"min_length,omitempty"`
|
|
||||||
DefaultAction string `json:"default_action,omitempty"`
|
|
||||||
} `json:"bayes,omitempty"`
|
|
||||||
AllCaps struct {
|
|
||||||
Enabled bool `json:"enabled,omitempty"`
|
|
||||||
MinLength int `json:"min_length,omitempty"`
|
|
||||||
DefaultAction string `json:"default_action,omitempty"`
|
|
||||||
} `json:"all_caps,omitempty"`
|
|
||||||
} `json:"channels"`
|
|
||||||
StartupStatus []string `json:"startup_status,omitempty"`
|
|
||||||
RuntimeStatus struct {
|
|
||||||
Text []string `json:"text,omitempty"`
|
|
||||||
RotationTime int64 `json:"rotation_time,omitempty"`
|
|
||||||
RotationSplayTime int64 `json:"rotation_splay_time,omitempty"`
|
|
||||||
} `json:"runtime_status,omitempty"`
|
|
||||||
BanDelay int64 `json:"ban_delay,omitempty"`
|
|
||||||
BanPhrasesEnable bool `json:"ban_phrases_enable,omitempty"`
|
|
||||||
BanPhrases []string `json:"ban_phrases,omitempty"`
|
|
||||||
} `json:"jabber,omitempty"`
|
|
||||||
|
|
||||||
CSign string `json:"csign,omitempty"`
|
// WhiteList - структурка с безусловно разрешёнными jid-ами.
|
||||||
Loglevel string `json:"loglevel,omitempty"`
|
WhiteList MyWhiteList
|
||||||
Log string `json:"log,omitempty"`
|
|
||||||
Version string `json:"version,omitempty"`
|
|
||||||
ExeName string `json:"exe_name,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// MyWhiteList прототип структурки с белым списком jid-ов.
|
// BlackList - структурка с запрещёнными по регуляркам фразами, никами, jid-ами.
|
||||||
type MyWhiteList struct {
|
BlackList MyBlackList
|
||||||
Whitelist []struct {
|
|
||||||
RoomName string `json:"room_name,omitempty"`
|
|
||||||
Jid []string `json:"jid,omitempty"`
|
|
||||||
WipeBans bool `json:"wipe_bans,omitempty"`
|
|
||||||
} `json:"whitelist,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// MyBlackList прототип структурки с чёрным списком jid-ов.
|
// Опции подключения к xmpp-серверу.
|
||||||
type MyBlackList struct {
|
Options *xmpp.Options
|
||||||
Blacklist []struct {
|
|
||||||
RoomName string `json:"room_name,omitempty"`
|
|
||||||
ReasonEnable bool `json:"reason_enable,omitempty"`
|
|
||||||
JidRe []string `json:"jid_re,omitempty"`
|
|
||||||
NickRe []string `json:"nick_re,omitempty"`
|
|
||||||
PhraseRe []string `json:"phrase_re,omitempty"`
|
|
||||||
UserAgent []struct {
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
Version string `json:"version,omitempty"`
|
|
||||||
Os string `json:"os,omitempty"`
|
|
||||||
} `json:"user_agent,omitempty"`
|
|
||||||
} `json:"blacklist,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Jabber основная структура-объект, содержащая стейты и проч.
|
// gTomb пул активных горутин.
|
||||||
type Jabber struct {
|
GTomb tomb.Tomb
|
||||||
// C - конфиг, как он распарсился из конфиг-файла.
|
|
||||||
C MyConfig
|
|
||||||
|
|
||||||
// WhiteList - структурка с безусловно разрешёнными jid-ами
|
// Talk основная структурка xmpp-клиента.
|
||||||
WhiteList MyWhiteList
|
Talk *xmpp.Client
|
||||||
|
|
||||||
// BlackList - структурка с запрещёнными по регуляркам фразами, никами, jid-ами.
|
// sync.Map-ка с капабилити сервера.
|
||||||
BlackList MyBlackList
|
ServerCapsList *Collection
|
||||||
|
|
||||||
// Опции подключения к xmpp-серверу.
|
// ServerCapsQueried показывает, были ли запрошены capabilities сервера.
|
||||||
Options *xmpp.Options
|
ServerCapsQueried bool
|
||||||
|
|
||||||
// gTomb пул активных горутин.
|
// Время последней активности, нужно для c2s пингов - посылаем пинги, только если давненько ничего не приходило с
|
||||||
GTomb tomb.Tomb
|
// сервера.
|
||||||
|
LastServerActivity int64
|
||||||
|
|
||||||
// Talk основная структурка xmpp-клиента.
|
// Время, когда был отправлен c2s ping.
|
||||||
Talk *xmpp.Client
|
ServerPingTimestampTx int64
|
||||||
|
|
||||||
// sync.Map-ка с капабилити сервера.
|
// Время, когда был принят s2c pong.
|
||||||
ServerCapsList *Collection
|
ServerPingTimestampRx int64
|
||||||
|
|
||||||
// ServerCapsQueried показывает, были ли запрошены capabilities сервера.
|
// Время последней активности, нужно для jabber:iq:last.
|
||||||
ServerCapsQueried bool
|
LastActivity int64
|
||||||
|
|
||||||
// Время последней активности, нужно для c2s пингов - посылаем пинги, только если давненько ничего не приходило с
|
// sync.Map-ка с комнатами и их capability.
|
||||||
// сервера.
|
MucCapsList *Collection
|
||||||
LastServerActivity int64
|
|
||||||
|
|
||||||
// Время, когда был отправлен c2s ping.
|
// Время последней активности MUC-ов, нужно для пингов - посылаем пинги, только если давненько ничего не приходило из
|
||||||
ServerPingTimestampTx int64
|
// muc-ов.
|
||||||
|
LastMucActivity *Collection
|
||||||
|
|
||||||
// Время, когда был принят s2c pong.
|
// Список комнат, в которых находится бот.
|
||||||
ServerPingTimestampRx int64
|
RoomsConnected []string
|
||||||
|
|
||||||
// Время последней активности, нужно для jabber:iq:last.
|
// sync.Map-ка со списком участников конференций (в json-формате, согласно структуре xmpp.Presence, "room".[]json).
|
||||||
LastActivity int64
|
RoomPresences *Collection
|
||||||
|
|
||||||
// sync.Map-ка с комнатами и их capability.
|
// Канал, по котором приходят сообщения о том, что ОС отправила некие сигналы процессу.
|
||||||
MucCapsList *Collection
|
SigChan chan os.Signal
|
||||||
|
|
||||||
// Время последней активности MUC-ов, нужно для пингов - посылаем пинги, только если давненько ничего не приходило из
|
// Индиктор того, что процесс завершается.
|
||||||
// muc-ов.
|
Shutdown bool
|
||||||
LastMucActivity *Collection
|
|
||||||
|
|
||||||
// Список комнат, в которых находится бот.
|
// Индиктор того, что соединение установлено.
|
||||||
RoomsConnected []string
|
IsConnected bool
|
||||||
|
|
||||||
// sync.Map-ка со списком участников конференций (в json-формате, согласно структуре xmpp.Presence, "room".[]json).
|
// Индикатор того, что соединение в процессе достукивания до сервера.
|
||||||
RoomPresences *Collection
|
Connecting bool
|
||||||
|
}
|
||||||
|
|
||||||
// Канал, по котором приходят сообщения о том, что ОС отправила некие сигналы процессу.
|
// SimpleIqGetQuery прототип структурки для разбора запросов xmpp discovery query, например,
|
||||||
SigChan chan os.Signal
|
// https://xmpp.org/extensions/xep-0030.html#example-18 .
|
||||||
|
SimpleIqGetQuery struct {
|
||||||
|
XMLName xml.Name `xml:"query"`
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Xmlns string `xml:"xmlns,attr"`
|
||||||
|
Node string `xml:"node,attr,omitempty"` // для xmlns="http://jabber.org/protocol/disco#items"
|
||||||
|
}
|
||||||
|
|
||||||
// Индиктор того, что процесс завершается.
|
// PubsubIQGetQuery прототип структурки для разбора запросов xmpp pubsub.
|
||||||
Shutdown bool
|
PubsubIQGetQuery struct {
|
||||||
|
XMLName xml.Name `xml:"pubsub"`
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Xmlns string `xml:"xmlns,attr"`
|
||||||
|
Items struct {
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Node string `xml:"node,attr"`
|
||||||
|
MaxItems string `xml:"max_items,attr"`
|
||||||
|
} `xml:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
// Индиктор того, что соединение установлено.
|
// TimeIqGetQuery прототип структурки для разбора IQ запросов на локальное время клиента,
|
||||||
IsConnected bool
|
// https://xmpp.org/extensions/xep-0202.html
|
||||||
|
TimeIqGetQuery struct {
|
||||||
|
// <time xmlns="urn:xmpp:time"/>.
|
||||||
|
XMLName xml.Name `xml:"time"`
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Xmlns string `xml:"xmlns,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
// Индикатор того, что соединение в процессе достукивания до сервера.
|
// IqPing прототип структурки для разбора IQ запросов на пинг клиента, https://xmpp.org/extensions/xep-0199.html
|
||||||
Connecting bool
|
IqPing struct {
|
||||||
}
|
XMLName xml.Name `xml:"ping"`
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Xmlns string `xml:"xmlns,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
// SimpleIqGetQuery прототип структурки для разбора запросов xmpp discovery query, например,
|
// IqErrorCancelNotAcceptable прототип структурки для разбора IQ ответов, когда сервис (сервер, клиент, etc) не
|
||||||
// https://xmpp.org/extensions/xep-0030.html#example-18 .
|
// может или не хочет принимать наш iq-запрос https://xmpp.org/extensions/xep-0099.html
|
||||||
type SimpleIqGetQuery struct {
|
IqErrorCancelNotAcceptable struct {
|
||||||
XMLName xml.Name `xml:"query"`
|
XMLName xml.Name `xml:"error"`
|
||||||
Text string `xml:",chardata"`
|
Text string `xml:",chardata"`
|
||||||
Xmlns string `xml:"xmlns,attr"`
|
Type string `xml:"type,attr"`
|
||||||
Node string `xml:"node,attr,omitempty"` // для xmlns="http://jabber.org/protocol/disco#items"
|
By string `xml:"by,attr"`
|
||||||
}
|
NotAcceptable struct {
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Xmlns string `xml:"xmlns,attr"`
|
||||||
|
} `xml:"not-acceptable"`
|
||||||
|
}
|
||||||
|
|
||||||
// PubsubIQGetQuery прототип структурки для разбора запросов xmpp pubsub.
|
// IqResultSoftwareVersion прототип структурки для разбора IQ ответов на запрос о названии и версии клиентского ПО.
|
||||||
type PubsubIQGetQuery struct {
|
IqResultSoftwareVersion struct {
|
||||||
XMLName xml.Name `xml:"pubsub"`
|
XMLName xml.Name `xml:"query"`
|
||||||
Text string `xml:",chardata"`
|
Text string `xml:",chardata"`
|
||||||
Xmlns string `xml:"xmlns,attr"`
|
Xmlns string `xml:"xmlns,attr"`
|
||||||
Items struct {
|
Name string `xml:"name"`
|
||||||
Text string `xml:",chardata"`
|
Version string `xml:"version"`
|
||||||
Node string `xml:"node,attr"`
|
Os string `xml:"os,omitempty"`
|
||||||
MaxItems string `xml:"max_items,attr"`
|
}
|
||||||
} `xml:"items"`
|
)
|
||||||
}
|
|
||||||
|
|
||||||
// TimeIqGetQuery прототип структурки для разбора IQ запросов на локальное время клиента,
|
|
||||||
// https://xmpp.org/extensions/xep-0202.html
|
|
||||||
type TimeIqGetQuery struct {
|
|
||||||
// <time xmlns="urn:xmpp:time"/>
|
|
||||||
XMLName xml.Name `xml:"time"`
|
|
||||||
Text string `xml:",chardata"`
|
|
||||||
Xmlns string `xml:"xmlns,attr"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// IqPing прототип структурки для разбора IQ запросов на пинг клиента, https://xmpp.org/extensions/xep-0199.html
|
|
||||||
type IqPing struct {
|
|
||||||
XMLName xml.Name `xml:"ping"`
|
|
||||||
Text string `xml:",chardata"`
|
|
||||||
Xmlns string `xml:"xmlns,attr"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// IqErrorCancelNotAcceptable прототип структурки для разбора IQ ответов, когда сервис (сервер, клиент, etc) не
|
|
||||||
// может или не хочет принимать наш iq-запрос https://xmpp.org/extensions/xep-0099.html
|
|
||||||
type IqErrorCancelNotAcceptable struct {
|
|
||||||
XMLName xml.Name `xml:"error"`
|
|
||||||
Text string `xml:",chardata"`
|
|
||||||
Type string `xml:"type,attr"`
|
|
||||||
By string `xml:"by,attr"`
|
|
||||||
NotAcceptable struct {
|
|
||||||
Text string `xml:",chardata"`
|
|
||||||
Xmlns string `xml:"xmlns,attr"`
|
|
||||||
} `xml:"not-acceptable"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// IqResultSoftwareVersion прототип структурки для разбора IQ ответов на запрос о названии и версии клиентского ПО.
|
|
||||||
type IqResultSoftwareVersion struct {
|
|
||||||
XMLName xml.Name `xml:"query"`
|
|
||||||
Text string `xml:",chardata"`
|
|
||||||
Xmlns string `xml:"xmlns,attr"`
|
|
||||||
Name string `xml:"name"`
|
|
||||||
Version string `xml:"version"`
|
|
||||||
Os string `xml:"os,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
|
|||||||
@@ -26,16 +26,16 @@ func RandomPhrase(list []string) string {
|
|||||||
|
|
||||||
// InterfaceToStringSlice превращает данный интерфейс в слайс строк.
|
// InterfaceToStringSlice превращает данный интерфейс в слайс строк.
|
||||||
// Если может, конечно :) .
|
// Если может, конечно :) .
|
||||||
func InterfaceToStringSlice(iface interface{}) []string {
|
func InterfaceToStringSlice(iface any) []string {
|
||||||
var mySlice []string
|
var mySlice []string
|
||||||
|
|
||||||
// А теперь мы начинаем дурдом, нам надо превратить ёбанный interface{} в []string
|
// А теперь мы начинаем дурдом, нам надо превратить ёбанный any в []string.
|
||||||
// Поскольку interface{} может быть чем угодно, перестрахуемся
|
// Поскольку any может быть чем угодно, перестрахуемся.
|
||||||
if reflect.TypeOf(iface).Kind() == reflect.Slice {
|
if reflect.TypeOf(iface).Kind() == reflect.Slice {
|
||||||
shit := reflect.ValueOf(iface)
|
shit := reflect.ValueOf(iface)
|
||||||
|
|
||||||
for i := 0; i < shit.Len(); i++ {
|
for v := range shit.Seq() {
|
||||||
mySlice = append(mySlice, fmt.Sprint(shit.Index(i)))
|
mySlice = append(mySlice, v.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,10 +49,10 @@ func (j *Jabber) GetRealJIDfromNick(fullNick string) string {
|
|||||||
|
|
||||||
room := (strings.SplitN(fullNick, "/", 2))[0]
|
room := (strings.SplitN(fullNick, "/", 2))[0]
|
||||||
|
|
||||||
// Достанем presence участника
|
// Достанем presence участника.
|
||||||
presenceJSONInterface, present := j.RoomPresences.Get(room)
|
presenceJSONInterface, present := j.RoomPresences.Get(room)
|
||||||
|
|
||||||
// Никого нет дома
|
// Никого нет дома.
|
||||||
if !present {
|
if !present {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -85,8 +85,9 @@ func (j *Jabber) GetBotNickFromRoomConfig(room string) string {
|
|||||||
// RotateStatus периодически изменяет статус бота в MUC-е согласно настройкам из кофига.
|
// RotateStatus периодически изменяет статус бота в MUC-е согласно настройкам из кофига.
|
||||||
func (j *Jabber) RotateStatus(room string) error {
|
func (j *Jabber) RotateStatus(room string) error {
|
||||||
for {
|
for {
|
||||||
// TODO: Переделать на ticker-ы
|
// TODO: Переделать на ticker-ы.
|
||||||
totalSleepTime := time.Duration(j.C.Jabber.RuntimeStatus.RotationTime) * time.Second
|
totalSleepTime := time.Duration(j.C.Jabber.RuntimeStatus.RotationTime) * time.Second
|
||||||
|
|
||||||
totalSleepTime += time.Duration(j.C.Jabber.RuntimeStatus.RotationSplayTime) * time.Second
|
totalSleepTime += time.Duration(j.C.Jabber.RuntimeStatus.RotationSplayTime) * time.Second
|
||||||
|
|
||||||
for {
|
for {
|
||||||
@@ -129,9 +130,9 @@ func (j *Jabber) JoinMuc(room string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ждём, пока muc нам вернёт список фичей.
|
// Ждём, пока muc нам вернёт список фичей.
|
||||||
for i := 0; i < (20 * int(j.C.Jabber.ConnectionTimeout)); i++ {
|
for range 20 * int(j.C.Jabber.ConnectionTimeout) {
|
||||||
var (
|
var (
|
||||||
myRoom interface{}
|
myRoom any
|
||||||
supported bool
|
supported bool
|
||||||
exist bool
|
exist bool
|
||||||
)
|
)
|
||||||
@@ -154,7 +155,7 @@ func (j *Jabber) JoinMuc(room string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Заходим в конфу, наконец-то
|
// Заходим в конфу, наконец-то.
|
||||||
if _, err := j.Talk.JoinMUCNoHistory(room, j.GetBotNickFromRoomConfig(room)); err != nil {
|
if _, err := j.Talk.JoinMUCNoHistory(room, j.GetBotNickFromRoomConfig(room)); err != nil {
|
||||||
return fmt.Errorf("unable to join to MUC: %s, %w", room, err)
|
return fmt.Errorf("unable to join to MUC: %s, %w", room, err)
|
||||||
}
|
}
|
||||||
@@ -164,7 +165,7 @@ func (j *Jabber) JoinMuc(room string) error {
|
|||||||
// Ждём, когда прилетит presence из комнаты, тогда мы точно знаем, что мы вошли.
|
// Ждём, когда прилетит presence из комнаты, тогда мы точно знаем, что мы вошли.
|
||||||
entered := false
|
entered := false
|
||||||
|
|
||||||
for i := 0; i < (20 * int(j.C.Jabber.ConnectionTimeout)); i++ {
|
for range 20 * int(j.C.Jabber.ConnectionTimeout) {
|
||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
|
||||||
if slices.Contains(j.RoomsConnected, room) {
|
if slices.Contains(j.RoomsConnected, room) {
|
||||||
@@ -189,7 +190,7 @@ func (j *Jabber) JoinMuc(room string) error {
|
|||||||
|
|
||||||
j.GTomb.Go(func() error { return j.RotateStatus(room) })
|
j.GTomb.Go(func() error { return j.RotateStatus(room) })
|
||||||
|
|
||||||
// Время проверить участников на предмет злобности
|
// Время проверить участников на предмет злобности.
|
||||||
namesInterface, present := j.RoomPresences.Get(room)
|
namesInterface, present := j.RoomPresences.Get(room)
|
||||||
|
|
||||||
// Если room есть в списке presence-ов, то фигачим. room там должен быть, просто обязан.
|
// Если room есть в списке presence-ов, то фигачим. room там должен быть, просто обязан.
|
||||||
@@ -221,6 +222,7 @@ func (j *Jabber) EstablishConnection() error {
|
|||||||
j.RoomsConnected = make([]string, 0)
|
j.RoomsConnected = make([]string, 0)
|
||||||
|
|
||||||
log.Debugf("Establishing connection to %s", j.Options.Host)
|
log.Debugf("Establishing connection to %s", j.Options.Host)
|
||||||
|
|
||||||
j.Talk, err = j.Options.NewClient()
|
j.Talk, err = j.Options.NewClient()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -234,7 +236,7 @@ func (j *Jabber) EstablishConnection() error {
|
|||||||
|
|
||||||
log.Info("Connected")
|
log.Info("Connected")
|
||||||
|
|
||||||
// Джойнимся к чятикам, но делаем это в фоне, чтобы не блочиться на ошибках, например, если бота забанили
|
// Джойнимся к чятикам, но делаем это в фоне, чтобы не блочиться на ошибках, например, если бота забанили.
|
||||||
for _, roomStruct := range j.C.Jabber.Channels {
|
for _, roomStruct := range j.C.Jabber.Channels {
|
||||||
room := roomStruct.Name
|
room := roomStruct.Name
|
||||||
|
|
||||||
@@ -268,6 +270,7 @@ func (j *Jabber) ProbeServerLiveness() error { //nolint:gocognit
|
|||||||
}
|
}
|
||||||
|
|
||||||
sleepTime := time.Duration(j.C.Jabber.ServerPingDelay) * 1000 * time.Millisecond
|
sleepTime := time.Duration(j.C.Jabber.ServerPingDelay) * 1000 * time.Millisecond
|
||||||
|
|
||||||
sleepTime += time.Duration(rand.Int63n(1000*j.C.Jabber.PingSplayDelay)) * time.Millisecond
|
sleepTime += time.Duration(rand.Int63n(1000*j.C.Jabber.PingSplayDelay)) * time.Millisecond
|
||||||
time.Sleep(sleepTime)
|
time.Sleep(sleepTime)
|
||||||
|
|
||||||
@@ -276,26 +279,25 @@ func (j *Jabber) ProbeServerLiveness() error { //nolint:gocognit
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Пингуем, только если не было никакой активности в течение > config.Jabber.ServerPingDelay,
|
// Пингуем, только если не было никакой активности в течение > config.Jabber.ServerPingDelay,
|
||||||
// в худшем случе это будет ~ (config.Jabber.PingSplayDelay * 2) + config.Jabber.PingSplayDelay
|
// в худшем случе это будет ~ (config.Jabber.PingSplayDelay * 2) + config.Jabber.PingSplayDelay.
|
||||||
// if (time.Now().Unix() - lastServerActivity) < (config.Jabber.ServerPingDelay + config.Jabber.PingSplayDelay) {
|
|
||||||
// continue
|
|
||||||
// }
|
|
||||||
|
|
||||||
if j.ServerCapsQueried { // Сервер ответил на disco#info
|
if j.ServerCapsQueried { // Сервер ответил на disco#info.
|
||||||
var (
|
var (
|
||||||
value interface{}
|
value any
|
||||||
exist bool
|
exist bool
|
||||||
)
|
)
|
||||||
|
|
||||||
value, exist = j.ServerCapsList.Get("urn:xmpp:ping")
|
value, exist = j.ServerCapsList.Get("urn:xmpp:ping")
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
// Сервер анонсировал, что умеет в c2s пинги
|
// Сервер анонсировал, что умеет в c2s пинги.
|
||||||
case exist && value.(bool):
|
case exist && value.(bool):
|
||||||
// Таймаут c2s пинга. Возьмём сумму задержки между пингами, добавим таймаут коннекта и добавим
|
// Таймаут c2s пинга. Возьмём сумму задержки между пингами, добавим таймаут коннекта и добавим
|
||||||
// максимальную корректировку разброса.
|
// максимальную корректировку разброса.
|
||||||
txTimeout := j.C.Jabber.ServerPingDelay + j.C.Jabber.ConnectionTimeout
|
txTimeout := j.C.Jabber.ServerPingDelay + j.C.Jabber.ConnectionTimeout
|
||||||
|
|
||||||
txTimeout += j.C.Jabber.PingSplayDelay
|
txTimeout += j.C.Jabber.PingSplayDelay
|
||||||
|
|
||||||
rxTimeout := txTimeout
|
rxTimeout := txTimeout
|
||||||
|
|
||||||
rxTimeAgo := time.Now().Unix() - j.ServerPingTimestampRx
|
rxTimeAgo := time.Now().Unix() - j.ServerPingTimestampRx
|
||||||
@@ -311,7 +313,7 @@ func (j *Jabber) ProbeServerLiveness() error { //nolint:gocognit
|
|||||||
|
|
||||||
return err
|
return err
|
||||||
|
|
||||||
// По-умолчанию, мы отправляем c2s пинг
|
// По-умолчанию, мы отправляем c2s пинг.
|
||||||
default:
|
default:
|
||||||
log.Debugf("Sending c2s ping from %s to %s", j.Talk.JID(), j.C.Jabber.Server)
|
log.Debugf("Sending c2s ping from %s to %s", j.Talk.JID(), j.C.Jabber.Server)
|
||||||
|
|
||||||
@@ -321,7 +323,7 @@ func (j *Jabber) ProbeServerLiveness() error { //nolint:gocognit
|
|||||||
|
|
||||||
j.ServerPingTimestampTx = time.Now().Unix()
|
j.ServerPingTimestampTx = time.Now().Unix()
|
||||||
}
|
}
|
||||||
} else { // Первая пуля пока не вылетела, отправляем
|
} else { // Первая пуля пока не вылетела, отправляем.
|
||||||
log.Debugf("Sending first c2s ping from %s to %s", j.Talk.JID(), j.C.Jabber.Server)
|
log.Debugf("Sending first c2s ping from %s to %s", j.Talk.JID(), j.C.Jabber.Server)
|
||||||
|
|
||||||
if err := j.Talk.PingC2S(j.Talk.JID(), j.C.Jabber.Server); err != nil {
|
if err := j.Talk.PingC2S(j.Talk.JID(), j.C.Jabber.Server); err != nil {
|
||||||
@@ -331,7 +333,7 @@ func (j *Jabber) ProbeServerLiveness() error { //nolint:gocognit
|
|||||||
j.ServerPingTimestampTx = time.Now().Unix()
|
j.ServerPingTimestampTx = time.Now().Unix()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Сервер не анонсировал, что умеет в c2s пинги
|
// Сервер не анонсировал, что умеет в c2s пинги.
|
||||||
default:
|
default:
|
||||||
log.Debug("Sending keepalive whitespace ping")
|
log.Debug("Sending keepalive whitespace ping")
|
||||||
|
|
||||||
@@ -339,7 +341,7 @@ func (j *Jabber) ProbeServerLiveness() error { //nolint:gocognit
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else { // Сервер не ответил на disco#info
|
} else { // Сервер не ответил на disco#info.
|
||||||
log.Debug("Sending keepalive whitespace ping")
|
log.Debug("Sending keepalive whitespace ping")
|
||||||
|
|
||||||
if _, err := j.Talk.SendKeepAlive(); err != nil {
|
if _, err := j.Talk.SendKeepAlive(); err != nil {
|
||||||
@@ -358,7 +360,7 @@ func (j *Jabber) ProbeMUCLiveness() { //nolint:gocognit
|
|||||||
for _, room := range j.RoomsConnected {
|
for _, room := range j.RoomsConnected {
|
||||||
var (
|
var (
|
||||||
exist bool
|
exist bool
|
||||||
lastActivityTS interface{}
|
lastActivityTS any
|
||||||
)
|
)
|
||||||
|
|
||||||
// Если записи про комнату нету, то пинговать её бессмысленно.
|
// Если записи про комнату нету, то пинговать её бессмысленно.
|
||||||
@@ -393,7 +395,7 @@ func (j *Jabber) ProbeMUCLiveness() { //nolint:gocognit
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
*/
|
*/
|
||||||
var roomMap interface{}
|
var roomMap any
|
||||||
|
|
||||||
roomMap, exist = j.MucCapsList.Get(room)
|
roomMap, exist = j.MucCapsList.Get(room)
|
||||||
|
|
||||||
|
|||||||
27
vendor/golang.org/x/exp/LICENSE
generated
vendored
27
vendor/golang.org/x/exp/LICENSE
generated
vendored
@@ -1,27 +0,0 @@
|
|||||||
Copyright 2009 The Go Authors.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above
|
|
||||||
copyright notice, this list of conditions and the following disclaimer
|
|
||||||
in the documentation and/or other materials provided with the
|
|
||||||
distribution.
|
|
||||||
* Neither the name of Google LLC nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
22
vendor/golang.org/x/exp/PATENTS
generated
vendored
22
vendor/golang.org/x/exp/PATENTS
generated
vendored
@@ -1,22 +0,0 @@
|
|||||||
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.
|
|
||||||
182
vendor/golang.org/x/exp/slices/slices.go
generated
vendored
182
vendor/golang.org/x/exp/slices/slices.go
generated
vendored
@@ -1,182 +0,0 @@
|
|||||||
// Copyright 2021 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 slices defines various functions useful with slices of any type.
|
|
||||||
package slices
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cmp"
|
|
||||||
"slices"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Equal reports whether two slices are equal: the same length and all
|
|
||||||
// elements equal. If the lengths are different, Equal returns false.
|
|
||||||
// Otherwise, the elements are compared in increasing index order, and the
|
|
||||||
// comparison stops at the first unequal pair.
|
|
||||||
// Floating point NaNs are not considered equal.
|
|
||||||
//
|
|
||||||
//go:fix inline
|
|
||||||
func Equal[S ~[]E, E comparable](s1, s2 S) bool {
|
|
||||||
return slices.Equal(s1, s2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EqualFunc reports whether two slices are equal using an equality
|
|
||||||
// function on each pair of elements. If the lengths are different,
|
|
||||||
// EqualFunc returns false. Otherwise, the elements are compared in
|
|
||||||
// increasing index order, and the comparison stops at the first index
|
|
||||||
// for which eq returns false.
|
|
||||||
//
|
|
||||||
//go:fix inline
|
|
||||||
func EqualFunc[S1 ~[]E1, S2 ~[]E2, E1, E2 any](s1 S1, s2 S2, eq func(E1, E2) bool) bool {
|
|
||||||
return slices.EqualFunc(s1, s2, eq)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compare compares the elements of s1 and s2, using [cmp.Compare] on each pair
|
|
||||||
// of elements. The elements are compared sequentially, starting at index 0,
|
|
||||||
// until one element is not equal to the other.
|
|
||||||
// The result of comparing the first non-matching elements is returned.
|
|
||||||
// If both slices are equal until one of them ends, the shorter slice is
|
|
||||||
// considered less than the longer one.
|
|
||||||
// The result is 0 if s1 == s2, -1 if s1 < s2, and +1 if s1 > s2.
|
|
||||||
//
|
|
||||||
//go:fix inline
|
|
||||||
func Compare[S ~[]E, E cmp.Ordered](s1, s2 S) int {
|
|
||||||
return slices.Compare(s1, s2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CompareFunc is like [Compare] but uses a custom comparison function on each
|
|
||||||
// pair of elements.
|
|
||||||
// The result is the first non-zero result of cmp; if cmp always
|
|
||||||
// returns 0 the result is 0 if len(s1) == len(s2), -1 if len(s1) < len(s2),
|
|
||||||
// and +1 if len(s1) > len(s2).
|
|
||||||
//
|
|
||||||
//go:fix inline
|
|
||||||
func CompareFunc[S1 ~[]E1, S2 ~[]E2, E1, E2 any](s1 S1, s2 S2, cmp func(E1, E2) int) int {
|
|
||||||
return slices.CompareFunc(s1, s2, cmp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Index returns the index of the first occurrence of v in s,
|
|
||||||
// or -1 if not present.
|
|
||||||
//
|
|
||||||
//go:fix inline
|
|
||||||
func Index[S ~[]E, E comparable](s S, v E) int {
|
|
||||||
return slices.Index(s, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IndexFunc returns the first index i satisfying f(s[i]),
|
|
||||||
// or -1 if none do.
|
|
||||||
//
|
|
||||||
//go:fix inline
|
|
||||||
func IndexFunc[S ~[]E, E any](s S, f func(E) bool) int {
|
|
||||||
return slices.IndexFunc(s, f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Contains reports whether v is present in s.
|
|
||||||
//
|
|
||||||
//go:fix inline
|
|
||||||
func Contains[S ~[]E, E comparable](s S, v E) bool {
|
|
||||||
return slices.Contains(s, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContainsFunc reports whether at least one
|
|
||||||
// element e of s satisfies f(e).
|
|
||||||
//
|
|
||||||
//go:fix inline
|
|
||||||
func ContainsFunc[S ~[]E, E any](s S, f func(E) bool) bool {
|
|
||||||
return slices.ContainsFunc(s, f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert inserts the values v... into s at index i,
|
|
||||||
// returning the modified slice.
|
|
||||||
// The elements at s[i:] are shifted up to make room.
|
|
||||||
// In the returned slice r, r[i] == v[0],
|
|
||||||
// and r[i+len(v)] == value originally at r[i].
|
|
||||||
// Insert panics if i is out of range.
|
|
||||||
// This function is O(len(s) + len(v)).
|
|
||||||
//
|
|
||||||
//go:fix inline
|
|
||||||
func Insert[S ~[]E, E any](s S, i int, v ...E) S {
|
|
||||||
return slices.Insert(s, i, v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete removes the elements s[i:j] from s, returning the modified slice.
|
|
||||||
// Delete panics if j > len(s) or s[i:j] is not a valid slice of s.
|
|
||||||
// Delete is O(len(s)-i), so if many items must be deleted, it is better to
|
|
||||||
// make a single call deleting them all together than to delete one at a time.
|
|
||||||
// Delete zeroes the elements s[len(s)-(j-i):len(s)].
|
|
||||||
//
|
|
||||||
//go:fix inline
|
|
||||||
func Delete[S ~[]E, E any](s S, i, j int) S {
|
|
||||||
return slices.Delete(s, i, j)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteFunc removes any elements from s for which del returns true,
|
|
||||||
// returning the modified slice.
|
|
||||||
// DeleteFunc zeroes the elements between the new length and the original length.
|
|
||||||
//
|
|
||||||
//go:fix inline
|
|
||||||
func DeleteFunc[S ~[]E, E any](s S, del func(E) bool) S {
|
|
||||||
return slices.DeleteFunc(s, del)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace replaces the elements s[i:j] by the given v, and returns the
|
|
||||||
// modified slice. Replace panics if s[i:j] is not a valid slice of s.
|
|
||||||
// When len(v) < (j-i), Replace zeroes the elements between the new length and the original length.
|
|
||||||
//
|
|
||||||
//go:fix inline
|
|
||||||
func Replace[S ~[]E, E any](s S, i, j int, v ...E) S {
|
|
||||||
return slices.Replace(s, i, j, v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clone returns a copy of the slice.
|
|
||||||
// The elements are copied using assignment, so this is a shallow clone.
|
|
||||||
//
|
|
||||||
//go:fix inline
|
|
||||||
func Clone[S ~[]E, E any](s S) S {
|
|
||||||
return slices.Clone(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compact replaces consecutive runs of equal elements with a single copy.
|
|
||||||
// This is like the uniq command found on Unix.
|
|
||||||
// Compact modifies the contents of the slice s and returns the modified slice,
|
|
||||||
// which may have a smaller length.
|
|
||||||
// Compact zeroes the elements between the new length and the original length.
|
|
||||||
//
|
|
||||||
//go:fix inline
|
|
||||||
func Compact[S ~[]E, E comparable](s S) S {
|
|
||||||
return slices.Compact(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CompactFunc is like [Compact] but uses an equality function to compare elements.
|
|
||||||
// For runs of elements that compare equal, CompactFunc keeps the first one.
|
|
||||||
// CompactFunc zeroes the elements between the new length and the original length.
|
|
||||||
//
|
|
||||||
//go:fix inline
|
|
||||||
func CompactFunc[S ~[]E, E any](s S, eq func(E, E) bool) S {
|
|
||||||
return slices.CompactFunc(s, eq)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Grow increases the slice's capacity, if necessary, to guarantee space for
|
|
||||||
// another n elements. After Grow(n), at least n elements can be appended
|
|
||||||
// to the slice without another allocation. If n is negative or too large to
|
|
||||||
// allocate the memory, Grow panics.
|
|
||||||
//
|
|
||||||
//go:fix inline
|
|
||||||
func Grow[S ~[]E, E any](s S, n int) S {
|
|
||||||
return slices.Grow(s, n)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clip removes unused capacity from the slice, returning s[:len(s):len(s)].
|
|
||||||
//
|
|
||||||
//go:fix inline
|
|
||||||
func Clip[S ~[]E, E any](s S) S {
|
|
||||||
return slices.Clip(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reverse reverses the elements of the slice in place.
|
|
||||||
//
|
|
||||||
//go:fix inline
|
|
||||||
func Reverse[S ~[]E, E any](s S) {
|
|
||||||
slices.Reverse(s)
|
|
||||||
}
|
|
||||||
115
vendor/golang.org/x/exp/slices/sort.go
generated
vendored
115
vendor/golang.org/x/exp/slices/sort.go
generated
vendored
@@ -1,115 +0,0 @@
|
|||||||
// 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 slices
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cmp"
|
|
||||||
"slices"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Sort sorts a slice of any ordered type in ascending order.
|
|
||||||
// When sorting floating-point numbers, NaNs are ordered before other values.
|
|
||||||
//
|
|
||||||
//go:fix inline
|
|
||||||
func Sort[S ~[]E, E cmp.Ordered](x S) {
|
|
||||||
slices.Sort(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SortFunc sorts the slice x in ascending order as determined by the cmp
|
|
||||||
// function. This sort is not guaranteed to be stable.
|
|
||||||
// cmp(a, b) should return a negative number when a < b, a positive number when
|
|
||||||
// a > b and zero when a == b or when a is not comparable to b in the sense
|
|
||||||
// of the formal definition of Strict Weak Ordering.
|
|
||||||
//
|
|
||||||
// SortFunc requires that cmp is a strict weak ordering.
|
|
||||||
// See https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings.
|
|
||||||
// To indicate 'uncomparable', return 0 from the function.
|
|
||||||
//
|
|
||||||
//go:fix inline
|
|
||||||
func SortFunc[S ~[]E, E any](x S, cmp func(a, b E) int) {
|
|
||||||
slices.SortFunc(x, cmp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SortStableFunc sorts the slice x while keeping the original order of equal
|
|
||||||
// elements, using cmp to compare elements in the same way as [SortFunc].
|
|
||||||
//
|
|
||||||
//go:fix inline
|
|
||||||
func SortStableFunc[S ~[]E, E any](x S, cmp func(a, b E) int) {
|
|
||||||
slices.SortStableFunc(x, cmp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsSorted reports whether x is sorted in ascending order.
|
|
||||||
//
|
|
||||||
//go:fix inline
|
|
||||||
func IsSorted[S ~[]E, E cmp.Ordered](x S) bool {
|
|
||||||
return slices.IsSorted(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsSortedFunc reports whether x is sorted in ascending order, with cmp as the
|
|
||||||
// comparison function as defined by [SortFunc].
|
|
||||||
//
|
|
||||||
//go:fix inline
|
|
||||||
func IsSortedFunc[S ~[]E, E any](x S, cmp func(a, b E) int) bool {
|
|
||||||
return slices.IsSortedFunc(x, cmp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Min returns the minimal value in x. It panics if x is empty.
|
|
||||||
// For floating-point numbers, Min propagates NaNs (any NaN value in x
|
|
||||||
// forces the output to be NaN).
|
|
||||||
//
|
|
||||||
//go:fix inline
|
|
||||||
func Min[S ~[]E, E cmp.Ordered](x S) E {
|
|
||||||
return slices.Min(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MinFunc returns the minimal value in x, using cmp to compare elements.
|
|
||||||
// It panics if x is empty. If there is more than one minimal element
|
|
||||||
// according to the cmp function, MinFunc returns the first one.
|
|
||||||
//
|
|
||||||
//go:fix inline
|
|
||||||
func MinFunc[S ~[]E, E any](x S, cmp func(a, b E) int) E {
|
|
||||||
return slices.MinFunc(x, cmp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Max returns the maximal value in x. It panics if x is empty.
|
|
||||||
// For floating-point E, Max propagates NaNs (any NaN value in x
|
|
||||||
// forces the output to be NaN).
|
|
||||||
//
|
|
||||||
//go:fix inline
|
|
||||||
func Max[S ~[]E, E cmp.Ordered](x S) E {
|
|
||||||
return slices.Max(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MaxFunc returns the maximal value in x, using cmp to compare elements.
|
|
||||||
// It panics if x is empty. If there is more than one maximal element
|
|
||||||
// according to the cmp function, MaxFunc returns the first one.
|
|
||||||
//
|
|
||||||
//go:fix inline
|
|
||||||
func MaxFunc[S ~[]E, E any](x S, cmp func(a, b E) int) E {
|
|
||||||
return slices.MaxFunc(x, cmp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BinarySearch searches for target in a sorted slice and returns the position
|
|
||||||
// where target is found, or the position where target would appear in the
|
|
||||||
// sort order; it also returns a bool saying whether the target is really found
|
|
||||||
// in the slice. The slice must be sorted in increasing order.
|
|
||||||
//
|
|
||||||
//go:fix inline
|
|
||||||
func BinarySearch[S ~[]E, E cmp.Ordered](x S, target E) (int, bool) {
|
|
||||||
return slices.BinarySearch(x, target)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BinarySearchFunc works like [BinarySearch], but uses a custom comparison
|
|
||||||
// function. The slice must be sorted in increasing order, where "increasing"
|
|
||||||
// is defined by cmp. cmp should return 0 if the slice element matches
|
|
||||||
// the target, a negative number if the slice element precedes the target,
|
|
||||||
// or a positive number if the slice element follows the target.
|
|
||||||
// cmp must implement the same ordering as the slice, such that if
|
|
||||||
// cmp(a, t) < 0 and cmp(b, t) >= 0, then a must precede b in the slice.
|
|
||||||
//
|
|
||||||
//go:fix inline
|
|
||||||
func BinarySearchFunc[S ~[]E, E, T any](x S, target T, cmp func(E, T) int) (int, bool) {
|
|
||||||
return slices.BinarySearchFunc(x, target, cmp)
|
|
||||||
}
|
|
||||||
3
vendor/modules.txt
vendored
3
vendor/modules.txt
vendored
@@ -19,9 +19,6 @@ github.com/sirupsen/logrus
|
|||||||
# golang.org/x/crypto v0.38.0
|
# golang.org/x/crypto v0.38.0
|
||||||
## explicit; go 1.23.0
|
## explicit; go 1.23.0
|
||||||
golang.org/x/crypto/pbkdf2
|
golang.org/x/crypto/pbkdf2
|
||||||
# golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6
|
|
||||||
## explicit; go 1.23.0
|
|
||||||
golang.org/x/exp/slices
|
|
||||||
# golang.org/x/net v0.40.0
|
# golang.org/x/net v0.40.0
|
||||||
## explicit; go 1.23.0
|
## explicit; go 1.23.0
|
||||||
golang.org/x/net/context
|
golang.org/x/net/context
|
||||||
|
|||||||
Reference in New Issue
Block a user