0

[WIP] update linter rules, fix many linter warnings

This commit is contained in:
eleksir
2025-11-01 02:15:08 +03:00
parent c31547ce4a
commit fbd0196a48
17 changed files with 961 additions and 251 deletions

1
.gitignore vendored
View File

@@ -6,3 +6,4 @@
/data/cache /data/cache
/aleesa-telegram-go /aleesa-telegram-go
/*.log /*.log
/*.bck.*

View File

@@ -1,28 +1,12 @@
version: "2"
run: run:
tests: false go: "1.25"
skip-dirs:
- data
- vendor
modules-download-mode: readonly modules-download-mode: readonly
tests: false
allow-parallel-runners: true allow-parallel-runners: true
go: '1.21'
output:
sort-results: true
linters-settings:
gosec:
excludes:
# Оно не используется для повышения безопасности или каких-то таких секъюрных вещей, оно используется для выборки
# произвольного элемента из массива, так что ИДИ НАХУЙ с этой проверкой и не еби мозги липовой безопасностью.
- G404 # Insecure random number source (rand)
linters: linters:
disable-all: true default: none
enable: enable:
- errcheck
- gosimple
- govet
- ineffassign
- staticcheck
- unused
- asasalint - asasalint
- asciicheck - asciicheck
- bodyclose - bodyclose
@@ -30,24 +14,30 @@ linters:
- contextcheck - contextcheck
- decorder - decorder
- durationcheck - durationcheck
- errcheck
- errchkjson - errchkjson
- errorlint - errorlint
- exhaustive - exhaustive
- exportloopref - exptostd
- gci
- ginkgolinter - ginkgolinter
- gocheckcompilerdirectives - gocheckcompilerdirectives
- gocritic - gocritic
- godoclint
- godot - godot
- gofmt - godox
- goimports - goprintffuncname
- gosec - gosec
- gosmopolitan - gosmopolitan
- govet
- grouper - grouper
- importas - importas
- ineffassign
- intrange
- ireturn - ireturn
- makezero - makezero
- mirror - mirror
- misspell
- modernize
- nakedret - nakedret
- nilerr - nilerr
- nilnil - nilnil
@@ -55,13 +45,714 @@ linters:
- noctx - noctx
- nolintlint - nolintlint
- nonamedreturns - nonamedreturns
- perfsprint
- predeclared - predeclared
- promlinter - promlinter
- reassign - reassign
- revive - revive
- rowserrcheck - rowserrcheck
- stylecheck - staticcheck
- unconvert - unconvert
- unused
- usestdlibvars
- wastedassign - wastedassign
- whitespace - 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$

View File

@@ -1,7 +1,7 @@
// Package main is the main package of aleesa-telegram-go.
package main package main
import ( import (
"context"
"fmt" "fmt"
"os" "os"
"os/signal" "os/signal"
@@ -26,7 +26,7 @@ func init() {
log.Fatalf("Unable to get current executable path: %s", err) 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{ locations := []string{
"~/.aleesa-telegram-go.json", "~/.aleesa-telegram-go.json",
@@ -68,16 +68,15 @@ func main() {
log.Init(tg.Config.Loglevel, logfile) log.Init(tg.Config.Loglevel, logfile)
// Main context
var ctx = context.Background()
// Инициализируем redis-клиента. // Инициализируем redis-клиента.
tg.RedisClient = redis.NewClient(&redis.Options{ tg.RedisClient = redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("%s:%d", tg.Config.Redis.Server, tg.Config.Redis.Port), 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) 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() redisMsgChan := tg.Subscriber.Channel()
// Самое время поставить траппер сигналов. // Самое время поставить траппер сигналов.

2
go.mod
View File

@@ -1,6 +1,6 @@
module aleesa-telegram-go module aleesa-telegram-go
go 1.22 go 1.25
require ( require (
github.com/NicoNex/echotron/v3 v3.33.1 github.com/NicoNex/echotron/v3 v3.33.1

View File

@@ -1,3 +1,4 @@
// Package log wraps log/slog for more easier and convinient use.
package log package log
import ( import (
@@ -11,17 +12,19 @@ import (
"time" "time"
) )
// Handler exposes slog handler struct. var (
var Handler slog.Handler // Handler exposes slog handler struct.
Handler slog.Handler
// Ctx log context. For now it is just sits here and do nothing. // Ctx log context. For now it is just sits here and do nothing.
var Ctx = context.Background() Ctx = context.Background()
// Writer go-writer eported for capturing other types of logs, even for std log go facility. // Writer go-writer eported for capturing other types of logs, even for std log go facility.
var Writer io.Writer Writer io.Writer
// reader is required for Writer to work properly. It is connected via io.Pipe() to Writer. // reader is required for Writer to work properly. It is connected via io.Pipe() to Writer.
var reader io.Reader reader io.Reader
)
// Init setup logger stuff. // Init setup logger stuff.
// level can be error, warn, info, debug if something other supplied info level selected. // 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) { func Init(level string, fileDescriptor *os.File) {
var loglevel slog.Level var loglevel slog.Level
// no panic, no trace.
switch level { switch level {
case "debug": case "debug":
loglevel = slog.LevelDebug loglevel = slog.LevelDebug
@@ -48,18 +50,17 @@ func Init(level string, fileDescriptor *os.File) {
} }
opts := &slog.HandlerOptions{ //nolint: exhaustruct opts := &slog.HandlerOptions{ //nolint: exhaustruct
// Use the ReplaceAttr function on the handler options // Use the ReplaceAttr function on the handler options to be able to replace any single attribute in the log
// to be able to replace any single attribute in the log output // output.
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { //nolint: revive 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 { if a.Key != slog.TimeKey {
return a return a
} }
t := a.Value.Time() t := a.Value.Time()
// change the value from a time.Time to a String // Change the value from a time.Time to the String. The string has correct time format.
// where the string has the correct time format.
a.Value = slog.StringValue(t.Format(time.DateTime)) a.Value = slog.StringValue(t.Format(time.DateTime))
return a return a
@@ -85,7 +86,7 @@ func readIoPipe() {
for { for {
for scanner.Scan() { 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) slog.Debug(line)
} }

View File

@@ -1,8 +1,10 @@
// Package tg implements most of aleesa-telegram-go functionality.
package tg package tg
import ( import (
"fmt" "fmt"
"regexp" "regexp"
"strconv"
"github.com/NicoNex/echotron/v3" "github.com/NicoNex/echotron/v3"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
@@ -18,6 +20,7 @@ func Admin(msg *echotron.Update) (bool, error) {
switch { switch {
case c == "admin" || c == "админ": case c == "admin" || c == "админ":
answer = "```\n" answer = "```\n"
answer += fmt.Sprintf("%sadmin censor - показать список состояния типов сообщений\n", Config.Csign) answer += fmt.Sprintf("%sadmin censor - показать список состояния типов сообщений\n", Config.Csign)
answer += fmt.Sprintf("%sадмин ценз - показать список состояния типов сообщений\n", Config.Csign) answer += fmt.Sprintf("%sадмин ценз - показать список состояния типов сообщений\n", Config.Csign)
answer += fmt.Sprintf("%sadmin censor type # - где 1 - вкл, 0 - выкл цензуры для означенного типа сообщений\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 += "```" answer += "```"
case regexp.MustCompile("^(admin|админ)[[:space:]](censor|ценз)$").MatchString(c): 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" answer += "Тип сообщения voice удаляется\n"
} else { } else {
answer += "Тип сообщения voice не удаляется\n" 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" answer += "Тип сообщения audio удаляется\n"
} else { } else {
answer += "Тип сообщения audio не удаляется\n" 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" answer += "Тип сообщения photo удаляется\n"
} else { } else {
answer += "Тип сообщения photo не удаляется\n" 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" answer += "Тип сообщения video удаляется\n"
} else { } else {
answer += "Тип сообщения video не удаляется\n" 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" answer += "Тип сообщения video_note удаляется\n"
} else { } else {
answer += "Тип сообщения video_note не удаляется\n" 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" answer += "Тип сообщения animation удаляется\n"
} else { } else {
answer += "Тип сообщения animation не удаляется\n" 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" answer += "Тип сообщения sticker удаляется\n"
} else { } else {
answer += "Тип сообщения sticker не удаляется\n" 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" answer += "Тип сообщения dice удаляется\n"
} else { } else {
answer += "Тип сообщения dice не удаляется\n" 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" answer += "Тип сообщения game удаляется\n"
} else { } else {
answer += "Тип сообщения game не удаляется\n" 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" answer += "Тип сообщения poll удаляется\n"
} else { } else {
answer += "Тип сообщения poll не удаляется\n" 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" answer += "Тип сообщения document удаляется\n"
} else { } else {
answer += "Тип сообщения document не удаляется\n" answer += "Тип сообщения document не удаляется\n"
@@ -139,14 +142,14 @@ func Admin(msg *echotron.Update) (bool, error) {
case "voice": case "voice":
switch cmdArray[3] { switch cmdArray[3] {
case "1": 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 return true, err
} }
answer += "Теперь сообщения с voice будут оставаться" answer += "Теперь сообщения с voice будут оставаться"
case "0": 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 return true, err
} }
@@ -159,14 +162,14 @@ func Admin(msg *echotron.Update) (bool, error) {
case "audio": case "audio":
switch cmdArray[3] { switch cmdArray[3] {
case "1": 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 return true, err
} }
answer += "Теперь сообщения с audio будут оставаться" answer += "Теперь сообщения с audio будут оставаться"
case "0": 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 return true, err
} }
@@ -179,14 +182,14 @@ func Admin(msg *echotron.Update) (bool, error) {
case "photo": case "photo":
switch cmdArray[3] { switch cmdArray[3] {
case "1": 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 return true, err
} }
answer += "Теперь сообщения с photo будут оставаться" answer += "Теперь сообщения с photo будут оставаться"
case "0": 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 return true, err
} }
@@ -199,14 +202,14 @@ func Admin(msg *echotron.Update) (bool, error) {
case "video": case "video":
switch cmdArray[3] { switch cmdArray[3] {
case "1": 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 return true, err
} }
answer += "Теперь сообщения с video будут оставаться" answer += "Теперь сообщения с video будут оставаться"
case "0": 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 return true, err
} }
@@ -219,14 +222,14 @@ func Admin(msg *echotron.Update) (bool, error) {
case "video_note": case "video_note":
switch cmdArray[3] { switch cmdArray[3] {
case "1": 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 return true, err
} }
answer += "Теперь сообщения с video_note будут оставаться" answer += "Теперь сообщения с video_note будут оставаться"
case "0": 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 return true, err
} }
@@ -239,14 +242,14 @@ func Admin(msg *echotron.Update) (bool, error) {
case "animation": case "animation":
switch cmdArray[3] { switch cmdArray[3] {
case "1": 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 return true, err
} }
answer += "Теперь сообщения с animation будут оставаться" answer += "Теперь сообщения с animation будут оставаться"
case "0": 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 return true, err
} }
@@ -259,14 +262,14 @@ func Admin(msg *echotron.Update) (bool, error) {
case "sticker": case "sticker":
switch cmdArray[3] { switch cmdArray[3] {
case "1": 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 return true, err
} }
answer += "Теперь сообщения с sticker будут оставаться" answer += "Теперь сообщения с sticker будут оставаться"
case "0": 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 return true, err
} }
@@ -279,14 +282,14 @@ func Admin(msg *echotron.Update) (bool, error) {
case "dice": case "dice":
switch cmdArray[3] { switch cmdArray[3] {
case "1": 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 return true, err
} }
answer += "Теперь сообщения с dice будут оставаться" answer += "Теперь сообщения с dice будут оставаться"
case "0": 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 return true, err
} }
@@ -299,14 +302,14 @@ func Admin(msg *echotron.Update) (bool, error) {
case "game": case "game":
switch cmdArray[3] { switch cmdArray[3] {
case "1": 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 return true, err
} }
answer += "Теперь сообщения с game будут оставаться" answer += "Теперь сообщения с game будут оставаться"
case "0": 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 return true, err
} }
@@ -319,14 +322,14 @@ func Admin(msg *echotron.Update) (bool, error) {
case "poll": case "poll":
switch cmdArray[3] { switch cmdArray[3] {
case "1": 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 return true, err
} }
answer += "Теперь сообщения с poll будут оставаться" answer += "Теперь сообщения с poll будут оставаться"
case "0": 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 return true, err
} }
@@ -339,14 +342,14 @@ func Admin(msg *echotron.Update) (bool, error) {
case "document": case "document":
switch cmdArray[3] { switch cmdArray[3] {
case "1": 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 return true, err
} }
answer += "Теперь сообщения с document будут оставаться" answer += "Теперь сообщения с document будут оставаться"
case "0": 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 return true, err
} }
@@ -361,7 +364,7 @@ func Admin(msg *echotron.Update) (bool, error) {
} }
case regexp.MustCompile("^(admin|админ)[[:space:]](fortune|фортунка)$").MatchString(c): 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 += "Фортунки с утра показываются" answer += "Фортунки с утра показываются"
} else { } else {
answer += "Фортунки с утра не показываются" answer += "Фортунки с утра не показываются"
@@ -375,14 +378,14 @@ func Admin(msg *echotron.Update) (bool, error) {
// мы не сможем вытащить список чятиков. // мы не сможем вытащить список чятиков.
switch cmdArray[2] { switch cmdArray[2] {
case "1": 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 return true, err
} }
answer += "Теперь сообщения с фортункой будут отправляться с утра" answer += "Теперь сообщения с фортункой будут отправляться с утра"
case "0": 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 return true, err
} }
@@ -393,7 +396,7 @@ func Admin(msg *echotron.Update) (bool, error) {
} }
case regexp.MustCompile("^(admin|админ)[[:space:]](greet|приветствие)$").MatchString(c): 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 += "Приветствуем вновьприбывших участников чатика" answer += "Приветствуем вновьприбывших участников чатика"
} else { } else {
answer += "Не приветствуем вновьприбывших участников чатика" answer += "Не приветствуем вновьприбывших участников чатика"
@@ -407,14 +410,14 @@ func Admin(msg *echotron.Update) (bool, error) {
// мы не сможем вытащить список чятиков. // мы не сможем вытащить список чятиков.
switch cmdArray[2] { switch cmdArray[2] {
case "1": 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 return true, err
} }
answer += "Будем приветствовать вновьприбывших участников чата" answer += "Будем приветствовать вновьприбывших участников чата"
case "0": 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 return true, err
} }
@@ -425,7 +428,7 @@ func Admin(msg *echotron.Update) (bool, error) {
} }
case regexp.MustCompile("^(admin|админ)[[:space:]](goodbye|прощание)$").MatchString(c): 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 += "Прощаемся с ушедшими участниками чатика" answer += "Прощаемся с ушедшими участниками чатика"
} else { } else {
answer += "Не прощаемся с ушедшими участниками чатика" answer += "Не прощаемся с ушедшими участниками чатика"
@@ -437,14 +440,14 @@ func Admin(msg *echotron.Update) (bool, error) {
switch cmdArray[2] { switch cmdArray[2] {
case "1": 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 return true, err
} }
answer += "Будем прощаться с ушедшими участниками чата" answer += "Будем прощаться с ушедшими участниками чата"
case "0": 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 return true, err
} }
@@ -460,14 +463,14 @@ func Admin(msg *echotron.Update) (bool, error) {
switch cmdArray[2] { switch cmdArray[2] {
case "1": 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 return true, err
} }
answer += "Будем удалять сообщения, написанные от имени канала" answer += "Будем удалять сообщения, написанные от имени канала"
case "0": 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 return true, err
} }

View File

@@ -1,7 +1,7 @@
package tg package tg
import ( import (
"fmt" "strconv"
"github.com/NicoNex/echotron/v3" "github.com/NicoNex/echotron/v3"
) )
@@ -10,10 +10,13 @@ import (
// Непотребными могут быть аудиосообщения, аудиофайлы, видеосообщения, сообщения от имени других каналов итп. // Непотребными могут быть аудиосообщения, аудиофайлы, видеосообщения, сообщения от имени других каналов итп.
// Это могут настроить админы чятика через команду !admin censor. // Это могут настроить админы чятика через команду !admin censor.
func Censor(msg *echotron.Update) bool { func Censor(msg *echotron.Update) bool {
result := false var (
result bool
chatID = msg.ChatID()
)
switch { switch {
case GetSetting(fmt.Sprintf("%d", msg.ChatID()), "VoiceMsg") == "1": case GetSetting(strconv.FormatInt(chatID, 10), "VoiceMsg") == "1":
result = true result = true
// Предполагаем, что у voice-ов здесь всегда не ноль. // Предполагаем, что у voice-ов здесь всегда не ноль.
@@ -23,7 +26,7 @@ func Censor(msg *echotron.Update) bool {
result = true 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 { if msg.Message.Audio.Duration != 0 {
delMsg(msg) delMsg(msg)
@@ -31,7 +34,7 @@ func Censor(msg *echotron.Update) bool {
result = true 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 { if len(msg.Message.Photo) != 0 {
delMsg(msg) delMsg(msg)
@@ -39,7 +42,7 @@ func Censor(msg *echotron.Update) bool {
result = true 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 { if msg.Message.Video.Duration != 0 {
delMsg(msg) delMsg(msg)
@@ -47,7 +50,7 @@ func Censor(msg *echotron.Update) bool {
result = true 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 { if msg.Message.VideoNote.Duration != 0 {
delMsg(msg) delMsg(msg)
@@ -55,7 +58,7 @@ func Censor(msg *echotron.Update) bool {
result = true 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 { if msg.Message.Animation.Duration != 0 {
delMsg(msg) delMsg(msg)
@@ -63,7 +66,7 @@ func Censor(msg *echotron.Update) bool {
result = true result = true
} }
case GetSetting(fmt.Sprintf("%d", msg.ChatID()), "StickerMsg") == "1": case GetSetting(strconv.FormatInt(chatID, 10), "StickerMsg") == "1":
// Предполагаем, что FileID не пустое только у стикера. // Предполагаем, что FileID не пустое только у стикера.
if msg.Message.Sticker.FileID != "" { if msg.Message.Sticker.FileID != "" {
delMsg(msg) delMsg(msg)
@@ -71,7 +74,7 @@ func Censor(msg *echotron.Update) bool {
result = true result = true
} }
case GetSetting(fmt.Sprintf("%d", msg.ChatID()), "DiceMsg") == "1": case GetSetting(strconv.FormatInt(chatID, 10), "DiceMsg") == "1":
// Предполагаем, что Value > 0 только у дайса. // Предполагаем, что Value > 0 только у дайса.
if msg.Message.Dice.Value != 0 { if msg.Message.Dice.Value != 0 {
delMsg(msg) delMsg(msg)
@@ -79,7 +82,7 @@ func Censor(msg *echotron.Update) bool {
result = true result = true
} }
case GetSetting(fmt.Sprintf("%d", msg.ChatID()), "GameMsg") == "1": case GetSetting(strconv.FormatInt(chatID, 10), "GameMsg") == "1":
// Предполагаем, что title только у game-а. // Предполагаем, что title только у game-а.
if msg.Message.Game.Title != "" { if msg.Message.Game.Title != "" {
delMsg(msg) delMsg(msg)
@@ -87,7 +90,7 @@ func Censor(msg *echotron.Update) bool {
result = true result = true
} }
case GetSetting(fmt.Sprintf("%d", msg.ChatID()), "PollMsg") == "1": case GetSetting(strconv.FormatInt(chatID, 10), "PollMsg") == "1":
// Предполагаем, что title только у game-а. // Предполагаем, что title только у game-а.
if msg.Message.Poll.Question != "" { if msg.Message.Poll.Question != "" {
delMsg(msg) delMsg(msg)
@@ -95,7 +98,7 @@ func Censor(msg *echotron.Update) bool {
result = true result = true
} }
case GetSetting(fmt.Sprintf("%d", msg.ChatID()), "DocumentMsg") == "1": case GetSetting(strconv.FormatInt(chatID, 10), "DocumentMsg") == "1":
// Предполагаем, что FileID есть только у document-а. // Предполагаем, что FileID есть только у document-а.
if msg.Message.Document.FileID != "" { if msg.Message.Document.FileID != "" {
delMsg(msg) delMsg(msg)
@@ -107,7 +110,7 @@ func Censor(msg *echotron.Update) bool {
// пидорство он будет удалять asap. // пидорство он будет удалять asap.
// 136817688 - это специальный id пользователя, который принимает облик канала, на него можно нажать и попасть // 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 { if msg.Message.From.ID == 136817688 {
delMsg(msg) delMsg(msg)

View File

@@ -5,6 +5,8 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"regexp" "regexp"
"slices"
"strconv"
"github.com/NicoNex/echotron/v3" "github.com/NicoNex/echotron/v3"
) )
@@ -20,8 +22,8 @@ func cmdParser(me echotron.APIResponseUser, cmd *echotron.Update) (bool, error)
smsg.From = "telegram" smsg.From = "telegram"
smsg.Plugin = "telegram" smsg.Plugin = "telegram"
smsg.Chatid = fmt.Sprintf("%d", cmd.Message.Chat.ID) smsg.Chatid = strconv.FormatInt(cmd.Message.Chat.ID, 10)
smsg.Userid = fmt.Sprintf("%d", cmd.Message.From.ID) smsg.Userid = strconv.FormatInt(cmd.Message.From.ID, 10)
smsg.Message = cmd.Message.Text smsg.Message = cmd.Message.Text
smsg.Misc.Answer = 1 smsg.Misc.Answer = 1
@@ -60,12 +62,8 @@ func cmdParser(me echotron.APIResponseUser, cmd *echotron.Update) (bool, error)
"рыба", "рыбка", "рыбалка", "karma", "карма", "fuck", "weather", "погода", "w", "п", "погодка", "погадка", "рыба", "рыбка", "рыбалка", "karma", "карма", "fuck", "weather", "погода", "w", "п", "погодка", "погадка",
} }
for _, c := range cmds { if slices.Contains(cmds, command) {
if command == c {
bingo = true bingo = true
break
}
} }
// Не нашлось команды в одно слово. Поищем команды с одним аргументом. // Не нашлось команды в одно слово. Поищем команды с одним аргументом.
@@ -103,20 +101,14 @@ func cmdParser(me echotron.APIResponseUser, cmd *echotron.Update) (bool, error)
"dig", "копать", "fish", "fishing", "рыба", "рыбка", "рыбалка", "dig", "копать", "fish", "fishing", "рыба", "рыбка", "рыбалка",
} }
for _, c := range cmds { if slices.Contains(cmds, command) {
// В vscode возникает ошибка при использовании if-а.
switch {
case command == c:
smsg.Misc.Username = ConstructTelegramHighlightName(cmd.Message.From) smsg.Misc.Username = ConstructTelegramHighlightName(cmd.Message.From)
smsg.Misc.Msgformat = 1 smsg.Misc.Msgformat = 1
break //nolint: gosimple
}
} }
// Если у нас супергруппа с тредами, надо проставить thread id. // Если у нас супергруппа с тредами, надо проставить thread id.
if cmd.Message.Chat.Type == "supergroup" && cmd.Message.IsTopicMessage && cmd.Message.ThreadID != 0 { 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 в редиску. // Заталкиваем наш 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) log.Warnf("Unable to send data to redis channel %s: %s", Config.Redis.Channel, err)
} else { } else {
log.Debugf("Sent msg to redis channel %s: %s", Config.Redis.Channel, string(data)) log.Debugf("Sent msg to redis channel %s: %s", Config.Redis.Channel, string(data))

View File

@@ -9,37 +9,42 @@ import (
"github.com/go-redis/redis/v8" "github.com/go-redis/redis/v8"
) )
// Config - это у нас глобальная штука. var (
var Config myConfig // Config - это у нас глобальная штука.
Config MyConfig
// To break circular message forwarding we must set some sane default, it can be overridden via config. // forwardMax is used to break circular message forwarding we must set some sane default, it can be overridden via config.
var forwardMax int64 = 5 forwardMax int64 = 5
// Объектики клиента-редиски. // RedisClient объектик клиента-редиски.
var RedisClient *redis.Client RedisClient *redis.Client
var Subscriber *redis.PubSub
// Main context. // Subscriber is redis PubSub object.
var ctx = context.Background() Subscriber *redis.PubSub
// Ставится в true, если мы получили сигнал на выключение. // Ctx is main context.
var Shutdown = false Ctx = context.Background()
// Канал, в который приходят уведомления для хэндлера сигналов от траппера сигналов. // Shutdown ставится в true, если мы получили сигнал на выключение.
var SigChan = make(chan os.Signal, 1) Shutdown = false
// Мапка с открытыми дескрипторами баз с настройками. // SigChan канал, в который приходят уведомления для хэндлера сигналов от траппера сигналов.
var settingsDB = make(map[string]*pebble.DB) SigChan = make(chan os.Signal, 1)
var tg echotron.API // settingsDB мапка с открытыми дескрипторами баз с настройками.
settingsDB = make(map[string]*pebble.DB)
// Приветсвенные фразы для новых участников чата. // tg is var for telegram api.
var introduceGreet = [...]string{ tg echotron.API
// introduceGreet приветсвенные фразы для новых участников чата.
introduceGreet = [...]string{
"Дратути", "Дратути",
"Дарована", "Дарована",
"Доброе утро, день или вечер", "Доброе утро, день или вечер",
"Добро пожаловать в наше скромное коммунити", "Добро пожаловать в наше скромное коммунити",
"Наше вам с кисточкой тут, на канальчике", "Наше вам с кисточкой тут, на канальчике",
} }
)
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */ /* vim: set ft=go noet ai ts=4 sw=4 sts=4: */

View File

@@ -10,6 +10,7 @@ import (
// Help Выводит в чат сообщение с основными командами бота. // Help Выводит в чат сообщение с основными командами бота.
func Help(cmd *echotron.Update) (bool, error) { func Help(cmd *echotron.Update) (bool, error) {
help := "```\n" help := "```\n"
help += fmt.Sprintf("%shelp | %sпомощь - список команд", Config.Csign, Config.Csign) 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("%sanek | %sанек | %sанекдот - рандомный анекдот с anekdot.ru", Config.Csign, Config.Csign, Config.Csign)
help += fmt.Sprintf("%sbuni - рандомный стрип hapi buni", Config.Csign) help += fmt.Sprintf("%sbuni - рандомный стрип hapi buni", Config.Csign)

View File

@@ -11,52 +11,52 @@ import (
) )
// ParseConfig разбирает и валидирует даденный конфиг. // ParseConfig разбирает и валидирует даденный конфиг.
func ParseConfig(path string) (myConfig, error) { func ParseConfig(path string) (MyConfig, error) {
fileInfo, err := os.Stat(path) fileInfo, err := os.Stat(path)
// Предполагаем, что файла либо нет, либо мы не можем его прочитать, второе надо бы логгировать, но пока забьём. // Предполагаем, что файла либо нет, либо мы не можем его прочитать, второе надо бы логгировать, но пока забьём.
if err != nil { if err != nil {
return myConfig{}, err return MyConfig{}, err
} }
// Конфиг-файл длинноват для конфига, попробуем следующего кандидата. // Конфиг-файл длинноват для конфига, попробуем следующего кандидата.
if fileInfo.Size() > 65535 { if fileInfo.Size() > 65535 {
err := fmt.Errorf("config file %s is too long for config, skipping", path) err := fmt.Errorf("config file %s is too long for config, skipping", path)
return myConfig{}, err return MyConfig{}, err
} }
buf, err := os.ReadFile(path) buf, err := os.ReadFile(path)
// Не удалось прочитать. // Не удалось прочитать.
if err != nil { if err != nil {
return myConfig{}, err return MyConfig{}, err
} }
// Исходя из документации, hjson какбы умеет парсить "кривой" json, но парсит его в map-ку. // Исходя из документации, hjson какбы умеет парсить "кривой" json, но парсит его в map-ку.
// Интереснее на выходе получить структурку: то есть мы вначале конфиг преобразуем в map-ку, затем эту map-ку // Интереснее на выходе получить структурку: то есть мы вначале конфиг преобразуем в map-ку, затем эту map-ку
// сериализуем в json, а потом json превращааем в структурку. Не очень эффективно, но он и не часто требуется. // сериализуем в json, а потом json превращааем в структурку. Не очень эффективно, но он и не часто требуется.
var ( var (
sampleConfig myConfig sampleConfig MyConfig
tmp map[string]interface{} tmp map[string]any
) )
err = hjson.Unmarshal(buf, &tmp) err = hjson.Unmarshal(buf, &tmp)
// Не удалось распарсить. // Не удалось распарсить.
if err != nil { if err != nil {
return myConfig{}, err return MyConfig{}, err
} }
tmpjson, err := json.Marshal(tmp) tmpjson, err := json.Marshal(tmp)
// Не удалось преобразовать map-ку в json. // Не удалось преобразовать map-ку в json.
if err != nil { if err != nil {
return myConfig{}, err return MyConfig{}, err
} }
if err := json.Unmarshal(tmpjson, &sampleConfig); err != nil { 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 == "" { if sampleConfig.Redis.Channel == "" {
err := fmt.Errorf("channel field in config file %s must be set", path) err := fmt.Errorf("channel field in config file %s must be set", path)
return myConfig{}, err return MyConfig{}, err
} }
if sampleConfig.Redis.MyChannel == "" { if sampleConfig.Redis.MyChannel == "" {
err := fmt.Errorf("my_channel field in config file %s must be set", path) err := fmt.Errorf("my_channel field in config file %s must be set", path)
return myConfig{}, err return MyConfig{}, err
} }
if sampleConfig.Telegram.Token == "" { 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 == "" { if sampleConfig.Loglevel == "" {
sampleConfig.Loglevel = "info" sampleConfig.Loglevel = "info"
} }
// sampleConfig.Log = "" if not set // sampleConfig.Log = "" if not set..
if sampleConfig.Csign == "" { if sampleConfig.Csign == "" {
err := fmt.Errorf("csign field in config file %s must be set", path) err := fmt.Errorf("csign field in config file %s must be set", path)
return myConfig{}, err return MyConfig{}, err
} }
if sampleConfig.ForwardsMax == 0 { if sampleConfig.ForwardsMax == 0 {
@@ -103,7 +103,7 @@ func ParseConfig(path string) (myConfig, error) {
} }
if sampleConfig.DataDir == "" { 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 return sampleConfig, err

View File

@@ -26,7 +26,7 @@ func RedisMsgParser(msg string) {
return return
} }
// Validate our j // Validate our j.
if exist := j.From; exist == "" { if exist := j.From; exist == "" {
log.Warnf("Incorrect msg from redis, no from field: %s", msg) log.Warnf("Incorrect msg from redis, no from field: %s", msg)
@@ -63,7 +63,7 @@ func RedisMsgParser(msg string) {
return return
} }
// j.Misc.Answer может и не быть, тогда ответа на такое сообщение не будет // j.Misc.Answer может и не быть, тогда ответа на такое сообщение не будет.
if j.Misc.Answer == 0 { if j.Misc.Answer == 0 {
log.Debug("Field Misc->Answer = 0, skipping message") log.Debug("Field Misc->Answer = 0, skipping message")
@@ -76,14 +76,14 @@ func RedisMsgParser(msg string) {
j.Misc.Csign = Config.Csign j.Misc.Csign = Config.Csign
} }
// j.Misc.FwdCnt если нам его не передали, то будет 0 // j.Misc.FwdCnt если нам его не передали, то будет 0.
if exist := j.Misc.Fwdcnt; exist == 0 { if exist := j.Misc.Fwdcnt; exist == 0 {
j.Misc.Fwdcnt = 1 j.Misc.Fwdcnt = 1
} }
// j.Misc.GoodMorning может быть 1 или 0, по-умолчанию 0 // j.Misc.GoodMorning может быть 1 или 0, по-умолчанию 0.
// j.Misc.MsgFormat может быть 1 или 0, по-умолчанию 0 // j.Misc.MsgFormat может быть 1 или 0, по-умолчанию 0.
// j.Misc.Username можно не передавать, тогда будет пустая строка // j.Misc.Username можно не передавать, тогда будет пустая строка.
// Отвалидировались, теперь вернёмся к нашим баранам. // Отвалидировались, теперь вернёмся к нашим баранам.

View File

@@ -52,7 +52,7 @@ func GetSetting(chatID string, setting string) string {
chatHash := sha256.Sum256([]byte(chatID)) chatHash := sha256.Sum256([]byte(chatID))
database := fmt.Sprintf("settings_db/%x", chatHash) database := fmt.Sprintf("settings_db/%x", chatHash)
// Если БД не открыта, откроем её // Если БД не открыта, откроем её.
if _, ok := settingsDB[database]; !ok { if _, ok := settingsDB[database]; !ok {
var options pebble.Options var options pebble.Options
// По дефолту ограничение ставится на мегабайты данных, а не на количество файлов, поэтому с дефолтными // По дефолту ограничение ставится на мегабайты данных, а не на количество файлов, поэтому с дефолтными

View File

@@ -30,7 +30,7 @@ func SigHandler() {
Shutdown = true 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) log.Errorf("Unable to unsubscribe from redis channels cleanly: %s", err)
} else { } else {
log.Debug("Unsubscribe from all redis channels") log.Debug("Unsubscribe from all redis channels")

View File

@@ -2,6 +2,7 @@ package tg
import ( import (
"fmt" "fmt"
"strconv"
"aleesa-telegram-go/internal/log" "aleesa-telegram-go/internal/log"
@@ -9,7 +10,7 @@ import (
) )
// Telega основная горутинка, реализующая бота. // Telega основная горутинка, реализующая бота.
func Telega(c myConfig) { func Telega(c MyConfig) {
tg = echotron.NewAPI(c.Telegram.Token) tg = echotron.NewAPI(c.Telegram.Token)
for u := range echotron.PollingUpdates(c.Telegram.Token) { for u := range echotron.PollingUpdates(c.Telegram.Token) {
@@ -86,11 +87,11 @@ func ConstructPartialUserUsername(u *echotron.User) string {
return u.LastName return u.LastName
default: default:
return fmt.Sprintf("%d", u.ID) return strconv.FormatInt(u.ID, 10)
} }
} }
// ConstructPartialUserUsername пытается найти и вытащить username, если такового нет, вытаскивает First/Last Name, если // ConstructPartialChatUsername пытается найти и вытащить username, если такового нет, вытаскивает First/Last Name, если
// такового нет, то возвращает ID. // такового нет, то возвращает ID.
func ConstructPartialChatUsername(c *echotron.Chat) string { func ConstructPartialChatUsername(c *echotron.Chat) string {
switch { switch {
@@ -107,7 +108,7 @@ func ConstructPartialChatUsername(c *echotron.Chat) string {
return c.LastName return c.LastName
default: 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 != "": case u.Username != "":
user = fmt.Sprintf("@%s", u.Username) user = fmt.Sprintf("@%s", u.Username)
default: default:
user = fmt.Sprintf("%d", u.ID) user = strconv.FormatInt(u.ID, 10)
} }
return user return user

View File

@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"math/rand/v2" "math/rand/v2"
"regexp" "regexp"
"strconv"
"strings" "strings"
"aleesa-telegram-go/internal/log" "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: поддержать миграцию настроек чата на новый chatId.
// TODO: подумать, что можно сделать с настройками бэкэндов. Кажись, ничего, но надо глянуть. // TODO: подумать, что можно сделать с настройками бэкэндов. Кажись, ничего, но надо глянуть.
return return
} }
@@ -56,16 +56,17 @@ func telegramMsgParser(msg *echotron.Update) {
} }
// Люди пришли в чят. // Люди пришли в чят.
if msg.Message.NewChatMembers != nil { //nolint: revive,staticcheck if msg.Message.NewChatMembers != nil {
// TODO: проверить, надо ли приветствовать, выбрать одну из приветственных фраз, потянуть время, типа, мы пишем // 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 var users string
if len(msg.Message.NewChatMembers) > 1 { if len(msg.Message.NewChatMembers) > 1 {
lastOne := msg.Message.NewChatMembers[len(msg.Message.NewChatMembers)-1] lastOne := msg.Message.NewChatMembers[len(msg.Message.NewChatMembers)-1]
theRest := msg.Message.NewChatMembers[:len(msg.Message.NewChatMembers)-1] theRest := msg.Message.NewChatMembers[:len(msg.Message.NewChatMembers)-1]
var usersSlice []string var usersSlice []string
for _, user := range theRest { for _, user := range theRest {
@@ -73,6 +74,7 @@ func telegramMsgParser(msg *echotron.Update) {
} }
users = strings.Join(usersSlice, ", ") users = strings.Join(usersSlice, ", ")
users += fmt.Sprintf(" and %s", ConstructTelegramHighlightName(lastOne)) users += fmt.Sprintf(" and %s", ConstructTelegramHighlightName(lastOne))
} else { } else {
users = ConstructTelegramHighlightName(msg.Message.NewChatMembers[0]) users = ConstructTelegramHighlightName(msg.Message.NewChatMembers[0])
@@ -88,7 +90,7 @@ func telegramMsgParser(msg *echotron.Update) {
resp, err := tg.SendChatAction( resp, err := tg.SendChatAction(
echotron.Typing, echotron.Typing,
msg.Message.From.ID, msg.Message.From.ID,
&echotron.ChatActionOptions{MessageThreadID: int(msg.Message.ThreadID)}, &echotron.ChatActionOptions{MessageThreadID: msg.Message.ThreadID},
) )
// TODO: вероятно, ошибку надо засылать в обработчик ошибок, так как в ней может быть миграция чятика или // TODO: вероятно, ошибку надо засылать в обработчик ошибок, так как в ней может быть миграция чятика или
@@ -123,7 +125,7 @@ func telegramMsgParser(msg *echotron.Update) {
// Человек ушёл из чата. // Человек ушёл из чата.
if msg.Message.LeftChatMember != nil { 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) user := ConstructUserFirstLastName(msg.Message.From)
goodbye := fmt.Sprintf("Прощаемся с %s", user) goodbye := fmt.Sprintf("Прощаемся с %s", user)
@@ -160,8 +162,8 @@ func telegramMsgParser(msg *echotron.Update) {
rmsg.Misc.Answer = 1 rmsg.Misc.Answer = 1
// Засылаем фразу в misc-канал (в роутер). // Засылаем фразу в misc-канал (в роутер).
rmsg.Chatid = fmt.Sprintf("%d", msg.Message.Chat.ID) rmsg.Chatid = strconv.FormatInt(msg.Message.Chat.ID, 10)
rmsg.Userid = fmt.Sprintf("%d", msg.Message.From.ID) rmsg.Userid = strconv.FormatInt(msg.Message.From.ID, 10)
rmsg.Message = msg.Message.Text rmsg.Message = msg.Message.Text
rmsg.Mode = "private" rmsg.Mode = "private"
rmsg.Plugin = "telegram" rmsg.Plugin = "telegram"
@@ -198,7 +200,7 @@ func telegramMsgParser(msg *echotron.Update) {
} }
// Заталкиваем наш json в редиску. // Заталкиваем наш 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) log.Warnf("Unable to send data to redis channel %s: %s", Config.Redis.Channel, err)
} else { } else {
log.Debugf("Sent msg to redis channel %s: %s", Config.Redis.Channel, string(data)) 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. // содержат картинки, видео аудио итп, это настраивается через команду !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) { if Censor(msg) {
return return
@@ -239,7 +241,7 @@ func telegramMsgParser(msg *echotron.Update) {
return return
} }
// Здесь же, если фраза обращена к боту (ник или имя), выставляем флажок, что надо ответить // Здесь же, если фраза обращена к боту (ник или имя), выставляем флажок, что надо ответить.
quotedBotUsername := regexp.QuoteMeta(me.Result.Username) quotedBotUsername := regexp.QuoteMeta(me.Result.Username)
quotedBotFirstName := regexp.QuoteMeta(me.Result.FirstName) quotedBotFirstName := regexp.QuoteMeta(me.Result.FirstName)
quotedBotLastName := regexp.QuoteMeta(me.Result.LastName) quotedBotLastName := regexp.QuoteMeta(me.Result.LastName)
@@ -250,7 +252,9 @@ func telegramMsgParser(msg *echotron.Update) {
return return
} else if messageContainsBotUsername { } else if messageContainsBotUsername {
rmsg.Misc.Answer = 1 rmsg.Misc.Answer = 1
m := regexp.MustCompile(quotedBotUsername) m := regexp.MustCompile(quotedBotUsername)
msg.Message.Text = m.ReplaceAllString(msg.Message.Text, "") msg.Message.Text = m.ReplaceAllString(msg.Message.Text, "")
} }
@@ -273,7 +277,9 @@ func telegramMsgParser(msg *echotron.Update) {
if messageContainsBotName { if messageContainsBotName {
rmsg.Misc.Answer = 1 rmsg.Misc.Answer = 1
m := regexp.MustCompile(quotedBotFirstName + "[[:space:]]+" + quotedBotLastName) m := regexp.MustCompile(quotedBotFirstName + "[[:space:]]+" + quotedBotLastName)
msg.Message.Text = m.ReplaceAllString(msg.Message.Text, "") msg.Message.Text = m.ReplaceAllString(msg.Message.Text, "")
} }
// Если у нас есть только имя, то попробуем его вырезать из входящей фразы. // Если у нас есть только имя, то попробуем его вырезать из входящей фразы.
@@ -288,7 +294,9 @@ func telegramMsgParser(msg *echotron.Update) {
if messageContainsBotName { if messageContainsBotName {
rmsg.Misc.Answer = 1 rmsg.Misc.Answer = 1
m := regexp.MustCompile(quotedBotFirstName) m := regexp.MustCompile(quotedBotFirstName)
msg.Message.Text = m.ReplaceAllString(msg.Message.Text, "") msg.Message.Text = m.ReplaceAllString(msg.Message.Text, "")
} }
// Если у нас есть только фамилия, то попробуем его вырезать из входящей фразы. // Если у нас есть только фамилия, то попробуем его вырезать из входящей фразы.
@@ -303,14 +311,16 @@ func telegramMsgParser(msg *echotron.Update) {
if messageContainsBotName { if messageContainsBotName {
rmsg.Misc.Answer = 1 rmsg.Misc.Answer = 1
m := regexp.MustCompile(quotedBotLastName) m := regexp.MustCompile(quotedBotLastName)
msg.Message.Text = m.ReplaceAllString(msg.Message.Text, "") msg.Message.Text = m.ReplaceAllString(msg.Message.Text, "")
} }
} }
// Засылаем фразу в misc-канал (в роутер). // Засылаем фразу в misc-канал (в роутер).
rmsg.Chatid = fmt.Sprintf("%d", msg.Message.Chat.ID) rmsg.Chatid = strconv.FormatInt(msg.Message.Chat.ID, 10)
rmsg.Userid = fmt.Sprintf("%d", msg.Message.From.ID) rmsg.Userid = strconv.FormatInt(msg.Message.From.ID, 10)
rmsg.Message = msg.Message.Text rmsg.Message = msg.Message.Text
rmsg.Mode = "public" rmsg.Mode = "public"
rmsg.Plugin = "telegram" rmsg.Plugin = "telegram"
@@ -335,7 +345,7 @@ func telegramMsgParser(msg *echotron.Update) {
} }
// Заталкиваем наш json в редиску. // Заталкиваем наш 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) log.Warnf("Unable to send data to redis channel %s: %s", Config.Redis.Channel, err)
} else { } else {
log.Debugf("Sent msg to redis channel %s: %s", Config.Redis.Channel, string(data)) log.Debugf("Sent msg to redis channel %s: %s", Config.Redis.Channel, string(data))

View File

@@ -1,27 +1,29 @@
package tg package tg
// myConfig структура, описывающая формат Конфига. type (
type myConfig struct { // MyConfig структура, описывающая формат Конфига.
Redis struct { MyConfig struct {
Server string `json:"server,omitempty"` Csign string `json:"csign,omitempty"`
Port int `json:"port,omitempty"` DataDir string `json:"data_dir,omitempty"`
Channel string `json:"channel,omitempty"` Log string `json:"log,omitempty"`
MyChannel string `json:"my_channel,omitempty"` Loglevel string `json:"loglevel,omitempty"`
} `json:"redis"`
Telegram struct { Telegram struct {
Token string `json:"token,omitempty"` 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"` // rMsg структура, описывающая формат входящего сообщения из pubsub-канала redis-ки.
Log string `json:"log,omitempty"` rMsg struct {
Csign string `json:"csign,omitempty"`
ForwardsMax int64 `json:"forwards_max,omitempty"`
DataDir string `json:"data_dir,omitempty"`
}
// rMsg структура, описывающая формат входящего сообщения из pubsub-канала redis-ки.
type rMsg struct {
From string `json:"from,omitempty"` From string `json:"from,omitempty"`
Chatid string `json:"chatid,omitempty"` Chatid string `json:"chatid,omitempty"`
Userid string `json:"userid,omitempty"` Userid string `json:"userid,omitempty"`
@@ -30,23 +32,23 @@ type rMsg struct {
Plugin string `json:"plugin,omitempty"` Plugin string `json:"plugin,omitempty"`
Mode string `json:"mode,omitempty"` Mode string `json:"mode,omitempty"`
Misc struct { Misc struct {
Answer int64 `json:"answer,omitempty"`
Botnick string `json:"bot_nick,omitempty"` Botnick string `json:"bot_nick,omitempty"`
Csign string `json:"csign,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"` Fwdcnt int64 `json:"fwd_cnt,omitempty"`
GoodMorning int64 `json:"good_morning,omitempty"` GoodMorning int64 `json:"good_morning,omitempty"`
// В этом поле мы пишем 1, если бэкэнд должен в ответе форматировать сообщение в MarkdownV2, что какбэ // В этом поле мы пишем 1, если бэкэнд должен в ответе форматировать сообщение в MarkdownV2, что какбэ
// не оч хорошо, так как мы выносим особенности реализации бэкэнда сюда. // не оч хорошо, так как мы выносим особенности реализации бэкэнда сюда.
Msgformat int64 `json:"msg_format,omitempty"` Msgformat int64 `json:"msg_format,omitempty"`
// Судя по перловой реализации, сюда всегда попадает [$fullname](tg://user?id=$userid).
// Согласно докуметации, и перловой реализации бота, мы проставляем это поле тогда, когда мы точно знаем, что в
// бэкэнде оно используется.
Username string `json:"username,omitempty"`
} `json:"Misc"` } `json:"Misc"`
} }
// sMsg структура, описывающая исходящее сообщение в pubsub-канал redis-ки. // sMsg структура, описывающая исходящее сообщение в pubsub-канал redis-ки.
type sMsg struct { sMsg struct {
From string `json:"from"` From string `json:"from"`
Chatid string `json:"chatid"` Chatid string `json:"chatid"`
Userid string `json:"userid"` Userid string `json:"userid"`
@@ -55,14 +57,15 @@ type sMsg struct {
Plugin string `json:"plugin"` Plugin string `json:"plugin"`
Mode string `json:"mode"` Mode string `json:"mode"`
Misc struct { Misc struct {
Answer int64 `json:"answer"`
Botnick string `json:"bot_nick"` Botnick string `json:"bot_nick"`
Csign string `json:"csign"` Csign string `json:"csign"`
Username string `json:"username"`
Answer int64 `json:"answer"`
Fwdcnt int64 `json:"fwd_cnt"` Fwdcnt int64 `json:"fwd_cnt"`
GoodMorning int64 `json:"good_morning"` GoodMorning int64 `json:"good_morning"`
Msgformat int64 `json:"msg_format"` Msgformat int64 `json:"msg_format"`
Username string `json:"username"`
} `json:"misc"` } `json:"misc"`
} }
)
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */ /* vim: set ft=go noet ai ts=4 sw=4 sts=4: */