From fbd0196a4839644db22ca82c40129d0bdda01078 Mon Sep 17 00:00:00 2001 From: eleksir Date: Sat, 1 Nov 2025 02:15:08 +0300 Subject: [PATCH] [WIP] update linter rules, fix many linter warnings --- .gitignore | 1 + .golangci.yml | 743 +++++++++++++++++++++++++++++-- cmd/aleesa-telegram-go/main.go | 11 +- go.mod | 2 +- internal/log/log.go | 31 +- internal/tg/admin.go | 91 ++-- internal/tg/censor.go | 31 +- internal/tg/cmdParser.go | 30 +- internal/tg/globals.go | 53 ++- internal/tg/help.go | 1 + internal/tg/parseConfig.go | 30 +- internal/tg/redisMsgParser.go | 12 +- internal/tg/settings-db-util.go | 2 +- internal/tg/sigHandler.go | 2 +- internal/tg/telegram.go | 11 +- internal/tg/telegramMsgParser.go | 38 +- internal/tg/types.go | 123 ++--- 17 files changed, 961 insertions(+), 251 deletions(-) diff --git a/.gitignore b/.gitignore index 787eda4..47fd96b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ /data/cache /aleesa-telegram-go /*.log +/*.bck.* diff --git a/.golangci.yml b/.golangci.yml index f241c16..7d341cd 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,28 +1,12 @@ +version: "2" run: - tests: false - skip-dirs: - - data - - vendor + go: "1.25" modules-download-mode: readonly + tests: false allow-parallel-runners: true - go: '1.21' -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 @@ -30,24 +14,30 @@ linters: - contextcheck - decorder - durationcheck + - errcheck - errchkjson - errorlint - exhaustive - - exportloopref - - gci + - exptostd - ginkgolinter - gocheckcompilerdirectives - gocritic + - godoclint - godot - - gofmt - - goimports + - godox + - goprintffuncname - gosec - gosmopolitan + - govet - grouper - importas + - ineffassign + - intrange - ireturn - makezero - mirror + - misspell + - modernize - nakedret - nilerr - nilnil @@ -55,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 + 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/aleesa-telegram-go/main.go b/cmd/aleesa-telegram-go/main.go index a02740f..edd0633 100644 --- a/cmd/aleesa-telegram-go/main.go +++ b/cmd/aleesa-telegram-go/main.go @@ -1,7 +1,7 @@ +// Package main is the main package of aleesa-telegram-go. package main import ( - "context" "fmt" "os" "os/signal" @@ -26,7 +26,7 @@ func init() { log.Fatalf("Unable to get current executable path: %s", err) } - configJSONPath := fmt.Sprintf("%s/data/config.json", filepath.Dir(executablePath)) + configJSONPath := filepath.Dir(executablePath) + "/data/config.json" locations := []string{ "~/.aleesa-telegram-go.json", @@ -68,16 +68,15 @@ func main() { log.Init(tg.Config.Loglevel, logfile) - // Main context - var ctx = context.Background() - // Инициализируем redis-клиента. tg.RedisClient = redis.NewClient(&redis.Options{ Addr: fmt.Sprintf("%s:%d", tg.Config.Redis.Server, tg.Config.Redis.Port), }) log.Debugf("Lazy connect() to redis at %s:%d", tg.Config.Redis.Server, tg.Config.Redis.Port) - tg.Subscriber = tg.RedisClient.Subscribe(ctx, tg.Config.Redis.MyChannel) + + tg.Subscriber = tg.RedisClient.Subscribe(tg.Ctx, tg.Config.Redis.MyChannel) + redisMsgChan := tg.Subscriber.Channel() // Самое время поставить траппер сигналов. diff --git a/go.mod b/go.mod index b39fde2..9cb2c3b 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module aleesa-telegram-go -go 1.22 +go 1.25 require ( github.com/NicoNex/echotron/v3 v3.33.1 diff --git a/internal/log/log.go b/internal/log/log.go index f7b120e..aca47e1 100644 --- a/internal/log/log.go +++ b/internal/log/log.go @@ -1,3 +1,4 @@ +// Package log wraps log/slog for more easier and convinient use. package log import ( @@ -11,17 +12,19 @@ import ( "time" ) -// Handler exposes slog handler struct. -var Handler slog.Handler +var ( + // Handler exposes slog handler struct. + Handler slog.Handler -// Ctx log context. For now it is just sits here and do nothing. -var Ctx = context.Background() + // Ctx log context. For now it is just sits here and do nothing. + Ctx = context.Background() -// Writer go-writer eported for capturing other types of logs, even for std log go facility. -var Writer io.Writer + // Writer go-writer eported for capturing other types of logs, even for std log go facility. + Writer io.Writer -// reader is required for Writer to work properly. It is connected via io.Pipe() to Writer. -var reader io.Reader + // reader is required for Writer to work properly. It is connected via io.Pipe() to Writer. + reader io.Reader +) // Init setup logger stuff. // level can be error, warn, info, debug if something other supplied info level selected. @@ -29,7 +32,6 @@ var reader io.Reader func Init(level string, fileDescriptor *os.File) { var loglevel slog.Level - // no panic, no trace. switch level { case "debug": loglevel = slog.LevelDebug @@ -48,18 +50,17 @@ func Init(level string, fileDescriptor *os.File) { } opts := &slog.HandlerOptions{ //nolint: exhaustruct - // Use the ReplaceAttr function on the handler options - // to be able to replace any single attribute in the log output + // Use the ReplaceAttr function on the handler options to be able to replace any single attribute in the log + // output. ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { //nolint: revive - // check that we are handling the time key + // Check that we are handling the time key. if a.Key != slog.TimeKey { return a } t := a.Value.Time() - // change the value from a time.Time to a String - // where the string has the correct time format. + // Change the value from a time.Time to the String. The string has correct time format. a.Value = slog.StringValue(t.Format(time.DateTime)) return a @@ -85,7 +86,7 @@ func readIoPipe() { for { for scanner.Scan() { - line := scanner.Text() // Get the current line as a string + line := scanner.Text() // Get the current line as a string. slog.Debug(line) } diff --git a/internal/tg/admin.go b/internal/tg/admin.go index 11392de..3473674 100644 --- a/internal/tg/admin.go +++ b/internal/tg/admin.go @@ -1,8 +1,10 @@ +// Package tg implements most of aleesa-telegram-go functionality. package tg import ( "fmt" "regexp" + "strconv" "github.com/NicoNex/echotron/v3" "github.com/davecgh/go-spew/spew" @@ -18,6 +20,7 @@ func Admin(msg *echotron.Update) (bool, error) { switch { case c == "admin" || c == "админ": answer = "```\n" + answer += fmt.Sprintf("%sadmin censor - показать список состояния типов сообщений\n", Config.Csign) answer += fmt.Sprintf("%sадмин ценз - показать список состояния типов сообщений\n", Config.Csign) answer += fmt.Sprintf("%sadmin censor type # - где 1 - вкл, 0 - выкл цензуры для означенного типа сообщений\n", Config.Csign) @@ -64,67 +67,67 @@ func Admin(msg *echotron.Update) (bool, error) { answer += "```" case regexp.MustCompile("^(admin|админ)[[:space:]](censor|ценз)$").MatchString(c): - if GetSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "VoiceMsg") == "1" { + if GetSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "VoiceMsg") == "1" { answer += "Тип сообщения voice удаляется\n" } else { answer += "Тип сообщения voice не удаляется\n" } - if GetSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "AudioMsg") == "1" { + if GetSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "AudioMsg") == "1" { answer += "Тип сообщения audio удаляется\n" } else { answer += "Тип сообщения audio не удаляется\n" } - if GetSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "PhotoMsg") == "1" { + if GetSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "PhotoMsg") == "1" { answer += "Тип сообщения photo удаляется\n" } else { answer += "Тип сообщения photo не удаляется\n" } - if GetSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "VideoMsg") == "1" { + if GetSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "VideoMsg") == "1" { answer += "Тип сообщения video удаляется\n" } else { answer += "Тип сообщения video не удаляется\n" } - if GetSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "VideoNoteMsg") == "1" { + if GetSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "VideoNoteMsg") == "1" { answer += "Тип сообщения video_note удаляется\n" } else { answer += "Тип сообщения video_note не удаляется\n" } - if GetSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "AnimationMsg") == "1" { + if GetSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "AnimationMsg") == "1" { answer += "Тип сообщения animation удаляется\n" } else { answer += "Тип сообщения animation не удаляется\n" } - if GetSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "StickerMsg") == "1" { + if GetSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "StickerMsg") == "1" { answer += "Тип сообщения sticker удаляется\n" } else { answer += "Тип сообщения sticker не удаляется\n" } - if GetSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "DiceMsg") == "1" { + if GetSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "DiceMsg") == "1" { answer += "Тип сообщения dice удаляется\n" } else { answer += "Тип сообщения dice не удаляется\n" } - if GetSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "GameMsg") == "1" { + if GetSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "GameMsg") == "1" { answer += "Тип сообщения game удаляется\n" } else { answer += "Тип сообщения game не удаляется\n" } - if GetSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "PollMsg") == "1" { + if GetSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "PollMsg") == "1" { answer += "Тип сообщения poll удаляется\n" } else { answer += "Тип сообщения poll не удаляется\n" } - if GetSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "DocumentMsg") == "1" { + if GetSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "DocumentMsg") == "1" { answer += "Тип сообщения document удаляется\n" } else { answer += "Тип сообщения document не удаляется\n" @@ -139,14 +142,14 @@ func Admin(msg *echotron.Update) (bool, error) { case "voice": switch cmdArray[3] { case "1": - if err := SaveSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "VoiceMsg", "1"); err != nil { + if err := SaveSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "VoiceMsg", "1"); err != nil { return true, err } answer += "Теперь сообщения с voice будут оставаться" case "0": - if err := SaveSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "VoiceMsg", "0"); err != nil { + if err := SaveSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "VoiceMsg", "0"); err != nil { return true, err } @@ -159,14 +162,14 @@ func Admin(msg *echotron.Update) (bool, error) { case "audio": switch cmdArray[3] { case "1": - if err := SaveSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "AudioMsg", "1"); err != nil { + if err := SaveSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "AudioMsg", "1"); err != nil { return true, err } answer += "Теперь сообщения с audio будут оставаться" case "0": - if err := SaveSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "AudioMsg", "0"); err != nil { + if err := SaveSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "AudioMsg", "0"); err != nil { return true, err } @@ -179,14 +182,14 @@ func Admin(msg *echotron.Update) (bool, error) { case "photo": switch cmdArray[3] { case "1": - if err := SaveSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "PhotoMsg", "1"); err != nil { + if err := SaveSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "PhotoMsg", "1"); err != nil { return true, err } answer += "Теперь сообщения с photo будут оставаться" case "0": - if err := SaveSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "PhotoMsg", "0"); err != nil { + if err := SaveSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "PhotoMsg", "0"); err != nil { return true, err } @@ -199,14 +202,14 @@ func Admin(msg *echotron.Update) (bool, error) { case "video": switch cmdArray[3] { case "1": - if err := SaveSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "VideoMsg", "1"); err != nil { + if err := SaveSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "VideoMsg", "1"); err != nil { return true, err } answer += "Теперь сообщения с video будут оставаться" case "0": - if err := SaveSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "VideoMsg", "0"); err != nil { + if err := SaveSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "VideoMsg", "0"); err != nil { return true, err } @@ -219,14 +222,14 @@ func Admin(msg *echotron.Update) (bool, error) { case "video_note": switch cmdArray[3] { case "1": - if err := SaveSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "VideoNoteMsg", "1"); err != nil { + if err := SaveSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "VideoNoteMsg", "1"); err != nil { return true, err } answer += "Теперь сообщения с video_note будут оставаться" case "0": - if err := SaveSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "VideoNoteMsg", "0"); err != nil { + if err := SaveSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "VideoNoteMsg", "0"); err != nil { return true, err } @@ -239,14 +242,14 @@ func Admin(msg *echotron.Update) (bool, error) { case "animation": switch cmdArray[3] { case "1": - if err := SaveSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "AnimationMsg", "1"); err != nil { + if err := SaveSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "AnimationMsg", "1"); err != nil { return true, err } answer += "Теперь сообщения с animation будут оставаться" case "0": - if err := SaveSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "AnimationMsg", "0"); err != nil { + if err := SaveSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "AnimationMsg", "0"); err != nil { return true, err } @@ -259,14 +262,14 @@ func Admin(msg *echotron.Update) (bool, error) { case "sticker": switch cmdArray[3] { case "1": - if err := SaveSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "StickerMsg", "1"); err != nil { + if err := SaveSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "StickerMsg", "1"); err != nil { return true, err } answer += "Теперь сообщения с sticker будут оставаться" case "0": - if err := SaveSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "StickerMsg", "0"); err != nil { + if err := SaveSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "StickerMsg", "0"); err != nil { return true, err } @@ -279,14 +282,14 @@ func Admin(msg *echotron.Update) (bool, error) { case "dice": switch cmdArray[3] { case "1": - if err := SaveSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "DiceMsg", "1"); err != nil { + if err := SaveSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "DiceMsg", "1"); err != nil { return true, err } answer += "Теперь сообщения с dice будут оставаться" case "0": - if err := SaveSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "diceMsg", "0"); err != nil { + if err := SaveSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "diceMsg", "0"); err != nil { return true, err } @@ -299,14 +302,14 @@ func Admin(msg *echotron.Update) (bool, error) { case "game": switch cmdArray[3] { case "1": - if err := SaveSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "GameMsg", "1"); err != nil { + if err := SaveSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "GameMsg", "1"); err != nil { return true, err } answer += "Теперь сообщения с game будут оставаться" case "0": - if err := SaveSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "GameMsg", "0"); err != nil { + if err := SaveSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "GameMsg", "0"); err != nil { return true, err } @@ -319,14 +322,14 @@ func Admin(msg *echotron.Update) (bool, error) { case "poll": switch cmdArray[3] { case "1": - if err := SaveSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "PollMsg", "1"); err != nil { + if err := SaveSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "PollMsg", "1"); err != nil { return true, err } answer += "Теперь сообщения с poll будут оставаться" case "0": - if err := SaveSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "PollMsg", "0"); err != nil { + if err := SaveSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "PollMsg", "0"); err != nil { return true, err } @@ -339,14 +342,14 @@ func Admin(msg *echotron.Update) (bool, error) { case "document": switch cmdArray[3] { case "1": - if err := SaveSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "DocumentMsg", "1"); err != nil { + if err := SaveSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "DocumentMsg", "1"); err != nil { return true, err } answer += "Теперь сообщения с document будут оставаться" case "0": - if err := SaveSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "DocumentMsg", "0"); err != nil { + if err := SaveSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "DocumentMsg", "0"); err != nil { return true, err } @@ -361,7 +364,7 @@ func Admin(msg *echotron.Update) (bool, error) { } case regexp.MustCompile("^(admin|админ)[[:space:]](fortune|фортунка)$").MatchString(c): - if GetSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "FortuneMsg") == "1" { + if GetSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "FortuneMsg") == "1" { answer += "Фортунки с утра показываются" } else { answer += "Фортунки с утра не показываются" @@ -375,14 +378,14 @@ func Admin(msg *echotron.Update) (bool, error) { // мы не сможем вытащить список чятиков. switch cmdArray[2] { case "1": - if err := SaveSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "FortuneMsg", "1"); err != nil { + if err := SaveSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "FortuneMsg", "1"); err != nil { return true, err } answer += "Теперь сообщения с фортункой будут отправляться с утра" case "0": - if err := SaveSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "FortuneMsg", "0"); err != nil { + if err := SaveSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "FortuneMsg", "0"); err != nil { return true, err } @@ -393,7 +396,7 @@ func Admin(msg *echotron.Update) (bool, error) { } case regexp.MustCompile("^(admin|админ)[[:space:]](greet|приветствие)$").MatchString(c): - if GetSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "GreetMsg") == "1" { + if GetSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "GreetMsg") == "1" { answer += "Приветствуем вновьприбывших участников чатика" } else { answer += "Не приветствуем вновьприбывших участников чатика" @@ -407,14 +410,14 @@ func Admin(msg *echotron.Update) (bool, error) { // мы не сможем вытащить список чятиков. switch cmdArray[2] { case "1": - if err := SaveSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "GreetMsg", "1"); err != nil { + if err := SaveSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "GreetMsg", "1"); err != nil { return true, err } answer += "Будем приветствовать вновьприбывших участников чата" case "0": - if err := SaveSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "GreetMsg", "0"); err != nil { + if err := SaveSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "GreetMsg", "0"); err != nil { return true, err } @@ -425,7 +428,7 @@ func Admin(msg *echotron.Update) (bool, error) { } case regexp.MustCompile("^(admin|админ)[[:space:]](goodbye|прощание)$").MatchString(c): - if GetSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "GoodbyeMsg") == "1" { + if GetSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "GoodbyeMsg") == "1" { answer += "Прощаемся с ушедшими участниками чатика" } else { answer += "Не прощаемся с ушедшими участниками чатика" @@ -437,14 +440,14 @@ func Admin(msg *echotron.Update) (bool, error) { switch cmdArray[2] { case "1": - if err := SaveSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "GoodbyeMsg", "1"); err != nil { + if err := SaveSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "GoodbyeMsg", "1"); err != nil { return true, err } answer += "Будем прощаться с ушедшими участниками чата" case "0": - if err := SaveSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "GoodbyeMsg", "0"); err != nil { + if err := SaveSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "GoodbyeMsg", "0"); err != nil { return true, err } @@ -460,14 +463,14 @@ func Admin(msg *echotron.Update) (bool, error) { switch cmdArray[2] { case "1": - if err := SaveSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "ChanMsg", "1"); err != nil { + if err := SaveSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "ChanMsg", "1"); err != nil { return true, err } answer += "Будем удалять сообщения, написанные от имени канала" case "0": - if err := SaveSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "ChanMsg", "0"); err != nil { + if err := SaveSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "ChanMsg", "0"); err != nil { return true, err } diff --git a/internal/tg/censor.go b/internal/tg/censor.go index 3703da7..e6d0481 100644 --- a/internal/tg/censor.go +++ b/internal/tg/censor.go @@ -1,7 +1,7 @@ package tg import ( - "fmt" + "strconv" "github.com/NicoNex/echotron/v3" ) @@ -10,10 +10,13 @@ import ( // Непотребными могут быть аудиосообщения, аудиофайлы, видеосообщения, сообщения от имени других каналов итп. // Это могут настроить админы чятика через команду !admin censor. func Censor(msg *echotron.Update) bool { - result := false + var ( + result bool + chatID = msg.ChatID() + ) switch { - case GetSetting(fmt.Sprintf("%d", msg.ChatID()), "VoiceMsg") == "1": + case GetSetting(strconv.FormatInt(chatID, 10), "VoiceMsg") == "1": result = true // Предполагаем, что у voice-ов здесь всегда не ноль. @@ -23,7 +26,7 @@ func Censor(msg *echotron.Update) bool { result = true } - case GetSetting(fmt.Sprintf("%d", msg.ChatID()), "AudioMsg") == "1": + case GetSetting(strconv.FormatInt(chatID, 10), "AudioMsg") == "1": // Предполагаем, что у аудио здесь всегда не ноль. if msg.Message.Audio.Duration != 0 { delMsg(msg) @@ -31,7 +34,7 @@ func Censor(msg *echotron.Update) bool { result = true } - case GetSetting(fmt.Sprintf("%d", msg.ChatID()), "PhotoMsg") == "1": + case GetSetting(strconv.FormatInt(chatID, 10), "PhotoMsg") == "1": // Обычное сообщение не содержит фоток. if len(msg.Message.Photo) != 0 { delMsg(msg) @@ -39,7 +42,7 @@ func Censor(msg *echotron.Update) bool { result = true } - case GetSetting(fmt.Sprintf("%d", msg.ChatID()), "VideoMsg") == "1": + case GetSetting(strconv.FormatInt(chatID, 10), "VideoMsg") == "1": // Предполагаем, что у видео здесь всегда не ноль. if msg.Message.Video.Duration != 0 { delMsg(msg) @@ -47,7 +50,7 @@ func Censor(msg *echotron.Update) bool { result = true } - case GetSetting(fmt.Sprintf("%d", msg.ChatID()), "VideoNoteMsg") == "1": + case GetSetting(strconv.FormatInt(chatID, 10), "VideoNoteMsg") == "1": // Предполагаем, что у видео-заметки здесь всегда не ноль. if msg.Message.VideoNote.Duration != 0 { delMsg(msg) @@ -55,7 +58,7 @@ func Censor(msg *echotron.Update) bool { result = true } - case GetSetting(fmt.Sprintf("%d", msg.ChatID()), "AnimationMsg") == "1": + case GetSetting(strconv.FormatInt(chatID, 10), "AnimationMsg") == "1": // Предполагаем, что у анимации здесь всегда не ноль. if msg.Message.Animation.Duration != 0 { delMsg(msg) @@ -63,7 +66,7 @@ func Censor(msg *echotron.Update) bool { result = true } - case GetSetting(fmt.Sprintf("%d", msg.ChatID()), "StickerMsg") == "1": + case GetSetting(strconv.FormatInt(chatID, 10), "StickerMsg") == "1": // Предполагаем, что FileID не пустое только у стикера. if msg.Message.Sticker.FileID != "" { delMsg(msg) @@ -71,7 +74,7 @@ func Censor(msg *echotron.Update) bool { result = true } - case GetSetting(fmt.Sprintf("%d", msg.ChatID()), "DiceMsg") == "1": + case GetSetting(strconv.FormatInt(chatID, 10), "DiceMsg") == "1": // Предполагаем, что Value > 0 только у дайса. if msg.Message.Dice.Value != 0 { delMsg(msg) @@ -79,7 +82,7 @@ func Censor(msg *echotron.Update) bool { result = true } - case GetSetting(fmt.Sprintf("%d", msg.ChatID()), "GameMsg") == "1": + case GetSetting(strconv.FormatInt(chatID, 10), "GameMsg") == "1": // Предполагаем, что title только у game-а. if msg.Message.Game.Title != "" { delMsg(msg) @@ -87,7 +90,7 @@ func Censor(msg *echotron.Update) bool { result = true } - case GetSetting(fmt.Sprintf("%d", msg.ChatID()), "PollMsg") == "1": + case GetSetting(strconv.FormatInt(chatID, 10), "PollMsg") == "1": // Предполагаем, что title только у game-а. if msg.Message.Poll.Question != "" { delMsg(msg) @@ -95,7 +98,7 @@ func Censor(msg *echotron.Update) bool { result = true } - case GetSetting(fmt.Sprintf("%d", msg.ChatID()), "DocumentMsg") == "1": + case GetSetting(strconv.FormatInt(chatID, 10), "DocumentMsg") == "1": // Предполагаем, что FileID есть только у document-а. if msg.Message.Document.FileID != "" { delMsg(msg) @@ -107,7 +110,7 @@ func Censor(msg *echotron.Update) bool { // пидорство он будет удалять asap. // 136817688 - это специальный id пользователя, который принимает облик канала, на него можно нажать и попасть // на рекламируемый канал. - case GetSetting(fmt.Sprintf("%d", msg.ChatID()), "ChanMsg") == "1": + case GetSetting(strconv.FormatInt(chatID, 10), "ChanMsg") == "1": if msg.Message.From.ID == 136817688 { delMsg(msg) diff --git a/internal/tg/cmdParser.go b/internal/tg/cmdParser.go index c64b05c..eb40dca 100644 --- a/internal/tg/cmdParser.go +++ b/internal/tg/cmdParser.go @@ -5,6 +5,8 @@ import ( "encoding/json" "fmt" "regexp" + "slices" + "strconv" "github.com/NicoNex/echotron/v3" ) @@ -20,8 +22,8 @@ func cmdParser(me echotron.APIResponseUser, cmd *echotron.Update) (bool, error) smsg.From = "telegram" smsg.Plugin = "telegram" - smsg.Chatid = fmt.Sprintf("%d", cmd.Message.Chat.ID) - smsg.Userid = fmt.Sprintf("%d", cmd.Message.From.ID) + smsg.Chatid = strconv.FormatInt(cmd.Message.Chat.ID, 10) + smsg.Userid = strconv.FormatInt(cmd.Message.From.ID, 10) smsg.Message = cmd.Message.Text smsg.Misc.Answer = 1 @@ -60,12 +62,8 @@ func cmdParser(me echotron.APIResponseUser, cmd *echotron.Update) (bool, error) "рыба", "рыбка", "рыбалка", "karma", "карма", "fuck", "weather", "погода", "w", "п", "погодка", "погадка", } - for _, c := range cmds { - if command == c { - bingo = true - - break - } + if slices.Contains(cmds, command) { + bingo = true } // Не нашлось команды в одно слово. Поищем команды с одним аргументом. @@ -103,20 +101,14 @@ func cmdParser(me echotron.APIResponseUser, cmd *echotron.Update) (bool, error) "dig", "копать", "fish", "fishing", "рыба", "рыбка", "рыбалка", } - for _, c := range cmds { - // В vscode возникает ошибка при использовании if-а. - switch { - case command == c: - smsg.Misc.Username = ConstructTelegramHighlightName(cmd.Message.From) - smsg.Misc.Msgformat = 1 - - break //nolint: gosimple - } + if slices.Contains(cmds, command) { + smsg.Misc.Username = ConstructTelegramHighlightName(cmd.Message.From) + smsg.Misc.Msgformat = 1 } // Если у нас супергруппа с тредами, надо проставить thread id. if cmd.Message.Chat.Type == "supergroup" && cmd.Message.IsTopicMessage && cmd.Message.ThreadID != 0 { - smsg.ThreadID = fmt.Sprintf("%d", cmd.Message.ThreadID) + smsg.ThreadID = strconv.Itoa(cmd.Message.ThreadID) } // Отправляем сообщение в редиску. @@ -129,7 +121,7 @@ func cmdParser(me echotron.APIResponseUser, cmd *echotron.Update) (bool, error) } // Заталкиваем наш json в редиску. - if err := RedisClient.Publish(ctx, Config.Redis.Channel, data).Err(); err != nil { + if err := RedisClient.Publish(Ctx, Config.Redis.Channel, data).Err(); err != nil { log.Warnf("Unable to send data to redis channel %s: %s", Config.Redis.Channel, err) } else { log.Debugf("Sent msg to redis channel %s: %s", Config.Redis.Channel, string(data)) diff --git a/internal/tg/globals.go b/internal/tg/globals.go index 9f44dc6..dfcb98c 100644 --- a/internal/tg/globals.go +++ b/internal/tg/globals.go @@ -9,37 +9,42 @@ import ( "github.com/go-redis/redis/v8" ) -// Config - это у нас глобальная штука. -var Config myConfig +var ( + // Config - это у нас глобальная штука. + Config MyConfig -// To break circular message forwarding we must set some sane default, it can be overridden via config. -var forwardMax int64 = 5 + // forwardMax is used to break circular message forwarding we must set some sane default, it can be overridden via config. + forwardMax int64 = 5 -// Объектики клиента-редиски. -var RedisClient *redis.Client -var Subscriber *redis.PubSub + // RedisClient объектик клиента-редиски. + RedisClient *redis.Client -// Main context. -var ctx = context.Background() + // Subscriber is redis PubSub object. + Subscriber *redis.PubSub -// Ставится в true, если мы получили сигнал на выключение. -var Shutdown = false + // Ctx is main context. + Ctx = context.Background() -// Канал, в который приходят уведомления для хэндлера сигналов от траппера сигналов. -var SigChan = make(chan os.Signal, 1) + // Shutdown ставится в true, если мы получили сигнал на выключение. + Shutdown = false -// Мапка с открытыми дескрипторами баз с настройками. -var settingsDB = make(map[string]*pebble.DB) + // SigChan канал, в который приходят уведомления для хэндлера сигналов от траппера сигналов. + SigChan = make(chan os.Signal, 1) -var tg echotron.API + // settingsDB мапка с открытыми дескрипторами баз с настройками. + settingsDB = make(map[string]*pebble.DB) -// Приветсвенные фразы для новых участников чата. -var introduceGreet = [...]string{ - "Дратути", - "Дарована", - "Доброе утро, день или вечер", - "Добро пожаловать в наше скромное коммунити", - "Наше вам с кисточкой тут, на канальчике", -} + // tg is var for telegram api. + tg echotron.API + + // introduceGreet приветсвенные фразы для новых участников чата. + introduceGreet = [...]string{ + "Дратути", + "Дарована", + "Доброе утро, день или вечер", + "Добро пожаловать в наше скромное коммунити", + "Наше вам с кисточкой тут, на канальчике", + } +) /* vim: set ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/internal/tg/help.go b/internal/tg/help.go index e94615d..ad744a7 100644 --- a/internal/tg/help.go +++ b/internal/tg/help.go @@ -10,6 +10,7 @@ import ( // Help Выводит в чат сообщение с основными командами бота. func Help(cmd *echotron.Update) (bool, error) { help := "```\n" + help += fmt.Sprintf("%shelp | %sпомощь - список команд", Config.Csign, Config.Csign) help += fmt.Sprintf("%sanek | %sанек | %sанекдот - рандомный анекдот с anekdot.ru", Config.Csign, Config.Csign, Config.Csign) help += fmt.Sprintf("%sbuni - рандомный стрип hapi buni", Config.Csign) diff --git a/internal/tg/parseConfig.go b/internal/tg/parseConfig.go index 69da713..91c0119 100644 --- a/internal/tg/parseConfig.go +++ b/internal/tg/parseConfig.go @@ -11,52 +11,52 @@ import ( ) // ParseConfig разбирает и валидирует даденный конфиг. -func ParseConfig(path string) (myConfig, error) { +func ParseConfig(path string) (MyConfig, error) { fileInfo, err := os.Stat(path) // Предполагаем, что файла либо нет, либо мы не можем его прочитать, второе надо бы логгировать, но пока забьём. if err != nil { - return myConfig{}, err + return MyConfig{}, err } // Конфиг-файл длинноват для конфига, попробуем следующего кандидата. if fileInfo.Size() > 65535 { err := fmt.Errorf("config file %s is too long for config, skipping", path) - return myConfig{}, err + return MyConfig{}, err } buf, err := os.ReadFile(path) // Не удалось прочитать. if err != nil { - return myConfig{}, err + return MyConfig{}, err } // Исходя из документации, hjson какбы умеет парсить "кривой" json, но парсит его в map-ку. // Интереснее на выходе получить структурку: то есть мы вначале конфиг преобразуем в map-ку, затем эту map-ку // сериализуем в json, а потом json превращааем в структурку. Не очень эффективно, но он и не часто требуется. var ( - sampleConfig myConfig - tmp map[string]interface{} + sampleConfig MyConfig + tmp map[string]any ) err = hjson.Unmarshal(buf, &tmp) // Не удалось распарсить. if err != nil { - return myConfig{}, err + return MyConfig{}, err } tmpjson, err := json.Marshal(tmp) // Не удалось преобразовать map-ку в json. if err != nil { - return myConfig{}, err + return MyConfig{}, err } if err := json.Unmarshal(tmpjson, &sampleConfig); err != nil { - return myConfig{}, err + return MyConfig{}, err } // Валидируем значения из конфига. @@ -73,29 +73,29 @@ func ParseConfig(path string) (myConfig, error) { if sampleConfig.Redis.Channel == "" { err := fmt.Errorf("channel field in config file %s must be set", path) - return myConfig{}, err + return MyConfig{}, err } if sampleConfig.Redis.MyChannel == "" { err := fmt.Errorf("my_channel field in config file %s must be set", path) - return myConfig{}, err + return MyConfig{}, err } if sampleConfig.Telegram.Token == "" { - return myConfig{}, fmt.Errorf("telegram.token field in config file %s must be set", path) + return MyConfig{}, fmt.Errorf("telegram.token field in config file %s must be set", path) } if sampleConfig.Loglevel == "" { sampleConfig.Loglevel = "info" } - // sampleConfig.Log = "" if not set + // sampleConfig.Log = "" if not set.. if sampleConfig.Csign == "" { err := fmt.Errorf("csign field in config file %s must be set", path) - return myConfig{}, err + return MyConfig{}, err } if sampleConfig.ForwardsMax == 0 { @@ -103,7 +103,7 @@ func ParseConfig(path string) (myConfig, error) { } if sampleConfig.DataDir == "" { - return myConfig{}, fmt.Errorf("data_dir field in config file %s must be set", path) + return MyConfig{}, fmt.Errorf("data_dir field in config file %s must be set", path) } return sampleConfig, err diff --git a/internal/tg/redisMsgParser.go b/internal/tg/redisMsgParser.go index cccde1a..5358802 100644 --- a/internal/tg/redisMsgParser.go +++ b/internal/tg/redisMsgParser.go @@ -26,7 +26,7 @@ func RedisMsgParser(msg string) { return } - // Validate our j + // Validate our j. if exist := j.From; exist == "" { log.Warnf("Incorrect msg from redis, no from field: %s", msg) @@ -63,7 +63,7 @@ func RedisMsgParser(msg string) { return } - // j.Misc.Answer может и не быть, тогда ответа на такое сообщение не будет + // j.Misc.Answer может и не быть, тогда ответа на такое сообщение не будет. if j.Misc.Answer == 0 { log.Debug("Field Misc->Answer = 0, skipping message") @@ -76,14 +76,14 @@ func RedisMsgParser(msg string) { j.Misc.Csign = Config.Csign } - // j.Misc.FwdCnt если нам его не передали, то будет 0 + // j.Misc.FwdCnt если нам его не передали, то будет 0. if exist := j.Misc.Fwdcnt; exist == 0 { j.Misc.Fwdcnt = 1 } - // j.Misc.GoodMorning может быть 1 или 0, по-умолчанию 0 - // j.Misc.MsgFormat может быть 1 или 0, по-умолчанию 0 - // j.Misc.Username можно не передавать, тогда будет пустая строка + // j.Misc.GoodMorning может быть 1 или 0, по-умолчанию 0. + // j.Misc.MsgFormat может быть 1 или 0, по-умолчанию 0. + // j.Misc.Username можно не передавать, тогда будет пустая строка. // Отвалидировались, теперь вернёмся к нашим баранам. diff --git a/internal/tg/settings-db-util.go b/internal/tg/settings-db-util.go index d2cd224..98374b1 100644 --- a/internal/tg/settings-db-util.go +++ b/internal/tg/settings-db-util.go @@ -52,7 +52,7 @@ func GetSetting(chatID string, setting string) string { chatHash := sha256.Sum256([]byte(chatID)) database := fmt.Sprintf("settings_db/%x", chatHash) - // Если БД не открыта, откроем её + // Если БД не открыта, откроем её. if _, ok := settingsDB[database]; !ok { var options pebble.Options // По дефолту ограничение ставится на мегабайты данных, а не на количество файлов, поэтому с дефолтными diff --git a/internal/tg/sigHandler.go b/internal/tg/sigHandler.go index efa3a75..eda7b8d 100644 --- a/internal/tg/sigHandler.go +++ b/internal/tg/sigHandler.go @@ -30,7 +30,7 @@ func SigHandler() { Shutdown = true // Отпишемся от всех каналов и закроем коннект к редиске. - if err = Subscriber.Unsubscribe(ctx); err != nil { + if err = Subscriber.Unsubscribe(Ctx); err != nil { log.Errorf("Unable to unsubscribe from redis channels cleanly: %s", err) } else { log.Debug("Unsubscribe from all redis channels") diff --git a/internal/tg/telegram.go b/internal/tg/telegram.go index 56b7d66..3a6f608 100644 --- a/internal/tg/telegram.go +++ b/internal/tg/telegram.go @@ -2,6 +2,7 @@ package tg import ( "fmt" + "strconv" "aleesa-telegram-go/internal/log" @@ -9,7 +10,7 @@ import ( ) // Telega основная горутинка, реализующая бота. -func Telega(c myConfig) { +func Telega(c MyConfig) { tg = echotron.NewAPI(c.Telegram.Token) for u := range echotron.PollingUpdates(c.Telegram.Token) { @@ -86,11 +87,11 @@ func ConstructPartialUserUsername(u *echotron.User) string { return u.LastName default: - return fmt.Sprintf("%d", u.ID) + return strconv.FormatInt(u.ID, 10) } } -// ConstructPartialUserUsername пытается найти и вытащить username, если такового нет, вытаскивает First/Last Name, если +// ConstructPartialChatUsername пытается найти и вытащить username, если такового нет, вытаскивает First/Last Name, если // такового нет, то возвращает ID. func ConstructPartialChatUsername(c *echotron.Chat) string { switch { @@ -107,7 +108,7 @@ func ConstructPartialChatUsername(c *echotron.Chat) string { return c.LastName default: - return fmt.Sprintf("%d", c.ID) + return strconv.FormatInt(c.ID, 10) } } @@ -126,7 +127,7 @@ func ConstructUserFirstLastName(u *echotron.User) string { case u.Username != "": user = fmt.Sprintf("@%s", u.Username) default: - user = fmt.Sprintf("%d", u.ID) + user = strconv.FormatInt(u.ID, 10) } return user diff --git a/internal/tg/telegramMsgParser.go b/internal/tg/telegramMsgParser.go index 4be16d4..a4b7955 100644 --- a/internal/tg/telegramMsgParser.go +++ b/internal/tg/telegramMsgParser.go @@ -5,6 +5,7 @@ import ( "fmt" "math/rand/v2" "regexp" + "strconv" "strings" "aleesa-telegram-go/internal/log" @@ -21,10 +22,9 @@ func telegramMsgParser(msg *echotron.Update) { } // Сообщение о том, что этот чятик изменился, например, превратился в супергруппу. - if (msg.Message.MigrateFromChatID < 0) && (msg.Message.MigrateToChatID < 0) { //nolint: revive,staticcheck + if (msg.Message.MigrateFromChatID < 0) && (msg.Message.MigrateToChatID < 0) { // TODO: поддержать миграцию настроек чата на новый chatId. // TODO: подумать, что можно сделать с настройками бэкэндов. Кажись, ничего, но надо глянуть. - return } @@ -56,16 +56,17 @@ func telegramMsgParser(msg *echotron.Update) { } // Люди пришли в чят. - if msg.Message.NewChatMembers != nil { //nolint: revive,staticcheck + if msg.Message.NewChatMembers != nil { // TODO: проверить, надо ли приветствовать, выбрать одну из приветственных фраз, потянуть время, типа, мы пишем // текст и поприветствовать вновь прибывшего. - if GetSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "GreetMsg") == "1" { + if GetSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "GreetMsg") == "1" { // Пользователи, которых мы приветсвуем, одной строкой. var users string if len(msg.Message.NewChatMembers) > 1 { lastOne := msg.Message.NewChatMembers[len(msg.Message.NewChatMembers)-1] theRest := msg.Message.NewChatMembers[:len(msg.Message.NewChatMembers)-1] + var usersSlice []string for _, user := range theRest { @@ -73,6 +74,7 @@ func telegramMsgParser(msg *echotron.Update) { } users = strings.Join(usersSlice, ", ") + users += fmt.Sprintf(" and %s", ConstructTelegramHighlightName(lastOne)) } else { users = ConstructTelegramHighlightName(msg.Message.NewChatMembers[0]) @@ -88,7 +90,7 @@ func telegramMsgParser(msg *echotron.Update) { resp, err := tg.SendChatAction( echotron.Typing, msg.Message.From.ID, - &echotron.ChatActionOptions{MessageThreadID: int(msg.Message.ThreadID)}, + &echotron.ChatActionOptions{MessageThreadID: msg.Message.ThreadID}, ) // TODO: вероятно, ошибку надо засылать в обработчик ошибок, так как в ней может быть миграция чятика или @@ -123,7 +125,7 @@ func telegramMsgParser(msg *echotron.Update) { // Человек ушёл из чата. if msg.Message.LeftChatMember != nil { - if GetSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "GoodbyeMsg") == "1" { + if GetSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "GoodbyeMsg") == "1" { user := ConstructUserFirstLastName(msg.Message.From) goodbye := fmt.Sprintf("Прощаемся с %s", user) @@ -160,8 +162,8 @@ func telegramMsgParser(msg *echotron.Update) { rmsg.Misc.Answer = 1 // Засылаем фразу в misc-канал (в роутер). - rmsg.Chatid = fmt.Sprintf("%d", msg.Message.Chat.ID) - rmsg.Userid = fmt.Sprintf("%d", msg.Message.From.ID) + rmsg.Chatid = strconv.FormatInt(msg.Message.Chat.ID, 10) + rmsg.Userid = strconv.FormatInt(msg.Message.From.ID, 10) rmsg.Message = msg.Message.Text rmsg.Mode = "private" rmsg.Plugin = "telegram" @@ -198,7 +200,7 @@ func telegramMsgParser(msg *echotron.Update) { } // Заталкиваем наш json в редиску. - if err := RedisClient.Publish(ctx, Config.Redis.Channel, data).Err(); err != nil { + if err := RedisClient.Publish(Ctx, Config.Redis.Channel, data).Err(); err != nil { log.Warnf("Unable to send data to redis channel %s: %s", Config.Redis.Channel, err) } else { log.Debugf("Sent msg to redis channel %s: %s", Config.Redis.Channel, string(data)) @@ -209,7 +211,7 @@ func telegramMsgParser(msg *echotron.Update) { // Обрабатываем сообщения для групп и супергрупп. По сути это примерно одинаковые вещи/сущности. // Цензор удаляет сообщения из чятика, если они "неправильные" - от имени других каналов, аудиосообщения, // содержат картинки, видео аудио итп, это настраивается через команду !admin censor. - if GetSetting(fmt.Sprintf("%d", msg.Message.Chat.ID), "censor") == "1" { + if GetSetting(strconv.FormatInt(msg.Message.Chat.ID, 10), "censor") == "1" { // Если цензор отработал, больше делать нечего, сообщения уже нету. if Censor(msg) { return @@ -239,7 +241,7 @@ func telegramMsgParser(msg *echotron.Update) { return } - // Здесь же, если фраза обращена к боту (ник или имя), выставляем флажок, что надо ответить + // Здесь же, если фраза обращена к боту (ник или имя), выставляем флажок, что надо ответить. quotedBotUsername := regexp.QuoteMeta(me.Result.Username) quotedBotFirstName := regexp.QuoteMeta(me.Result.FirstName) quotedBotLastName := regexp.QuoteMeta(me.Result.LastName) @@ -250,7 +252,9 @@ func telegramMsgParser(msg *echotron.Update) { return } else if messageContainsBotUsername { rmsg.Misc.Answer = 1 + m := regexp.MustCompile(quotedBotUsername) + msg.Message.Text = m.ReplaceAllString(msg.Message.Text, "") } @@ -273,7 +277,9 @@ func telegramMsgParser(msg *echotron.Update) { if messageContainsBotName { rmsg.Misc.Answer = 1 + m := regexp.MustCompile(quotedBotFirstName + "[[:space:]]+" + quotedBotLastName) + msg.Message.Text = m.ReplaceAllString(msg.Message.Text, "") } // Если у нас есть только имя, то попробуем его вырезать из входящей фразы. @@ -288,7 +294,9 @@ func telegramMsgParser(msg *echotron.Update) { if messageContainsBotName { rmsg.Misc.Answer = 1 + m := regexp.MustCompile(quotedBotFirstName) + msg.Message.Text = m.ReplaceAllString(msg.Message.Text, "") } // Если у нас есть только фамилия, то попробуем его вырезать из входящей фразы. @@ -303,14 +311,16 @@ func telegramMsgParser(msg *echotron.Update) { if messageContainsBotName { rmsg.Misc.Answer = 1 + m := regexp.MustCompile(quotedBotLastName) + msg.Message.Text = m.ReplaceAllString(msg.Message.Text, "") } } // Засылаем фразу в misc-канал (в роутер). - rmsg.Chatid = fmt.Sprintf("%d", msg.Message.Chat.ID) - rmsg.Userid = fmt.Sprintf("%d", msg.Message.From.ID) + rmsg.Chatid = strconv.FormatInt(msg.Message.Chat.ID, 10) + rmsg.Userid = strconv.FormatInt(msg.Message.From.ID, 10) rmsg.Message = msg.Message.Text rmsg.Mode = "public" rmsg.Plugin = "telegram" @@ -335,7 +345,7 @@ func telegramMsgParser(msg *echotron.Update) { } // Заталкиваем наш json в редиску. - if err := RedisClient.Publish(ctx, Config.Redis.Channel, data).Err(); err != nil { + if err := RedisClient.Publish(Ctx, Config.Redis.Channel, data).Err(); err != nil { log.Warnf("Unable to send data to redis channel %s: %s", Config.Redis.Channel, err) } else { log.Debugf("Sent msg to redis channel %s: %s", Config.Redis.Channel, string(data)) diff --git a/internal/tg/types.go b/internal/tg/types.go index 3970300..c6274a2 100644 --- a/internal/tg/types.go +++ b/internal/tg/types.go @@ -1,68 +1,71 @@ package tg -// myConfig структура, описывающая формат Конфига. -type myConfig struct { - Redis struct { - Server string `json:"server,omitempty"` - Port int `json:"port,omitempty"` - Channel string `json:"channel,omitempty"` - MyChannel string `json:"my_channel,omitempty"` - } `json:"redis"` +type ( + // MyConfig структура, описывающая формат Конфига. + MyConfig struct { + Csign string `json:"csign,omitempty"` + DataDir string `json:"data_dir,omitempty"` + Log string `json:"log,omitempty"` + Loglevel string `json:"loglevel,omitempty"` - Telegram struct { - Token string `json:"token,omitempty"` + Telegram struct { + Token string `json:"token,omitempty"` + } `json:"telegram"` + + Redis struct { + Channel string `json:"channel,omitempty"` + MyChannel string `json:"my_channel,omitempty"` + Server string `json:"server,omitempty"` + Port int `json:"port,omitempty"` + } `json:"redis"` + + ForwardsMax int64 `json:"forwards_max,omitempty"` } - Loglevel string `json:"loglevel,omitempty"` - Log string `json:"log,omitempty"` - Csign string `json:"csign,omitempty"` - ForwardsMax int64 `json:"forwards_max,omitempty"` - DataDir string `json:"data_dir,omitempty"` -} + // rMsg структура, описывающая формат входящего сообщения из pubsub-канала redis-ки. + rMsg struct { + From string `json:"from,omitempty"` + Chatid string `json:"chatid,omitempty"` + Userid string `json:"userid,omitempty"` + ThreadID string `json:"threadid,omitempty"` + Message string `json:"message,omitempty"` + Plugin string `json:"plugin,omitempty"` + Mode string `json:"mode,omitempty"` + Misc struct { + Botnick string `json:"bot_nick,omitempty"` + Csign string `json:"csign,omitempty"` + // Судя по перловой реализации, сюда всегда попадает [$fullname](tg://user?id=$userid). + // Согласно докуметации, и перловой реализации бота, мы проставляем это поле тогда, когда мы точно знаем, что в + // бэкэнде оно используется. + Username string `json:"username,omitempty"` + Answer int64 `json:"answer,omitempty"` + Fwdcnt int64 `json:"fwd_cnt,omitempty"` + GoodMorning int64 `json:"good_morning,omitempty"` + // В этом поле мы пишем 1, если бэкэнд должен в ответе форматировать сообщение в MarkdownV2, что какбэ + // не оч хорошо, так как мы выносим особенности реализации бэкэнда сюда. + Msgformat int64 `json:"msg_format,omitempty"` + } `json:"Misc"` + } -// rMsg структура, описывающая формат входящего сообщения из pubsub-канала redis-ки. -type rMsg struct { - From string `json:"from,omitempty"` - Chatid string `json:"chatid,omitempty"` - Userid string `json:"userid,omitempty"` - ThreadID string `json:"threadid,omitempty"` - Message string `json:"message,omitempty"` - Plugin string `json:"plugin,omitempty"` - Mode string `json:"mode,omitempty"` - Misc struct { - Answer int64 `json:"answer,omitempty"` - Botnick string `json:"bot_nick,omitempty"` - Csign string `json:"csign,omitempty"` - Fwdcnt int64 `json:"fwd_cnt,omitempty"` - GoodMorning int64 `json:"good_morning,omitempty"` - // В этом поле мы пишем 1, если бэкэнд должен в ответе форматировать сообщение в MarkdownV2, что какбэ - // не оч хорошо, так как мы выносим особенности реализации бэкэнда сюда. - Msgformat int64 `json:"msg_format,omitempty"` - // Судя по перловой реализации, сюда всегда попадает [$fullname](tg://user?id=$userid). - // Согласно докуметации, и перловой реализации бота, мы проставляем это поле тогда, когда мы точно знаем, что в - // бэкэнде оно используется. - Username string `json:"username,omitempty"` - } `json:"Misc"` -} - -// sMsg структура, описывающая исходящее сообщение в pubsub-канал redis-ки. -type sMsg struct { - From string `json:"from"` - Chatid string `json:"chatid"` - Userid string `json:"userid"` - ThreadID string `json:"threadid,omitempty"` - Message string `json:"message"` - Plugin string `json:"plugin"` - Mode string `json:"mode"` - Misc struct { - Answer int64 `json:"answer"` - Botnick string `json:"bot_nick"` - Csign string `json:"csign"` - Fwdcnt int64 `json:"fwd_cnt"` - GoodMorning int64 `json:"good_morning"` - Msgformat int64 `json:"msg_format"` - Username string `json:"username"` - } `json:"misc"` -} + // sMsg структура, описывающая исходящее сообщение в pubsub-канал redis-ки. + sMsg struct { + From string `json:"from"` + Chatid string `json:"chatid"` + Userid string `json:"userid"` + ThreadID string `json:"threadid,omitempty"` + Message string `json:"message"` + Plugin string `json:"plugin"` + Mode string `json:"mode"` + Misc struct { + Botnick string `json:"bot_nick"` + Csign string `json:"csign"` + Username string `json:"username"` + Answer int64 `json:"answer"` + Fwdcnt int64 `json:"fwd_cnt"` + GoodMorning int64 `json:"good_morning"` + Msgformat int64 `json:"msg_format"` + } `json:"misc"` + } +) /* vim: set ft=go noet ai ts=4 sw=4 sts=4: */