From 41afa7d9efeffa20b561290833f150d9a23d5d11 Mon Sep 17 00:00:00 2001 From: eleksir Date: Sun, 11 Jun 2023 21:03:41 +0300 Subject: [PATCH] Initial commit --- .gitignore | 6 + Design | 51 + LICENSE.txt | 20 + Makefile | 20 + README.md | 26 + TODO | 5 + collection.go | 69 + data/sample_config.json | 60 + event_parser.go | 587 + globals.go | 59 + go.mod | 16 + go.sum | 24 + lib.go | 524 + main.go | 163 + types.go | 74 + vendor/github.com/davecgh/go-spew/LICENSE | 15 + .../github.com/davecgh/go-spew/spew/bypass.go | 145 + .../davecgh/go-spew/spew/bypasssafe.go | 38 + .../github.com/davecgh/go-spew/spew/common.go | 341 + .../github.com/davecgh/go-spew/spew/config.go | 306 + vendor/github.com/davecgh/go-spew/spew/doc.go | 211 + .../github.com/davecgh/go-spew/spew/dump.go | 509 + .../github.com/davecgh/go-spew/spew/format.go | 419 + .../github.com/davecgh/go-spew/spew/spew.go | 148 + vendor/github.com/eleksir/go-xmpp/.travis.yml | 5 + vendor/github.com/eleksir/go-xmpp/LICENSE | 27 + vendor/github.com/eleksir/go-xmpp/README.md | 6 + vendor/github.com/eleksir/go-xmpp/xmpp.go | 1430 +++ .../github.com/eleksir/go-xmpp/xmpp_avatar.go | 125 + .../github.com/eleksir/go-xmpp/xmpp_disco.go | 102 + .../eleksir/go-xmpp/xmpp_information_query.go | 54 + vendor/github.com/eleksir/go-xmpp/xmpp_muc.go | 135 + .../github.com/eleksir/go-xmpp/xmpp_ping.go | 33 + .../github.com/eleksir/go-xmpp/xmpp_pubsub.go | 133 + .../eleksir/go-xmpp/xmpp_subscription.go | 25 + vendor/github.com/hjson/hjson-go/.gitignore | 0 vendor/github.com/hjson/hjson-go/LICENSE | 21 + vendor/github.com/hjson/hjson-go/README.md | 181 + .../hjson/hjson-go/build_release.sh | 58 + vendor/github.com/hjson/hjson-go/decode.go | 480 + vendor/github.com/hjson/hjson-go/encode.go | 492 + .../github.com/hjson/hjson-go/parseNumber.go | 108 + vendor/github.com/sirupsen/logrus/.gitignore | 4 + .../github.com/sirupsen/logrus/.golangci.yml | 40 + vendor/github.com/sirupsen/logrus/.travis.yml | 15 + .../github.com/sirupsen/logrus/CHANGELOG.md | 259 + vendor/github.com/sirupsen/logrus/LICENSE | 21 + vendor/github.com/sirupsen/logrus/README.md | 515 + vendor/github.com/sirupsen/logrus/alt_exit.go | 76 + .../github.com/sirupsen/logrus/appveyor.yml | 14 + .../github.com/sirupsen/logrus/buffer_pool.go | 43 + vendor/github.com/sirupsen/logrus/doc.go | 26 + vendor/github.com/sirupsen/logrus/entry.go | 442 + vendor/github.com/sirupsen/logrus/exported.go | 270 + .../github.com/sirupsen/logrus/formatter.go | 78 + vendor/github.com/sirupsen/logrus/hooks.go | 34 + .../sirupsen/logrus/json_formatter.go | 128 + vendor/github.com/sirupsen/logrus/logger.go | 417 + vendor/github.com/sirupsen/logrus/logrus.go | 186 + .../logrus/terminal_check_appengine.go | 11 + .../sirupsen/logrus/terminal_check_bsd.go | 13 + .../sirupsen/logrus/terminal_check_js.go | 7 + .../logrus/terminal_check_no_terminal.go | 11 + .../logrus/terminal_check_notappengine.go | 17 + .../sirupsen/logrus/terminal_check_solaris.go | 11 + .../sirupsen/logrus/terminal_check_unix.go | 13 + .../sirupsen/logrus/terminal_check_windows.go | 27 + .../sirupsen/logrus/text_formatter.go | 339 + vendor/github.com/sirupsen/logrus/writer.go | 102 + vendor/golang.org/x/crypto/LICENSE | 27 + vendor/golang.org/x/crypto/PATENTS | 22 + vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go | 77 + vendor/golang.org/x/exp/LICENSE | 27 + vendor/golang.org/x/exp/PATENTS | 22 + .../x/exp/constraints/constraints.go | 50 + vendor/golang.org/x/exp/slices/slices.go | 258 + vendor/golang.org/x/exp/slices/sort.go | 128 + vendor/golang.org/x/exp/slices/zsortfunc.go | 479 + .../golang.org/x/exp/slices/zsortordered.go | 481 + vendor/golang.org/x/sys/LICENSE | 27 + vendor/golang.org/x/sys/PATENTS | 22 + .../sys/internal/unsafeheader/unsafeheader.go | 30 + vendor/golang.org/x/sys/unix/.gitignore | 2 + vendor/golang.org/x/sys/unix/README.md | 184 + .../golang.org/x/sys/unix/affinity_linux.go | 86 + vendor/golang.org/x/sys/unix/aliases.go | 15 + vendor/golang.org/x/sys/unix/asm_aix_ppc64.s | 18 + vendor/golang.org/x/sys/unix/asm_bsd_386.s | 29 + vendor/golang.org/x/sys/unix/asm_bsd_amd64.s | 29 + vendor/golang.org/x/sys/unix/asm_bsd_arm.s | 29 + vendor/golang.org/x/sys/unix/asm_bsd_arm64.s | 29 + vendor/golang.org/x/sys/unix/asm_bsd_ppc64.s | 31 + .../golang.org/x/sys/unix/asm_bsd_riscv64.s | 29 + vendor/golang.org/x/sys/unix/asm_linux_386.s | 66 + .../golang.org/x/sys/unix/asm_linux_amd64.s | 58 + vendor/golang.org/x/sys/unix/asm_linux_arm.s | 57 + .../golang.org/x/sys/unix/asm_linux_arm64.s | 53 + .../golang.org/x/sys/unix/asm_linux_loong64.s | 54 + .../golang.org/x/sys/unix/asm_linux_mips64x.s | 57 + .../golang.org/x/sys/unix/asm_linux_mipsx.s | 55 + .../golang.org/x/sys/unix/asm_linux_ppc64x.s | 45 + .../golang.org/x/sys/unix/asm_linux_riscv64.s | 49 + .../golang.org/x/sys/unix/asm_linux_s390x.s | 57 + .../x/sys/unix/asm_openbsd_mips64.s | 30 + .../golang.org/x/sys/unix/asm_solaris_amd64.s | 18 + vendor/golang.org/x/sys/unix/asm_zos_s390x.s | 426 + .../golang.org/x/sys/unix/bluetooth_linux.go | 36 + vendor/golang.org/x/sys/unix/cap_freebsd.go | 196 + vendor/golang.org/x/sys/unix/constants.go | 14 + vendor/golang.org/x/sys/unix/dev_aix_ppc.go | 27 + vendor/golang.org/x/sys/unix/dev_aix_ppc64.go | 29 + vendor/golang.org/x/sys/unix/dev_darwin.go | 24 + vendor/golang.org/x/sys/unix/dev_dragonfly.go | 30 + vendor/golang.org/x/sys/unix/dev_freebsd.go | 30 + vendor/golang.org/x/sys/unix/dev_linux.go | 42 + vendor/golang.org/x/sys/unix/dev_netbsd.go | 29 + vendor/golang.org/x/sys/unix/dev_openbsd.go | 29 + vendor/golang.org/x/sys/unix/dev_zos.go | 29 + vendor/golang.org/x/sys/unix/dirent.go | 103 + vendor/golang.org/x/sys/unix/endian_big.go | 10 + vendor/golang.org/x/sys/unix/endian_little.go | 10 + vendor/golang.org/x/sys/unix/env_unix.go | 32 + vendor/golang.org/x/sys/unix/epoll_zos.go | 221 + vendor/golang.org/x/sys/unix/fcntl.go | 37 + vendor/golang.org/x/sys/unix/fcntl_darwin.go | 24 + .../x/sys/unix/fcntl_linux_32bit.go | 14 + vendor/golang.org/x/sys/unix/fdset.go | 30 + vendor/golang.org/x/sys/unix/fstatfs_zos.go | 164 + vendor/golang.org/x/sys/unix/gccgo.go | 60 + vendor/golang.org/x/sys/unix/gccgo_c.c | 45 + .../x/sys/unix/gccgo_linux_amd64.go | 21 + vendor/golang.org/x/sys/unix/ifreq_linux.go | 142 + vendor/golang.org/x/sys/unix/ioctl_linux.go | 233 + vendor/golang.org/x/sys/unix/ioctl_signed.go | 70 + .../golang.org/x/sys/unix/ioctl_unsigned.go | 70 + vendor/golang.org/x/sys/unix/ioctl_zos.go | 72 + vendor/golang.org/x/sys/unix/mkall.sh | 249 + vendor/golang.org/x/sys/unix/mkerrors.sh | 783 ++ vendor/golang.org/x/sys/unix/pagesize_unix.go | 16 + .../golang.org/x/sys/unix/pledge_openbsd.go | 163 + vendor/golang.org/x/sys/unix/ptrace_darwin.go | 18 + vendor/golang.org/x/sys/unix/ptrace_ios.go | 18 + vendor/golang.org/x/sys/unix/race.go | 31 + vendor/golang.org/x/sys/unix/race0.go | 26 + .../x/sys/unix/readdirent_getdents.go | 13 + .../x/sys/unix/readdirent_getdirentries.go | 20 + .../x/sys/unix/sockcmsg_dragonfly.go | 16 + .../golang.org/x/sys/unix/sockcmsg_linux.go | 85 + vendor/golang.org/x/sys/unix/sockcmsg_unix.go | 107 + .../x/sys/unix/sockcmsg_unix_other.go | 47 + vendor/golang.org/x/sys/unix/syscall.go | 87 + vendor/golang.org/x/sys/unix/syscall_aix.go | 599 ++ .../golang.org/x/sys/unix/syscall_aix_ppc.go | 53 + .../x/sys/unix/syscall_aix_ppc64.go | 84 + vendor/golang.org/x/sys/unix/syscall_bsd.go | 624 ++ .../golang.org/x/sys/unix/syscall_darwin.go | 826 ++ .../x/sys/unix/syscall_darwin_amd64.go | 52 + .../x/sys/unix/syscall_darwin_arm64.go | 52 + .../x/sys/unix/syscall_darwin_libSystem.go | 27 + .../x/sys/unix/syscall_dragonfly.go | 545 + .../x/sys/unix/syscall_dragonfly_amd64.go | 57 + .../golang.org/x/sys/unix/syscall_freebsd.go | 645 ++ .../x/sys/unix/syscall_freebsd_386.go | 65 + .../x/sys/unix/syscall_freebsd_amd64.go | 65 + .../x/sys/unix/syscall_freebsd_arm.go | 61 + .../x/sys/unix/syscall_freebsd_arm64.go | 61 + .../x/sys/unix/syscall_freebsd_riscv64.go | 61 + vendor/golang.org/x/sys/unix/syscall_hurd.go | 30 + .../golang.org/x/sys/unix/syscall_hurd_386.go | 29 + .../golang.org/x/sys/unix/syscall_illumos.go | 79 + vendor/golang.org/x/sys/unix/syscall_linux.go | 2544 +++++ .../x/sys/unix/syscall_linux_386.go | 315 + .../x/sys/unix/syscall_linux_alarm.go | 14 + .../x/sys/unix/syscall_linux_amd64.go | 146 + .../x/sys/unix/syscall_linux_amd64_gc.go | 13 + .../x/sys/unix/syscall_linux_arm.go | 217 + .../x/sys/unix/syscall_linux_arm64.go | 185 + .../golang.org/x/sys/unix/syscall_linux_gc.go | 15 + .../x/sys/unix/syscall_linux_gc_386.go | 17 + .../x/sys/unix/syscall_linux_gc_arm.go | 14 + .../x/sys/unix/syscall_linux_gccgo_386.go | 31 + .../x/sys/unix/syscall_linux_gccgo_arm.go | 21 + .../x/sys/unix/syscall_linux_loong64.go | 217 + .../x/sys/unix/syscall_linux_mips64x.go | 190 + .../x/sys/unix/syscall_linux_mipsx.go | 176 + .../x/sys/unix/syscall_linux_ppc.go | 205 + .../x/sys/unix/syscall_linux_ppc64x.go | 117 + .../x/sys/unix/syscall_linux_riscv64.go | 179 + .../x/sys/unix/syscall_linux_s390x.go | 297 + .../x/sys/unix/syscall_linux_sparc64.go | 113 + .../golang.org/x/sys/unix/syscall_netbsd.go | 621 ++ .../x/sys/unix/syscall_netbsd_386.go | 38 + .../x/sys/unix/syscall_netbsd_amd64.go | 38 + .../x/sys/unix/syscall_netbsd_arm.go | 38 + .../x/sys/unix/syscall_netbsd_arm64.go | 38 + .../golang.org/x/sys/unix/syscall_openbsd.go | 403 + .../x/sys/unix/syscall_openbsd_386.go | 42 + .../x/sys/unix/syscall_openbsd_amd64.go | 42 + .../x/sys/unix/syscall_openbsd_arm.go | 42 + .../x/sys/unix/syscall_openbsd_arm64.go | 42 + .../x/sys/unix/syscall_openbsd_libc.go | 27 + .../x/sys/unix/syscall_openbsd_mips64.go | 39 + .../x/sys/unix/syscall_openbsd_ppc64.go | 42 + .../x/sys/unix/syscall_openbsd_riscv64.go | 42 + .../golang.org/x/sys/unix/syscall_solaris.go | 1135 ++ .../x/sys/unix/syscall_solaris_amd64.go | 28 + vendor/golang.org/x/sys/unix/syscall_unix.go | 596 ++ .../golang.org/x/sys/unix/syscall_unix_gc.go | 16 + .../x/sys/unix/syscall_unix_gc_ppc64x.go | 25 + .../x/sys/unix/syscall_zos_s390x.go | 1994 ++++ vendor/golang.org/x/sys/unix/sysvshm_linux.go | 21 + vendor/golang.org/x/sys/unix/sysvshm_unix.go | 52 + .../x/sys/unix/sysvshm_unix_other.go | 14 + vendor/golang.org/x/sys/unix/timestruct.go | 77 + .../golang.org/x/sys/unix/unveil_openbsd.go | 42 + vendor/golang.org/x/sys/unix/xattr_bsd.go | 281 + .../golang.org/x/sys/unix/zerrors_aix_ppc.go | 1385 +++ .../x/sys/unix/zerrors_aix_ppc64.go | 1386 +++ .../x/sys/unix/zerrors_darwin_amd64.go | 1911 ++++ .../x/sys/unix/zerrors_darwin_arm64.go | 1911 ++++ .../x/sys/unix/zerrors_dragonfly_amd64.go | 1738 +++ .../x/sys/unix/zerrors_freebsd_386.go | 2043 ++++ .../x/sys/unix/zerrors_freebsd_amd64.go | 2040 ++++ .../x/sys/unix/zerrors_freebsd_arm.go | 2034 ++++ .../x/sys/unix/zerrors_freebsd_arm64.go | 2034 ++++ .../x/sys/unix/zerrors_freebsd_riscv64.go | 2148 ++++ vendor/golang.org/x/sys/unix/zerrors_linux.go | 3493 ++++++ .../x/sys/unix/zerrors_linux_386.go | 829 ++ .../x/sys/unix/zerrors_linux_amd64.go | 829 ++ .../x/sys/unix/zerrors_linux_arm.go | 835 ++ .../x/sys/unix/zerrors_linux_arm64.go | 827 ++ .../x/sys/unix/zerrors_linux_loong64.go | 819 ++ .../x/sys/unix/zerrors_linux_mips.go | 836 ++ .../x/sys/unix/zerrors_linux_mips64.go | 836 ++ .../x/sys/unix/zerrors_linux_mips64le.go | 836 ++ .../x/sys/unix/zerrors_linux_mipsle.go | 836 ++ .../x/sys/unix/zerrors_linux_ppc.go | 888 ++ .../x/sys/unix/zerrors_linux_ppc64.go | 892 ++ .../x/sys/unix/zerrors_linux_ppc64le.go | 892 ++ .../x/sys/unix/zerrors_linux_riscv64.go | 816 ++ .../x/sys/unix/zerrors_linux_s390x.go | 891 ++ .../x/sys/unix/zerrors_linux_sparc64.go | 934 ++ .../x/sys/unix/zerrors_netbsd_386.go | 1780 ++++ .../x/sys/unix/zerrors_netbsd_amd64.go | 1770 +++ .../x/sys/unix/zerrors_netbsd_arm.go | 1759 +++ .../x/sys/unix/zerrors_netbsd_arm64.go | 1770 +++ .../x/sys/unix/zerrors_openbsd_386.go | 1906 ++++ .../x/sys/unix/zerrors_openbsd_amd64.go | 1906 ++++ .../x/sys/unix/zerrors_openbsd_arm.go | 1906 ++++ .../x/sys/unix/zerrors_openbsd_arm64.go | 1906 ++++ .../x/sys/unix/zerrors_openbsd_mips64.go | 1906 ++++ .../x/sys/unix/zerrors_openbsd_ppc64.go | 1905 ++++ .../x/sys/unix/zerrors_openbsd_riscv64.go | 1904 ++++ .../x/sys/unix/zerrors_solaris_amd64.go | 1557 +++ .../x/sys/unix/zerrors_zos_s390x.go | 860 ++ .../x/sys/unix/zptrace_armnn_linux.go | 42 + .../x/sys/unix/zptrace_linux_arm64.go | 17 + .../x/sys/unix/zptrace_mipsnn_linux.go | 51 + .../x/sys/unix/zptrace_mipsnnle_linux.go | 51 + .../x/sys/unix/zptrace_x86_linux.go | 81 + .../golang.org/x/sys/unix/zsyscall_aix_ppc.go | 1484 +++ .../x/sys/unix/zsyscall_aix_ppc64.go | 1443 +++ .../x/sys/unix/zsyscall_aix_ppc64_gc.go | 1189 +++ .../x/sys/unix/zsyscall_aix_ppc64_gccgo.go | 1070 ++ .../x/sys/unix/zsyscall_darwin_amd64.go | 2572 +++++ .../x/sys/unix/zsyscall_darwin_amd64.s | 903 ++ .../x/sys/unix/zsyscall_darwin_arm64.go | 2572 +++++ .../x/sys/unix/zsyscall_darwin_arm64.s | 903 ++ .../x/sys/unix/zsyscall_dragonfly_amd64.go | 1689 +++ .../x/sys/unix/zsyscall_freebsd_386.go | 1909 ++++ .../x/sys/unix/zsyscall_freebsd_amd64.go | 1909 ++++ .../x/sys/unix/zsyscall_freebsd_arm.go | 1909 ++++ .../x/sys/unix/zsyscall_freebsd_arm64.go | 1909 ++++ .../x/sys/unix/zsyscall_freebsd_riscv64.go | 1909 ++++ .../x/sys/unix/zsyscall_illumos_amd64.go | 102 + .../golang.org/x/sys/unix/zsyscall_linux.go | 2188 ++++ .../x/sys/unix/zsyscall_linux_386.go | 487 + .../x/sys/unix/zsyscall_linux_amd64.go | 654 ++ .../x/sys/unix/zsyscall_linux_arm.go | 602 ++ .../x/sys/unix/zsyscall_linux_arm64.go | 553 + .../x/sys/unix/zsyscall_linux_loong64.go | 487 + .../x/sys/unix/zsyscall_linux_mips.go | 654 ++ .../x/sys/unix/zsyscall_linux_mips64.go | 648 ++ .../x/sys/unix/zsyscall_linux_mips64le.go | 637 ++ .../x/sys/unix/zsyscall_linux_mipsle.go | 654 ++ .../x/sys/unix/zsyscall_linux_ppc.go | 659 ++ .../x/sys/unix/zsyscall_linux_ppc64.go | 705 ++ .../x/sys/unix/zsyscall_linux_ppc64le.go | 705 ++ .../x/sys/unix/zsyscall_linux_riscv64.go | 533 + .../x/sys/unix/zsyscall_linux_s390x.go | 496 + .../x/sys/unix/zsyscall_linux_sparc64.go | 649 ++ .../x/sys/unix/zsyscall_netbsd_386.go | 1860 ++++ .../x/sys/unix/zsyscall_netbsd_amd64.go | 1860 ++++ .../x/sys/unix/zsyscall_netbsd_arm.go | 1860 ++++ .../x/sys/unix/zsyscall_netbsd_arm64.go | 1860 ++++ .../x/sys/unix/zsyscall_openbsd_386.go | 2251 ++++ .../x/sys/unix/zsyscall_openbsd_386.s | 674 ++ .../x/sys/unix/zsyscall_openbsd_amd64.go | 2253 ++++ .../x/sys/unix/zsyscall_openbsd_amd64.s | 674 ++ .../x/sys/unix/zsyscall_openbsd_arm.go | 2251 ++++ .../x/sys/unix/zsyscall_openbsd_arm.s | 674 ++ .../x/sys/unix/zsyscall_openbsd_arm64.go | 2251 ++++ .../x/sys/unix/zsyscall_openbsd_arm64.s | 674 ++ .../x/sys/unix/zsyscall_openbsd_mips64.go | 2251 ++++ .../x/sys/unix/zsyscall_openbsd_mips64.s | 674 ++ .../x/sys/unix/zsyscall_openbsd_ppc64.go | 2251 ++++ .../x/sys/unix/zsyscall_openbsd_ppc64.s | 808 ++ .../x/sys/unix/zsyscall_openbsd_riscv64.go | 2251 ++++ .../x/sys/unix/zsyscall_openbsd_riscv64.s | 674 ++ .../x/sys/unix/zsyscall_solaris_amd64.go | 2104 ++++ .../x/sys/unix/zsyscall_zos_s390x.go | 1265 +++ .../x/sys/unix/zsysctl_openbsd_386.go | 281 + .../x/sys/unix/zsysctl_openbsd_amd64.go | 281 + .../x/sys/unix/zsysctl_openbsd_arm.go | 281 + .../x/sys/unix/zsysctl_openbsd_arm64.go | 281 + .../x/sys/unix/zsysctl_openbsd_mips64.go | 281 + .../x/sys/unix/zsysctl_openbsd_ppc64.go | 281 + .../x/sys/unix/zsysctl_openbsd_riscv64.go | 282 + .../x/sys/unix/zsysnum_darwin_amd64.go | 440 + .../x/sys/unix/zsysnum_darwin_arm64.go | 438 + .../x/sys/unix/zsysnum_dragonfly_amd64.go | 317 + .../x/sys/unix/zsysnum_freebsd_386.go | 394 + .../x/sys/unix/zsysnum_freebsd_amd64.go | 394 + .../x/sys/unix/zsysnum_freebsd_arm.go | 394 + .../x/sys/unix/zsysnum_freebsd_arm64.go | 394 + .../x/sys/unix/zsysnum_freebsd_riscv64.go | 394 + .../x/sys/unix/zsysnum_linux_386.go | 450 + .../x/sys/unix/zsysnum_linux_amd64.go | 372 + .../x/sys/unix/zsysnum_linux_arm.go | 414 + .../x/sys/unix/zsysnum_linux_arm64.go | 317 + .../x/sys/unix/zsysnum_linux_loong64.go | 311 + .../x/sys/unix/zsysnum_linux_mips.go | 434 + .../x/sys/unix/zsysnum_linux_mips64.go | 364 + .../x/sys/unix/zsysnum_linux_mips64le.go | 364 + .../x/sys/unix/zsysnum_linux_mipsle.go | 434 + .../x/sys/unix/zsysnum_linux_ppc.go | 441 + .../x/sys/unix/zsysnum_linux_ppc64.go | 413 + .../x/sys/unix/zsysnum_linux_ppc64le.go | 413 + .../x/sys/unix/zsysnum_linux_riscv64.go | 316 + .../x/sys/unix/zsysnum_linux_s390x.go | 378 + .../x/sys/unix/zsysnum_linux_sparc64.go | 392 + .../x/sys/unix/zsysnum_netbsd_386.go | 275 + .../x/sys/unix/zsysnum_netbsd_amd64.go | 275 + .../x/sys/unix/zsysnum_netbsd_arm.go | 275 + .../x/sys/unix/zsysnum_netbsd_arm64.go | 275 + .../x/sys/unix/zsysnum_openbsd_386.go | 220 + .../x/sys/unix/zsysnum_openbsd_amd64.go | 220 + .../x/sys/unix/zsysnum_openbsd_arm.go | 220 + .../x/sys/unix/zsysnum_openbsd_arm64.go | 219 + .../x/sys/unix/zsysnum_openbsd_mips64.go | 222 + .../x/sys/unix/zsysnum_openbsd_ppc64.go | 218 + .../x/sys/unix/zsysnum_openbsd_riscv64.go | 219 + .../x/sys/unix/zsysnum_zos_s390x.go | 2670 +++++ .../golang.org/x/sys/unix/ztypes_aix_ppc.go | 354 + .../golang.org/x/sys/unix/ztypes_aix_ppc64.go | 358 + .../x/sys/unix/ztypes_darwin_amd64.go | 806 ++ .../x/sys/unix/ztypes_darwin_arm64.go | 806 ++ .../x/sys/unix/ztypes_dragonfly_amd64.go | 474 + .../x/sys/unix/ztypes_freebsd_386.go | 651 ++ .../x/sys/unix/ztypes_freebsd_amd64.go | 656 ++ .../x/sys/unix/ztypes_freebsd_arm.go | 642 ++ .../x/sys/unix/ztypes_freebsd_arm64.go | 636 ++ .../x/sys/unix/ztypes_freebsd_riscv64.go | 638 ++ vendor/golang.org/x/sys/unix/ztypes_linux.go | 5858 ++++++++++ .../golang.org/x/sys/unix/ztypes_linux_386.go | 696 ++ .../x/sys/unix/ztypes_linux_amd64.go | 711 ++ .../golang.org/x/sys/unix/ztypes_linux_arm.go | 691 ++ .../x/sys/unix/ztypes_linux_arm64.go | 690 ++ .../x/sys/unix/ztypes_linux_loong64.go | 691 ++ .../x/sys/unix/ztypes_linux_mips.go | 696 ++ .../x/sys/unix/ztypes_linux_mips64.go | 693 ++ .../x/sys/unix/ztypes_linux_mips64le.go | 693 ++ .../x/sys/unix/ztypes_linux_mipsle.go | 696 ++ .../golang.org/x/sys/unix/ztypes_linux_ppc.go | 704 ++ .../x/sys/unix/ztypes_linux_ppc64.go | 699 ++ .../x/sys/unix/ztypes_linux_ppc64le.go | 699 ++ .../x/sys/unix/ztypes_linux_riscv64.go | 718 ++ .../x/sys/unix/ztypes_linux_s390x.go | 713 ++ .../x/sys/unix/ztypes_linux_sparc64.go | 694 ++ .../x/sys/unix/ztypes_netbsd_386.go | 586 + .../x/sys/unix/ztypes_netbsd_amd64.go | 594 ++ .../x/sys/unix/ztypes_netbsd_arm.go | 591 + .../x/sys/unix/ztypes_netbsd_arm64.go | 594 ++ .../x/sys/unix/ztypes_openbsd_386.go | 569 + .../x/sys/unix/ztypes_openbsd_amd64.go | 569 + .../x/sys/unix/ztypes_openbsd_arm.go | 576 + .../x/sys/unix/ztypes_openbsd_arm64.go | 569 + .../x/sys/unix/ztypes_openbsd_mips64.go | 569 + .../x/sys/unix/ztypes_openbsd_ppc64.go | 571 + .../x/sys/unix/ztypes_openbsd_riscv64.go | 571 + .../x/sys/unix/ztypes_solaris_amd64.go | 517 + .../golang.org/x/sys/unix/ztypes_zos_s390x.go | 415 + vendor/golang.org/x/sys/windows/aliases.go | 13 + .../golang.org/x/sys/windows/dll_windows.go | 416 + vendor/golang.org/x/sys/windows/empty.s | 9 + .../golang.org/x/sys/windows/env_windows.go | 54 + vendor/golang.org/x/sys/windows/eventlog.go | 21 + .../golang.org/x/sys/windows/exec_windows.go | 183 + .../x/sys/windows/memory_windows.go | 48 + vendor/golang.org/x/sys/windows/mkerrors.bash | 70 + .../x/sys/windows/mkknownfolderids.bash | 27 + vendor/golang.org/x/sys/windows/mksyscall.go | 10 + vendor/golang.org/x/sys/windows/race.go | 31 + vendor/golang.org/x/sys/windows/race0.go | 26 + .../x/sys/windows/security_windows.go | 1444 +++ vendor/golang.org/x/sys/windows/service.go | 254 + .../x/sys/windows/setupapi_windows.go | 1425 +++ vendor/golang.org/x/sys/windows/str.go | 23 + vendor/golang.org/x/sys/windows/syscall.go | 105 + .../x/sys/windows/syscall_windows.go | 1819 ++++ .../golang.org/x/sys/windows/types_windows.go | 3349 ++++++ .../x/sys/windows/types_windows_386.go | 35 + .../x/sys/windows/types_windows_amd64.go | 34 + .../x/sys/windows/types_windows_arm.go | 35 + .../x/sys/windows/types_windows_arm64.go | 34 + .../x/sys/windows/zerrors_windows.go | 9468 +++++++++++++++++ .../x/sys/windows/zknownfolderids_windows.go | 149 + .../x/sys/windows/zsyscall_windows.go | 4341 ++++++++ vendor/modules.txt | 24 + 419 files changed, 225448 insertions(+) create mode 100644 .gitignore create mode 100644 Design create mode 100644 LICENSE.txt create mode 100644 Makefile create mode 100644 README.md create mode 100644 TODO create mode 100644 collection.go create mode 100644 data/sample_config.json create mode 100644 event_parser.go create mode 100644 globals.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 lib.go create mode 100644 main.go create mode 100644 types.go create mode 100644 vendor/github.com/davecgh/go-spew/LICENSE create mode 100644 vendor/github.com/davecgh/go-spew/spew/bypass.go create mode 100644 vendor/github.com/davecgh/go-spew/spew/bypasssafe.go create mode 100644 vendor/github.com/davecgh/go-spew/spew/common.go create mode 100644 vendor/github.com/davecgh/go-spew/spew/config.go create mode 100644 vendor/github.com/davecgh/go-spew/spew/doc.go create mode 100644 vendor/github.com/davecgh/go-spew/spew/dump.go create mode 100644 vendor/github.com/davecgh/go-spew/spew/format.go create mode 100644 vendor/github.com/davecgh/go-spew/spew/spew.go create mode 100644 vendor/github.com/eleksir/go-xmpp/.travis.yml create mode 100644 vendor/github.com/eleksir/go-xmpp/LICENSE create mode 100644 vendor/github.com/eleksir/go-xmpp/README.md create mode 100644 vendor/github.com/eleksir/go-xmpp/xmpp.go create mode 100644 vendor/github.com/eleksir/go-xmpp/xmpp_avatar.go create mode 100644 vendor/github.com/eleksir/go-xmpp/xmpp_disco.go create mode 100644 vendor/github.com/eleksir/go-xmpp/xmpp_information_query.go create mode 100644 vendor/github.com/eleksir/go-xmpp/xmpp_muc.go create mode 100644 vendor/github.com/eleksir/go-xmpp/xmpp_ping.go create mode 100644 vendor/github.com/eleksir/go-xmpp/xmpp_pubsub.go create mode 100644 vendor/github.com/eleksir/go-xmpp/xmpp_subscription.go create mode 100644 vendor/github.com/hjson/hjson-go/.gitignore create mode 100644 vendor/github.com/hjson/hjson-go/LICENSE create mode 100644 vendor/github.com/hjson/hjson-go/README.md create mode 100644 vendor/github.com/hjson/hjson-go/build_release.sh create mode 100644 vendor/github.com/hjson/hjson-go/decode.go create mode 100644 vendor/github.com/hjson/hjson-go/encode.go create mode 100644 vendor/github.com/hjson/hjson-go/parseNumber.go create mode 100644 vendor/github.com/sirupsen/logrus/.gitignore create mode 100644 vendor/github.com/sirupsen/logrus/.golangci.yml create mode 100644 vendor/github.com/sirupsen/logrus/.travis.yml create mode 100644 vendor/github.com/sirupsen/logrus/CHANGELOG.md create mode 100644 vendor/github.com/sirupsen/logrus/LICENSE create mode 100644 vendor/github.com/sirupsen/logrus/README.md create mode 100644 vendor/github.com/sirupsen/logrus/alt_exit.go create mode 100644 vendor/github.com/sirupsen/logrus/appveyor.yml create mode 100644 vendor/github.com/sirupsen/logrus/buffer_pool.go create mode 100644 vendor/github.com/sirupsen/logrus/doc.go create mode 100644 vendor/github.com/sirupsen/logrus/entry.go create mode 100644 vendor/github.com/sirupsen/logrus/exported.go create mode 100644 vendor/github.com/sirupsen/logrus/formatter.go create mode 100644 vendor/github.com/sirupsen/logrus/hooks.go create mode 100644 vendor/github.com/sirupsen/logrus/json_formatter.go create mode 100644 vendor/github.com/sirupsen/logrus/logger.go create mode 100644 vendor/github.com/sirupsen/logrus/logrus.go create mode 100644 vendor/github.com/sirupsen/logrus/terminal_check_appengine.go create mode 100644 vendor/github.com/sirupsen/logrus/terminal_check_bsd.go create mode 100644 vendor/github.com/sirupsen/logrus/terminal_check_js.go create mode 100644 vendor/github.com/sirupsen/logrus/terminal_check_no_terminal.go create mode 100644 vendor/github.com/sirupsen/logrus/terminal_check_notappengine.go create mode 100644 vendor/github.com/sirupsen/logrus/terminal_check_solaris.go create mode 100644 vendor/github.com/sirupsen/logrus/terminal_check_unix.go create mode 100644 vendor/github.com/sirupsen/logrus/terminal_check_windows.go create mode 100644 vendor/github.com/sirupsen/logrus/text_formatter.go create mode 100644 vendor/github.com/sirupsen/logrus/writer.go create mode 100644 vendor/golang.org/x/crypto/LICENSE create mode 100644 vendor/golang.org/x/crypto/PATENTS create mode 100644 vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go create mode 100644 vendor/golang.org/x/exp/LICENSE create mode 100644 vendor/golang.org/x/exp/PATENTS create mode 100644 vendor/golang.org/x/exp/constraints/constraints.go create mode 100644 vendor/golang.org/x/exp/slices/slices.go create mode 100644 vendor/golang.org/x/exp/slices/sort.go create mode 100644 vendor/golang.org/x/exp/slices/zsortfunc.go create mode 100644 vendor/golang.org/x/exp/slices/zsortordered.go create mode 100644 vendor/golang.org/x/sys/LICENSE create mode 100644 vendor/golang.org/x/sys/PATENTS create mode 100644 vendor/golang.org/x/sys/internal/unsafeheader/unsafeheader.go create mode 100644 vendor/golang.org/x/sys/unix/.gitignore create mode 100644 vendor/golang.org/x/sys/unix/README.md create mode 100644 vendor/golang.org/x/sys/unix/affinity_linux.go create mode 100644 vendor/golang.org/x/sys/unix/aliases.go create mode 100644 vendor/golang.org/x/sys/unix/asm_aix_ppc64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_bsd_386.s create mode 100644 vendor/golang.org/x/sys/unix/asm_bsd_amd64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_bsd_arm.s create mode 100644 vendor/golang.org/x/sys/unix/asm_bsd_arm64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_bsd_ppc64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_bsd_riscv64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_linux_386.s create mode 100644 vendor/golang.org/x/sys/unix/asm_linux_amd64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_linux_arm.s create mode 100644 vendor/golang.org/x/sys/unix/asm_linux_arm64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_linux_loong64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_linux_mips64x.s create mode 100644 vendor/golang.org/x/sys/unix/asm_linux_mipsx.s create mode 100644 vendor/golang.org/x/sys/unix/asm_linux_ppc64x.s create mode 100644 vendor/golang.org/x/sys/unix/asm_linux_riscv64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_linux_s390x.s create mode 100644 vendor/golang.org/x/sys/unix/asm_openbsd_mips64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_solaris_amd64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_zos_s390x.s create mode 100644 vendor/golang.org/x/sys/unix/bluetooth_linux.go create mode 100644 vendor/golang.org/x/sys/unix/cap_freebsd.go create mode 100644 vendor/golang.org/x/sys/unix/constants.go create mode 100644 vendor/golang.org/x/sys/unix/dev_aix_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/dev_aix_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/dev_darwin.go create mode 100644 vendor/golang.org/x/sys/unix/dev_dragonfly.go create mode 100644 vendor/golang.org/x/sys/unix/dev_freebsd.go create mode 100644 vendor/golang.org/x/sys/unix/dev_linux.go create mode 100644 vendor/golang.org/x/sys/unix/dev_netbsd.go create mode 100644 vendor/golang.org/x/sys/unix/dev_openbsd.go create mode 100644 vendor/golang.org/x/sys/unix/dev_zos.go create mode 100644 vendor/golang.org/x/sys/unix/dirent.go create mode 100644 vendor/golang.org/x/sys/unix/endian_big.go create mode 100644 vendor/golang.org/x/sys/unix/endian_little.go create mode 100644 vendor/golang.org/x/sys/unix/env_unix.go create mode 100644 vendor/golang.org/x/sys/unix/epoll_zos.go create mode 100644 vendor/golang.org/x/sys/unix/fcntl.go create mode 100644 vendor/golang.org/x/sys/unix/fcntl_darwin.go create mode 100644 vendor/golang.org/x/sys/unix/fcntl_linux_32bit.go create mode 100644 vendor/golang.org/x/sys/unix/fdset.go create mode 100644 vendor/golang.org/x/sys/unix/fstatfs_zos.go create mode 100644 vendor/golang.org/x/sys/unix/gccgo.go create mode 100644 vendor/golang.org/x/sys/unix/gccgo_c.c create mode 100644 vendor/golang.org/x/sys/unix/gccgo_linux_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/ifreq_linux.go create mode 100644 vendor/golang.org/x/sys/unix/ioctl_linux.go create mode 100644 vendor/golang.org/x/sys/unix/ioctl_signed.go create mode 100644 vendor/golang.org/x/sys/unix/ioctl_unsigned.go create mode 100644 vendor/golang.org/x/sys/unix/ioctl_zos.go create mode 100644 vendor/golang.org/x/sys/unix/mkall.sh create mode 100644 vendor/golang.org/x/sys/unix/mkerrors.sh create mode 100644 vendor/golang.org/x/sys/unix/pagesize_unix.go create mode 100644 vendor/golang.org/x/sys/unix/pledge_openbsd.go create mode 100644 vendor/golang.org/x/sys/unix/ptrace_darwin.go create mode 100644 vendor/golang.org/x/sys/unix/ptrace_ios.go create mode 100644 vendor/golang.org/x/sys/unix/race.go create mode 100644 vendor/golang.org/x/sys/unix/race0.go create mode 100644 vendor/golang.org/x/sys/unix/readdirent_getdents.go create mode 100644 vendor/golang.org/x/sys/unix/readdirent_getdirentries.go create mode 100644 vendor/golang.org/x/sys/unix/sockcmsg_dragonfly.go create mode 100644 vendor/golang.org/x/sys/unix/sockcmsg_linux.go create mode 100644 vendor/golang.org/x/sys/unix/sockcmsg_unix.go create mode 100644 vendor/golang.org/x/sys/unix/sockcmsg_unix_other.go create mode 100644 vendor/golang.org/x/sys/unix/syscall.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_aix.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_aix_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_aix_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_bsd.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_darwin.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_darwin_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_darwin_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_darwin_libSystem.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_dragonfly.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_dragonfly_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_freebsd.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_freebsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_freebsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_freebsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_freebsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_freebsd_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_hurd.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_hurd_386.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_illumos.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_386.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_alarm.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_amd64_gc.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_arm.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_gc.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_gc_386.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_gc_arm.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_gccgo_386.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_gccgo_arm.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_loong64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_mips64x.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_mipsx.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_ppc64x.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_s390x.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_sparc64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_netbsd.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_netbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_netbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_netbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_netbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_openbsd.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_openbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_openbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_openbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_openbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_openbsd_libc.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_openbsd_mips64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_openbsd_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_openbsd_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_solaris.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_solaris_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_unix.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_unix_gc.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_unix_gc_ppc64x.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_zos_s390x.go create mode 100644 vendor/golang.org/x/sys/unix/sysvshm_linux.go create mode 100644 vendor/golang.org/x/sys/unix/sysvshm_unix.go create mode 100644 vendor/golang.org/x/sys/unix/sysvshm_unix_other.go create mode 100644 vendor/golang.org/x/sys/unix/timestruct.go create mode 100644 vendor/golang.org/x/sys/unix/unveil_openbsd.go create mode 100644 vendor/golang.org/x/sys/unix/xattr_bsd.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_aix_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_aix_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_darwin_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_darwin_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_dragonfly_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_freebsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_freebsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_freebsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_freebsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_freebsd_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_386.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_mips.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_netbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_netbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_netbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_netbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_openbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_openbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_openbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_openbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_openbsd_mips64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_openbsd_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_openbsd_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_solaris_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_zos_s390x.go create mode 100644 vendor/golang.org/x/sys/unix/zptrace_armnn_linux.go create mode 100644 vendor/golang.org/x/sys/unix/zptrace_linux_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zptrace_mipsnn_linux.go create mode 100644 vendor/golang.org/x/sys/unix/zptrace_mipsnnle_linux.go create mode 100644 vendor/golang.org/x/sys/unix/zptrace_x86_linux.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_aix_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_aix_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_aix_ppc64_gc.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_aix_ppc64_gccgo.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.s create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.s create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_dragonfly_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_freebsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_freebsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_freebsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_freebsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_freebsd_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_illumos_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_386.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_loong64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_mips.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_mips64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_mips64le.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_mipsle.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_ppc64le.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_s390x.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_sparc64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_netbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_netbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_netbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_netbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_386.s create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_amd64.s create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm.s create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm64.s create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_mips64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_mips64.s create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_ppc64.s create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_riscv64.s create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_solaris_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_zos_s390x.go create mode 100644 vendor/golang.org/x/sys/unix/zsysctl_openbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/zsysctl_openbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysctl_openbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zsysctl_openbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysctl_openbsd_mips64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysctl_openbsd_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysctl_openbsd_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_darwin_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_darwin_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_dragonfly_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_freebsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_freebsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_freebsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_freebsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_freebsd_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_386.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_mips.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_mips64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_mips64le.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_mipsle.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64le.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_s390x.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_sparc64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_netbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_netbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_netbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_netbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_openbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_openbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_openbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_openbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_openbsd_mips64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_openbsd_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_openbsd_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_zos_s390x.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_aix_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_aix_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_darwin_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_darwin_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_dragonfly_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_freebsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_freebsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_freebsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_freebsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_freebsd_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_386.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_arm.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_loong64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_mips.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_mips64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_mips64le.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_mipsle.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_ppc64le.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_sparc64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_netbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_netbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_netbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_netbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_openbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_openbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_openbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_openbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_openbsd_mips64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_openbsd_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_openbsd_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_solaris_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_zos_s390x.go create mode 100644 vendor/golang.org/x/sys/windows/aliases.go create mode 100644 vendor/golang.org/x/sys/windows/dll_windows.go create mode 100644 vendor/golang.org/x/sys/windows/empty.s create mode 100644 vendor/golang.org/x/sys/windows/env_windows.go create mode 100644 vendor/golang.org/x/sys/windows/eventlog.go create mode 100644 vendor/golang.org/x/sys/windows/exec_windows.go create mode 100644 vendor/golang.org/x/sys/windows/memory_windows.go create mode 100644 vendor/golang.org/x/sys/windows/mkerrors.bash create mode 100644 vendor/golang.org/x/sys/windows/mkknownfolderids.bash create mode 100644 vendor/golang.org/x/sys/windows/mksyscall.go create mode 100644 vendor/golang.org/x/sys/windows/race.go create mode 100644 vendor/golang.org/x/sys/windows/race0.go create mode 100644 vendor/golang.org/x/sys/windows/security_windows.go create mode 100644 vendor/golang.org/x/sys/windows/service.go create mode 100644 vendor/golang.org/x/sys/windows/setupapi_windows.go create mode 100644 vendor/golang.org/x/sys/windows/str.go create mode 100644 vendor/golang.org/x/sys/windows/syscall.go create mode 100644 vendor/golang.org/x/sys/windows/syscall_windows.go create mode 100644 vendor/golang.org/x/sys/windows/types_windows.go create mode 100644 vendor/golang.org/x/sys/windows/types_windows_386.go create mode 100644 vendor/golang.org/x/sys/windows/types_windows_amd64.go create mode 100644 vendor/golang.org/x/sys/windows/types_windows_arm.go create mode 100644 vendor/golang.org/x/sys/windows/types_windows_arm64.go create mode 100644 vendor/golang.org/x/sys/windows/zerrors_windows.go create mode 100644 vendor/golang.org/x/sys/windows/zknownfolderids_windows.go create mode 100644 vendor/golang.org/x/sys/windows/zsyscall_windows.go create mode 100644 vendor/modules.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8f910d5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.idea/** +.vscode/** +.vstags +data/config.json +echo-jabber-bot + diff --git a/Design b/Design new file mode 100644 index 0000000..55a54fe --- /dev/null +++ b/Design @@ -0,0 +1,51 @@ +Oсобенности реализации клиентской части. + +Общие положения +------------------------------------------------------------------------------------------------------------------------ + +Nick для room-ов един и не может задаваться на per-conference основе. На этом завязаны отсылка ping-ов и определение +pong-ов, как минимум. Хотя формально джойниться можно с любым ником :) но что-то не хочется потом распутывать этот +клубок. + + +XEP-0410 MUC Self-Ping (Schrödinger's Chat) +------------------------------------------------------------------------------------------------------------------------ + +xep-0410 реализован не польностью. Согласно https://xmpp.org/extensions/xep-0410.html, существует минимум 2 варианта +реализации обнаружения, что киент оотвалился от чатика. + +* С двухступенчатым пингом. Когда с jid-а локального пнгуется jid в конференции. Ответ доставляется на один из клиентов. +Который в свою очередь должен ответить на этот ответ (отвечайте на мои ответы!), результат этого ответа передаётся +оригинальному запросившему клиенту. + +* С серверной орптимизацией. (оптимизация анонсируется в ответе на запрос disco#info MUC-а) +В этом случае пинг будет одноступенчатым и нам сразу же должен по идее вернуться result (то бишь pong) + +Двухступенчатая реализация вызывает вопросы. Во-первых, своей двухступенчатастью, а во-вторых, ответ на начальный пинг +доставляется на один из клиентов (не на все!), а если этот клиент испытывает сетевые проблемы, то получается, страдают +все клиенты. В нашем случае предполагается, что если клиентов больше одного, то он не должен своим состоянием аффектить +других клиентов. + +И... тут есть другой момент - двухступенчатый пинг гораздо сложнее в реализации на клиентской стороне, т.к. надо +обрабатывать значительно бОльшее количество кейсов сочетаний ошибок, которые могут возникнуть в ходе пинга. + +Вобщем, поддерживаем только второй вариант пинга, который с серверной оптимизацией. + +N.B. Но на IQ get ping с xmlns="urn:xmpp:ping" из первого шага мы всё-таки будем отвечать обычным result-ом (понгом, то + бишь) + +N.B. Каждый пинг комнаты происходит в отдельной goroutne-ке, так что заходить в огромное количеств комнат (>1k) + не рекомендуется + + +xep-0199 XMPP Ping +------------------------------------------------------------------------------------------------------------------------ + +Cуществует несколько видов пинга: +* Server-To-Client Pings +* Client-To-Server Pings +* Server-To-Server Pings +* Client-to-Client Pings +* Component-to-Client Pings + +Явным образом я реализовываю с2s. Плюс частично Component-to-Client в рамках N.B. по xep-0410. diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..13255ce --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,20 @@ +Copyright (c) 2022 Sergei Fedosoff + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..bf6d203 --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ +#!/usr/bin/env gmake -f + +GOOPTS=CGO_ENABLED=0 +BUILDOPTS=-ldflags="-s -w" -a -gcflags=all=-l -trimpath + +all: clean build + +build: + ${GOOPTS} go build ${BUILDOPTS} -o echo-jabber-bot collection.go types.go globals.go lib.go event_parser.go main.go + +clean: + go clean + +upgrade: + rm -rf vendor + go get -d -u -t ./... + go mod tidy + go mod vendor + +# vim: set ft=make noet ai ts=4 sw=4 sts=4: diff --git a/README.md b/README.md new file mode 100644 index 0000000..be61834 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# Echo Jabber Bot + +Простой бот, работающий по протоколу xmpp. Его задача - демонстрация возможностей библиотеки github.com/mattn/go-xmpp. + +На его основе (будет?) построен xmpp-фронтэнд бота aleesa-bot. + +## Что он может? + +Может повторять все сказанные в чят сообщения. Умеет работать с приватными и публичными чатами. + +Имеет возможность соединяться с сервером по протоколу xmpp как с шифрованием, так и без. Шифрование работает по +классическим механикам (шифрованный канал связи, без возможности коммуникации в незашифрованном виде), без поддержки +механизма start tls. Что-то в гошке или на стороне сервера при выборе StartTLS не работает. + +## Что он не может? + +* Не может заходить в комнаты, защищённые паролем. +* Не умеет разгадывать капчу. + +## Как заставить его работать? + +Не надо этого делать. Он уже работает, потому что существует. + +## Дисклеймер + +Весь дисклеймер в файле LICENSE.txt :) diff --git a/TODO b/TODO new file mode 100644 index 0000000..392ba47 --- /dev/null +++ b/TODO @@ -0,0 +1,5 @@ +Избавиться от рекурсии - на любых ошибках возвращать контекст как можно выше по стэку. В идеале - прям в main. +Хэндлить ошибки оттуда. + +Сейчас у меня в случае ошибки копится рекусия реконнектов и потом бот хочет много раз подцепиться к комнатам и +оповестить их о своём состоянии. diff --git a/collection.go b/collection.go new file mode 100644 index 0000000..9805c97 --- /dev/null +++ b/collection.go @@ -0,0 +1,69 @@ +package main + +import ( + "sync" +) + +// Collection это sync.Map-ка, хранящая в себе произвольные итемы +type Collection struct { + items sync.Map + close chan struct{} +} + +// Item представляет собой интерфейс для хранения произвольных данных +type item struct { + data interface{} +} + +// NewCollection создаёт инстанс коллекции +func NewCollection() *Collection { + c := &Collection{ + close: make(chan struct{}), + } + + return c +} + +// Get достаёт данные по заданному ключу из коллекции +func (collection *Collection) Get(key interface{}) (interface{}, bool) { + obj, exists := collection.items.Load(key) + + if !exists { + return nil, false + } + + item := obj.(item) + + return item.data, true +} + +// Set сохраняет данные с заданным ключом в коллекцию +func (collection *Collection) Set(key interface{}, value interface{}) { + collection.items.Store(key, item{ + data: value, + }) +} + +// Range применяет функцию f ко всем ключам в коллекции +func (collection *Collection) Range(f func(key, value interface{}) bool) { + fn := func(key, value interface{}) bool { + item := value.(item) + + return f(key, item.data) + } + + collection.items.Range(fn) +} + +// Delete удаляет ключ и значение из коллекции данных +func (collection *Collection) Delete(key interface{}) { + collection.items.Delete(key) +} + +// Close очищает и высвобождает ресурсы, занятые коллекцией +func (collection *Collection) Close() { + collection.close <- struct{}{} + collection.items = sync.Map{} +} + +/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/data/sample_config.json b/data/sample_config.json new file mode 100644 index 0000000..9bf7024 --- /dev/null +++ b/data/sample_config.json @@ -0,0 +1,60 @@ +{ + # Фронт-энд бота + "jabber": { + # Если не задан, будет localhost + "server": "jabber.tld", + # Если не задано - 5222 для нешифрованного соединения или для starttls и 5223 для голого (старорежимного) ssl + "port": 5222, + # Включить шифрование + "ssl": true, + # Это более современный протокол и как раз на нём работет большинство jabber-серверов, порт 5222, + # jabber.ru по этому протоколу как-то странно работает, если его включить, я получаю + # tls: first record does not look like a TLS handshake + "starttls" : true, + # Не проверяем сертификаты по-умолчанию. + # Вам правда хочется всё это проверять? Там по идее могут быть всякие Lets Encrypt, которые хоть и работают, но + # создают кучу проблем + "ssl_verify": false, + # Позволять авторизацию логин-паролем по незашифрованному каналу связи + "insecureallowunencryptedauth": false, + # Таймаут установления соединения с сервером, в секундах. 10 секунд, если не задано. + "connection_timeout": 10, + # Задержка перед пере-коннекчиванием. 3 секунды, если не задано. + "reconnect_delay": 3, + # Промежуток между пингами сервера, для определения его живости. По-умолчанию 60 секунд. + # Пинги посылаются, если в течение этого времени не было активности со стороны сервера. + "server_ping_delay": 60, + # Промежуток между пингами muc-ов. По-умолчанию 900 секунд. (Согласно рекомендации в xep-0410.) + # Пинги посылаются, если в течение этого времени не было активности со стороны muc-а. + "muc_ping_delay": 900, + # Сколько ждать перед переджоиванием к комнате. По-умолчанию, 3 секунды. + "muc_rejoin_delay": 3, + # Ping-to-ping splay delay. Варьируемая задержка между пингами, чтобы не набирать высокий message rate. + # По-умолчанию, 3 секунды. Под капотом преобразуются в миллисекунды. Важна для пингов MUC-ов, особенно, + # если их МНОГО. + "ping_splay_delay": 3, + # Ник бота, с которым он будет заходить в комнаты + "nick": "echo", + # Имя пользователя для авторизации на сервере + "user": "echo@jabber.tld", + # Пароль для авторизации + "password": "secret", + # Jabber-каналы, к которым бот попробует присоединиться при старте + "channels": [ + "my_channel@conference.jabber.tld", + "another_channel@conference.jabber.tld" + ] + }, + + # Многословность логов. Если не задано, то info. Debug - ДЕЙСТВИТЕЛЬНО вербозный уровень логгирования. + # Есть ещё trace :) + "loglevel": "debug", + + # Если не задан, логи идут в STDOUT + "log": "/var/log/echo-jabber-bot/echo-jabber-bot.log", + + # Суть эхо бота в том, чтобы повторять всё что скажут + # (вообще говоря, суть в том, чтобы реализовать минимальный набор фич, сделать работающий вариант клиента) + # Если true - бот будет молчать, если false - бот будет повторять сказанное + "noecho": false +} diff --git a/event_parser.go b/event_parser.go new file mode 100644 index 0000000..cf29acb --- /dev/null +++ b/event_parser.go @@ -0,0 +1,587 @@ +package main + +import ( + "encoding/xml" + "fmt" + "strings" + "time" + + "github.com/davecgh/go-spew/spew" + "github.com/eleksir/go-xmpp" + log "github.com/sirupsen/logrus" + "golang.org/x/exp/slices" +) + +func parseEvent(e interface{}) { + lastServerActivity = time.Now().Unix() + + switch v := e.(type) { + // Сообщение в чяти + case xmpp.Chat: + log.Debugf("Looks like message, ChatType: %s, From: %s, Subject: %s Text: %s", + v.Type, v.Remote, v.Subject, v.Text) + + if config.NoEcho { + return + } + + // Топик чятика присылается в виде сообщения с subject, но без text + // В то же время сообщения от людей приходят с пустым subject, но с заполненным text + if v.Text != "" { + // Чятики бывают групповые и не групповые, от этого зависит Remote, куда направлять сообщение + switch v.Type { //nolint:whitespace + + // Групповой чятик + case "groupchat": + log.Debugf("Message from public chat") + dest := strings.SplitN(v.Remote, "/", 2)[0] + nick := strings.SplitN(v.Remote, "/", 2)[1] + + if nick == config.Jabber.Nick { + log.Debug("Skipping message from myself") + + return + } + + log.Debugf("Trying to echo message back to chat %s", dest) + + if _, err := talk.Send( + xmpp.Chat{ //nolint:exhaustruct + Remote: dest, + Text: v.Text, + Type: v.Type, + }, + ); err != nil { + log.Errorf("Unable to send message to %s: %s", v.Remote, err) + reEstablishConnection() + } + + lastActivity = lastServerActivity + + if muc, _ := strings.CutSuffix(v.Remote, "/"); muc != "" { + lastMucActivity.Set(muc, lastServerActivity) + } + + // Приватный чятик + case "chat": + // Здесь у нас может быть 2 вида From: + // slackware-current@conference.jabber.ru/eleksir - это если сообщение из публичного чятика + // eleksir@jabber.ru/array.lan - это если мы работаем через ростер + log.Debug("Definitely not public chat") + log.Trace(spew.Sdump(e)) + + if config.NoEcho { + return + } + + log.Debugf("Trying to echo message back to chat %s", v.Remote) + + if _, err := talk.Send( + xmpp.Chat{ //nolint:exhaustruct + Remote: v.Remote, + Text: v.Text, + Type: v.Type, + }, + ); err != nil { + log.Errorf("Unable to send message to %s: %s", v.Remote, err) + reEstablishConnection() + } + + lastActivity = lastServerActivity + + // Внезапно, ошибки. По идее они должны ассоциироваться с отправляемыми сообщениями, но по ходу это + // не реализовано, поэтому мы получаем ошибки в форме отдельного чятика + case "error": + // Это ошибка со стороны сервера, видимо, в ответ на наши действия и поскольку такие вещи происходят + // асинхронно, связать их с конкретными нашими действиями довольно сложно. + log.Warn(spew.Sdump(e)) + } + } + + // IQ-сообщение - пинг, понг, что-то ещё... + case xmpp.IQ: + // По правилам IQ обязательно должна содержать ID, причём, не пустой + if v.ID == "" { + log.Info("Got an IQ stanza with empty id, discarding") + + return + } + + switch v.Type { + case xmpp.IQTypeGet: + log.Debug("Looks like IQ get query") + + if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" { + if slices.Contains(roomsConnected, muc) { + lastMucActivity.Set(muc, lastServerActivity) + } + } + + var iqStruct jabberSimpleIqGetQuery + var parseError bool + + if err := xml.Unmarshal(v.Query, &iqStruct); err == nil { + switch iqStruct.Xmlns { //nolint:whitespace + + // Запрос номера версии приложения + case "jabber:iq:version": + log.Infof("Got IQ get request for version from %s", v.From) + + if id, err := talk.RawInformation( + v.To, + v.From, + v.ID, + xmpp.IQTypeResult, + "echo-jabber-bot4.с_чем-толинупс", + ); err != nil { + log.Errorf("Unable to send verion info to jabber server: id=%s, err=%s", id, err) + reEstablishConnection() + } + + parseError = false + + // Нам прислали попингуй + case "urn:xmpp:ping": + log.Infof("Got IQ get request for pong from %s", v.From) + + if id, err := talk.RawInformation( + v.To, + v.From, + v.ID, + xmpp.IQTypeResult, + "", + ); err != nil { + log.Errorf("Unable to send pong to jabber server: id=%s, err=%s", id, err) + reEstablishConnection() + } + + parseError = false + + // У нас запросили время последней активности. В нашем случае это либо время запуска клиента, либо время + // последней фразы в чяти. + case "jabber:iq:last": + log.Infof("Got IQ get last activity time request from %s", v.From) + + answer := fmt.Sprintf( + "", + time.Now().Unix()-lastActivity, + ) + + if id, err := talk.RawInformation( + v.To, + v.From, + v.ID, + xmpp.IQTypeResult, + answer, + ); err != nil { + log.Errorf("Unable to send last activity time to jabber server: id=%s, err=%s", id, err) + reEstablishConnection() + } + + parseError = false + + // Запрос на список поддерживаемых фич - версия, локальное время, итд + case xmpp.XMPPNS_DISCO_INFO: + log.Infof("Got IQ get disco#info request from %s", v.From) + + answer := "" + answer += "" + answer += "" + answer += "" + answer += "" + answer += "" + answer += "" + + if id, err := talk.RawInformation( + v.To, + v.From, + v.ID, + xmpp.IQTypeResult, + answer, + ); err != nil { + log.Errorf("Unable to send disco#info to jabber server: id=%s, err=%s", id, err) + reEstablishConnection() + } + + parseError = false + + // Спрашивают список команд, чтобы поуправлять этим клиентом + case xmpp.XMPPNS_DISCO_ITEMS: + log.Infof("Got IQ get disco#items request from %s, answer service unavailable", v.From) + + answer := "" + answer += "" + answer += "" + answer += "" + + if id, err := talk.RawInformation( + v.To, + v.From, + v.ID, + xmpp.IQTypeError, + answer, + ); err != nil { + log.Errorf("Unable to send disco#items to jabber server: id=%s, err=%s", id, err) + reEstablishConnection() + } + + parseError = false + + // Запрашивают что-то, о чём мы не имеем представления + default: + log.Info("Got an unknown IQ get request, discarding") + log.Info(spew.Sdump(e)) + + return + } + } else { + parseError = true + } + + // Попробуем распарсить входящий запрос как urn:xmpp:time + if parseError { + var iqStruct jabberTimeIqGetQuery + + if err := xml.Unmarshal(v.Query, &iqStruct); err == nil { + switch iqStruct.Xmlns { + case "urn:xmpp:time": + log.Infof("Got IQ get time request from %s", v.From) + + answer := fmt.Sprintf( + "", + time.Now().UTC().Format(time.RFC3339), + ) + + if id, err := talk.RawInformation( + v.To, + v.From, + v.ID, + xmpp.IQTypeResult, + answer, + ); err != nil { + log.Errorf("Unable to send urn:xmpp:time to jabber server: id=%s, err=%s", id, err) + reEstablishConnection() + } + + parseError = false + + default: + log.Info("Got bogus IQ get time request, discarding") + log.Info(spew.Sdump(e)) + + return + } + } else { + parseError = true + } + } + + // Попробуем распарсить входящий запрос как http://jabber.org/protocol/pubsub + if parseError { + var iqStruct jabberPubsubIQGetQuery + + if err := xml.Unmarshal(v.Query, &iqStruct); err == nil { + switch iqStruct.Xmlns { + case xmpp.XMPPNS_PUBSUB: + log.Infof("Got IQ get pubsub request from %s, answer feature unimplemented", v.From) + + answer := "" + answer += "" + answer += "" + answer += "" + + if id, err := talk.RawInformation( + v.To, + v.From, + v.ID, + xmpp.IQTypeError, + answer, + ); err != nil { + log.Errorf( + "Unable to send pubsub feature unimplemented to jabber server: id=%s, err=%s", + id, + err, + ) + + reEstablishConnection() + } + + parseError = false + + default: + log.Info("Got unknown IQ get pubsub something request, discarding") + log.Info(spew.Sdump(e)) + parseError = true + } + } else { + parseError = true + } + } + + // Попробуем распарсить входящий запрос как urn:xmpp:ping он приходит при MUC Self-Ping (Schrödinger's Chat) + // Предполагается, что такие респонсы должны приходить только для пинга без серверной оптимизации. + // Хотя идейно мы не поддерживаем работу без севрерной оптимизации, но на пинг ответим, нам не сложно. + if parseError { + var iqStruct jabberIqPing + + if err := xml.Unmarshal(v.Query, &iqStruct); err == nil { + log.Debugf("Got IQ get request (actually, response) for MUC Self-Ping from %s", v.From) + + if id, err := talk.RawInformation( + v.To, + v.From, + v.ID, + xmpp.IQTypeResult, + "", + ); err != nil { + log.Errorf("Unable to send pong to jabber server: id=%s, err=%s", id, err) + reEstablishConnection() + } + } + + parseError = false + } + + // Не знаю, как парсить... залоггируем это дело и пойдём дальше + if parseError { + log.Infof("Does not look like parsable via iqStruct") + log.Info(spew.Sdump(e)) + } + + case xmpp.IQTypeResult: + if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" { + if slices.Contains(roomsConnected, muc) { + lastMucActivity.Set(muc, lastServerActivity) + } + } + + switch { + // Похоже на pong от сервера (по стандарту в ответе нету query, но go-xmpp нам подсовывает это) + case v.From == config.Jabber.Server && v.To == talk.JID() && string(v.Query) == "": + log.Debugf("Got S2C pong answer from %s to %s", v.From, v.To) + serverPingTimestampRx = time.Now().Unix() + + // Похоже на понг второй стадии xep-0410 MUC-Ping-а, который у нас не реализован + case v.To == talk.JID() && string(v.Query) == "": + mucNameMatch := slices.Contains(roomsConnected, v.From) + + if mucNameMatch { + log.Debugf("Got server-optimized MUC pong answer (xep-0410) from %s to %s", v.From, v.To) + } else { + mucNickMatch := false + + for _, room := range roomsConnected { + mucNick := fmt.Sprintf("%s/%s", room, config.Jabber.Nick) + + if v.From == mucNick { + mucNickMatch = true + + break + } + } + + if mucNickMatch { + log.Debugf("Got 2-nd stage MUC pong answer (xep-0410) from %s to %s", v.From, v.To) + // Поскольку никакой логики у нас на этот счёт не предусмотрено, то просто пропускаем ответ + } else { + log.Infof("Got unknown pong from %s to %s", v.From, v.To) + log.Debug(spew.Sdump(e)) + } + } + default: + log.Info("Got an IQ result. Dunno how deal with it, discarding") + log.Debug(spew.Sdump(e)) + } + + // Этот бот не управляется со стороны сервера, поэтому все попытки порулить игнорируем + case xmpp.IQTypeSet: + if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" { + if slices.Contains(roomsConnected, muc) { + lastMucActivity.Set(muc, lastServerActivity) + } + } + + log.Info("Got an IQ reqest for set something. Answer not implemented") + + answer := "" + answer += "" + answer += "" + answer += "" + + if id, err := talk.RawInformation( + v.To, + v.From, + v.ID, + xmpp.IQTypeError, + answer, + ); err != nil { + log.Errorf("Unable to send set feature unimplemented to jabber server: id=%s, err=%s", + id, + err, + ) + + reEstablishConnection() + } + + log.Debug(spew.Sdump(e)) + + // Нам прилетело сообщение об ошибке + case xmpp.IQTypeError: + // Если сервер не хочет пинговаться и отвечает ошибкой на пинг, то наверно он не умеет в пинги, + // хотя если мы его пингуем, значит он анонсировал такой capability. Вот, засранец! + var iqPingStruct jabberIqPing + var parseError = true + + if err := xml.Unmarshal(v.Query, &iqPingStruct); err == nil { + if iqPingStruct.Xmlns == "urn:xmpp:ping" { + if v.From == config.Jabber.Server { + msg := "Server announced that it can answer c2s ping, but gives us an error to such query, " + msg += "fallback to keepalive whitespace pings" + log.Error(msg) + + serverCapsList.Set("urn:xmpp:ping", false) + } else { + log.Errorf("Got 'ping unsupported' message from: %s to: %s", v.From, v.To) + } + } else { + log.Error("Iq parsed as ping, but does not belong to xmlns urn:xmpp:ping") + log.Error(spew.Sdump(e)) + } + + parseError = false + } + + // Это у нас пинг xep-0410 и мы не в комнате, предполагается, что надо бы заджойниться + if parseError { + var iqErrorCancelNotAcceptable jabberIqErrorCancelNotAccepatble + + if err := xml.Unmarshal(v.Query, &iqErrorCancelNotAcceptable); err == nil { + if v.To == talk.JID() { + nick := strings.SplitN(v.From, "/", 2)[1] + + if slices.Contains(roomsConnected, iqErrorCancelNotAcceptable.By) && nick == config.Jabber.Nick { + log.Errorf( + "Got Iq error message from: %s to: %s. Looks like i'm not in MUC anymore", + v.From, v.To, + ) + + time.Sleep(time.Duration(config.Jabber.MucRejoinDelay) * time.Second) + + if _, err := talk.JoinMUCNoHistory(iqErrorCancelNotAcceptable.By, nick); err != nil { + log.Errorf("Looks like connection to server also lost") + + reEstablishConnection() + } + } else { + log.Error( + "looks like message parsed as jabberIqErrorCancelNotAccepatble but we're not in given room", + ) + + log.Error(spew.Sdump(e)) + } + } else { + log.Error( + "Looks like message parsed as jabberIqErrorCancelNotAccepatble but addressed not to us", + ) + + log.Error(spew.Sdump(e)) + } + + parseError = false + } + } + + if parseError { + log.Error("Unhandled IQ Error message") + log.Error(spew.Sdump(e)) + } + // Нам прилетело что-то неизвестное из семейства IQ stanza + default: + log.Info("Got an unknown IQ reqest. Dunno how deal with it, discarding") + log.Info(spew.Sdump(e)) + } + + // Смена статуса участника + case xmpp.Presence: + if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" { + if slices.Contains(roomsConnected, muc) { + lastMucActivity.Set(muc, lastServerActivity) + } + } + + if v.Type == xmpp.IQTypeError { + // Это событие происходит, когда из чятика выходит другой инстанс клиента. + // Такое бывает, когда 2 инстанса с одинаковым ресурсом, например, начинают "драться" за возможность + // остаться на сервере. Ситуация которую допускать нельзя, на самом деле, потому что рано или поздно такого + // клиента забанят из-за спама "вошёл-вышел". + // Однако, это не значит, что такую ситуацию мы не должны корректным образом обрабатывать. + if v.Type == "unavailable" { + log.Error("Presence notification - looks like another instance of client leaves room") + + if slices.Contains(roomsConnected, v.From) { + go joinMuc(v.From) + } + } else { + log.Errorf("Presence notification, Type: %s, From: %s, Show: %s, Status: %s", + v.Type, v.From, v.Show, v.Status) + log.Errorf(spew.Sdump(v)) + } + } else { + log.Infof("Presence notification, Type: %s, From: %s, To: %s Show: %s, Status: %s", + v.Type, v.From, v.To, v.Show, v.Status) + + if v.Show == "" && v.Status == "" { + if from := strings.Split(v.From, "/"); from[1] == config.Jabber.Nick { + roomsConnected = append(roomsConnected, from[0]) + } + } + } + + // Ответ на запрос поддерживаемых фич, который "http://jabber.org/protocol/disco#info" + case xmpp.DiscoResult: + if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" { + if slices.Contains(roomsConnected, muc) { + lastMucActivity.Set(muc, lastServerActivity) + } + } + + // Я видел 2 типа disco result и они отличались только []identities. Попробуем вытащить известный identity + // TODO: проверять адресата v.To + for _, ident := range v.Identities { + switch ident.Category { + case "server": + // Конкретно сейчас нас интересует только поддержка c2s ping + for _, feature := range v.Features { + log.Debugf("Server %s announced that it supports feature: %s", v.From, feature) + serverCapsList.Set(feature, true) + } + + serverCapsQueried = true + + case "conference": + mucCaps := make(map[string]bool) + + for _, feature := range v.Features { + log.Debugf("MUC %s announced that it supports feature: %s", v.From, feature) + mucCaps[feature] = true + } + + mucCapsList.Set(v.From, mucCaps) + + case "pubsub": + log.Debugf("PubSub component %s reply to disco#info, skipping", v.From) + + default: + log.Debug("Got unknown reply to disco#info") + log.Debug(spew.Sdump(e)) + } + } + + // Это что-то неизвестное, подампим событие в лог + default: + log.Info(spew.Sdump(e)) + } +} + +/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/globals.go b/globals.go new file mode 100644 index 0000000..7fdad30 --- /dev/null +++ b/globals.go @@ -0,0 +1,59 @@ +package main + +import ( + "os" + + "github.com/eleksir/go-xmpp" +) + +// Config - это у нас глобальная штука :) +var config myConfig + +// Ставится в true, если мы получили сигнал на выключение +var shutdown = false + +// Чтобы не организовывать драку за установку коннекта +var connecting = false + +// Глобальное состояние соединения +var isConnected = false + +// Канал, в который приходят уведомления для хэндлера сигналов от траппера сигналов +var sigChan = make(chan os.Signal, 1) + +// Основной инстанс xmpp-клиента +var talk *xmpp.Client + +// Опции подключения к xmpp-серверу +var options *xmpp.Options + +// Список комнат, в которых находится бот +var roomsConnected []string + +// Время последней активности, нужно для jabber:iq:last +var lastActivity int64 + +// Время последней активности, нужно для c2s пингов - посылаем пинги, только если давненько ничего не приходило с +// сервера +var lastServerActivity int64 + +// Время последней активности MUC-ов, нужно для пингов - посылаем пинги, только если давненько ничего не приходило из +// muc-ов +var lastMucActivity = NewCollection() + +// Получен ли ответ на запрос disco#info к серверу +var serverCapsQueried bool + +// sync.Map-ка с капабилити сервера +var serverCapsList = NewCollection() + +// sync.Map-ка с комнатами и их capability +var mucCapsList = NewCollection() + +// Время, когда был отправлен c2s ping +var serverPingTimestampTx int64 + +// Время, когда был принят s2c pong +var serverPingTimestampRx int64 + +/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b70b05f --- /dev/null +++ b/go.mod @@ -0,0 +1,16 @@ +module echo-jabber-bot + +go 1.20 + +require ( + github.com/davecgh/go-spew v1.1.1 + github.com/eleksir/go-xmpp v1.2.1 + github.com/hjson/hjson-go v3.3.0+incompatible + github.com/sirupsen/logrus v1.9.3 + golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 +) + +require ( + golang.org/x/crypto v0.10.0 // indirect + golang.org/x/sys v0.9.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..d9a2ed0 --- /dev/null +++ b/go.sum @@ -0,0 +1,24 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/eleksir/go-xmpp v1.2.1 h1:3I5nEvkKrPOOyE29B2Z5XwzPuiph8b7ckBzCja7f+S8= +github.com/eleksir/go-xmpp v1.2.1/go.mod h1:KnvbXvUpttEVFl8l5eDO2Q7J97ZxtTMvOsDWULD2Rn8= +github.com/hjson/hjson-go v3.3.0+incompatible h1:Rqr+Ya+0aCJMjaE4s8E9YKvuJLuLVpEvz4ONum52vnI= +github.com/hjson/hjson-go v3.3.0+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= +golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= +golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc= +golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/lib.go b/lib.go new file mode 100644 index 0000000..7cb3da2 --- /dev/null +++ b/lib.go @@ -0,0 +1,524 @@ +package main + +import ( + "encoding/json" + "fmt" + "math/rand" + "os" + "path/filepath" + "syscall" + "time" + + "github.com/eleksir/go-xmpp" + "github.com/hjson/hjson-go" + log "github.com/sirupsen/logrus" + "golang.org/x/exp/slices" +) + +// Хэндлер сигналов закрывает все бд, все сетевые соединения и сваливает из приложения +func sigHandler() { + log.Debug("Installing signal handler") + + for s := range sigChan { + switch s { + case syscall.SIGINT: + log.Infoln("Got SIGINT, quitting") + case syscall.SIGTERM: + log.Infoln("Got SIGTERM, quitting") + case syscall.SIGQUIT: + log.Infoln("Got SIGQUIT, quitting") + + // Заходим на новую итерацию, если у нас "неинтересный" сигнал + default: + continue + } + + // Чтобы не срать в логи ошибками, проставим shutdown state приложения в true + shutdown = true + + if isConnected && !shutdown { + log.Debug("Try to set our presence to Unavailable and status to Offline") + + // Вот тут понадобится коллекция известных пользователей, чтобы им разослать presence, что бот свалил в offline + // Пока за неимением лучшего сообщим об этом самим себе + for _, room := range roomsConnected { + if _, err := talk.SendPresence( + xmpp.Presence{ //nolint:exhaustruct + To: room, + Status: "Offline", + Type: "unavailable", + }, + ); err != nil { + log.Infof("Unable to send presence to jabber server: %s", err) + } + } + + // И закрываем соединение + log.Debugf("Closing connection to jabber server") + + if err := talk.Close(); err != nil { + log.Infof("Unable to close connection to jabber server: %s", err) + } + } + + os.Exit(0) + } +} + +// Читает и валидирует конфиг, а также выставляет некоторые default-ы, если значений для параметров в конфиге нет +func readConfig() { + configLoaded := false + executablePath, err := os.Executable() + + if err != nil { + log.Errorf("Unable to get current executable path: %s", err) + } + + configJSONPath := fmt.Sprintf("%s/data/config.json", filepath.Dir(executablePath)) + + locations := []string{ + "~/.echo-jabber-bot.json", + "~/echo-jabber-bot.json", + "/etc/echo-jabber-bot.json", + configJSONPath, + } + + for _, location := range locations { + fileInfo, err := os.Stat(location) + + // Предполагаем, что файла либо нет, либо мы не можем его прочитать, второе надо бы логгировать, но пока забьём + if err != nil { + continue + } + + // Конфиг-файл длинноват для конфига, попробуем следующего кандидата + if fileInfo.Size() > 65535 { + log.Warnf("Config file %s is too long for config, skipping", location) + + continue + } + + buf, err := os.ReadFile(location) + + // Не удалось прочитать, попробуем следующего кандидата + if err != nil { + log.Warnf("Skip reading config file %s: %s", location, err) + + continue + } + + // Исходя из документации, hjson какбы умеет парсить "кривой" json, но парсит его в map-ку. + // Интереснее на выходе получить структурку: то есть мы вначале конфиг преобразуем в map-ку, затем эту map-ку + // сериализуем в json, а потом json преврщааем в стркутурку. Не очень эффективно, но он и не часто требуется. + var sampleConfig myConfig + var tmp map[string]interface{} + err = hjson.Unmarshal(buf, &tmp) + + // Не удалось распарсить - попробуем следующего кандидата + if err != nil { + log.Warnf("Skip parsing config file %s: %s", location, err) + + continue + } + + tmpjson, err := json.Marshal(tmp) + + // Не удалось преобразовать map-ку в json + if err != nil { + log.Warnf("Skip parsing config file %s: %s", location, err) + + continue + } + + if err := json.Unmarshal(tmpjson, &sampleConfig); err != nil { + log.Warnf("Skip parsing config file %s: %s", location, err) + + continue + } + + // Значения для Jabber-клиента + if sampleConfig.Jabber.Server == "" { + sampleConfig.Jabber.Server = "localhost" + log.Errorf("Jabber server is not defined in config, using localhost") + } + + if sampleConfig.Jabber.Port == 0 { + sampleConfig.Jabber.Port = 5552 + + if sampleConfig.Jabber.Ssl { + if !sampleConfig.Jabber.StartTLS { + sampleConfig.Jabber.Port = 5553 + } + } + + log.Infof("Jabber port is not defined in config, using 6667") + } + + if !sampleConfig.Jabber.Ssl { + sampleConfig.Jabber.StartTLS = false + } + + if !sampleConfig.Jabber.Ssl || !sampleConfig.Jabber.StartTLS { + sampleConfig.Jabber.SslVerify = false + } + + // sampleConfig.Jabber.InsecureAllowUnencryptedAuth = false, если не задан + + if sampleConfig.Jabber.ConnectionTimeout == 0 { + sampleConfig.Jabber.ConnectionTimeout = 10 + } + + if sampleConfig.Jabber.ReconnectDelay == 0 { + sampleConfig.Jabber.ReconnectDelay = 3 + } + + if sampleConfig.Jabber.ServerPingDelay == 0 { + sampleConfig.Jabber.ServerPingDelay = 60 + } + + if sampleConfig.Jabber.MucPingDelay == 0 { + sampleConfig.Jabber.MucPingDelay = 900 + } + + if sampleConfig.Jabber.MucRejoinDelay == 0 { + sampleConfig.Jabber.MucRejoinDelay = 3 + } + + if sampleConfig.Jabber.PingSplayDelay == 0 { + sampleConfig.Jabber.PingSplayDelay = 3 + } + + if sampleConfig.Jabber.Nick == "" { + log.Errorf("Jabber nick is not defined in config, quitting") + os.Exit(1) + } + + if sampleConfig.Jabber.User == "" { + sampleConfig.Jabber.User = fmt.Sprintf("%s@%s", sampleConfig.Jabber.Nick, sampleConfig.Jabber.Server) + } + + // Если sampleConfig.Jabber.Password не задан, то авторизации через Nickserv или SASL не будет + // Если sampleConfig.Jabber.Sasl не задан, то авторизация происходит через NickServ + + // Нам бот нужен в каких-то чат-румах, а не "просто так" + if len(sampleConfig.Jabber.Channels) < 1 { + log.Errorf("No jabber channels/rooms defined in config, quitting") + os.Exit(1) + } + + if sampleConfig.Loglevel == "" { + sampleConfig.Loglevel = "info" + } + + // sampleConfig.Log = "" if not set + + config = sampleConfig + configLoaded = true + log.Infof("Using %s as config file", location) + + break + } + + if !configLoaded { + log.Error("Config was not loaded! Refusing to start.") + os.Exit(1) + } +} + +func establishConnection() { + var err error + + if connecting && !isConnected { + return + } + + // проставляем глобальные переменные + connecting = true + isConnected = false + roomsConnected = make([]string, 0) + + for { + talk, err = options.NewClient() + + if err != nil { + log.Error(err) + + reEstablishConnection() + } else { + break + } + } + + // По идее keepalive должен же проходить только, если мы уже на сервере, так? + for { + if _, err := talk.SendKeepAlive(); err != nil { + log.Errorf("Try to send initial KeepAlive, got error: %s", err) + time.Sleep(time.Duration(config.Jabber.ReconnectDelay) * time.Second) + + // Понятно, что мы уже не в процессе установления соединения, а произошла ошибка + connecting = false + reEstablishConnection() + } else { + log.Info("Connected") + + break + } + } + + // Джойнимся к чятикам, но делаем это в фоне, чтобы не блочиться на ошибках, например, если бота забанили + for _, room := range config.Jabber.Channels { + go joinMuc(room) + } + + if _, err := talk.SendPresence( + xmpp.Presence{ //nolint:exhaustruct + Status: "Ready to chat", + }, + ); err != nil { + log.Infof("Unable to send presence to jabber server: %s", err) + + reEstablishConnection() + } + + lastActivity = time.Now().Unix() + connecting = false + isConnected = true + + log.Debugf("Sending disco#info to %s", config.Jabber.Server) + + _, err = talk.DiscoverInfo(talk.JID(), config.Jabber.Server) + + if err != nil { + log.Infof("Unable to send disco#info to jabber server: %s", err) + + reEstablishConnection() + } +} + +func reEstablishConnection() { + // Серверные capabilities нам *уже* неизвестны + serverCapsQueried = false + serverCapsList = NewCollection() + mucCapsList = NewCollection() + lastMucActivity = NewCollection() + + // Пока соединение не установлено, мы про пинги ничего не знаем + serverPingTimestampRx = time.Now().Unix() // Считаем, что если коннект запустился, то первый пинг успешен + serverPingTimestampTx = 0 + time.Sleep(time.Duration(config.Jabber.ReconnectDelay) * time.Second) + connecting = false + establishConnection() +} + +// Джойнится к конференциям/каналам/комнатам в джаббере +func joinMuc(room string) { + log.Debugf("Sending disco#info from %s to %s", talk.JID(), room) + + if _, err := talk.DiscoverInfo(talk.JID(), room); err != nil { + log.Infof("Unable to send disco#info to MUC %s: %s", room, err) + + reEstablishConnection() + } + + // Ждём, пока muc нам вернёт список фичей + for i := 0; i < (20 * int(config.Jabber.ConnectionTimeout)); i++ { + var myRoom interface{} + var supported bool + var exist bool + + time.Sleep(50 * time.Millisecond) + + if myRoom, exist = mucCapsList.Get(room); !exist { + // Пока не задискаверилась + continue + } + + if supported, exist = myRoom.(map[string]bool)["muc_unsecured"]; exist { + if supported { + break + } else { + log.Infof("Unable to join to password-protected room. Don't know how to enter passwords :)") + + return + } + } + } + + if _, err := talk.JoinMUCNoHistory(room, config.Jabber.Nick); err != nil { + log.Errorf("Unable to join to MUC: %s", room) + + reEstablishConnection() + } else { + log.Infof("Joining to MUC: %s", room) + } + + // Ждём, когда прилетит presence из комнаты, тогда мы точно знаем, что мы вошли + for i := 0; i < (20 * int(config.Jabber.ConnectionTimeout)); i++ { + time.Sleep(50 * time.Millisecond) + + if slices.Contains(roomsConnected, room) { + break + } + } + + // Вот теперь точно можно слать статус + log.Infof("Joined to MUC: %s", room) + + if _, err := talk.SendPresence( + xmpp.Presence{ //nolint:exhaustruct + To: room, + Status: "Ready to chat", + }, + ); err != nil { + log.Infof("Unable to send presence to MUC %s: %s", room, err) + reEstablishConnection() + } +} + +// Проверяет живость соединения с сервером +func probeServerLiveness() { + for { + if shutdown { + return + } + + sleepTime := time.Duration(config.Jabber.ServerPingDelay) * 1000 * time.Millisecond + sleepTime += time.Duration(rand.Int63n(1000*config.Jabber.PingSplayDelay)) * time.Millisecond //nolint:gosec + time.Sleep(sleepTime) + + if !isConnected { + continue + } + + // Пингуем, только если не было никакой активности в течение > config.Jabber.ServerPingDelay, + // в худшем случе это будет ~ (config.Jabber.PingSplayDelay * 2) + config.Jabber.PingSplayDelay + if (time.Now().Unix() - lastServerActivity) < (config.Jabber.ServerPingDelay + config.Jabber.PingSplayDelay) { + continue + } + + if serverCapsQueried { // Сервер ответил на disco#info + var value interface{} + var exist bool + + value, exist = serverCapsList.Get("urn:xmpp:ping") + + switch { + // Сервер анонсировал, что умеет в c2s пинги + case exist && value.(bool): + // Таймаут c2s пинга. Возьмём сумму задержки между пингами, добавим таймаут коннекта и добавим + // максимальную корректировку разброса. + txTimeout := config.Jabber.ServerPingDelay + config.Jabber.ConnectionTimeout + txTimeout += config.Jabber.PingSplayDelay + rxTimeout := txTimeout + + rxTimeAgo := time.Now().Unix() - serverPingTimestampRx + + if serverPingTimestampTx > 0 { // Первая пуля от нас ушла... + switch { + // Давненько мы не получали понгов от сервера, вероятно, соединение с сервером утеряно? + case rxTimeAgo > (rxTimeout * 2): + log.Debugf( + "Stall connection detected. No c2s pong for %d seconds", + rxTimeAgo, + ) + + reEstablishConnection() + + // По-умолчанию, мы отправляем c2s пинг + default: + log.Debugf("Sending c2s ping from %s to %s", talk.JID(), config.Jabber.Server) + + if err := talk.PingC2S(talk.JID(), config.Jabber.Server); err != nil { + reEstablishConnection() + } else { + serverPingTimestampTx = time.Now().Unix() + } + } + } else { // Первая пуля пока не вылетела, отправляем + log.Debugf("Sending first c2s ping from %s to %s", talk.JID(), config.Jabber.Server) + + if err := talk.PingC2S(talk.JID(), config.Jabber.Server); err != nil { + reEstablishConnection() + } else { + serverPingTimestampTx = time.Now().Unix() + } + } + + // Сервер не анонсировал, что умеет в c2s пинги + default: + log.Debug("Sending keepalive whitespace ping") + + if _, err := talk.SendKeepAlive(); err != nil { + reEstablishConnection() + + continue + } + } + } else { // Сервер не ответил на disco#info + log.Debug("Sending keepalive whitespace ping") + + if _, err := talk.SendKeepAlive(); err != nil { + reEstablishConnection() + + continue + } + } + } +} + +// Пингует MUC-и, нужно для проверки, что клиент ещё находится в MUC-е +func probeMUCLiveness() { + for { + for _, room := range roomsConnected { + var exist bool + var lastActivityTs interface{} + + // Если записи про комнату нету, то пинговать её бессмысленно. + if lastActivityTs, exist = lastMucActivity.Get(room); !exist { + continue + } + + // Если время последней активности в чятике не превысило + // config.Jabber.ServerPingDelay + config.Jabber.PingSplayDelay, ничего не пингуем + if (time.Now().Unix() - lastActivityTs.(int64)) < (config.Jabber.ServerPingDelay + config.Jabber.PingSplayDelay) { + continue + } + + /* Пинг MUC-а по сценарию без серверной оптимизации мы реализовывать не будем. Это как-то не надёжно. + go func(room string) { + // Небольшая рандомная задержка перед пингом комнаты + sleepTime := time.Duration(rand.Int63n(1000*config.Jabber.PingSplayDelay)) * time.Millisecond //nolint:gosec + time.Sleep(sleepTime) + + if err := talk.PingS2S(talk.JID(), room+"/"+config.Jabber.Nick); err != nil { + reEstablishConnection() + } + }(room) + */ + + var roomMap interface{} + + roomMap, exist = mucCapsList.Get(room) + + // Пинги комнаты проводим, только если она записана, как прошедшая disco#info и поддерживающая + // Server Optimization + if exist && roomMap.(map[string]bool)["http://jabber.org/protocol/muc#self-ping-optimization"] { + go func(room string) { + // Небольшая рандомная задержка перед пингом комнаты + sleepTime := time.Duration(rand.Int63n(1000*config.Jabber.PingSplayDelay)) * time.Millisecond //nolint:gosec + time.Sleep(sleepTime) + + log.Debugf("Sending MUC ping from %s to %s", talk.JID(), room) + + if err := talk.PingS2S(talk.JID(), room); err != nil { + reEstablishConnection() + } + }(room) + } + } + + time.Sleep(time.Duration(config.Jabber.MucPingDelay) * time.Second) + } +} + +/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/main.go b/main.go new file mode 100644 index 0000000..ac01258 --- /dev/null +++ b/main.go @@ -0,0 +1,163 @@ +package main + +import ( + "crypto/tls" + "errors" + "fmt" + "io" + "net" + "os" + "os/signal" + "time" + + "github.com/eleksir/go-xmpp" + log "github.com/sirupsen/logrus" +) + +func init() { + log.SetFormatter(&log.TextFormatter{ //nolint:exhaustruct + DisableQuote: true, + DisableLevelTruncation: false, + DisableColors: true, + FullTimestamp: true, + TimestampFormat: "2006-01-02 15:04:05", + }) + + readConfig() + + // no panic + switch config.Loglevel { + case "fatal": + log.SetLevel(log.FatalLevel) + case "error": + log.SetLevel(log.ErrorLevel) + case "warn": + log.SetLevel(log.WarnLevel) + case "info": + log.SetLevel(log.InfoLevel) + case "debug": + log.SetLevel(log.DebugLevel) + case "trace": + log.SetLevel(log.TraceLevel) + default: + log.SetLevel(log.InfoLevel) + } + + // Пока про capabilities мы ничего не знаем + serverCapsQueried = false + + // Мы пока что не пинговали сервер. + serverPingTimestampRx = 0 + serverPingTimestampTx = 0 +} + +func main() { + // Откроем лог и скормим его логгеру + if config.Log != "" { + logfile, err := os.OpenFile(config.Log, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) + + if err != nil { + log.Fatalf("Unable to open log file %s: %s", config.Log, err) + } + + log.SetOutput(logfile) + } + + myLogLevel := log.GetLevel() + log.Warnf("Loglevel set to %v", myLogLevel) + + verboseClient := false + + if myLogLevel == log.TraceLevel { + verboseClient = true + } + + // github.com/mattn/go-xmpp пишет в stdio, нам этого не надо, ловим выхлоп его в logrus с уровнем trace + xmpp.DebugWriter = log.WithFields(log.Fields{"logger": "stdlib"}).WriterLevel(log.TraceLevel) + + options = &xmpp.Options{ //nolint:exhaustruct + Host: fmt.Sprintf("%s:%d", config.Jabber.Server, config.Jabber.Port), + User: config.Jabber.User, + Password: config.Jabber.Password, + Resource: "echo bot", + NoTLS: !config.Jabber.Ssl, + StartTLS: config.Jabber.StartTLS, + TLSConfig: &tls.Config{ //nolint:exhaustruct + ServerName: config.Jabber.Server, + InsecureSkipVerify: !config.Jabber.SslVerify, //nolint:gosec + }, + InsecureAllowUnencryptedAuth: config.Jabber.InsecureAllowUnencryptedAuth, + Debug: verboseClient, + Session: false, + Status: "xa", + StatusMessage: "echo bot is starting up", + DialTimeout: 10 * time.Second, + } + + go sigHandler() + signal.Notify(sigChan, os.Interrupt) + // Установим коннект + establishConnection() + + serverPingTimestampRx = time.Now().Unix() // Считаем, что если коннект запустился, то первый пинг успешен + + // Тыкаем сервер палочкой, проверяем, что коннект жив и переустанавливаем его, если он не жив + go probeServerLiveness() + + // Тыкаем muc-и палочкой, проверяем, что они живы и пере-заходим в них, если пинги пропали + go probeMUCLiveness() + + // Гребём сообщения... + for { + // Стриггерилось завершение работы приложения, или соединение не установлено (порвалось, например) + // грести не надо + if shutdown { + break + } + + if !isConnected { + continue + } + + chat, err := talk.Recv() + + if err != nil { + log.Errorf("Unable to get events from server: %s", err) + + switch { + // Стрим не читается, он закрылся с той стороны во время чтения + case errors.Is(err, io.EOF): + reEstablishConnection() + + continue + + // Пытаемся читать закрытый сокет + case errors.Is(err, net.ErrClosed): + // reEstablishConnection() + + continue + + // Не смогли записать в сокет + case errors.Is(err, net.ErrWriteToConnected): + reEstablishConnection() + + continue + + // Не сетевая проблема + default: + // Это уже что-то странное. + // Вероятно, ошибка парсинга xml. Собственно, баг сервера, тут мы ничего поделать не можем, разве что + // пропустить станцу... + // Cooldown + + time.Sleep(300 * time.Second) + + continue + } + } + + parseEvent(chat) + } +} + +/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/types.go b/types.go new file mode 100644 index 0000000..9d81519 --- /dev/null +++ b/types.go @@ -0,0 +1,74 @@ +package main + +import ( + "encoding/xml" +) + +// Конфиг +type myConfig struct { + Jabber struct { + Server string `json:"server,omitempty"` + Port int `json:"port,omitempty"` + Ssl bool `json:"ssl,omitempty"` + StartTLS bool `json:"starttls,omitempty"` + SslVerify bool `json:"ssl_verify,omitempty"` + InsecureAllowUnencryptedAuth bool `json:"insecureallowunencryptedauth,omitempty"` + ConnectionTimeout int64 `json:"connection_timeout,omitempty"` + ReconnectDelay int64 `json:"reconnect_delay,omitempty"` + ServerPingDelay int64 `json:"server_ping_delay,omitempty"` + MucPingDelay int64 `json:"muc_ping_delay,omitempty"` + MucRejoinDelay int64 `json:"muc_rejoin_delay,omitempty"` + PingSplayDelay int64 `json:"ping_splay_delay,omitempty"` + Nick string `json:"nick,omitempty"` + User string `json:"user,omitempty"` + Password string `json:"password,omitempty"` + Channels []string `json:"channels"` + } + Loglevel string `json:"loglevel,omitempty"` + Log string `json:"log,omitempty"` + NoEcho bool `json:"noecho,omitempty"` +} + +type jabberSimpleIqGetQuery struct { + XMLName xml.Name `xml:"query"` + Text string `xml:",chardata"` + Xmlns string `xml:"xmlns,attr"` + Node string `xml:"node,attr,omitempty"` // для xmlns="http://jabber.org/protocol/disco#items" +} + +type jabberPubsubIQGetQuery struct { + XMLName xml.Name `xml:"pubsub"` + Text string `xml:",chardata"` + Xmlns string `xml:"xmlns,attr"` + Items struct { + Text string `xml:",chardata"` + Node string `xml:"node,attr"` + MaxItems string `xml:"max_items,attr"` + } `xml:"items"` +} + +type jabberTimeIqGetQuery struct { + //