From ee5d40cfeebe685b20a5f213377ac1d9168626f9 Mon Sep 17 00:00:00 2001 From: eleksir Date: Sat, 15 Nov 2025 15:52:34 +0300 Subject: [PATCH] Update linter, fix linter errors --- .golangci.yml | 747 ++++++++++++++++++++++- cmd/buny-jabber-bot/main.go | 6 +- go.mod | 3 +- go.sum | 2 - internal/jabber/bf.go | 16 +- internal/jabber/black-whilte_lists.go | 28 +- internal/jabber/buny.go | 51 +- internal/jabber/collection.go | 31 +- internal/jabber/commands.go | 41 +- internal/jabber/event_parser.go | 100 +-- internal/jabber/main_loop.go | 16 +- internal/jabber/read_config.go | 39 +- internal/jabber/sig_handler.go | 2 + internal/jabber/squash.go | 1 + internal/jabber/types.go | 355 +++++------ internal/jabber/util.go | 56 +- vendor/golang.org/x/exp/LICENSE | 27 - vendor/golang.org/x/exp/PATENTS | 22 - vendor/golang.org/x/exp/slices/slices.go | 182 ------ vendor/golang.org/x/exp/slices/sort.go | 115 ---- vendor/modules.txt | 3 - 21 files changed, 1106 insertions(+), 737 deletions(-) delete mode 100644 vendor/golang.org/x/exp/LICENSE delete mode 100644 vendor/golang.org/x/exp/PATENTS delete mode 100644 vendor/golang.org/x/exp/slices/slices.go delete mode 100644 vendor/golang.org/x/exp/slices/sort.go diff --git a/.golangci.yml b/.golangci.yml index 585a6b9..cb1f509 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,29 +1,12 @@ +version: "2" run: - tests: false + go: "1.25" modules-download-mode: readonly + tests: false 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: - disable-all: true + default: none enable: - - errcheck - - gosimple - - govet - - ineffassign - - staticcheck - - unused - asasalint - asciicheck - bodyclose @@ -31,25 +14,30 @@ linters: - contextcheck - decorder - durationcheck + - errcheck - errchkjson - errorlint - exhaustive - - exhaustruct - - copyloopvar + - exptostd - ginkgolinter - gocheckcompilerdirectives - gocritic + - godoclint - godot - - err113 - - gofmt - - goimports + - godox + - goprintffuncname - gosec - gosmopolitan + - govet - grouper - importas + - ineffassign + - intrange - ireturn - makezero - mirror + - misspell + - modernize - nakedret - nilerr - nilnil @@ -57,13 +45,714 @@ linters: - noctx - nolintlint - nonamedreturns + - perfsprint - predeclared - promlinter - reassign - revive - rowserrcheck - - stylecheck + - staticcheck - unconvert + - unused + - usestdlibvars - wastedassign - - whitespace - - wsl + - wsl_v5 + settings: + decorder: + dec-order: + - type + - const + - var + - func + gocritic: + disable-all: false + enable-all: true + disabled-checks: + - singleCaseSwitch + - octalLiteral + - regexpSimplify + - paramTypeCombine + - whyNoLint + - hugeParam + godoclint: + default: all + ignore-unexported: false + ignore-exported: false + include-unexported: true + disable: + - max-len + godot: + scope: all + exclude: + # Exclude todo and fixme comments. + - "^fixme:" + - "^todo:" + period: true + capital: false + godox: + keywords: + - FIXME + gosec: # go-fucking-sic + excludes: + # Боже, как же вы затрахали своей крипто-ебучкой, наркоманы хреновы! + - G405 # Detect the usage of DES or RC4 + - G406 # Detect the usage of MD4 or RIPEMD160 + # Оно не используется для повышения безопасности или каких-то таких секъюрных вещей, оно используется для выборки + # произвольного элемента из массива, так что ИДИ НАХУЙ с этой проверкой и не еби мозги липовой безопасностью. + - G404 # Insecure random number source (rand) + - G501 # Import blocklist: crypto/md5 + - G502 # Import blocklist: crypto/des + - G503 # Import blocklist: crypto/rc4 + - G504 # Import blocklist: net/http/cgi + - G505 # Import blocklist: crypto/sha1 + - G506 # Import blocklist: golang.org/x/crypto/md4 + - G507 # Import blocklist: golang.org/x/crypto/ripemd160 + config: + global: + audit: true + # Ваша сраная паранойя при создании файлов и каталогов мешает нормальной работе сервисов. Группы придуманы для + # удобства, группе удобно давать право чтения. Поэтому идите нахуй со своими ограничениями на чтение для + # группы, "безопасные" клоуны. + G301: "0755" + G306: "0644" + govet: + disable-all: false + enable-all: true + disable: + - shadow + - fieldalignment + grouper: + # Require the use of a single global 'const' declaration only. + # Default: false + const-require-single-const: true + # Require the use of grouped global 'const' declarations. + # Default: false + const-require-grouping: true + # Require the use of a single 'import' declaration only. + # Default: false + import-require-single-import: true + # Require the use of grouped 'import' declarations. + # Default: false + import-require-grouping: true + # Require the use of a single global 'type' declaration only. + # Default: false + type-require-single-type: true + # Require the use of grouped global 'type' declarations. + # Default: false + type-require-grouping: true + # Require the use of a single global 'var' declaration only. + # Default: false + var-require-single-var: true + # Require the use of grouped global 'var' declarations. + # Default: false + var-require-grouping: true + misspell: + # Correct spellings using locale preferences for US or UK. + # Setting locale to US will correct the British spelling of 'colour' to 'color'. + # Default is to use a neutral variety of English. + locale: US + # Typos to ignore. + # Should be in lower case. + # Default: [] + ignore-rules: + - someword + # Extra word corrections. + # `typo` and `correction` should only contain letters. + # The words are case-insensitive. + # Default: [] + extra-words: + - typo: "iff" + correction: "if" + - typo: "cancelation" + correction: "cancellation" + # Mode of the analysis: + # - default: checks all the file content. + # - restricted: checks only comments. + # Default: "" + mode: restricted + nonamedreturns: + # Report named error if it is assigned inside defer. + # Default: false + report-error-in-defer: true + perfsprint: + integer-format: true + int-conversion: true + error-format: true + err-error: false + errorf: true + string-format: true + sprintf1: true + strconcat: true + bool-format: true + hex-format: true + concat-loop: true + loop-other-ops: false + revive: + enable-all-rules: false + staticcheck: + # https://staticcheck.dev/docs/configuration/options/#dot_import_whitelist + # Default: ["github.com/mmcloughlin/avo/build", "github.com/mmcloughlin/avo/operand", "github.com/mmcloughlin/avo/reg"] + dot-import-whitelist: + - fmt + # https://staticcheck.dev/docs/configuration/options/#initialisms + # Default: ["ACL", "API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", "IP", "JSON", "QPS", "RAM", "RPC", "SLA", "SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", "UDP", "UI", "GID", "UID", "UUID", "URI", "URL", "UTF8", "VM", "XML", "XMPP", "XSRF", "XSS", "SIP", "RTP", "AMQP", "DB", "TS"] + initialisms: ["ACL", "API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", "IP", "JSON", "QPS", "RAM", "RPC", "SLA", "SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", "UDP", "UI", "GID", "UID", "UUID", "URI", "URL", "UTF8", "VM", "XML", "XMPP", "XSRF", "XSS", "SIP", "RTP", "AMQP", "DB", "TS"] + # https://staticcheck.dev/docs/configuration/options/#http_status_code_whitelist + # Default: ["200", "400", "404", "500"] + http-status-code-whitelist: ["200", "400", "404", "500"] + # SAxxxx checks in https://staticcheck.dev/docs/configuration/options/#checks + # Example (to disable some checks): [ "all", "-SA1000", "-SA1001"] + # Run `GL_DEBUG=staticcheck golangci-lint run --enable=staticcheck` to see all available checks and enabled by config checks. + # Default: ["all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022"] + checks: + # Invalid regular expression. + # https://staticcheck.dev/docs/checks/#SA1000 + - SA1000 + # Invalid template. + # https://staticcheck.dev/docs/checks/#SA1001 + - SA1001 + # Invalid format in 'time.Parse'. + # https://staticcheck.dev/docs/checks/#SA1002 + - SA1002 + # Unsupported argument to functions in 'encoding/binary'. + # https://staticcheck.dev/docs/checks/#SA1003 + - SA1003 + # Suspiciously small untyped constant in 'time.Sleep'. + # https://staticcheck.dev/docs/checks/#SA1004 + - SA1004 + # Invalid first argument to 'exec.Command'. + # https://staticcheck.dev/docs/checks/#SA1005 + - SA1005 + # 'Printf' with dynamic first argument and no further arguments. + # https://staticcheck.dev/docs/checks/#SA1006 + - SA1006 + # Invalid URL in 'net/url.Parse'. + # https://staticcheck.dev/docs/checks/#SA1007 + - SA1007 + # Non-canonical key in 'http.Header' map. + # https://staticcheck.dev/docs/checks/#SA1008 + - SA1008 + # '(*regexp.Regexp).FindAll' called with 'n == 0', which will always return zero results. + # https://staticcheck.dev/docs/checks/#SA1010 + - SA1010 + # Various methods in the "strings" package expect valid UTF-8, but invalid input is provided. + # https://staticcheck.dev/docs/checks/#SA1011 + - SA1011 + # A nil 'context.Context' is being passed to a function, consider using 'context.TODO' instead. + # https://staticcheck.dev/docs/checks/#SA1012 + - SA1012 + # 'io.Seeker.Seek' is being called with the whence constant as the first argument, but it should be the second. + # https://staticcheck.dev/docs/checks/#SA1013 + - SA1013 + # Non-pointer value passed to 'Unmarshal' or 'Decode'. + # https://staticcheck.dev/docs/checks/#SA1014 + - SA1014 + # Using 'time.Tick' in a way that will leak. Consider using 'time.NewTicker', and only use 'time.Tick' in tests, commands and endless functions. + # https://staticcheck.dev/docs/checks/#SA1015 + - SA1015 + # Trapping a signal that cannot be trapped. + # https://staticcheck.dev/docs/checks/#SA1016 + - SA1016 + # Channels used with 'os/signal.Notify' should be buffered. + # https://staticcheck.dev/docs/checks/#SA1017 + - SA1017 + # 'strings.Replace' called with 'n == 0', which does nothing. + # https://staticcheck.dev/docs/checks/#SA1018 + - SA1018 + # Using a deprecated function, variable, constant or field. + # https://staticcheck.dev/docs/checks/#SA1019 + - SA1019 + # Using an invalid host:port pair with a 'net.Listen'-related function. + # https://staticcheck.dev/docs/checks/#SA1020 + - SA1020 + # Using 'bytes.Equal' to compare two 'net.IP'. + # https://staticcheck.dev/docs/checks/#SA1021 + - SA1021 + # Modifying the buffer in an 'io.Writer' implementation. + # https://staticcheck.dev/docs/checks/#SA1023 + - SA1023 + # A string cutset contains duplicate characters. + # https://staticcheck.dev/docs/checks/#SA1024 + - SA1024 + # It is not possible to use '(*time.Timer).Reset''s return value correctly. + # https://staticcheck.dev/docs/checks/#SA1025 + - SA1025 + # Cannot marshal channels or functions. + # https://staticcheck.dev/docs/checks/#SA1026 + - SA1026 + # Atomic access to 64-bit variable must be 64-bit aligned. + # https://staticcheck.dev/docs/checks/#SA1027 + - SA1027 + # 'sort.Slice' can only be used on slices. + # https://staticcheck.dev/docs/checks/#SA1028 + - SA1028 + # Inappropriate key in call to 'context.WithValue'. + # https://staticcheck.dev/docs/checks/#SA1029 + - SA1029 + # Invalid argument in call to a 'strconv' function. + # https://staticcheck.dev/docs/checks/#SA1030 + - SA1030 + # Overlapping byte slices passed to an encoder. + # https://staticcheck.dev/docs/checks/#SA1031 + - SA1031 + # Wrong order of arguments to 'errors.Is'. + # https://staticcheck.dev/docs/checks/#SA1032 + - SA1032 + # 'sync.WaitGroup.Add' called inside the goroutine, leading to a race condition. + # https://staticcheck.dev/docs/checks/#SA2000 + - SA2000 + # Empty critical section, did you mean to defer the unlock?. + # https://staticcheck.dev/docs/checks/#SA2001 + - SA2001 + # Called 'testing.T.FailNow' or 'SkipNow' in a goroutine, which isn't allowed. + # https://staticcheck.dev/docs/checks/#SA2002 + - SA2002 + # Deferred 'Lock' right after locking, likely meant to defer 'Unlock' instead. + # https://staticcheck.dev/docs/checks/#SA2003 + - SA2003 + # 'TestMain' doesn't call 'os.Exit', hiding test failures. + # https://staticcheck.dev/docs/checks/#SA3000 + - SA3000 + # Assigning to 'b.N' in benchmarks distorts the results. + # https://staticcheck.dev/docs/checks/#SA3001 + - SA3001 + # Binary operator has identical expressions on both sides. + # https://staticcheck.dev/docs/checks/#SA4000 + - SA4000 + # '&*x' gets simplified to 'x', it does not copy 'x'. + # https://staticcheck.dev/docs/checks/#SA4001 + - SA4001 + # Comparing unsigned values against negative values is pointless. + # https://staticcheck.dev/docs/checks/#SA4003 + - SA4003 + # The loop exits unconditionally after one iteration. + # https://staticcheck.dev/docs/checks/#SA4004 + - SA4004 + # Field assignment that will never be observed. Did you mean to use a pointer receiver?. + # https://staticcheck.dev/docs/checks/#SA4005 + - SA4005 + # A value assigned to a variable is never read before being overwritten. Forgotten error check or dead code?. + # https://staticcheck.dev/docs/checks/#SA4006 + - SA4006 + # The variable in the loop condition never changes, are you incrementing the wrong variable?. + # https://staticcheck.dev/docs/checks/#SA4008 + - SA4008 + # A function argument is overwritten before its first use. + # https://staticcheck.dev/docs/checks/#SA4009 + - SA4009 + # The result of 'append' will never be observed anywhere. + # https://staticcheck.dev/docs/checks/#SA4010 + - SA4010 + # Break statement with no effect. Did you mean to break out of an outer loop?. + # https://staticcheck.dev/docs/checks/#SA4011 + - SA4011 + # Comparing a value against NaN even though no value is equal to NaN. + # https://staticcheck.dev/docs/checks/#SA4012 + - SA4012 + # Negating a boolean twice ('!!b') is the same as writing 'b'. This is either redundant, or a typo. + # https://staticcheck.dev/docs/checks/#SA4013 + - SA4013 + # An if/else if chain has repeated conditions and no side-effects; if the condition didn't match the first time, it won't match the second time, either. + # https://staticcheck.dev/docs/checks/#SA4014 + - SA4014 + # Calling functions like 'math.Ceil' on floats converted from integers doesn't do anything useful. + # https://staticcheck.dev/docs/checks/#SA4015 + - SA4015 + # Certain bitwise operations, such as 'x ^ 0', do not do anything useful. + # https://staticcheck.dev/docs/checks/#SA4016 + - SA4016 + # Discarding the return values of a function without side effects, making the call pointless. + # https://staticcheck.dev/docs/checks/#SA4017 + - SA4017 + # Self-assignment of variables. + # https://staticcheck.dev/docs/checks/#SA4018 + - SA4018 + # Multiple, identical build constraints in the same file. + # https://staticcheck.dev/docs/checks/#SA4019 + - SA4019 + # Unreachable case clause in a type switch. + # https://staticcheck.dev/docs/checks/#SA4020 + - SA4020 + # "x = append(y)" is equivalent to "x = y". + # https://staticcheck.dev/docs/checks/#SA4021 + - SA4021 + # Comparing the address of a variable against nil. + # https://staticcheck.dev/docs/checks/#SA4022 + - SA4022 + # Impossible comparison of interface value with untyped nil. + # https://staticcheck.dev/docs/checks/#SA4023 + - SA4023 + # Checking for impossible return value from a builtin function. + # https://staticcheck.dev/docs/checks/#SA4024 + - SA4024 + # Integer division of literals that results in zero. + # https://staticcheck.dev/docs/checks/#SA4025 + - SA4025 + # Go constants cannot express negative zero. + # https://staticcheck.dev/docs/checks/#SA4026 + - SA4026 + # '(*net/url.URL).Query' returns a copy, modifying it doesn't change the URL. + # https://staticcheck.dev/docs/checks/#SA4027 + - SA4027 + # 'x % 1' is always zero. + # https://staticcheck.dev/docs/checks/#SA4028 + - SA4028 + # Ineffective attempt at sorting slice. + # https://staticcheck.dev/docs/checks/#SA4029 + - SA4029 + # Ineffective attempt at generating random number. + # https://staticcheck.dev/docs/checks/#SA4030 + - SA4030 + # Checking never-nil value against nil. + # https://staticcheck.dev/docs/checks/#SA4031 + - SA4031 + # Comparing 'runtime.GOOS' or 'runtime.GOARCH' against impossible value. + # https://staticcheck.dev/docs/checks/#SA4032 + - SA4032 + # Assignment to nil map. + # https://staticcheck.dev/docs/checks/#SA5000 + - SA5000 + # Deferring 'Close' before checking for a possible error. + # https://staticcheck.dev/docs/checks/#SA5001 + - SA5001 + # The empty for loop ("for {}") spins and can block the scheduler. + # https://staticcheck.dev/docs/checks/#SA5002 + - SA5002 + # Defers in infinite loops will never execute. + # https://staticcheck.dev/docs/checks/#SA5003 + - SA5003 + # "for { select { ..." with an empty default branch spins. + # https://staticcheck.dev/docs/checks/#SA5004 + - SA5004 + # The finalizer references the finalized object, preventing garbage collection. + # https://staticcheck.dev/docs/checks/#SA5005 + - SA5005 + # Infinite recursive call. + # https://staticcheck.dev/docs/checks/#SA5007 + - SA5007 + # Invalid struct tag. + # https://staticcheck.dev/docs/checks/#SA5008 + - SA5008 + # Invalid Printf call. + # https://staticcheck.dev/docs/checks/#SA5009 + - SA5009 + # Impossible type assertion. + # https://staticcheck.dev/docs/checks/#SA5010 + - SA5010 + # Possible nil pointer dereference. + # https://staticcheck.dev/docs/checks/#SA5011 + - SA5011 + # Passing odd-sized slice to function expecting even size. + # https://staticcheck.dev/docs/checks/#SA5012 + - SA5012 + # Using 'regexp.Match' or related in a loop, should use 'regexp.Compile'. + # https://staticcheck.dev/docs/checks/#SA6000 + - SA6000 + # Missing an optimization opportunity when indexing maps by byte slices. + # https://staticcheck.dev/docs/checks/#SA6001 + - SA6001 + # Storing non-pointer values in 'sync.Pool' allocates memory. + # https://staticcheck.dev/docs/checks/#SA6002 + - SA6002 + # Converting a string to a slice of runes before ranging over it. + # https://staticcheck.dev/docs/checks/#SA6003 + - SA6003 + # Inefficient string comparison with 'strings.ToLower' or 'strings.ToUpper'. + # https://staticcheck.dev/docs/checks/#SA6005 + - SA6005 + # Using io.WriteString to write '[]byte'. + # https://staticcheck.dev/docs/checks/#SA6006 + - SA6006 + # Defers in range loops may not run when you expect them to. + # https://staticcheck.dev/docs/checks/#SA9001 + - SA9001 + # Using a non-octal 'os.FileMode' that looks like it was meant to be in octal. + # https://staticcheck.dev/docs/checks/#SA9002 + - SA9002 + # Empty body in an if or else branch. + # https://staticcheck.dev/docs/checks/#SA9003 + - SA9003 + # Only the first constant has an explicit type. + # https://staticcheck.dev/docs/checks/#SA9004 + - SA9004 + # Trying to marshal a struct with no public fields nor custom marshaling. + # https://staticcheck.dev/docs/checks/#SA9005 + - SA9005 + # Dubious bit shifting of a fixed size integer value. + # https://staticcheck.dev/docs/checks/#SA9006 + - SA9006 + # Deleting a directory that shouldn't be deleted. + # https://staticcheck.dev/docs/checks/#SA9007 + - SA9007 + # 'else' branch of a type assertion is probably not reading the right value. + # https://staticcheck.dev/docs/checks/#SA9008 + - SA9008 + # Ineffectual Go compiler directive. + # https://staticcheck.dev/docs/checks/#SA9009 + - SA9009 + # Incorrect or missing package comment. + # https://staticcheck.dev/docs/checks/#ST1000 + - ST1000 + # Dot imports are discouraged. + # https://staticcheck.dev/docs/checks/#ST1001 + - ST1001 + # Poorly chosen identifier. + # https://staticcheck.dev/docs/checks/#ST1003 + - ST1003 + # Incorrectly formatted error string. + # https://staticcheck.dev/docs/checks/#ST1005 + - ST1005 + # Poorly chosen receiver name. + # https://staticcheck.dev/docs/checks/#ST1006 + - ST1006 + # A function's error value should be its last return value. + # https://staticcheck.dev/docs/checks/#ST1008 + - ST1008 + # Poorly chosen name for variable of type 'time.Duration'. + # https://staticcheck.dev/docs/checks/#ST1011 + - ST1011 + # Poorly chosen name for error variable. + # https://staticcheck.dev/docs/checks/#ST1012 + - ST1012 + # Should use constants for HTTP error codes, not magic numbers. + # https://staticcheck.dev/docs/checks/#ST1013 + - ST1013 + # A switch's default case should be the first or last case. + # https://staticcheck.dev/docs/checks/#ST1015 + - ST1015 + # Use consistent method receiver names. + # https://staticcheck.dev/docs/checks/#ST1016 + - ST1016 + # Don't use Yoda conditions. + # https://staticcheck.dev/docs/checks/#ST1017 + - ST1017 + # Avoid zero-width and control characters in string literals. + # https://staticcheck.dev/docs/checks/#ST1018 + - ST1018 + # Importing the same package multiple times. + # https://staticcheck.dev/docs/checks/#ST1019 + - ST1019 + # The documentation of an exported function should start with the function's name. + # https://staticcheck.dev/docs/checks/#ST1020 + - ST1020 + # The documentation of an exported type should start with type's name. + # https://staticcheck.dev/docs/checks/#ST1021 + - ST1021 + # The documentation of an exported variable or constant should start with variable's name. + # https://staticcheck.dev/docs/checks/#ST1022 + - ST1022 + # Redundant type in variable declaration. + # https://staticcheck.dev/docs/checks/#ST1023 + - ST1023 + # Use plain channel send or receive instead of single-case select. + # https://staticcheck.dev/docs/checks/#S1000 + - S1000 + # Replace for loop with call to copy. + # https://staticcheck.dev/docs/checks/#S1001 + - S1001 + # Omit comparison with boolean constant. + # https://staticcheck.dev/docs/checks/#S1002 + - S1002 + # Replace call to 'strings.Index' with 'strings.Contains'. + # https://staticcheck.dev/docs/checks/#S1003 + - S1003 + # Replace call to 'bytes.Compare' with 'bytes.Equal'. + # https://staticcheck.dev/docs/checks/#S1004 + - S1004 + # Drop unnecessary use of the blank identifier. + # https://staticcheck.dev/docs/checks/#S1005 + - S1005 + # Use "for { ... }" for infinite loops. + # https://staticcheck.dev/docs/checks/#S1006 + - S1006 + # Simplify regular expression by using raw string literal. + # https://staticcheck.dev/docs/checks/#S1007 + - S1007 + # Simplify returning boolean expression. + # https://staticcheck.dev/docs/checks/#S1008 + - S1008 + # Omit redundant nil check on slices, maps, and channels. + # https://staticcheck.dev/docs/checks/#S1009 + - S1009 + # Omit default slice index. + # https://staticcheck.dev/docs/checks/#S1010 + - S1010 + # Use a single 'append' to concatenate two slices. + # https://staticcheck.dev/docs/checks/#S1011 + - S1011 + # Replace 'time.Now().Sub(x)' with 'time.Since(x)'. + # https://staticcheck.dev/docs/checks/#S1012 + - S1012 + # Use a type conversion instead of manually copying struct fields. + # https://staticcheck.dev/docs/checks/#S1016 + - S1016 + # Replace manual trimming with 'strings.TrimPrefix'. + # https://staticcheck.dev/docs/checks/#S1017 + - S1017 + # Use "copy" for sliding elements. + # https://staticcheck.dev/docs/checks/#S1018 + - S1018 + # Simplify "make" call by omitting redundant arguments. + # https://staticcheck.dev/docs/checks/#S1019 + - S1019 + # Omit redundant nil check in type assertion. + # https://staticcheck.dev/docs/checks/#S1020 + - S1020 + # Merge variable declaration and assignment. + # https://staticcheck.dev/docs/checks/#S1021 + - S1021 + # Omit redundant control flow. + # https://staticcheck.dev/docs/checks/#S1023 + - S1023 + # Replace 'x.Sub(time.Now())' with 'time.Until(x)'. + # https://staticcheck.dev/docs/checks/#S1024 + - S1024 + # Don't use 'fmt.Sprintf("%s", x)' unnecessarily. + # https://staticcheck.dev/docs/checks/#S1025 + - S1025 + # Simplify error construction with 'fmt.Errorf'. + # https://staticcheck.dev/docs/checks/#S1028 + - S1028 + # Range over the string directly. + # https://staticcheck.dev/docs/checks/#S1029 + - S1029 + # Use 'bytes.Buffer.String' or 'bytes.Buffer.Bytes'. + # https://staticcheck.dev/docs/checks/#S1030 + - S1030 + # Omit redundant nil check around loop. + # https://staticcheck.dev/docs/checks/#S1031 + - S1031 + # Use 'sort.Ints(x)', 'sort.Float64s(x)', and 'sort.Strings(x)'. + # https://staticcheck.dev/docs/checks/#S1032 + - S1032 + # Unnecessary guard around call to "delete". + # https://staticcheck.dev/docs/checks/#S1033 + - S1033 + # Use result of type assertion to simplify cases. + # https://staticcheck.dev/docs/checks/#S1034 + - S1034 + # Redundant call to 'net/http.CanonicalHeaderKey' in method call on 'net/http.Header'. + # https://staticcheck.dev/docs/checks/#S1035 + - S1035 + # Unnecessary guard around map access. + # https://staticcheck.dev/docs/checks/#S1036 + - S1036 + # Elaborate way of sleeping. + # https://staticcheck.dev/docs/checks/#S1037 + - S1037 + # Unnecessarily complex way of printing formatted string. + # https://staticcheck.dev/docs/checks/#S1038 + - S1038 + # Unnecessary use of 'fmt.Sprint'. + # https://staticcheck.dev/docs/checks/#S1039 + - S1039 + # Type assertion to current type. + # https://staticcheck.dev/docs/checks/#S1040 + - S1040 + # Apply De Morgan's law. + # https://staticcheck.dev/docs/checks/#QF1001 + - QF1001 + # Convert untagged switch to tagged switch. + # https://staticcheck.dev/docs/checks/#QF1002 + - QF1002 + # Convert if/else-if chain to tagged switch. + # https://staticcheck.dev/docs/checks/#QF1003 + - QF1003 + # Use 'strings.ReplaceAll' instead of 'strings.Replace' with 'n == -1'. + # https://staticcheck.dev/docs/checks/#QF1004 + - QF1004 + # Expand call to 'math.Pow'. + # https://staticcheck.dev/docs/checks/#QF1005 + - QF1005 + # Lift 'if'+'break' into loop condition. + # https://staticcheck.dev/docs/checks/#QF1006 + - QF1006 + # Merge conditional assignment into variable declaration. + # https://staticcheck.dev/docs/checks/#QF1007 + - QF1007 + # Omit embedded fields from selector expression. + # https://staticcheck.dev/docs/checks/#QF1008 + - QF1008 + # Use 'time.Time.Equal' instead of '==' operator. + # https://staticcheck.dev/docs/checks/#QF1009 + - QF1009 + # Convert slice of bytes to string when printing it. + # https://staticcheck.dev/docs/checks/#QF1010 + - QF1010 + # Omit redundant type from variable declaration. + # https://staticcheck.dev/docs/checks/#QF1011 + - QF1011 + # Use 'fmt.Fprintf(x, ...)' instead of 'x.Write(fmt.Sprintf(...))'. + # https://staticcheck.dev/docs/checks/#QF1012 + - QF1012 + unconvert: + # Remove conversions that force intermediate rounding. + # Default: false + fast-math: true + # Be more conservative (experimental). + # Default: false + safe: true + usestdlibvars: + # Suggest the use of http.MethodXX. + # Default: true + http-method: false + # Suggest the use of http.StatusXX. + # Default: true + http-status-code: false + # Suggest the use of time.Month in time.Date. + # Default: false + time-date-month: true + # Suggest the use of time.Weekday.String(). + # Default: true + time-weekday: true + # Suggest the use of time.Month.String(). + # Default: false + time-month: true + # Suggest the use of time.Layout. + # Default: false + time-layout: true + # Suggest the use of crypto.Hash.String(). + # Default: false + crypto-hash: true + # Suggest the use of rpc.DefaultXXPath. + # Default: false + default-rpc-path: true + # Suggest the use of sql.LevelXX.String(). + # Default: false + sql-isolation-level: true + # Suggest the use of tls.SignatureScheme.String(). + # Default: false + tls-signature-scheme: true + # Suggest the use of constant.Kind.String(). + # Default: false + constant-kind: true + wsl_v5: + allow-first-in-block: true + allow-whole-block: true + branch-max-lines: 2 + case-max-lines: 2 + default: all + disable: + - if + - err + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gofmt + - goimports + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/cmd/buny-jabber-bot/main.go b/cmd/buny-jabber-bot/main.go index 72009bd..08e4990 100644 --- a/cmd/buny-jabber-bot/main.go +++ b/cmd/buny-jabber-bot/main.go @@ -1,3 +1,4 @@ +// Package main содержит основную функцию для запуска логики бота. package main import ( @@ -18,7 +19,7 @@ import ( ) //go:embed version -var version string +var version string //nolint: grouper // main - фактичеcки, начало и основное тело программы. func main() { @@ -36,7 +37,7 @@ func main() { DisableLevelTruncation: false, DisableColors: true, FullTimestamp: true, - TimestampFormat: "2006-01-02 15:04:05", + TimestampFormat: time.DateTime, }) if err := j.ReadConfig(); err != nil { @@ -47,7 +48,6 @@ func main() { j.C.Version = version - // no panic switch j.C.Loglevel { case "fatal": log.SetLevel(log.FatalLevel) diff --git a/go.mod b/go.mod index dee8df2..2a2ce1f 100644 --- a/go.mod +++ b/go.mod @@ -5,15 +5,14 @@ go 1.24.3 require ( github.com/davecgh/go-spew v1.1.1 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/jbrukh/bayesian v0.0.0-20231117143245-13ae6f916c7a 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 ) require ( - github.com/google/uuid v1.6.0 // indirect golang.org/x/crypto v0.38.0 // indirect golang.org/x/net v0.40.0 // indirect golang.org/x/sys v0.33.0 // indirect diff --git a/go.sum b/go.sum index 069f795..b15ded3 100644 --- a/go.sum +++ b/go.sum @@ -18,8 +18,6 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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/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/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/internal/jabber/bf.go b/internal/jabber/bf.go index 7b2cb9c..29bdaf7 100644 --- a/internal/jabber/bf.go +++ b/internal/jabber/bf.go @@ -1,3 +1,4 @@ +// Package jabber contains all buny jabber bot guts. package jabber import ( @@ -12,11 +13,16 @@ import ( ) const ( - goodDictionaryFile string = "./data/good_dictionary.txt" - badDictionaryFile string = "./data/bad_dictionary.txt" - dataFile string = "./data/data.bin" - Bad bayesian.Class = "Bad" - Good bayesian.Class = "Good" + // goodDictionaryFile путь до файла с заведомо хорошими фразами. + goodDictionaryFile string = "./data/good_dictionary.txt" //nolint: unused + // badDictionaryFile путь до файла с заведемо плохими фразами. + badDictionaryFile string = "./data/bad_dictionary.txt" //nolint: unused + // 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" ) // Выдаёт "очки похожести" для данной фразы. diff --git a/internal/jabber/black-whilte_lists.go b/internal/jabber/black-whilte_lists.go index 809fb27..985ebc6 100644 --- a/internal/jabber/black-whilte_lists.go +++ b/internal/jabber/black-whilte_lists.go @@ -27,7 +27,7 @@ func (j *Jabber) ReadWhitelist() error { return err } - whitelistJSONPath := fmt.Sprintf("%s/data/whitelist.json", filepath.Dir(executablePath)) + whitelistJSONPath := filepath.Dir(executablePath) + "/data/whitelist.json" locations := []string{ "~/.buny-jabber-bot-whitelist.json", @@ -39,12 +39,12 @@ func (j *Jabber) ReadWhitelist() error { for _, location := range locations { fileInfo, err := os.Stat(location) - // Предполагаем, что файла либо нет, либо мы не можем его прочитать, второе надо бы логгировать, но пока забьём + // Предполагаем, что файла либо нет, либо мы не можем его прочитать, второе надо бы логгировать, но пока забьём. if err != nil { continue } - // Файл белого списка длинноват для белого списка, попробуем следующего кандидата + // Файл белого списка длинноват для белого списка, попробуем следующего кандидата. if fileInfo.Size() > 2097152 { 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) - // Не удалось прочитать, попробуем следующего кандидата + // Не удалось прочитать, попробуем следующего кандидата. if err != nil { log.Warnf("Skip reading whitelist file %s: %s", location, err) @@ -65,12 +65,12 @@ func (j *Jabber) ReadWhitelist() error { // сериализуем в json, а потом json превращаем в структурку. Не очень эффективно, но он и нечасто требуется. var ( sampleWhitelist MyWhiteList - tmp map[string]interface{} + tmp map[string]any ) err = hjson.Unmarshal(buf, &tmp) - // Не удалось распарсить - попробуем следующего кандидата + // Не удалось распарсить - попробуем следующего кандидата. if err != nil { log.Warnf("Skip parsing whitelist file %s: %s", location, err) @@ -79,7 +79,7 @@ func (j *Jabber) ReadWhitelist() error { tmpJSON, err := json.Marshal(tmp) - // Не удалось преобразовать map-ку в json + // Не удалось преобразовать map-ку в json. if err != nil { log.Warnf("Skip parsing whitelist file %s: %s", location, err) @@ -123,7 +123,7 @@ func (j *Jabber) ReadBlacklist() error { return err } - whitelistJSONPath := fmt.Sprintf("%s/data/blacklist.json", filepath.Dir(executablePath)) + whitelistJSONPath := filepath.Dir(executablePath) + "/data/blacklist.json" locations := []string{ "~/.bunyPresense-jabber-bot-blacklist.json", @@ -135,12 +135,12 @@ func (j *Jabber) ReadBlacklist() error { for _, location := range locations { fileInfo, err := os.Stat(location) - // Предполагаем, что файла либо нет, либо мы не можем его прочитать, второе надо бы логгировать, но пока забьём + // Предполагаем, что файла либо нет, либо мы не можем его прочитать, второе надо бы логгировать, но пока забьём. if err != nil { continue } - // Файл чёрного списка длинноват для чёрного списка, попробуем следующего кандидата + // Файл чёрного списка длинноват для чёрного списка, попробуем следующего кандидата. if fileInfo.Size() > 16777216 { 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) - // Не удалось прочитать, попробуем следующего кандидата + // Не удалось прочитать, попробуем следующего кандидата. if err != nil { log.Warnf("Skip reading blacklist file %s: %s", location, err) @@ -161,12 +161,12 @@ func (j *Jabber) ReadBlacklist() error { // сериализуем в json, а потом json превращаем в структурку. Не очень эффективно, но он и нечасто требуется. var ( sampleBlacklist MyBlackList - tmp map[string]interface{} + tmp map[string]any ) err = hjson.Unmarshal(buf, &tmp) - // Не удалось распарсить - попробуем следующего кандидата + // Не удалось распарсить - попробуем следующего кандидата. if err != nil { log.Warnf("Skip parsing blacklist file %s: %s", location, err) @@ -175,7 +175,7 @@ func (j *Jabber) ReadBlacklist() error { tmpJSON, err := json.Marshal(tmp) - // Не удалось преобразовать map-ку в json + // Не удалось преобразовать map-ку в json. if err != nil { log.Warnf("Skip parsing blacklist file %s: %s", location, err) diff --git a/internal/jabber/buny.go b/internal/jabber/buny.go index c8895b3..e02e330 100644 --- a/internal/jabber/buny.go +++ b/internal/jabber/buny.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "regexp" + "slices" "strings" "github.com/davecgh/go-spew/spew" @@ -17,9 +18,9 @@ func (j *Jabber) BunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo var err error // Если у presence-а есть JID и presence из одной из комнат, в которой мы есть и если его домен в чёрном - // списке, заносим пидора в список outcast-ов + // списке, заносим пидора в список outcast-ов. if v.JID != "" { - // На всякий случай: себя никогда не баним, явным образом + // На всякий случай: себя никогда не баним, явным образом. if v.JID == j.Talk.JID() { return err } @@ -34,23 +35,19 @@ func (j *Jabber) BunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo goodJid := strings.SplitN(v.JID, "/", 2)[0] - // Обрабатываем правила белого списка + // Обрабатываем правила белого списка. for _, good := range j.WhiteList.Whitelist { - // Глобальный белый список + // Глобальный белый список. if good.RoomName == "" { - for _, person := range good.Jid { - if person == goodJid { - return err - } + if slices.Contains(good.Jid, goodJid) { + return err } } - // Белый список для конкретных каналов + // Белый список для конкретных каналов. if good.RoomName == room { - for _, person := range good.Jid { - if person == goodJid { - return err - } + if slices.Contains(good.Jid, goodJid) { + return err } } } @@ -74,16 +71,16 @@ func (j *Jabber) BunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo evilNick := "" evilNicks := strings.SplitN(v.From, "/", 2) - // Давайте-ка похэндлим нонсенс - когда у нас в строке нету разделителя + // Давайте-ка похэндлим нонсенс - когда у нас в строке нету разделителя. if len(evilNicks) > 1 { evilNick = evilNicks[1] } - // Обрабатываем правила чёрного списка + // Обрабатываем правила чёрного списка. for _, cRoom := range j.RoomsConnected { if cRoom == room { for _, bEntry := range j.BlackList.Blacklist { - // Обработаем правила глобального чёрного списка + // Обработаем правила глобального чёрного списка. if bEntry.RoomName == "" { for _, jidRegexp := range bEntry.JidRe { if jidRegexp == "" { @@ -147,7 +144,7 @@ func (j *Jabber) BunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo nickRegexp, ) - // Баним именно jid + // Баним именно jid. id, err := j.Squash(room, evilJid, bEntry.ReasonEnable, v.Type) if err != nil { @@ -167,7 +164,7 @@ func (j *Jabber) BunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo continue } - // Обработаем правила конкретного канала, комнаты конференции + // Обработаем правила конкретного канала, комнаты конференции. if bEntry.RoomName == room { for _, jidRegexp := range bEntry.JidRe { if jidRegexp == "" { @@ -232,7 +229,7 @@ func (j *Jabber) BunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo nickRegexp, ) - // Баним именно jid + // Баним именно jid. id, err := j.Squash(room, evilJid, bEntry.ReasonEnable, v.Type) 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 { var ( 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 { // Перебирём правила чёрных списков. for _, bEntry := range j.BlackList.Blacklist { - // Обработаем правила глобального чёрного списка + // Обработаем правила глобального чёрного списка. if bEntry.RoomName == "" { for _, phraseRegexp := range bEntry.PhraseRe { if phraseRegexp == "" { @@ -317,7 +313,7 @@ func (j *Jabber) BunyChat(v xmpp.Chat) error { } } - // Обработаем правила чёрного списка для конкретной комнаты + // Обработаем правила чёрного списка для конкретной комнаты. if bEntry.RoomName == room { for _, phraseRegexp := range bEntry.PhraseRe { if phraseRegexp == "" { @@ -364,10 +360,10 @@ func (j *Jabber) BunyChat(v xmpp.Chat) error { // Если включено, проверяем фразу на КАПС. for _, channel := range j.C.Jabber.Channels { if channel.Name == room && channel.AllCaps.Enabled { - // Нормализуем строку и вырежем из неё пробелы + // Нормализуем строку и вырежем из неё пробелы. normPhrase := strings.ReplaceAll(nString(v.Text), " ", "") - // Проверяем согласно тому, что длина фразы более чем сколько-то символов + // Проверяем согласно тому, что длина фразы более чем сколько-то символов. if len(normPhrase) >= channel.AllCaps.MinLength { normPhraseUpper := strings.ReplaceAll(nStringUpper(v.Text), " ", "") @@ -401,7 +397,7 @@ func (j *Jabber) BunySoftwareVersion(v xmpp.IQ, ver IqResultSoftwareVersion) err var err error if v.From != "" { - // На всякий случай: себя никогда не баним, явным образом + // На всякий случай: себя никогда не баним, явным образом. if v.From == j.Talk.JID() { return err } @@ -420,6 +416,7 @@ func (j *Jabber) BunySoftwareVersion(v xmpp.IQ, ver IqResultSoftwareVersion) err for _, presenceJSONString := range presenceJSONStrings { var p xmpp.Presence + _ = json.Unmarshal([]byte(presenceJSONString), &p) 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 { if cRoom == room { for _, bEntry := range j.BlackList.Blacklist { - // Обработаем правила глобального чёрного списка + // Обработаем правила глобального чёрного списка. if bEntry.RoomName == "" { for _, useragent := range bEntry.UserAgent { switch { diff --git a/internal/jabber/collection.go b/internal/jabber/collection.go index 653d6a7..53e8c9c 100644 --- a/internal/jabber/collection.go +++ b/internal/jabber/collection.go @@ -4,16 +4,18 @@ import ( "sync" ) -// Collection это sync.Map-ка, хранящая в себе произвольные итемы. -type Collection struct { - items sync.Map - close chan struct{} -} +type ( + // Collection это sync.Map-ка, хранящая в себе произвольные итемы. + Collection struct { + close chan struct{} + items sync.Map + } -// Item представляет собой интерфейс для хранения произвольных данных. -type item struct { - data interface{} -} + // Item представляет собой интерфейс для хранения произвольных данных. + item struct { + data any + } +) // NewCollection создаёт инстанс коллекции. func NewCollection() *Collection { @@ -25,7 +27,7 @@ func NewCollection() *Collection { } // Get достаёт данные по заданному ключу из коллекции. -func (collection *Collection) Get(key interface{}) (interface{}, bool) { +func (collection *Collection) Get(key any) (any, bool) { obj, exists := collection.items.Load(key) if !exists { @@ -38,15 +40,15 @@ func (collection *Collection) Get(key interface{}) (interface{}, bool) { } // Set сохраняет данные с заданным ключом в коллекцию. -func (collection *Collection) Set(key interface{}, value interface{}) { +func (collection *Collection) Set(key any, value any) { collection.items.Store(key, item{ data: value, }) } // Range применяет функцию f ко всем ключам в коллекции. -func (collection *Collection) Range(f func(key, value interface{}) bool) { - fn := func(key, value interface{}) bool { +func (collection *Collection) Range(f func(key, value any) bool) { + fn := func(key, value any) bool { item := value.(item) return f(key, item.data) @@ -56,13 +58,14 @@ func (collection *Collection) Range(f func(key, value interface{}) bool) { } // Delete удаляет ключ и значение из коллекции данных. -func (collection *Collection) Delete(key interface{}) { +func (collection *Collection) Delete(key any) { collection.items.Delete(key) } // Close очищает и высвобождает ресурсы, занятые коллекцией. func (collection *Collection) Close() { collection.close <- struct{}{} + collection.items = sync.Map{} } diff --git a/internal/jabber/commands.go b/internal/jabber/commands.go index a35948a..a027487 100644 --- a/internal/jabber/commands.go +++ b/internal/jabber/commands.go @@ -14,8 +14,8 @@ import ( func (j *Jabber) Cmd(v xmpp.Chat) error { var err error - switch { - case v.Text == fmt.Sprintf("%shelp", j.C.CSign) || v.Text == fmt.Sprintf("%sпомощь", j.C.CSign): + switch v.Text { + case j.C.CSign + "help", j.C.CSign + "помощь": var ( answer string chosenOneTalks = false @@ -30,10 +30,11 @@ func (j *Jabber) Cmd(v xmpp.Chat) error { } if chosenOneTalks { - answer = fmt.Sprintf("%sпомощь - этот список команд\n", j.C.CSign) - 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 += fmt.Sprintf("%sver|%sversion - prints version of software", j.C.CSign, j.C.CSign) + answer = j.C.CSign + "помощь - этот список команд\n" + + answer += j.C.CSign + "help - this commands list\n" + 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 { answer = "Ничем помочь не могу. Луна не светит на тебя." } @@ -56,15 +57,14 @@ func (j *Jabber) Cmd(v xmpp.Chat) error { return err } - case v.Text == fmt.Sprintf("%srehash", j.C.CSign): //nolint:wsl + case j.C.CSign + "rehash": /* Эта команда может прилетать из приватной беседы с realjid-ом, из приватной беседы с chat-nick-ом, а также из * чятика. * Groupchat мы можем определить по v.Type (groupchat) и тогда понятно что делать. * А вот, например, приватные притязания определить несколько сложно-вато. Но можно попробовать, например, * пытаться по списку presence-ов находить персонажа и его real jid и уже из этого определять master он или нет. */ - - // Попробуем определить каков алгоритм наших действий, в зависимости от типа сообщения + // Попробуем определить каков алгоритм наших действий, в зависимости от типа сообщения. switch v.Type { case "groupchat": // Проверим, является ли "командир" избранным или это самозванец? @@ -90,6 +90,7 @@ func (j *Jabber) Cmd(v xmpp.Chat) error { ) var msg xmpp.Chat + msg.Remote = v.Remote msg.Type = v.Type msg.Text = "Ничем помочь не могу. Луна не светит на тебя." @@ -103,8 +104,9 @@ func (j *Jabber) Cmd(v xmpp.Chat) error { listsLoaded = false var msg xmpp.Chat + msg.Remote = v.Remote - msg.Type = "chat" // private message + msg.Type = "chat" // private message. msg.Text = fmt.Sprint(err) if _, err := j.Talk.Send(msg); err != nil { @@ -116,8 +118,9 @@ func (j *Jabber) Cmd(v xmpp.Chat) error { listsLoaded = false var msg xmpp.Chat + msg.Remote = v.Remote - msg.Type = "chat" // private message + msg.Type = "chat" // private message. msg.Text = fmt.Sprint(err) 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] var msg xmpp.Chat + msg.Remote = room msg.Type = v.Type msg.Text = "Сделано" @@ -137,17 +141,18 @@ func (j *Jabber) Cmd(v xmpp.Chat) error { return err } } + case "chat": room := (strings.SplitN(v.Remote, "/", 2))[0] - // Реагировать на приватную команду "из комнаты", только если бот в комнате + // Реагировать на приватную команду "из комнаты", только если бот в комнате. if slices.Contains(j.RoomsConnected, room) { var ( chosenOneTalks = false listsLoaded = true ) - // Реагировать на rehash только если собеседник является bot master-ом + // Реагировать на rehash только если собеседник является bot master-ом. realJID := j.GetRealJIDfromNick(v.Remote) for _, master := range j.C.Jabber.BotMasters { @@ -165,6 +170,7 @@ func (j *Jabber) Cmd(v xmpp.Chat) error { ) var msg xmpp.Chat + msg.Remote = v.Remote msg.Type = v.Type msg.Text = "Ничем помочь не могу. Луна не светит на тебя." @@ -178,6 +184,7 @@ func (j *Jabber) Cmd(v xmpp.Chat) error { listsLoaded = false var msg xmpp.Chat + msg.Remote = v.Remote msg.Type = v.Type msg.Text = fmt.Sprint(err) @@ -191,6 +198,7 @@ func (j *Jabber) Cmd(v xmpp.Chat) error { listsLoaded = false var msg xmpp.Chat + msg.Remote = v.Remote msg.Type = v.Type msg.Text = fmt.Sprint(err) @@ -202,6 +210,7 @@ func (j *Jabber) Cmd(v xmpp.Chat) error { if listsLoaded { var msg xmpp.Chat + msg.Remote = v.Remote msg.Type = v.Type msg.Text = "Сделано" @@ -214,12 +223,14 @@ func (j *Jabber) Cmd(v xmpp.Chat) error { default: log.Infof("Rehash command from outer space: %s", spew.Sdump(v)) + } 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 ( - answer = fmt.Sprintf("Version %s", j.C.Version) + answer = "Version " + j.C.Version ) dest := v.Remote diff --git a/internal/jabber/event_parser.go b/internal/jabber/event_parser.go index b287e05..67202b9 100644 --- a/internal/jabber/event_parser.go +++ b/internal/jabber/event_parser.go @@ -5,6 +5,7 @@ import ( "encoding/xml" "fmt" "runtime" + "slices" "sort" "strings" "time" @@ -12,26 +13,24 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/eleksir/go-xmpp" log "github.com/sirupsen/logrus" - "golang.org/x/exp/slices" ) // 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() switch v := e.(type) { - // Сообщение в чяти + // Сообщение в чяти. case xmpp.Chat: log.Debugf("Looks like message, ChatType: %s, From: %s, Subject: %s Text: %s", v.Type, v.Remote, v.Subject, v.Text) - // Топик чятика присылается в виде сообщения с subject, но без text - // В то же время сообщения от людей приходят с пустым subject, но с заполненным text + // Топик чятика присылается в виде сообщения с subject, но без text. + // В то же время сообщения от людей приходят с пустым subject, но с заполненным text. if v.Text != "" { - // Чятики бывают групповые и не групповые, от этого зависит Remote, куда направлять сообщение + // Чятики бывают групповые и не групповые, от этого зависит Remote, куда направлять сообщение. switch v.Type { //nolint:wsl,whitespace - - // Групповой чятик + // Групповой чятик. case "groupchat": var ( 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) } - // Приватный чятик + // Приватный чятик. case "chat": // Здесь у нас может быть 2 вида From: // slackware-current@conference.jabber.ru/eleksir - это если сообщение из публичного чятика @@ -80,7 +79,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo j.LastActivity = j.LastServerActivity // Внезапно, ошибки. По идее они должны ассоциироваться с отправляемыми сообщениями, но по ходу это - // не реализовано, поэтому мы получаем ошибки в форме отдельного чятика + // не реализовано, поэтому мы получаем ошибки в форме отдельного чятика. case "error": // Это ошибка со стороны сервера, видимо, в ответ на наши действия и поскольку такие вещи происходят // асинхронно, связать их с конкретными нашими действиями довольно сложно. @@ -90,7 +89,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo // IQ-сообщение - пинг, понг, что-то ещё... case xmpp.IQ: - // По правилам IQ обязательно должна содержать ID, причём не пустой + // По правилам IQ обязательно должна содержать ID, причём не пустой. if v.ID == "" { 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 { switch iqStruct.Xmlns { //nolint:wsl,whitespace - - // Запрос номера версии приложения + // Запрос номера версии приложения. case "jabber:iq:version": 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 - // Нам прислали попингуй + // Нам прислали попингуй. case "urn:xmpp:ping": 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 - // Запрос на список поддерживаемых фич - версия, локальное время, итд + // Запрос на список поддерживаемых фич - версия, локальное время, итд. case xmpp.XMPPNS_DISCO_INFO: log.Infof("Got IQ get disco#info request from %s", v.From) answer := "" + answer += "" answer += "" answer += "" @@ -209,7 +208,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo parseError = false - // Спрашивают список команд, чтобы поуправлять этим клиентом (xep-0030) + // Спрашивают список команд, чтобы поуправлять этим клиентом (xep-0030). case xmpp.XMPPNS_DISCO_ITEMS: 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 - // Запрашивают что-то, о чём мы не имеем представления + // Запрашивают что-то, о чём мы не имеем представления. default: log.Info("Got an unknown IQ get request, discarding") log.Info(spew.Sdump(e)) @@ -242,7 +241,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo parseError = true } - // Попробуем распарсить входящий запрос как urn:xmpp:time + // Попробуем распарсить входящий запрос как urn:xmpp:time. if parseError { 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 { var iqStruct PubsubIQGetQuery @@ -305,6 +304,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo default: log.Info("Got unknown IQ get pubsub something request, discarding") log.Info(spew.Sdump(e)) + parseError = true //nolint:wsl } } 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 { @@ -337,7 +337,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo parseError = false } - // Не знаю, как парсить... залоггируем это дело и пойдём дальше + // Не знаю, как парсить... залоггируем это дело и пойдём дальше. if parseError { log.Infof("Does not look like parsable via iqStruct") log.Info(spew.Sdump(e)) @@ -351,9 +351,10 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo } switch { - // Похоже на pong от сервера (по стандарту в ответе нету query, но go-xmpp нам подсовывает это) + // Похоже на pong от сервера (по стандарту в ответе нету query, но go-xmpp нам подсовывает это). case v.From == j.C.Jabber.Server && v.To == j.Talk.JID() && string(v.Query) == "": log.Debugf("Got S2C pong answer from %s to %s", v.From, v.To) + j.ServerPingTimestampRx = time.Now().Unix() //nolint:wsl // Похоже на понг второй стадии xep-0410 MUC-Ping-а, который у нас не реализован. @@ -378,13 +379,13 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo if mucNickMatch { log.Debugf("Got 2-nd stage MUC pong answer (xep-0410) from %s to %s", v.From, v.To) } else { - // Поскольку никакой логики у нас на этот счёт не предусмотрено, то просто пропускаем ответ + // Поскольку никакой логики у нас на этот счёт не предусмотрено, то просто пропускаем ответ. log.Infof("Got unknown pong from %s to %s", v.From, v.To) log.Debug(spew.Sdump(e)) } } - // Подтверждение бана + // Подтверждение бана. case v.To == j.Talk.JID() && v.ID == "ban1": mucNickMatch := false @@ -402,15 +403,15 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo } } - // Формально, ответ должен парситься как пустой result и совпадать с типом jabberSimpleIqGetQuery + // Формально, ответ должен парситься как пустой result и совпадать с типом jabberSimpleIqGetQuery. // Ответ приходит с 2 xmlns, похоже, он парсится неправильно в go-xmpp. - // TODO: после исправления go-xmpp, надо исправить это тут + // TODO: после исправления go-xmpp, надо исправить это тут. var iqStruct SimpleIqGetQuery if err := xml.Unmarshal(v.Query, &iqStruct); err == nil { if iqStruct.Xmlns == "http://jabber.org/protocol/muc#admin" && iqStruct.Text == "" { if mucNickMatch { - // Поскольку никакой логики у нас на этот счёт не предусмотрено, то просто пропускаем ответ + // Поскольку никакой логики у нас на этот счёт не предусмотрено, то просто пропускаем ответ. log.Infof("Got ban successful from %s to %s", v.From, v.To) } else { 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)) } - // Этот бот не управляется со стороны сервера, поэтому все попытки порулить игнорируем + // Этот бот не управляется со стороны сервера, поэтому все попытки порулить игнорируем. case xmpp.IQTypeSet: if muc, _ := strings.CutSuffix(v.To, "/"); 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)) - // Нам прилетело сообщение об ошибке + // Нам прилетело сообщение об ошибке. case xmpp.IQTypeError: // Если сервер не хочет пинговаться и отвечает ошибкой на пинг, то наверно он не умеет в пинги, // хотя если мы его пингуем, значит он анонсировал такой capability. Вот, засранец! @@ -506,6 +507,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo if iqPingStruct.Xmlns == "urn:xmpp:ping" { 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 += "fallback to keepalive whitespace pings" log.Error(msg) @@ -521,7 +523,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo parseError = false } - // Это у нас пинг xep-0410 и мы не в комнате, предполагается, что надо бы заджойниться + // Это у нас пинг xep-0410 и мы не в комнате, предполагается, что надо бы заджойниться. if parseError { 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(spew.Sdump(e)) } - // Нам прилетело что-то неизвестное из семейства IQ stanza + // Нам прилетело что-то неизвестное из семейства IQ stanza. default: log.Info("Got an unknown IQ request. Dunno how deal with it, discarding") log.Info(spew.Sdump(e)) } - // Смена статуса участника + // Смена статуса участника. case xmpp.Presence: if muc, _ := strings.CutSuffix(v.To, "/"); 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" { - // Считаем, что мы больше не в комнате, поэтому не знаем, кто там есть + // Считаем, что мы больше не в комнате, поэтому не знаем, кто там есть. j.RoomPresences.Delete(v.From) log.Error("Presence notification - looks like another instance of client leaves room") 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 { 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 } - // Это наш собственный Presence + // Это наш собственный Presence. if v.Show == "" && v.Status == "" { if nick == j.GetBotNickFromRoomConfig(room) { j.RoomsConnected = append(j.RoomsConnected, room) // На всякий случай дедуплицируем список комнат, к которым мы заджойнились. sort.Strings(j.RoomsConnected) - slices.Compact(j.RoomsConnected) + + j.RoomsConnected = slices.Compact(j.RoomsConnected) } } switch v.Role { - // Участник ушёл + // Участник ушёл. case "none": if presenceJSONInterface, present := j.RoomPresences.Get(room); present { presenceJSONStrings := InterfaceToStringSlice(presenceJSONInterface) @@ -641,6 +652,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo for _, presenceJSONstring := range presenceJSONStrings { var p xmpp.Presence + _ = json.Unmarshal([]byte(presenceJSONstring), &p) if p.From == v.From { @@ -652,7 +664,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo j.RoomPresences.Set(room, newPresenceJSONStrings) } - // Участник пришёл + // Участник пришёл. default: var ( presenceJSONStrings []string @@ -666,6 +678,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo for _, presenceJSONString := range presenceJSONStrings { var p xmpp.Presence + _ = json.Unmarshal([]byte(presenceJSONString), &p) // Если находим, что у нас уже есть клиент с таким же 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: if muc, _ := strings.CutSuffix(v.To, "/"); 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 - // TODO: проверять адресата v.To + // Я видел 2 типа disco result и они отличались только []identities. Попробуем вытащить известный identity. + // TODO: проверять адресата v.To. for _, ident := range v.Identities { switch ident.Category { case "server": - // Конкретно сейчас нас интересует только поддержка c2s ping + // Конкретно сейчас нас интересует только поддержка c2s ping. for _, feature := range v.Features { log.Debugf("Server %s announced that it supports feature: %s", v.From, feature) j.ServerCapsList.Set(feature, true) @@ -722,6 +735,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo for _, feature := range v.Features { log.Debugf("MUC %s announced that it supports feature: %s", v.From, feature) + mucCaps[feature] = true //nolint:wsl } @@ -736,7 +750,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo } } - // Это что-то неизвестное, подампим событие в лог + // Это что-то неизвестное, подампим событие в лог. default: log.Info(spew.Sdump(e)) } diff --git a/internal/jabber/main_loop.go b/internal/jabber/main_loop.go index bdf2736..bdb6a7d 100644 --- a/internal/jabber/main_loop.go +++ b/internal/jabber/main_loop.go @@ -9,7 +9,7 @@ import ( // MyLoop - основной цикл программы. func (j *Jabber) MyLoop() error { for { - // Зададим начальное значение глобальным переменным + // Зададим начальное значение глобальным переменным. j.ServerPingTimestampRx = 0 j.ServerPingTimestampTx = 0 j.RoomsConnected = make([]string, 1) @@ -23,7 +23,7 @@ func (j *Jabber) MyLoop() error { j.ServerPingTimestampRx = 0 j.RoomPresences = NewCollection() - // Установим коннект + // Установим коннект. if err := j.EstablishConnection(); err != nil { log.Error(err) @@ -48,14 +48,8 @@ func (j *Jabber) MyLoop() error { // j.GTomb.Go() тут зовётся внутри на каждую комнату, если у нас ошибка, эта горутинка рано или поздно выйдет. go j.ProbeMUCLiveness() - // Гребём ивенты... - for { - // Стриггерилось завершение работы приложения, или соединение не установлено (порвалось, например) - // грести не надо - if j.Shutdown { - break - } - + // Гребём ивенты, пока стриггерилось завершение работы приложения, или соединение не порвалось. + for !j.Shutdown { if !j.IsConnected { // Tight loop - это наверно не очень хорошо, думаю, ничего страшного не будет, если мы поспим 100мс. time.Sleep(100 * time.Millisecond) @@ -63,7 +57,7 @@ func (j *Jabber) MyLoop() error { continue } - // Вынимаем ивент из "провода" + // Вынимаем ивент из "провода". chat, err := j.Talk.Recv() if err != nil { diff --git a/internal/jabber/read_config.go b/internal/jabber/read_config.go index cc6866f..f3b6d2a 100644 --- a/internal/jabber/read_config.go +++ b/internal/jabber/read_config.go @@ -28,7 +28,7 @@ func (j *Jabber) ReadConfig() error { //nolint:gocognit,gocyclo return err } - configJSONPath := fmt.Sprintf("%s/data/config.json", filepath.Dir(executablePath)) + configJSONPath := filepath.Dir(executablePath) + "/data/config.json" locations := []string{ "~/.buny-jabber-bot.json", @@ -66,7 +66,7 @@ func (j *Jabber) ReadConfig() error { //nolint:gocognit,gocyclo // сериализуем в json, а потом json превращаем в структурку. Не очень эффективно, но он и нечасто требуется. var ( sampleConfig MyConfig - tmp map[string]interface{} + tmp map[string]any ) err = hjson.Unmarshal(buf, &tmp) @@ -80,7 +80,7 @@ func (j *Jabber) ReadConfig() error { //nolint:gocognit,gocyclo tmpJSON, err := json.Marshal(tmp) - // Не удалось преобразовать map-ку в json + // Не удалось преобразовать map-ку в json. if err != nil { log.Warnf("Skip parsing config file %s: %s", location, err) @@ -93,9 +93,10 @@ func (j *Jabber) ReadConfig() error { //nolint:gocognit,gocyclo continue } - // Значения для Jabber-клиента + // Значения для Jabber-клиента. if sampleConfig.Jabber.Server == "" { log.Error("Jabber server is not defined in config, using localhost") + sampleConfig.Jabber.Server = "localhost" //nolint:wsl } @@ -121,7 +122,7 @@ func (j *Jabber) ReadConfig() error { //nolint:gocognit,gocyclo sampleConfig.Jabber.SslVerify = false } - // sampleConfig.Jabber.InsecureAllowUnencryptedAuth = false, если не задан + // sampleConfig.Jabber.InsecureAllowUnencryptedAuth = false, если не задан. if sampleConfig.Jabber.ConnectionTimeout == 0 { 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) } - // Если sampleConfig.Jabber.Password не задан, то авторизации не будет + // Если sampleConfig.Jabber.Password не задан, то авторизации не будет. - // Если не задано ни одного мастера, то бот сам себе мастер + // Если не задано ни одного мастера, то бот сам себе мастер. if len(sampleConfig.Jabber.BotMasters) == 0 { sampleConfig.Jabber.BotMasters[0] = sampleConfig.Jabber.User } - // Нам бот нужен в каких-то чат-румах, а не "просто так" + // Нам бот нужен в каких-то чат-румах, а не "просто так". if len(sampleConfig.Jabber.Channels) < 1 { 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 } - // channel.Password может быть пустым, тогда пароля нет - // channel.Bayes.Enabled будет false, если не проставлен + // channel.Password может быть пустым, тогда пароля нет. + // channel.Bayes.Enabled будет false, если не проставлен. if 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.AllCaps.Enabled будет false, если не указан + // channel.AllCaps.Enabled будет false, если не указан. if 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 { sampleConfig.Jabber.StartupStatus[0] = "" } - // Если список статусов, с которыми работает бот пустой, вносим в него 1 запись с пустой строкой + // Если список статусов, с которыми работает бот пустой, вносим в него 1 запись с пустой строкой. if len(sampleConfig.Jabber.RuntimeStatus.Text) == 0 { sampleConfig.Jabber.RuntimeStatus.Text[0] = "" } - // Если sampleConfig.Jabber.RuntimeStatus.RotationTime не задан, то он равен 0 - // Если sampleConfig.Jabber.RuntimeStatus.RotationSplayTime не задан, то он равен 0 + // Если sampleConfig.Jabber.RuntimeStatus.RotationTime не задан, то он равен 0. + // Если sampleConfig.Jabber.RuntimeStatus.RotationSplayTime не задан, то он равен 0. - // Если sampleConfig.Jabber.BanDelay не задан, то он равен 0 - // Если sampleConfig.Jabber.BanPhrasesEnable не задан, то он false + // Если sampleConfig.Jabber.BanDelay не задан, то он равен 0. + // Если sampleConfig.Jabber.BanPhrasesEnable не задан, то он false. - // Если список фраз, с которыми банят пустой, то вносим в него одну позицию с пустой строкой + // Если список фраз, с которыми банят пустой, то вносим в него одну позицию с пустой строкой. if len(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") } - // sampleConfig.Log = "" if not set + // sampleConfig.Log = "" if not set. j.C = sampleConfig configLoaded = true diff --git a/internal/jabber/sig_handler.go b/internal/jabber/sig_handler.go index 33319f4..d35a5b6 100644 --- a/internal/jabber/sig_handler.go +++ b/internal/jabber/sig_handler.go @@ -8,6 +8,8 @@ import ( log "github.com/sirupsen/logrus" ) +// SigHandler срабатывает если происходит завершение программы, инициированное либо init-ом, либо пользователем. +// Обрабатвает сигналы INT, TERM, QUIT. func (j *Jabber) SigHandler() error { log.Debug("Installing signal handler") diff --git a/internal/jabber/squash.go b/internal/jabber/squash.go index 1bfdee6..7423780 100644 --- a/internal/jabber/squash.go +++ b/internal/jabber/squash.go @@ -38,6 +38,7 @@ func (j *Jabber) Squash(room, jid string, reasonEnable bool, vType string) (stri if reasonEnable { var t = time.Now() + ban += fmt.Sprintf( "autoban at %04d.%02d.%02d %02d:%02d:%02d", t.Year(), diff --git a/internal/jabber/types.go b/internal/jabber/types.go index ee960e0..7ee8c1a 100644 --- a/internal/jabber/types.go +++ b/internal/jabber/types.go @@ -8,208 +8,209 @@ import ( "gopkg.in/tomb.v2" ) -// MyConfig прототип структурки с конфигом. -type MyConfig struct { +type ( + // 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 { - Server string `json:"server,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"` - 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"` + // C - конфиг, как он распарсился из конфиг-файла. + C MyConfig - 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"` -} + // WhiteList - структурка с безусловно разрешёнными jid-ами. + WhiteList MyWhiteList -// MyWhiteList прототип структурки с белым списком jid-ов. -type MyWhiteList struct { - Whitelist []struct { - RoomName string `json:"room_name,omitempty"` - Jid []string `json:"jid,omitempty"` - WipeBans bool `json:"wipe_bans,omitempty"` - } `json:"whitelist,omitempty"` -} + // BlackList - структурка с запрещёнными по регуляркам фразами, никами, jid-ами. + BlackList MyBlackList -// MyBlackList прототип структурки с чёрным списком jid-ов. -type MyBlackList struct { - 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"` -} + // Опции подключения к xmpp-серверу. + Options *xmpp.Options -// Jabber основная структура-объект, содержащая стейты и проч. -type Jabber struct { - // C - конфиг, как он распарсился из конфиг-файла. - C MyConfig + // gTomb пул активных горутин. + GTomb tomb.Tomb - // WhiteList - структурка с безусловно разрешёнными jid-ами - WhiteList MyWhiteList + // Talk основная структурка xmpp-клиента. + Talk *xmpp.Client - // BlackList - структурка с запрещёнными по регуляркам фразами, никами, jid-ами. - BlackList MyBlackList + // sync.Map-ка с капабилити сервера. + ServerCapsList *Collection - // Опции подключения к xmpp-серверу. - Options *xmpp.Options + // ServerCapsQueried показывает, были ли запрошены capabilities сервера. + ServerCapsQueried bool - // gTomb пул активных горутин. - GTomb tomb.Tomb + // Время последней активности, нужно для c2s пингов - посылаем пинги, только если давненько ничего не приходило с + // сервера. + LastServerActivity int64 - // Talk основная структурка xmpp-клиента. - Talk *xmpp.Client + // Время, когда был отправлен c2s ping. + ServerPingTimestampTx int64 - // sync.Map-ка с капабилити сервера. - ServerCapsList *Collection + // Время, когда был принят s2c pong. + ServerPingTimestampRx int64 - // ServerCapsQueried показывает, были ли запрошены capabilities сервера. - ServerCapsQueried bool + // Время последней активности, нужно для jabber:iq:last. + LastActivity int64 - // Время последней активности, нужно для c2s пингов - посылаем пинги, только если давненько ничего не приходило с - // сервера. - LastServerActivity int64 + // sync.Map-ка с комнатами и их capability. + MucCapsList *Collection - // Время, когда был отправлен c2s ping. - ServerPingTimestampTx int64 + // Время последней активности MUC-ов, нужно для пингов - посылаем пинги, только если давненько ничего не приходило из + // muc-ов. + LastMucActivity *Collection - // Время, когда был принят s2c pong. - ServerPingTimestampRx int64 + // Список комнат, в которых находится бот. + RoomsConnected []string - // Время последней активности, нужно для jabber:iq:last. - LastActivity int64 + // sync.Map-ка со списком участников конференций (в json-формате, согласно структуре xmpp.Presence, "room".[]json). + RoomPresences *Collection - // sync.Map-ка с комнатами и их capability. - MucCapsList *Collection + // Канал, по котором приходят сообщения о том, что ОС отправила некие сигналы процессу. + SigChan chan os.Signal - // Время последней активности MUC-ов, нужно для пингов - посылаем пинги, только если давненько ничего не приходило из - // muc-ов. - LastMucActivity *Collection + // Индиктор того, что процесс завершается. + Shutdown bool - // Список комнат, в которых находится бот. - RoomsConnected []string + // Индиктор того, что соединение установлено. + IsConnected bool - // sync.Map-ка со списком участников конференций (в json-формате, согласно структуре xmpp.Presence, "room".[]json). - RoomPresences *Collection + // Индикатор того, что соединение в процессе достукивания до сервера. + Connecting bool + } - // Канал, по котором приходят сообщения о том, что ОС отправила некие сигналы процессу. - SigChan chan os.Signal + // SimpleIqGetQuery прототип структурки для разбора запросов xmpp discovery query, например, + // 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" + } - // Индиктор того, что процесс завершается. - Shutdown bool + // PubsubIQGetQuery прототип структурки для разбора запросов xmpp pubsub. + 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"` + } - // Индиктор того, что соединение установлено. - IsConnected bool + // TimeIqGetQuery прототип структурки для разбора IQ запросов на локальное время клиента, + // https://xmpp.org/extensions/xep-0202.html + TimeIqGetQuery struct { + //