Update linter, fix linter errors

This commit is contained in:
eleksir
2025-11-15 15:52:34 +03:00
parent 3ebd2b48d1
commit ee5d40cfee
21 changed files with 1106 additions and 737 deletions

View File

@@ -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$

View File

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

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

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

View File

@@ -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"
) )
// Выдаёт "очки похожести" для данной фразы. // Выдаёт "очки похожести" для данной фразы.

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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{}
} }

View File

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

View File

@@ -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))
} }

View File

@@ -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 {

View File

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

View File

@@ -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")

View File

@@ -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(),

View File

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

View File

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

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

@@ -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.

View File

@@ -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)
}

View File

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

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