Compare commits

..

No commits in common. "master" and "cpp" have entirely different histories.
master ... cpp

25 changed files with 605 additions and 4176 deletions

View File

@ -1,5 +0,0 @@
[registry]
default = "crates-io"
[registries.elnafo-vcs]
index = "sparse+https://vcs.elnafo.ru/api/packages/L-Nafaryus/cargo/"

5
.clang-format Normal file
View File

@ -0,0 +1,5 @@
---
BasedOnStyle: LLVM
IndentWidth: 4
---
Language: Cpp

6
.containerignore Normal file
View File

@ -0,0 +1,6 @@
**
!lib/**
!CMakeLists.txt
!main.cc
!include.hh
!extern.cc

View File

@ -1,23 +0,0 @@
name: nix-build-publish
on:
push:
branches:
- master
jobs:
check:
runs-on: nix-runner
steps:
- uses: actions/checkout@v4
- run: |
NIXPKGS_ALLOW_BROKEN=1 nix flake check --allow-import-from-derivation --keep-going --impure
build:
runs-on: nix-runner
steps:
- uses: actions/checkout@v4
- uses: cachix/cachix-action@v14
with:
name: bonfire
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- run: nix build -L

123
.gitignore vendored
View File

@ -1,5 +1,118 @@
/result
/target
Secrets*.toml
/temp
.tmp
build*/
.floo
token.dat
cmake-build-*/
scratch.txt
env
# Prerequisites
*.d
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
*.smod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
# Created by https://www.gitignore.io/api/jetbrains+all
### JetBrains+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff:
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/dictionaries
# Sensitive or high-churn files:
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.xml
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
# Gradle:
.idea/**/gradle.xml
.idea/**/libraries
# CMake
cmake-build-debug/
# Mongo Explorer plugin:
.idea/**/mongoSettings.xml
## File-based project format:
*.iws
## Plugin-specific files:
# IntelliJ
/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
### JetBrains+all Patch ###
# Ignores the whole idea folder
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
.idea/
# End of https://www.gitignore.io/api/jetbrains+all
# Created by https://www.gitignore.io/api/visualstudiocode
# Edit at https://www.gitignore.io/?templates=visualstudiocode
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
# End of https://www.gitignore.io/api/visualstudiocode
*.log

59
CMakeLists.txt Normal file
View File

@ -0,0 +1,59 @@
cmake_minimum_required(VERSION 3.16)
project(discord-oscuro LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic")
include(cmake/macros.cmake)
include(cmake/CPM.cmake)
CPMAddPackage("gh:DiscordPP/discordpp#daf04ea")
CPMAddPackage("gh:DiscordPP/rest-simpleweb#4cf911b")
CPMAddPackage("gh:DiscordPP/websocket-simpleweb#9082991")
CPMAddPackage("gh:DiscordPP/plugin-native#238b7e8")
CPMAddPackage("gh:DiscordPP/plugin-overload#8862ac5")
CPMAddPackage("gh:DiscordPP/plugin-responder#3c2c485")
CPMAddPackage("gh:DiscordPP/plugin-interactionhandler#df07fd3")
CPMAddPackage("gh:DiscordPP/plugin-ratelimit#2c8cb34")
CPMAddPackage("gh:libcpr/cpr#1.10.4")
set(DISCORDPP_USE_BOOST OFF CACHE BOOL "Override option" FORCE)
CREATE_DISCORDPP_DEFINITIONS()
CREATE_DISCORDPP_INCLUDE()
set(THREADS_PREFER_PTHREAD_FLAG ON)
if(${DISCORDPP_USE_BOOST})
find_package(Boost 1.71.0 REQUIRED system date_time)
endif()
find_package(Threads REQUIRED)
find_package(OpenSSL REQUIRED)
add_executable(${PROJECT_NAME}
source/main.cpp
)
#target_precompile_headers(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/source/main.hpp)
target_link_libraries(${PROJECT_NAME}
${Boost_LIBRARIES}
Threads::Threads
${OPENSSL_LIBRARIES}
discordpp
discordpp-rest-simpleweb
discordpp-websocket-simpleweb
discordpp-plugin-native
discordpp-plugin-overload
discordpp-plugin-responder
discordpp-plugin-interactionhandler
discordpp-plugin-ratelimit
cpr::cpr
)
copy_file("env")

2881
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,24 +0,0 @@
[package]
name = "oscuro"
version = "0.1.1"
edition = "2021"
description = "Oscuro is a fancy multibot"
license = "MIT"
repository = "https://vcs.elnafo.ru/L-Nafaryus/oscuro"
publish = ["vcs-elnafo"]
[dependencies]
async-process = "2.2.3"
poise = "0.6.1"
rand = "0.8.5"
serde = { version = "1.0.204", features = ["derive"] }
serde_json = "1.0.120"
teloxide = { version = "0.12.2", features = ["macros"] }
tokio = { version = "1.26.0", features = ["macros", "rt-multi-thread"] }
toml = "0.8.15"
tracing = "0.1.40"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
[workspace]
resolver = "2"
members = []

29
Containerfile Normal file
View File

@ -0,0 +1,29 @@
FROM alpine:latest AS build
RUN apk update
RUN apk add --no-cache \
build-base \
cmake \
samurai \
boost-dev \
git \
openssl-dev
WORKDIR /echo_bot
COPY . .
RUN cmake -B build -G Ninja .
RUN cmake --build build
FROM alpine:latest
RUN apk update
RUN apk add --no-cache \
libgcc \
libstdc++ \
libc6-compat \
openssl-dev
WORKDIR /echo_bot
COPY --from=build /echo_bot/build/echo_bot .
CMD source /run/secrets/discord-oscuro-env; ./echo_bot

35
LICENSE
View File

@ -1,22 +1,21 @@
The MIT License (MIT)
MIT License
Copyright (c) 2023-2024 George Kusayko [L-Nafaryus] <l.nafaryus@gmail.com>
Copyright (c) 2023 George Kusayko <L-Nafaryus>
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:
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 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.
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.

View File

@ -1,4 +1,10 @@
# Oscuro - Discord Bot
# Oscuro - Discord Bot (c++)
## Note
- Create `env` file:
```sh
export BOT_TOKEN="Bot ....."
```
# License

23
cmake/CPM.cmake Normal file
View File

@ -0,0 +1,23 @@
set(CPM_DOWNLOAD_VERSION 0.38.1)
if(CPM_SOURCE_CACHE)
# Expand relative path. This is important if the provided path contains a tilde (~)
get_filename_component(CPM_SOURCE_CACHE ${CPM_SOURCE_CACHE} ABSOLUTE)
set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
elseif(DEFINED ENV{CPM_SOURCE_CACHE})
set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
else()
set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
endif()
if(NOT (EXISTS ${CPM_DOWNLOAD_LOCATION}))
message(STATUS "Downloading CPM.cmake to ${CPM_DOWNLOAD_LOCATION}")
file(DOWNLOAD
https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake
${CPM_DOWNLOAD_LOCATION}
)
endif()
include(${CPM_DOWNLOAD_LOCATION})

8
cmake/macros.cmake Normal file
View File

@ -0,0 +1,8 @@
function(copy_file FILE)
if (EXISTS ${CMAKE_SOURCE_DIR}/${FILE})
configure_file(${FILE} ${CMAKE_CURRENT_BINARY_DIR} COPYONLY)
elseif (EXISTS ${CMAKE_CURRENT_BINARY_DIR}/${FILE})
file(REMOVE ${CMAKE_CURRENT_BINARY_DIR}/${FILE})
endif ()
endfunction()

View File

@ -1,535 +0,0 @@
{
"nodes": {
"ags": {
"inputs": {
"nixpkgs": [
"bonfire",
"nixpkgs"
],
"systems": "systems"
},
"locked": {
"lastModified": 1721306136,
"narHash": "sha256-VKPsIGf3/a+RONBipx4lEE4LXG2sdMNkWQu22LNQItg=",
"owner": "Aylur",
"repo": "ags",
"rev": "344ea72cd3b8d4911f362fec34bce7d8fb37028c",
"type": "github"
},
"original": {
"owner": "Aylur",
"repo": "ags",
"type": "github"
}
},
"blobs": {
"flake": false,
"locked": {
"lastModified": 1604995301,
"narHash": "sha256-wcLzgLec6SGJA8fx1OEN1yV/Py5b+U5iyYpksUY/yLw=",
"owner": "simple-nixos-mailserver",
"repo": "blobs",
"rev": "2cccdf1ca48316f2cfd1c9a0017e8de5a7156265",
"type": "gitlab"
},
"original": {
"owner": "simple-nixos-mailserver",
"repo": "blobs",
"type": "gitlab"
}
},
"bonfire": {
"inputs": {
"ags": "ags",
"catppuccin": "catppuccin",
"crane": "crane",
"fenix": "fenix",
"home-manager": "home-manager",
"nixos-mailserver": "nixos-mailserver",
"nixpkgs": "nixpkgs",
"nixvim": "nixvim",
"obs-image-reaction": "obs-image-reaction",
"oscuro": [],
"sops-nix": "sops-nix",
"wezterm": "wezterm"
},
"locked": {
"lastModified": 1723204928,
"narHash": "sha256-r6oYWnHf5H9+5n80QocRTRwpZxsDK+BEq2Hg9QS4pxo=",
"owner": "L-Nafaryus",
"repo": "bonfire",
"rev": "bb7c9204f5070d47334da89adf611e5fda8a3f41",
"type": "github"
},
"original": {
"owner": "L-Nafaryus",
"repo": "bonfire",
"type": "github"
}
},
"catppuccin": {
"locked": {
"lastModified": 1720472194,
"narHash": "sha256-CYscFEts6tyvosc1T29nxhzIYJAj/1CCEkV3ZMzSN/c=",
"owner": "catppuccin",
"repo": "nix",
"rev": "d75d5803852fb0833767dc969a4581ac13204e22",
"type": "github"
},
"original": {
"owner": "catppuccin",
"repo": "nix",
"type": "github"
}
},
"crane": {
"inputs": {
"nixpkgs": [
"bonfire",
"nixpkgs"
]
},
"locked": {
"lastModified": 1721322122,
"narHash": "sha256-a0G1NvyXGzdwgu6e1HQpmK5R5yLsfxeBe07nNDyYd+g=",
"owner": "ipetkov",
"repo": "crane",
"rev": "8a68b987c476a33e90f203f0927614a75c3f47ea",
"type": "github"
},
"original": {
"owner": "ipetkov",
"repo": "crane",
"type": "github"
}
},
"fenix": {
"inputs": {
"nixpkgs": [
"bonfire",
"nixpkgs"
],
"rust-analyzer-src": [
"bonfire"
]
},
"locked": {
"lastModified": 1721629802,
"narHash": "sha256-GKlvM9M0mkKJrL6N1eMG4DrROO25Ds1apFw3/b8594w=",
"owner": "nix-community",
"repo": "fenix",
"rev": "1270fb024c6987dd825a20cd27319384a8d8569e",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "fenix",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": [
"bonfire",
"nixvim",
"nixpkgs"
]
},
"locked": {
"lastModified": 1719994518,
"narHash": "sha256-pQMhCCHyQGRzdfAkdJ4cIWiw+JNuWsTX7f0ZYSyz0VY=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "9227223f6d922fee3c7b190b2cc238a99527bbb7",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"freetype2": {
"flake": false,
"locked": {
"lastModified": 1687587065,
"narHash": "sha256-+Fh+/k+NWL5Ow9sDLtp8Cv/8rLNA1oByQQCIQS/bysY=",
"owner": "wez",
"repo": "freetype2",
"rev": "e4586d960f339cf75e2e0b34aee30a0ed8353c0d",
"type": "github"
},
"original": {
"owner": "wez",
"repo": "freetype2",
"rev": "e4586d960f339cf75e2e0b34aee30a0ed8353c0d",
"type": "github"
}
},
"harfbuzz": {
"flake": false,
"locked": {
"lastModified": 1711722720,
"narHash": "sha256-GdxcAPx5QyniSHPAN1ih28AD9JLUPR0ItqW9JEsl3pU=",
"owner": "harfbuzz",
"repo": "harfbuzz",
"rev": "63973005bc07aba599b47fdd4cf788647b601ccd",
"type": "github"
},
"original": {
"owner": "harfbuzz",
"ref": "8.4.0",
"repo": "harfbuzz",
"type": "github"
}
},
"home-manager": {
"inputs": {
"nixpkgs": [
"bonfire",
"nixpkgs"
]
},
"locked": {
"lastModified": 1721534365,
"narHash": "sha256-XpZOkaSJKdOsz1wU6JfO59Rx2fqtcarQ0y6ndIOKNpI=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "635563f245309ef5320f80c7ebcb89b2398d2949",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "home-manager",
"type": "github"
}
},
"libpng": {
"flake": false,
"locked": {
"lastModified": 1549245649,
"narHash": "sha256-1+cRp0Ungme/OGfc9kGJbklYIWAFxk8Il1M+NV4KSgw=",
"owner": "glennrp",
"repo": "libpng",
"rev": "8439534daa1d3a5705ba92e653eda9251246dd61",
"type": "github"
},
"original": {
"owner": "glennrp",
"repo": "libpng",
"rev": "8439534daa1d3a5705ba92e653eda9251246dd61",
"type": "github"
}
},
"nixos-mailserver": {
"inputs": {
"blobs": "blobs",
"flake-compat": "flake-compat",
"nixpkgs": [
"bonfire",
"nixpkgs"
],
"nixpkgs-24_05": "nixpkgs-24_05"
},
"locked": {
"lastModified": 1721121314,
"narHash": "sha256-zwc7YXga/1ppaZMWFreZykXtFwBgXodxUZiUx969r+g=",
"owner": "simple-nixos-mailserver",
"repo": "nixos-mailserver",
"rev": "059b50b2e729729ea00c6831124d3837c494f3d5",
"type": "gitlab"
},
"original": {
"owner": "simple-nixos-mailserver",
"repo": "nixos-mailserver",
"type": "gitlab"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1721379653,
"narHash": "sha256-8MUgifkJ7lkZs3u99UDZMB4kbOxvMEXQZ31FO3SopZ0=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "1d9c2c9b3e71b9ee663d11c5d298727dace8d374",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-24_05": {
"locked": {
"lastModified": 1717144377,
"narHash": "sha256-F/TKWETwB5RaR8owkPPi+SPJh83AQsm6KrQAlJ8v/uA=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "805a384895c696f802a9bf5bf4720f37385df547",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-24.05",
"type": "indirect"
}
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1721524707,
"narHash": "sha256-5NctRsoE54N86nWd0psae70YSLfrOek3Kv1e8KoXe/0=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "556533a23879fc7e5f98dd2e0b31a6911a213171",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "release-24.05",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1719223410,
"narHash": "sha256-jtIo8xR0Zp4SalIwmD+OdCwHF4l7OU6PD63UUK4ckt4=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "efb39c6052f3ce51587cf19733f5f4e5d515aa13",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixvim": {
"inputs": {
"devshell": [
"bonfire"
],
"flake-compat": [
"bonfire"
],
"flake-parts": "flake-parts",
"git-hooks": [
"bonfire"
],
"home-manager": [
"bonfire"
],
"nix-darwin": [
"bonfire"
],
"nixpkgs": [
"bonfire",
"nixpkgs"
],
"treefmt-nix": [
"bonfire"
]
},
"locked": {
"lastModified": 1721772245,
"narHash": "sha256-//9p3Qm8gLbPUTsSGN2EMYkDwE5Sqq9B9P2X/z2+npw=",
"owner": "nix-community",
"repo": "nixvim",
"rev": "ab67ee7e8b33e788fc53d26dc6f423f9358e3e66",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixvim",
"type": "github"
}
},
"obs-image-reaction": {
"inputs": {
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1719314544,
"narHash": "sha256-GZa3+2OELKp/9b2+EwwzaIMNvR9niCy/YZ5OERhG9Hg=",
"owner": "L-Nafaryus",
"repo": "obs-image-reaction",
"rev": "0dcb3c27de5782dfdf95cb047ccceb3e65360e6b",
"type": "github"
},
"original": {
"owner": "L-Nafaryus",
"repo": "obs-image-reaction",
"type": "github"
}
},
"root": {
"inputs": {
"bonfire": "bonfire",
"nixpkgs": [
"bonfire",
"nixpkgs"
]
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": [
"bonfire",
"wezterm",
"nixpkgs"
]
},
"locked": {
"lastModified": 1721441897,
"narHash": "sha256-gYGX9/22tPNeF7dR6bWN5rsrpU4d06GnQNNgZ6ZiXz0=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "b7996075da11a2d441cfbf4e77c2939ce51506fd",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"sops-nix": {
"inputs": {
"nixpkgs": [
"bonfire",
"nixpkgs"
],
"nixpkgs-stable": "nixpkgs-stable"
},
"locked": {
"lastModified": 1721531171,
"narHash": "sha256-AsvPw7T0tBLb53xZGcUC3YPqlIpdxoSx56u8vPCr6gU=",
"owner": "Mic92",
"repo": "sops-nix",
"rev": "909e8cfb60d83321d85c8d17209d733658a21c95",
"type": "github"
},
"original": {
"owner": "Mic92",
"repo": "sops-nix",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1689347949,
"narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=",
"owner": "nix-systems",
"repo": "default-linux",
"rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default-linux",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"wezterm": {
"inputs": {
"flake-utils": "flake-utils",
"freetype2": "freetype2",
"harfbuzz": "harfbuzz",
"libpng": "libpng",
"nixpkgs": [
"bonfire",
"nixpkgs"
],
"rust-overlay": "rust-overlay",
"zlib": "zlib"
},
"locked": {
"dir": "nix",
"lastModified": 1722353247,
"narHash": "sha256-pPH+IJ8pljR+PmeOdckoHvbQVfSBdStKbgXcaqdkTRk=",
"owner": "wez",
"repo": "wezterm",
"rev": "56a27e93a9ee50aab50ff4d78308f9b3154b5122",
"type": "github"
},
"original": {
"dir": "nix",
"owner": "wez",
"repo": "wezterm",
"type": "github"
}
},
"zlib": {
"flake": false,
"locked": {
"lastModified": 1484501380,
"narHash": "sha256-j5b6aki1ztrzfCqu8y729sPar8GpyQWIrajdzpJC+ww=",
"owner": "madler",
"repo": "zlib",
"rev": "cacf7f1d4e3d44d871b605da3b647f07d718623f",
"type": "github"
},
"original": {
"owner": "madler",
"ref": "v1.2.11",
"repo": "zlib",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

191
flake.nix
View File

@ -1,191 +0,0 @@
{
description = "Oscuro is a fancy multibot";
nixConfig = {
extra-substituters = [
"https://cache.elnafo.ru"
"https://bonfire.cachix.org"
];
extra-trusted-public-keys = [
"cache.elnafo.ru:j3VD+Hn+is2Qk3lPXDSdPwHJQSatizk7V82iJ2RP1yo="
"bonfire.cachix.org-1:mzAGBy/Crdf8NhKail5ciK7ZrGRbPJJobW6TwFb7WYM="
];
};
inputs = {
bonfire = {
url = "github:L-Nafaryus/bonfire";
inputs = {
oscuro.follows = "";
};
};
nixpkgs.follows = "bonfire/nixpkgs";
};
outputs = {
self,
nixpkgs,
bonfire,
...
}: let
pkgs = nixpkgs.legacyPackages.x86_64-linux;
lib = pkgs.lib;
fenixPkgs = bonfire.inputs.fenix.packages.x86_64-linux;
craneLib = (bonfire.inputs.crane.mkLib pkgs).overrideToolchain fenixPkgs.complete.toolchain;
in {
packages.x86_64-linux = rec {
oscuro = let
common = {
src = pkgs.lib.cleanSourceWith {
src = ./.;
filter = path: type: (craneLib.filterCargoSources path type);
};
strictDeps = true;
nativeBuildInputs = [pkgs.pkg-config pkgs.makeWrapper];
buildInputs = [pkgs.openssl];
};
cargoArtifacts = craneLib.buildDepsOnly common;
in
craneLib.buildPackage (common
// rec {
pname = "oscuro";
version = "0.1.0";
inherit cargoArtifacts;
postInstall = ''
wrapProgram $out/bin/${pname} \
--prefix LD_LIBRARY_PATH : ${pkgs.lib.makeLibraryPath common.buildInputs} \
'';
});
default = oscuro;
};
hydraJobs = {
packages = self.packages;
};
devShells.x86_64-linux.default = pkgs.mkShell {
nativeBuildInputs = [pkgs.pkg-config];
buildInputs = [
fenixPkgs.complete.toolchain
pkgs.cargo-release
pkgs.openssl
];
LD_LIBRARY_PATH = lib.makeLibraryPath [pkgs.openssl];
};
nixosModules = rec {
oscuro = {
config,
lib,
pkgs,
...
}:
with lib; let
cfg = config.services.oscuro;
opt = options.services.oscuro;
pkg = self.packages.${pkgs.system}.oscuro;
configFile = pkgs.writeText "config.toml" ''
discord_token = "#discord_token#"
'';
in {
options.services.oscuro = {
enable = mkEnableOption "Enables the Oscuro bot";
package = mkPackageOption pkgs "oscuro" {};
dataDir = mkOption {
type = types.path;
default = "/var/lib/oscuro";
description = lib.mdDoc "Directory to store Oscuro files";
};
discordToken = mkOption {
type = types.nullOr types.str;
default = null;
example = "Bot TOKENTOKENTOKEN";
};
discordTokenFile = mkOption {
type = types.nullOr types.str;
default = null;
example = "/var/lib/secrets/oscuro/discord_token";
};
};
config = mkIf cfg.enable {
assertions = [
{
assertion = cfg.discordToken != null || cfg.discordTokenFile != null;
message = "Discord token must be set. Use `services.oscuro.discordToken` or `services.oscuro.discordTokenFile`.";
}
];
users.users.oscuro = {
description = "Oscuro bot service user";
home = cfg.dataDir;
createHome = true;
isSystemUser = true;
group = "oscuro";
};
users.groups.oscuro = {};
systemd.services.oscuro = {
description = "Oscuro";
wantedBy = ["multi-user.target"];
after = ["network.target"];
serviceConfig = {
Restart = "always";
ExecStart = "${pkg}/bin/oscuro";
User = "oscuro";
WorkingDirectory = cfg.dataDir;
};
preStart = let
runConfig = "${cfg.dataDir}/config.toml";
replaceSecret = "${pkgs.replace-secret}/bin/replace-secret";
in ''
cp -f '${configFile}' '${runConfig}'
chmod u+w '${runConfig}'
${lib.optionalString (cfg.discordTokenFile != null) ''
${replaceSecret} '#discord_token#' '${cfg.discordTokenFile}' '${runConfig}'
''}
${lib.optionalString (cfg.discordToken != null) ''
sed -i 's/#discord_token#/${cfg.discordToken}/g' '${runConfig}'
''}
'';
};
};
};
default = oscuro;
};
nixosConfigurations.oscuro = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
self.nixosModules.oscuro
({pkgs, ...}: {
boot.isContainer = true;
networking.hostName = "oscuro";
networking.useDHCP = false;
services.oscuro = {
enable = true;
discordToken = ""; # insert token
};
system.stateVersion = "24.05";
})
];
};
};
}

22
source/bot.hpp Normal file
View File

@ -0,0 +1,22 @@
#pragma once
#include <discordpp/bot.hh>
#include <discordpp/rest-simpleweb.hh>
#include <discordpp/websocket-simpleweb.hh>
#include <discordpp/plugin-native.hh>
#include <discordpp/plugin-overload.hh>
#include <discordpp/plugin-responder.hh>
#include <discordpp/plugin-interactionhandler.hh>
#include <discordpp/plugin-ratelimit.hh>
using Bot = discordpp::PluginRateLimit
<discordpp::PluginInteractionHandler
<discordpp::PluginResponder
<discordpp::PluginOverload
<discordpp::PluginNative
<discordpp::WebsocketSimpleWeb
<discordpp::RestSimpleWeb
<discordpp::Bot>>>>>>>;
namespace dpp = discordpp;

19
source/commands.hpp Normal file
View File

@ -0,0 +1,19 @@
#pragma once
#include <functional>
#include <nlohmann/json.hpp>
#include "bot.hpp"
using json = nlohmann::json;
using event = std::function<void(const json)>;
auto help(const std::shared_ptr<Bot>& bot)
{
return [&bot](const dpp::MessageCreateEvent& msg)
{
};
}

218
source/main.cpp Normal file
View File

@ -0,0 +1,218 @@
#include <fstream>
#include <iostream>
#include <regex>
#ifdef ASIO_STANDALONE
#include <asio.hpp>
#else
#include <boost/asio.hpp>
namespace asio = boost::asio;
#endif
#include "bot.hpp"
using json = nlohmann::json;
#include <cpr/cpr.h>
#include "utils.hpp"
#include "commands.hpp"
int main() {
dpp::log::filter = dpp::log::debug;
dpp::log::out = &std::cerr;
std::cout << "Starting bot ..." << std::endl;
std::string token = getToken();
if (token.empty()) {
std::cerr << "Failed to read token from environment. Exiting ..." << std::endl;
exit(1);
}
dpp::User self;
auto bot = std::make_shared<Bot>();
bot->debugUnhandled = true;
bot->intents = dpp::intents::NONE | dpp::intents::GUILD_MESSAGES;
bot->handlers.insert({
"READY",
[&self](dpp::ReadyEvent ready){ self = *ready.user; }
});
bot->prefix = "/";
bot->respond("test", help(bot));
bot->respond("help", "Mention me and I'll echo your message back!");
bot->respond("about", [&bot](dpp::MessageCreateEvent msg) {
std::ostringstream content;
content << "Sure thing, "
<< *(msg.member->nick ? msg.author->username : msg.member->nick)
<< "!\n"
<< "I'm a simple bot meant to demonstrate the "
"Discord++ library.\n"
<< "You can learn more about Discord++ at "
"https://discord.gg/VHAyrvspCx";
bot->createMessage()
->channel_id(*msg.channel_id)
->content(content.str())
->run();
});
bot->respond("lookatthis", [&bot](dpp::MessageCreateEvent msg) {
std::ifstream ifs("image.jpg", std::ios::binary);
if (!ifs) {
std::cerr << "Couldn't load file 'image.jpg'!\n";
return;
}
ifs.seekg(0, std::ios::end);
std::ifstream::pos_type fileSize = ifs.tellg();
ifs.seekg(0, std::ios::beg);
auto file = std::make_shared<std::string>(fileSize, '\0');
ifs.read(file->data(), fileSize);
bot->createMessage()
->channel_id(*msg.channel_id)
->content("Look at this photograph")
->filename("image.jpg")
->filetype("image/jpg")
->file(file)
->run();
});
bot->respond("notacat", [&bot](dpp::MessageCreateEvent msg)
{
cpr::Response res = cpr::Get(cpr::Url{"https://cataas.com/cat"});
std::cout << "Fetching a cat: " << res.status_code << ", " << res.header["content-type"] << std::endl;
bot->createMessage()
->channel_id(*msg.channel_id)
->filename("cat.jpg")
->filetype(res.header["content-type"])
->file(res.text)
->run();
});
bot->respond("channelinfo", [&bot](dpp::MessageCreateEvent msg) {
bot->getChannel()
->channel_id(*msg.channel_id)
->onRead([&bot, msg](bool error, json res) {
bot->createMessage()
->channel_id(*msg.channel_id)
->content("```json\n" + res["body"].dump(4) + "\n```")
->run();
})
->run();
});
bot->respond("register", [&bot, &self](dpp::MessageCreateEvent msg) {
if (*msg.author->id == 272712928074006528) {
bot->createGuildApplicationCommand()
->application_id(*self.id)
->guild_id(*msg.guild_id)
->name("echo")
->description("Echoes what you say")
->options({dpp::ApplicationCommandOption(
dpp::ApplicationCommandOptionType::STRING,
std::string("message"), dpp::omitted, std::string("The message to echo"),
dpp::omitted, true)})
->command_type(dpp::ApplicationCommandType::CHAT_INPUT)
->onRead([](bool error, json res) {
std::cout << res.dump(4) << std::endl;
})
->run();
}
bot->createGuildApplicationCommand()
->application_id(*self.id)
->guild_id(*msg.guild_id)
->name("lookatthis")
->description("Help information")
->options({dpp::ApplicationCommandOption(
dpp::ApplicationCommandOptionType::STRING,
std::string("message"), dpp::omitted, std::string("The message to echo"),
dpp::omitted, true)})
->command_type(dpp::ApplicationCommandType::CHAT_INPUT)
->onRead([](bool error, json res) {
std::cout << res.dump(4) << std::endl;
})
->run();
});
bot->interactionHandlers.insert(
{1102290596896460850, [&bot](dpp::Interaction msg) {
bot->createResponse()
->interaction_id(*msg.id)
->interaction_token(*msg.token)
->interaction_type(
dpp::InteractionCallbackType::CHANNEL_MESSAGE_WITH_SOURCE)
->data({{
"content",
*std::get<dpp::ApplicationCommandData>(*msg.data).options->at(0).value
}})
->run();
}});
// Create handler for the MESSAGE_CREATE payload, this receives all messages
// sent that the bot can see.
bot->handlers.insert(
{"MESSAGE_CREATE", [&bot, &self](const dpp::MessageCreateEvent msg) {
// Ignore messages from other bots
if (msg.webhook_id || (msg.author->bot && *msg.author->bot)) {
return;
}
// Scan through mentions in the message for self
bool mentioned = false;
for (const dpp::User &mention : *msg.mentions) {
mentioned = mentioned || (*mention.id == *self.id);
}
if (mentioned) {
// Identify and remove mentions of self from the message
std::stringstream content;
content << "чё тебе надо, ";//*msg.content;
/*unsigned int oldlength, length = content.length();
do {
oldlength = length;
content = std::regex_replace(
content,
std::regex(R"(<@!?)" + std::to_string(*self.id) +
R"(> ?)"),
"");
length = content.length();
} while (oldlength > length);
*/
// Get the target user's display name
std::string name = *(msg.member->nick ? msg.member->nick
: msg.author->username);
content << name << "?";
//std::cout << "Echoing " << name << '\n';
// Echo the created message
bot->createMessage()
->channel_id(*msg.channel_id)
->content(content.str())
->run();
// Set status to Playing "with [author]"
/*bot->send(3,
{{"game", {{"name", "with " + name}, {"type", 0}}},
{"status", "online"},
{"afk", false},
{"since", "null"}});*/
}
}});
auto asioCtx = std::make_shared<asio::io_context>();
bot->initBot(9, token, asioCtx);
bot->run();
return 0;
}

74
source/utils.hpp Normal file
View File

@ -0,0 +1,74 @@
#pragma once
#include <fstream>
#include <iostream>
/*/
* Source: https://stackoverflow.com/a/6089413/1526048
/*/
std::istream &safeGetline(std::istream& is, std::string& str)
{
str.clear();
// The characters in the stream are read one-by-one using a std::streambuf.
// That is faster than reading them one-by-one using the std::istream.
// Code that uses streambuf this way must be guarded by a sentry object.
// The sentry object performs various tasks,
// such as thread synchronization and updating the stream state.
std::istream::sentry se(is, true);
std::streambuf *sb = is.rdbuf();
for (;;)
{
int c = sb->sbumpc();
switch (c)
{
case '\n':
return is;
case '\r':
if (sb->sgetc() == '\n') {
sb->sbumpc();
}
return is;
case std::streambuf::traits_type::eof():
// Also handle the case when the last line has no line ending
if (str.empty())
is.setstate(std::ios::eofbit);
return is;
default:
str += (char)c;
}
}
}
std::string getToken(const std::string& filename = "")
{
std::string token;
char const *env = std::getenv("BOT_TOKEN");
if (env != nullptr)
{
token = std::string(env);
}
else
{
std::ifstream tokenFile(filename);
if (!tokenFile)
return "";
safeGetline(tokenFile, token);
tokenFile.close();
}
return token;
}

View File

@ -1,99 +0,0 @@
use std::env;
use std::fs;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
pub discord_token: Option<String>,
pub telegram_token: Option<String>,
}
impl Config {
pub fn data_dir() -> Result<std::path::PathBuf, ConfigError> {
let cwd = std::env::current_dir()?;
if cfg!(debug_assertions) {
Ok(cwd.join("temp"))
} else {
Ok(cwd)
}
}
pub fn open(path: &std::path::Path) -> Result<Config, ConfigError> {
fs::read_to_string(path)?.parse()
}
pub fn to_string(&self) -> Result<String, ConfigError> {
Ok(toml::to_string(self)?)
}
pub fn write(&self, path: &std::path::Path) -> Result<(), ConfigError> {
Ok(fs::write(path, self.to_string()?)?)
}
pub fn with_env(mut self) -> Self {
if let Ok(token) = env::var("OSCURO_DISCORD_TOKEN") {
self.discord_token = Some(token);
};
if let Ok(token) = env::var("OSCURO_TELEGRAM_TOKEN") {
self.telegram_token = Some(token);
};
self
}
}
impl Default for Config {
fn default() -> Self {
Self {
discord_token: Some(String::new()),
telegram_token: Some(String::new()),
}
}
}
impl std::str::FromStr for Config {
type Err = ConfigError;
fn from_str(s: &str) -> Result<Self, ConfigError> {
toml::from_str(s).map_err(|_| ConfigError::Parse)
}
}
#[derive(Debug)]
pub enum ConfigError {
Parse,
StringParse,
Serialize,
IO,
}
impl std::error::Error for ConfigError {}
impl std::fmt::Display for ConfigError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::Parse => write!(f, "Failed to parse config from string"),
Self::StringParse => write!(f, "Failed to parse environment variable"),
Self::Serialize => write!(f, "Failed to serialize Config to TOML"),
Self::IO => write!(f, "Failed to write file"),
}
}
}
impl From<toml::ser::Error> for ConfigError {
fn from(_: toml::ser::Error) -> Self {
ConfigError::Serialize
}
}
impl From<std::io::Error> for ConfigError {
fn from(_: std::io::Error) -> Self {
ConfigError::IO
}
}
impl From<std::num::ParseIntError> for ConfigError {
fn from(_: std::num::ParseIntError) -> Self {
ConfigError::StringParse
}
}

View File

@ -1,84 +0,0 @@
use async_process::Command;
use poise::serenity_prelude as serenity;
use rand::Rng;
use std::collections::HashMap;
use std::str;
use super::Context;
use crate::errors::BoxedError;
#[poise::command(prefix_command)]
pub async fn register(ctx: Context<'_>) -> Result<(), BoxedError> {
poise::builtins::register_application_commands_buttons(ctx).await?;
Ok(())
}
#[poise::command(slash_command, prefix_command)]
pub async fn age(
ctx: Context<'_>,
#[description = "Ooph user"] user: Option<serenity::User>,
) -> Result<(), BoxedError> {
let u = user.as_ref().unwrap_or_else(|| ctx.author());
let response = format!("{}'s account was created at {}", u.name, u.created_at());
ctx.say(response).await?;
Ok(())
}
#[poise::command(slash_command, prefix_command)]
pub async fn dice(ctx: Context<'_>) -> Result<(), BoxedError> {
let number = {
let mut rng = rand::thread_rng();
rng.gen_range(1..21)
};
let response = format!("{} throws {}.", ctx.author(), number);
let response = match number {
20 => format!("{} Critical success.", response),
1 => format!("{} Critical failure.", response),
_ => response,
};
ctx.say(response).await?;
Ok(())
}
#[derive(Debug, poise::ChoiceParameter)]
pub enum ServiceChoice {
#[name = "Elnafo VCS"]
ElnafoVcs,
#[name = "Elnafo Mail"]
ElnafoMail,
}
#[poise::command(slash_command, prefix_command)]
pub async fn status(
ctx: Context<'_>,
#[description = "Check service status"] service: ServiceChoice,
) -> Result<(), BoxedError> {
let mut systemctl = Command::new("systemctl");
let service_info = match service {
ServiceChoice::ElnafoVcs => systemctl.arg("show").arg("gitea.service"),
ServiceChoice::ElnafoMail => systemctl.arg("show").arg("acpid.service"),
};
let output = service_info.output().await?;
let mut data: HashMap<&str, &str> = HashMap::new();
for line in str::from_utf8(&output.stdout)?.lines() {
let kv: Vec<&str> = line.split('=').collect();
data.insert(kv[0], kv[1]);
}
println!("{:?} {:?}", data["LoadState"], data["SubState"]);
if data["LoadState"] == "loaded" && data["SubState"] == "running" {
ctx.say(format!(
"{:?} is up and running for {}",
service, data["ExecMainStartTimestamp"]
))
.await?;
} else {
ctx.say(format!("{:?} is dead", service)).await?;
}
Ok(())
}

View File

@ -1,148 +0,0 @@
pub mod commands;
use crate::config::Config;
use crate::errors::BoxedError;
use std::sync::Arc;
use poise::serenity_prelude::{
self as serenity,
builder::{CreateEmbed, CreateMessage},
model::id::ChannelId,
prelude::TypeMapKey,
//Client,
};
use serenity::GatewayIntents;
use teloxide::prelude::*;
use teloxide::types::Recipient;
use crate::telegram;
#[derive(Debug, Clone)]
pub struct BotState {
pub config: Config,
pub telegram_agent: Option<telegram::Client>,
}
impl TypeMapKey for BotState {
type Value = BotState;
}
type Context<'a> = poise::Context<'a, BotState, BoxedError>;
pub struct Client {
client: serenity::Client,
}
impl Client {
pub async fn new(config: Config) -> Result<Self, BoxedError> {
let telegram_agent = if config.clone().telegram_token.is_some() {
Some(telegram::Client::new(config.clone()))
} else {
None
};
let state = BotState {
config: config.clone(),
telegram_agent: telegram_agent,
};
let intents = GatewayIntents::GUILDS
| GatewayIntents::GUILD_MESSAGES
| GatewayIntents::MESSAGE_CONTENT;
let state_copy = state.clone();
let framework = poise::Framework::builder()
.options(poise::FrameworkOptions {
commands: vec![
commands::register(),
commands::age(),
commands::dice(),
commands::status(),
],
event_handler: |ctx, event, framework, data| {
Box::pin(event_handler(ctx, event, framework, data))
},
..Default::default()
})
.setup(|ctx, _ready, framework| {
Box::pin(async move {
poise::builtins::register_globally(ctx, &framework.options().commands).await?;
Ok(state_copy)
})
})
.build();
let client =
serenity::ClientBuilder::new(state.clone().config.discord_token.unwrap(), intents)
.framework(framework)
.await?;
{
let mut data = client.data.write().await;
data.insert::<BotState>(state);
}
Ok(Self { client })
}
pub async fn start(&mut self) -> Result<(), BoxedError> {
self.client.start().await;
Ok(())
}
pub async fn send(&self, chat_id: u64, msg: String) -> Result<(), BoxedError> {
let builder = CreateMessage::new().content(msg);
let message = ChannelId::new(chat_id)
.send_message(&self.client.http, builder)
.await?;
Ok(())
}
}
async fn event_handler(
ctx: &serenity::Context,
event: &serenity::FullEvent,
_framework: poise::FrameworkContext<'_, BotState, BoxedError>,
_state: &BotState,
) -> Result<(), BoxedError> {
match event {
serenity::FullEvent::Ready { data_about_bot, .. } => {
tracing::info!("discord: Logged in as {}", data_about_bot.user.name);
// We can use ChannelId directly to send a message to a specific channel; in this case, the
// message would be sent to the #testing channel on the discord server.
/*let embed = CreateEmbed::new().title("System Resource Load").field(
"CPU Load Average",
format!("{:.2}%", 10.0),
false,
);
let builder = CreateMessage::new().embed(embed);
let message = ChannelId::new(1145642256443904002)
.send_message(&ctx, builder)
.await;
if let Err(why) = message {
eprintln!("Error sending message: {why:?}");
};*/
}
serenity::FullEvent::Message { new_message } => {
let mut data = ctx.data.write().await;
let state = data.get_mut::<BotState>().unwrap();
println!("{:?}", new_message);
let author = new_message
.author
.global_name
.clone()
.or(Some(new_message.author.name.clone()))
.unwrap();
if let Some(agent) = &state.telegram_agent {
agent
.send(-4221527632, format!("{}: {}", author, new_message.content))
.await;
}
}
_ => {}
}
Ok(())
}

View File

@ -1 +0,0 @@
pub type BoxedError = Box<dyn std::error::Error + Send + Sync>;

View File

@ -1,76 +0,0 @@
mod config;
mod discord;
mod errors;
mod telegram;
use config::Config;
use tokio::signal;
use tokio::task::JoinSet;
#[tokio::main]
async fn main() -> Result<(), errors::BoxedError> {
tracing_subscriber::fmt()
.with_target(false)
.compact()
.init();
tracing::info!("Working directory: {:?}", Config::data_dir()?);
let config = match Config::open(Config::data_dir()?.join("config.toml").as_path()) {
Ok(config) => config,
Err(err) => {
tracing::debug!("{}", err);
tracing::info!("Using default configuration");
Config::default()
}
}
.with_env();
let mut runset = JoinSet::new();
if !config.clone().discord_token.is_some() {
tracing::warn!("Missing discord token");
} else {
let mut discord_client = discord::Client::new(config.clone())
.await
.expect("Failed to create discord client");
runset.spawn(async move {
let res = discord_client.start().await;
if let Err(err) = res {
tracing::error!("{}", err);
}
});
}
if !config.clone().telegram_token.is_some() {
tracing::warn!("Missing telegram token");
} else {
let telegram_client = telegram::Client::new(config);
runset.spawn(async move {
let res = telegram_client.start().await;
if let Err(err) = res {
tracing::error!("{}", err);
}
});
}
while let Some(res) = runset.join_next().await {
if let Err(err) = res {
tracing::error!("{}", err);
}
}
match signal::ctrl_c().await {
Ok(()) => {}
Err(err) => {
eprintln!("Unable to listen for shutdown signal: {}", err);
// we also shut down in case of error
}
}
Ok(())
}

View File

@ -1,85 +0,0 @@
use teloxide::prelude::*;
use teloxide::types::Recipient;
use teloxide::utils::command::BotCommands;
use crate::config::Config;
use crate::errors;
use rand::Rng;
async fn main() {
let bot = Bot::from_env();
/*let http = Http::new("");
let webhook = Webhook::from_url(&http, "https://discord.com/api/webhooks/1259860143579987999/whI0ozB5uc17Wdzkb2-HSrVGi8h_MyR2_4eyCsGuGpQN4KcjMhq7rfQH1JIdbD1HNaW_")
.await
.expect("Replace the webhook with your own");
let builder = ExecuteWebhook::new().content("hello there").username("Webhook test");
webhook.execute(&http, false, builder).await.expect("Could not execute webhook.");
*/
teloxide::repl(bot, |bot: Bot, msg: Message| async move {
bot.send_dice(msg.chat.id).await?;
Ok(())
})
.await;
/*bot.send_message(Recipient::Id(ChatId(-4221527632)), "Heya!")
.await
.expect("err");*/
}
#[derive(Clone, Debug)]
pub struct Client {
bot: Bot,
}
impl Client {
pub fn new(config: Config) -> Self {
Self {
bot: Bot::new(config.telegram_token.unwrap()),
}
}
pub async fn start(&self) -> Result<(), errors::BoxedError> {
Command::repl(self.bot.clone(), event_handler).await;
Ok(())
}
pub async fn send(&self, chat_id: i64, msg: String) -> ResponseResult<()> {
self.bot
.send_message(Recipient::Id(ChatId(chat_id)), msg)
.await?;
Ok(())
}
}
#[derive(BotCommands, Clone)]
#[command(rename_rule = "lowercase")]
pub enum Command {
#[command()]
Dice,
}
async fn event_handler(bot: Bot, msg: Message, cmd: Command) -> ResponseResult<()> {
match cmd {
Command::Dice => {
let number = {
let mut rng = rand::thread_rng();
rng.gen_range(1..21)
};
let response = format!("{} throws {}.", "test", number);
let response = match number {
20 => format!("{} Critical success.", response),
1 => format!("{} Critical failure.", response),
_ => response,
};
// -4221527632
bot.send_message(Recipient::Id(msg.chat.id), response)
.await?;
}
};
Ok(())
}