Rewrite bot into Python
This commit is contained in:
2
.env.example
Normal file
2
.env.example
Normal file
@ -0,0 +1,2 @@
|
||||
DISCORD_TOKEN=
|
||||
OPENAI_TOKEN=
|
22
.github/workflows/docker-publish.yml
vendored
22
.github/workflows/docker-publish.yml
vendored
@ -1,4 +1,4 @@
|
||||
name: Test and Build Docker Image
|
||||
name: Build Docker Image
|
||||
|
||||
on:
|
||||
push:
|
||||
@ -7,30 +7,14 @@ on:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
env:
|
||||
DISCORD_TOKEN: 0
|
||||
OPENAI_TOKEN: 0
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "stable"
|
||||
- run: go test ./... -v
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
needs: test
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
env:
|
||||
DISCORD_TOKEN: 0
|
||||
OPENAI_TOKEN: 0
|
||||
DISCORD_TOKEN: '0'
|
||||
OPENAI_TOKEN: '0'
|
||||
if: github.event_name != 'pull_request' && github.event_name != 'schedule'
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
172
.gitignore
vendored
172
.gitignore
vendored
@ -1,26 +1,162 @@
|
||||
# If you prefer the allow list template instead of the deny list, see community template:
|
||||
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||
#
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Dependency directories
|
||||
vendor/
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Discord bot token
|
||||
settings.json
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
||||
.pdm.toml
|
||||
.pdm-python
|
||||
.pdm-build/
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
.idea/
|
||||
|
12
.vscode/launch.json
vendored
12
.vscode/launch.json
vendored
@ -1,12 +0,0 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch Package",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"program": "${fileDirname}"
|
||||
}
|
||||
]
|
||||
}
|
21
.vscode/settings.json
vendored
21
.vscode/settings.json
vendored
@ -1,25 +1,8 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"Andreas",
|
||||
"anewdawn",
|
||||
"bwmarrin",
|
||||
"discordgo",
|
||||
"Filip",
|
||||
"forgefilip",
|
||||
"Fredrik",
|
||||
"godotenv",
|
||||
"GoodAnimemes",
|
||||
"joho",
|
||||
"forgor",
|
||||
"lovibot",
|
||||
"openai",
|
||||
"Piplup",
|
||||
"plubplub",
|
||||
"Ryouiki",
|
||||
"sashabaranov",
|
||||
"startswith",
|
||||
"vartanbeno",
|
||||
"waifu",
|
||||
"waifus",
|
||||
"Zettai"
|
||||
"plubplub"
|
||||
]
|
||||
}
|
||||
|
46
Dockerfile
46
Dockerfile
@ -1,44 +1,10 @@
|
||||
FROM golang:alpine
|
||||
FROM python:3.12-slim
|
||||
|
||||
# Git is required for go mod download
|
||||
RUN apk update && apk add --no-cache git ca-certificates
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
ENV PYTHONDONTWRITEBYTECODE=1
|
||||
|
||||
ENV USER=anewdawn
|
||||
ENV UID=10001
|
||||
|
||||
# Create anewdawn user
|
||||
RUN adduser \
|
||||
--disabled-password \
|
||||
--gecos "" \
|
||||
--home "/nonexistent" \
|
||||
--shell "/sbin/nologin" \
|
||||
--no-create-home \
|
||||
--uid "${UID}" \
|
||||
"${USER}"
|
||||
|
||||
# Set the working directory
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Copy the current directory contents into the container at /usr/src/app
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
RUN pip install .
|
||||
|
||||
# Download dependencies
|
||||
RUN go get -d -v
|
||||
|
||||
# Build the binary
|
||||
RUN GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o /usr/local/bin/anewdawn
|
||||
|
||||
FROM scratch
|
||||
|
||||
COPY --from=0 /etc/passwd /etc/passwd
|
||||
COPY --from=0 /etc/group /etc/group
|
||||
COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||
|
||||
# Copy the binary from the first stage
|
||||
COPY --from=0 /usr/local/bin/anewdawn /usr/local/bin/anewdawn
|
||||
|
||||
# Use an unprivileged user.
|
||||
USER anewdawn:anewdawn
|
||||
|
||||
# Command to run the executable
|
||||
ENTRYPOINT ["/usr/local/bin/anewdawn"]
|
||||
CMD ["python", "main.py"]
|
||||
|
18
go.mod
18
go.mod
@ -1,18 +0,0 @@
|
||||
module github.com/TheLovinator1/ANewDawn
|
||||
|
||||
go 1.22.2
|
||||
|
||||
require (
|
||||
github.com/bwmarrin/discordgo v0.28.1
|
||||
github.com/sashabaranov/go-openai v1.26.3
|
||||
github.com/vartanbeno/go-reddit/v2 v2.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.1 // indirect
|
||||
golang.org/x/crypto v0.23.0 // indirect
|
||||
golang.org/x/net v0.25.0 // indirect
|
||||
golang.org/x/oauth2 v0.20.0 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
)
|
62
go.sum
62
go.sum
@ -1,62 +0,0 @@
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/bwmarrin/discordgo v0.28.1 h1:gXsuo2GBO7NbR6uqmrrBDplPUx2T3nzu775q/Rd1aG4=
|
||||
github.com/bwmarrin/discordgo v0.28.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
||||
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/sashabaranov/go-openai v1.24.0 h1:4H4Pg8Bl2RH/YSnU8DYumZbuHnnkfioor/dtNlB20D4=
|
||||
github.com/sashabaranov/go-openai v1.24.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
||||
github.com/sashabaranov/go-openai v1.24.1 h1:DWK95XViNb+agQtuzsn+FyHhn3HQJ7Va8z04DQDJ1MI=
|
||||
github.com/sashabaranov/go-openai v1.24.1/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
||||
github.com/sashabaranov/go-openai v1.24.2 h1:DZxL5CGahIeRcseuJhvMSMT5SVs1urfVZG9c6/Lyn7M=
|
||||
github.com/sashabaranov/go-openai v1.24.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
||||
github.com/sashabaranov/go-openai v1.25.0 h1:3h3DtJ55zQJqc+BR4y/iTcPhLk4pewJpyO+MXW2RdW0=
|
||||
github.com/sashabaranov/go-openai v1.25.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
||||
github.com/sashabaranov/go-openai v1.26.0 h1:upM565hxdqvCxNzuAcEBZ1XsfGehH0/9kgk9rFVpDxQ=
|
||||
github.com/sashabaranov/go-openai v1.26.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
||||
github.com/sashabaranov/go-openai v1.26.1 h1:B5plrmc/r7hKgYX69oT2VSt5w0O6u9BJYTjB8lNCesI=
|
||||
github.com/sashabaranov/go-openai v1.26.1/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
||||
github.com/sashabaranov/go-openai v1.26.2 h1:cVlQa3gn3eYqNXRW03pPlpy6zLG52EU4g0FrWXc0EFI=
|
||||
github.com/sashabaranov/go-openai v1.26.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
||||
github.com/sashabaranov/go-openai v1.26.3 h1:Tjnh4rcvsSU68f66r05mys+Zou4vo4qyvkne6AIRJPI=
|
||||
github.com/sashabaranov/go-openai v1.26.3/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/vartanbeno/go-reddit/v2 v2.0.1 h1:P6ITpf5YHjdy7DHZIbUIDn/iNAoGcEoDQnMa+L4vutw=
|
||||
github.com/vartanbeno/go-reddit/v2 v2.0.1/go.mod h1:758/S10hwZSLm43NPtwoNQdZFSg3sjB5745Mwjb0ANI=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo=
|
||||
golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
390
main.go
390
main.go
@ -1,390 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
|
||||
"github.com/bwmarrin/discordgo"
|
||||
|
||||
"github.com/vartanbeno/go-reddit/v2/reddit"
|
||||
)
|
||||
|
||||
var config Config
|
||||
|
||||
func init() {
|
||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||
}
|
||||
|
||||
func init() {
|
||||
loadedConfig, err := Load()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
config = *loadedConfig
|
||||
}
|
||||
|
||||
func GetPostsFromReddit(subreddit string) (string, error) {
|
||||
if subreddit == "" {
|
||||
return "", fmt.Errorf("subreddit cannot be empty")
|
||||
}
|
||||
|
||||
client, err := reddit.NewReadonlyClient()
|
||||
if err != nil {
|
||||
log.Println("Failed to create Reddit client:", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
posts, _, err := client.Subreddit.TopPosts(context.Background(), subreddit, &reddit.ListPostOptions{
|
||||
ListOptions: reddit.ListOptions{
|
||||
Limit: 100,
|
||||
},
|
||||
Time: "all",
|
||||
})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get posts from Reddit: %v", err)
|
||||
}
|
||||
|
||||
// Check if the subreddit exists
|
||||
if len(posts) == 0 {
|
||||
return "", fmt.Errorf("subreddit '%v' does not exist", subreddit)
|
||||
}
|
||||
|
||||
// [Title](<https://old.reddit.com{Permalink}>)\n{URL}
|
||||
randInt := rand.Intn(len(posts))
|
||||
discordMessage := fmt.Sprintf("[%v](<https://old.reddit.com%v>)\n%v", posts[randInt].Title, posts[randInt].Permalink, posts[randInt].URL)
|
||||
|
||||
return discordMessage, nil
|
||||
|
||||
}
|
||||
|
||||
func handleRedditCommand(s *discordgo.Session, i *discordgo.InteractionCreate, subreddit string) {
|
||||
post, err := GetPostsFromReddit(subreddit)
|
||||
if err = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: fmt.Sprintf("Cannot get a random post: %v", err),
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
}); err != nil {
|
||||
log.Println("Failed to respond to interaction:", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: post,
|
||||
},
|
||||
}); err != nil {
|
||||
log.Println("Failed to respond to interaction:", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
commands = []*discordgo.ApplicationCommand{
|
||||
{
|
||||
Name: "dank_memes",
|
||||
Description: "Sends dank meme from /r/GoodAnimemes",
|
||||
},
|
||||
{
|
||||
Name: "waifus",
|
||||
Description: "Sends waifu from /r/WatchItForThePlot",
|
||||
},
|
||||
{
|
||||
Name: "milkers",
|
||||
Description: "Sends milkers from /r/RetrousseTits",
|
||||
},
|
||||
{
|
||||
Name: "thighs",
|
||||
Description: "Sends thighs from /r/ZettaiRyouiki",
|
||||
},
|
||||
}
|
||||
|
||||
commandHandlers = map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){
|
||||
// Dank memes command
|
||||
"dank_memes": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
handleRedditCommand(s, i, "GoodAnimemes")
|
||||
},
|
||||
|
||||
// Waifus command
|
||||
"waifus": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
handleRedditCommand(s, i, "WatchItForThePlot")
|
||||
},
|
||||
|
||||
// Milkers command
|
||||
"milkers": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
handleRedditCommand(s, i, "RetrousseTits")
|
||||
},
|
||||
|
||||
// Thighs command
|
||||
"thighs": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
handleRedditCommand(s, i, "ZettaiRyouiki")
|
||||
},
|
||||
// Echo command
|
||||
"echo": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
// Check if the user provided a message
|
||||
if len(i.ApplicationCommandData().Options) == 0 {
|
||||
// If not, send an ephemeral message to the user
|
||||
err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "You need to provide a message!",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
log.Println("Failed to respond to interaction:", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the option is not empty
|
||||
if i.ApplicationCommandData().Options[0].StringValue() == "" {
|
||||
// If not, send an ephemeral message to the user
|
||||
err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "The message cannot be empty!",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
log.Println("Failed to respond to interaction:", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Respond to the original message so we don't get "This interaction failed" error
|
||||
if _, err := s.ChannelMessageSend(i.ChannelID, i.ApplicationCommandData().Options[0].StringValue()); err != nil {
|
||||
log.Println("Failed to send message to channel:", err)
|
||||
return
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func onMessageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
|
||||
if m.Author.ID == s.State.User.ID {
|
||||
return
|
||||
}
|
||||
|
||||
// Don't allow DMs
|
||||
if m.GuildID == "" {
|
||||
return
|
||||
}
|
||||
|
||||
allowedUsers := []string{
|
||||
"thelovinator",
|
||||
"killyoy",
|
||||
"forgefilip",
|
||||
"plubplub",
|
||||
"nobot",
|
||||
"kao172",
|
||||
}
|
||||
|
||||
// Have a 1/100 chance of replying to a message if written by a user in allowedUsers
|
||||
randInt := rand.Intn(100)
|
||||
log.Println("Random number:", randInt)
|
||||
log.Println("Mentions:", m.Mentions)
|
||||
if len(m.Mentions) == 0 && randInt == 4 {
|
||||
for _, user := range allowedUsers {
|
||||
log.Println("User:", user)
|
||||
if m.Author.Username == user {
|
||||
log.Println("User is in allowedUsers")
|
||||
r, err := GenerateGPT4Response(m.Content, m.Author.Username)
|
||||
if err != nil {
|
||||
log.Println("Failed to get OpenAI response:", err)
|
||||
return
|
||||
}
|
||||
log.Println("OpenAI response:", r)
|
||||
log.Println("Channel ID:", m.ChannelID)
|
||||
_, err = s.ChannelMessageSend(m.ChannelID, r)
|
||||
if err != nil {
|
||||
log.Println("Failed to send message to channel:", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if m.Mentions != nil {
|
||||
for _, mention := range m.Mentions {
|
||||
if mention.ID == s.State.User.ID {
|
||||
r, err := GenerateGPT4Response(m.Content, m.Author.Username)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "prompt is too long") {
|
||||
if _, err := s.ChannelMessageSend(m.ChannelID, "Message is too long!"); err != nil {
|
||||
log.Println("Failed to send message to channel:", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if strings.Contains(err.Error(), "prompt is too short") {
|
||||
if _, err := s.ChannelMessageSend(m.ChannelID, "Message is too short!"); err != nil {
|
||||
log.Println("Failed to send message to channel:", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
message, err := s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("Brain broke :flushed: %v", err))
|
||||
if err != nil {
|
||||
log.Println("Failed to send message to channel:", err)
|
||||
return
|
||||
}
|
||||
_ = message
|
||||
return
|
||||
}
|
||||
_, err = s.ChannelMessageSend(m.ChannelID, r)
|
||||
if err != nil {
|
||||
log.Println("Failed to send message to channel:", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(strings.ToLower(m.Content), "lovibot") {
|
||||
r, err := GenerateGPT4Response(m.Content, m.Author.Username)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "prompt is too long") {
|
||||
_, err := s.ChannelMessageSend(m.ChannelID, "Message is too long!")
|
||||
if err != nil {
|
||||
log.Println("Failed to send message to channel:", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if strings.Contains(err.Error(), "prompt is too short") {
|
||||
_, err := s.ChannelMessageSend(m.ChannelID, "Message is too short!")
|
||||
if err != nil {
|
||||
log.Println("Failed to send message to channel:", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
message, err := s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("Brain broke :flushed: %v", err))
|
||||
if err != nil {
|
||||
log.Println("Failed to send message to channel:", err)
|
||||
return
|
||||
}
|
||||
_ = message
|
||||
return
|
||||
}
|
||||
_, err = s.ChannelMessageSend(m.ChannelID, r)
|
||||
if err != nil {
|
||||
log.Println("Failed to send message to channel:", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func main() {
|
||||
discordToken := config.DiscordToken
|
||||
|
||||
// Create a new Discord session using the provided bot token.
|
||||
session, err := discordgo.New("Bot " + discordToken)
|
||||
if err != nil {
|
||||
log.Fatalf("Cannot create a new Discord session: %v", err)
|
||||
}
|
||||
|
||||
// Add a handler function to the discordgo.Session that is triggered when a slash command is received.
|
||||
session.AddHandler(func(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
if h, ok := commandHandlers[i.ApplicationCommandData().Name]; ok {
|
||||
log.Printf("Handling '%v' command. %+v", i.ApplicationCommandData().Name, i.ApplicationCommandData())
|
||||
h(s, i)
|
||||
}
|
||||
})
|
||||
|
||||
// Add a handler function to the discordgo.Session that is triggered when a message is received.
|
||||
session.AddHandler(onMessageCreate)
|
||||
|
||||
// Print the user we are logging in as.
|
||||
session.AddHandler(func(s *discordgo.Session, _ *discordgo.Ready) {
|
||||
log.Printf("Logged in as: %v#%v", s.State.User.Username, s.State.User.Discriminator)
|
||||
})
|
||||
|
||||
// Open a websocket connection to Discord and begin listening.
|
||||
err = session.Open()
|
||||
if err != nil {
|
||||
log.Fatalf("Cannot open the session: %v", err)
|
||||
}
|
||||
|
||||
// Remove all existing commands.
|
||||
appID := session.State.User.ID
|
||||
log.Println("Removing existing commands from all servers for the bot", appID)
|
||||
|
||||
// Remove the commands for guild 98905546077241344
|
||||
log.Println("Removing commands for Killyoy's server...")
|
||||
old_commands, err := session.ApplicationCommands(appID, "98905546077241344")
|
||||
if err != nil {
|
||||
log.Panicf("Cannot get commands for guild 98905546077241344: %v", err)
|
||||
}
|
||||
for _, v := range old_commands {
|
||||
err := session.ApplicationCommandDelete(session.State.User.ID, "98905546077241344", v.ID)
|
||||
if err != nil {
|
||||
log.Panicf("Cannot delete '%v' command: %v", v.Name, err)
|
||||
}
|
||||
log.Printf("Deleted '%v' command.", v.Name)
|
||||
}
|
||||
|
||||
// Remove the commands for guild 341001473661992962
|
||||
log.Println("Removing commands for TheLovinator's server...")
|
||||
old_commands2, err := session.ApplicationCommands(appID, "341001473661992962")
|
||||
if err != nil {
|
||||
log.Panicf("Cannot get commands for guild 341001473661992962: %v", err)
|
||||
}
|
||||
for _, v := range old_commands2 {
|
||||
err := session.ApplicationCommandDelete(session.State.User.ID, "341001473661992962", v.ID)
|
||||
if err != nil {
|
||||
log.Panicf("Cannot delete '%v' command: %v", v.Name, err)
|
||||
}
|
||||
log.Printf("Deleted '%v' command.", v.Name)
|
||||
}
|
||||
|
||||
// Register the commands for guild 98905546077241344
|
||||
log.Println("Registering commands for Killyoy's server...")
|
||||
registeredCommands := make([]*discordgo.ApplicationCommand, len(commands))
|
||||
for i, v := range commands {
|
||||
cmd, err := session.ApplicationCommandCreate(session.State.User.ID, "98905546077241344", v)
|
||||
if err != nil {
|
||||
log.Panicf("Cannot create '%v' command: %v", v.Name, err)
|
||||
}
|
||||
registeredCommands[i] = cmd
|
||||
log.Printf("Registered '%v' command.", cmd.Name)
|
||||
}
|
||||
|
||||
// Register the commands for guild 341001473661992962
|
||||
log.Println("Registering commands for TheLovinator's server...")
|
||||
registeredCommands2 := make([]*discordgo.ApplicationCommand, len(commands))
|
||||
for i, v := range commands {
|
||||
cmd, err := session.ApplicationCommandCreate(session.State.User.ID, "341001473661992962", v)
|
||||
if err != nil {
|
||||
log.Panicf("Cannot create '%v' command: %v", v.Name, err)
|
||||
}
|
||||
registeredCommands2[i] = cmd
|
||||
log.Printf("Registered '%v' command.", cmd.Name)
|
||||
}
|
||||
|
||||
// Run s.Close() when the program exits.
|
||||
defer session.Close()
|
||||
|
||||
// Wait here until CTRL-C or other term signal is received.
|
||||
stop := make(chan os.Signal, 1)
|
||||
signal.Notify(stop, os.Interrupt)
|
||||
log.Println("Press Ctrl+C to exit")
|
||||
<-stop
|
||||
|
||||
// Bye bye!
|
||||
log.Println("Gracefully shutting down.")
|
||||
}
|
174
main.py
Normal file
174
main.py
Normal file
@ -0,0 +1,174 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from openai import OpenAI
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from openai.types.chat.chat_completion import ChatCompletion
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
logger: logging.Logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
# Load the environment variables from the .env file
|
||||
load_dotenv(verbose=True)
|
||||
|
||||
# Get the Discord token and OpenAI API key from the environment variables
|
||||
discord_token: str | None = os.getenv("DISCORD_TOKEN")
|
||||
openai_api_key: str | None = os.getenv("OPENAI_TOKEN")
|
||||
if not discord_token or not openai_api_key:
|
||||
logger.error("You haven't configured the bot correctly. Please set the environment variables.")
|
||||
sys.exit(1)
|
||||
|
||||
# Use OpenAI for chatting with the bot
|
||||
openai_client = OpenAI(api_key=openai_api_key)
|
||||
|
||||
# Create a bot with the necessary intents
|
||||
# TODO(TheLovinator): We should only enable the intents we need # noqa: TD003
|
||||
intents: discord.Intents = discord.Intents.default()
|
||||
intents.message_content = True
|
||||
bot = commands.Bot(command_prefix="!", intents=intents)
|
||||
|
||||
|
||||
@bot.event
|
||||
async def on_ready() -> None: # noqa: RUF029
|
||||
"""Print a message when the bot is ready."""
|
||||
logger.info("Logged on as %s", bot.user)
|
||||
|
||||
|
||||
def chat(msg: str) -> str | None:
|
||||
"""Chat with the bot using the OpenAI API.
|
||||
|
||||
Args:
|
||||
msg: The message to send to the bot.
|
||||
|
||||
Returns:
|
||||
The response from the bot.
|
||||
"""
|
||||
completion: ChatCompletion = openai_client.chat.completions.create(
|
||||
model="gpt-4o-mini",
|
||||
messages=[
|
||||
{
|
||||
"role": "system",
|
||||
"content": "You are a chatbot. Use Markdown to format your messages if you want.",
|
||||
},
|
||||
{"role": "user", "content": msg},
|
||||
],
|
||||
)
|
||||
response: str | None = completion.choices[0].message.content
|
||||
logger.info("AI response: %s from message: %s", response, msg)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def get_allowed_users() -> list[str]:
|
||||
"""Get the list of allowed users to interact with the bot.
|
||||
|
||||
Returns:
|
||||
The list of allowed users.
|
||||
"""
|
||||
return [
|
||||
"thelovinator",
|
||||
"killyoy",
|
||||
"forgefilip",
|
||||
"plubplub",
|
||||
"nobot",
|
||||
"kao172",
|
||||
]
|
||||
|
||||
|
||||
def remove_mentions(message_content: str) -> str:
|
||||
"""Remove mentions of the bot from the message content.
|
||||
|
||||
Args:
|
||||
message_content: The message content to process.
|
||||
|
||||
Returns:
|
||||
The message content without the mentions of the bot.
|
||||
"""
|
||||
message_content = message_content.removeprefix("lovibot").strip()
|
||||
message_content = message_content.removeprefix(",").strip()
|
||||
if bot.user:
|
||||
message_content = message_content.replace(f"<@!{bot.user.id}>", "").strip()
|
||||
message_content = message_content.replace(f"<@{bot.user.id}>", "").strip()
|
||||
|
||||
return message_content
|
||||
|
||||
|
||||
@bot.event
|
||||
async def on_message(message: discord.Message) -> None:
|
||||
"""Respond to a message."""
|
||||
logger.info("Message received: %s", message.content)
|
||||
|
||||
message_content: str = message.content.lower()
|
||||
|
||||
# Ignore messages from the bot itself to prevent an infinite loop
|
||||
if message.author == bot.user:
|
||||
return
|
||||
|
||||
# Only allow certain users to interact with the bot
|
||||
allowed_users: list[str] = get_allowed_users()
|
||||
if message.author.name not in allowed_users:
|
||||
logger.info("Ignoring message from: %s", message.author.name)
|
||||
return
|
||||
|
||||
# Check if the message mentions the bot or starts with the bot's name
|
||||
things_to_notify_on: list[str] = ["lovibot"]
|
||||
if bot.user:
|
||||
things_to_notify_on.extend((f"<@!{bot.user.id}>", f"<@{bot.user.id}>"))
|
||||
|
||||
# Only respond to messages that mention the bot or are a reply to a bot message
|
||||
if any(thing.lower() in message_content for thing in things_to_notify_on) or message.reference:
|
||||
if message.reference:
|
||||
# Get the message that the current message is replying to
|
||||
message_id: int | None = message.reference.message_id
|
||||
if message_id is None:
|
||||
return
|
||||
|
||||
try:
|
||||
reply_message: discord.Message | None = await message.channel.fetch_message(message_id)
|
||||
except discord.errors.NotFound:
|
||||
return
|
||||
|
||||
# Get the message content and author
|
||||
reply_content: str = reply_message.content
|
||||
reply_author: str = reply_message.author.name
|
||||
|
||||
# Add the reply message to the current message
|
||||
message.content = f"{reply_author}: {reply_content}\n{message.author.name}: {message.content}"
|
||||
|
||||
# Remove the mention of the bot from the message
|
||||
message_content = remove_mentions(message_content)
|
||||
|
||||
# Grab 10 messages before the current one to provide context
|
||||
old_messages: list[str] = [
|
||||
f"{old_message.author.name}: {old_message.content}"
|
||||
async for old_message in message.channel.history(limit=10)
|
||||
]
|
||||
old_messages.reverse()
|
||||
|
||||
# Get the response from OpenAI
|
||||
response: str | None = chat("\n".join(old_messages) + "\n" + f"{message.author.name}: {message.content}")
|
||||
|
||||
# Remove LoviBot: from the response
|
||||
if response:
|
||||
response = response.removeprefix("LoviBot:").strip()
|
||||
|
||||
if response:
|
||||
logger.info("Responding to message: %s with: %s", message.content, response)
|
||||
await message.channel.send(response)
|
||||
else:
|
||||
logger.warning("No response from the AI model. Message: %s", message.content)
|
||||
await message.channel.send("I forgor how to think 💀")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logger.info("Starting the bot.")
|
||||
bot.run(token=discord_token, root_logger=True)
|
40
main_test.go
40
main_test.go
@ -1,40 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Returns a string with a length between 1 and 2000 characters
|
||||
func TestGetPostsFromReddit_ReturnsPostWithValidLength(t *testing.T) {
|
||||
if os.Getenv("CI") == "" {
|
||||
subreddit := "celebs"
|
||||
post, err := GetPostsFromReddit(subreddit)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
if len(post) < 1 || len(post) > 2000 {
|
||||
t.Errorf("Post length is not within the valid range")
|
||||
}
|
||||
} else {
|
||||
t.Skip("Skipping test in CI environment as the IP is probably blocked by Reddit")
|
||||
}
|
||||
}
|
||||
|
||||
// Returns an error when the subreddit does not exist
|
||||
func TestGetPostsFromReddit_ReturnsErrorWhenSubredditDoesNotExist(t *testing.T) {
|
||||
subreddit := "nonexistent"
|
||||
_, err := GetPostsFromReddit(subreddit)
|
||||
if err == nil {
|
||||
t.Errorf("Expected error, but got nil")
|
||||
}
|
||||
}
|
||||
|
||||
// Returns an error when the subreddit is empty
|
||||
func TestGetPostsFromReddit_ReturnsErrorWhenSubredditIsEmpty(t *testing.T) {
|
||||
subreddit := ""
|
||||
_, err := GetPostsFromReddit(subreddit)
|
||||
if err.Error() != "subreddit cannot be empty" {
|
||||
t.Errorf("Expected error 'subreddit cannot be empty', but got '%v'", err)
|
||||
}
|
||||
}
|
89
openai.go
89
openai.go
@ -1,89 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
openai "github.com/sashabaranov/go-openai"
|
||||
)
|
||||
|
||||
/*
|
||||
Prompt is the Discord message that the user sent to the bot.
|
||||
*/
|
||||
func GenerateGPT4Response(prompt string, author string) (string, error) {
|
||||
openAIToken := config.OpenAIToken
|
||||
if openAIToken == "" {
|
||||
return "", fmt.Errorf("OPENAI_API_KEY is not set")
|
||||
}
|
||||
|
||||
// Print the prompt
|
||||
fmt.Println("Prompt:", author, ":", prompt)
|
||||
|
||||
// Check if the prompt is too long
|
||||
if len(prompt) > 2048 {
|
||||
return "", fmt.Errorf("prompt is too long")
|
||||
}
|
||||
|
||||
// Add additional information to the system message
|
||||
var additionalInfo string
|
||||
switch author {
|
||||
case "thelovinator":
|
||||
additionalInfo = "User (TheLovinator) is a programmer. Wants to live in the woods. Real name is Joakim. He made the bot."
|
||||
case "killyoy":
|
||||
additionalInfo = "User (KillYoy) likes to play video games. Real name is Andreas. Good at CSS."
|
||||
case "forgefilip":
|
||||
additionalInfo = "User (ForgeFilip) likes watches. Real name is Filip."
|
||||
case "plubplub":
|
||||
additionalInfo = "User (Piplup) likes to play WoW and Path of Exile. Real name is Axel. Is also called Bambi."
|
||||
case "nobot":
|
||||
additionalInfo = "User (Nobot) likes to play WoW. Real name is Gustav. Really good at programming."
|
||||
case "kao172":
|
||||
additionalInfo = "User (kao172) likes cars. Real name is Fredrik."
|
||||
}
|
||||
|
||||
// Create a new client
|
||||
client := openai.NewClient(openAIToken)
|
||||
|
||||
// System message
|
||||
var systemMessage string
|
||||
systemMessage = `You are in a Discord server.
|
||||
You are Swedish.
|
||||
Use Markdown for formatting.
|
||||
Please respond with a short message.
|
||||
`
|
||||
|
||||
// Add additional information to the system message
|
||||
if additionalInfo != "" {
|
||||
systemMessage = fmt.Sprintf("%s\n%s", systemMessage, additionalInfo)
|
||||
}
|
||||
|
||||
// Print the system message
|
||||
fmt.Println("System message:", systemMessage)
|
||||
|
||||
// Create a completion
|
||||
resp, err := client.CreateChatCompletion(
|
||||
context.Background(),
|
||||
openai.ChatCompletionRequest{
|
||||
Model: openai.GPT4o,
|
||||
Messages: []openai.ChatCompletionMessage{
|
||||
{
|
||||
Role: openai.ChatMessageRoleSystem,
|
||||
Content: systemMessage,
|
||||
},
|
||||
{
|
||||
Role: openai.ChatMessageRoleUser,
|
||||
Content: prompt,
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get response from OpenAI: %w", err)
|
||||
}
|
||||
|
||||
ourResponse := resp.Choices[0].Message.Content
|
||||
|
||||
fmt.Println("Response:", ourResponse)
|
||||
return ourResponse, nil
|
||||
}
|
51
pyproject.toml
Normal file
51
pyproject.toml
Normal file
@ -0,0 +1,51 @@
|
||||
[project]
|
||||
name = "anewdawn"
|
||||
version = "0.1.0"
|
||||
description = "My shit bot"
|
||||
dependencies = ["discord.py", "openai", "python-dotenv"]
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[tool.ruff]
|
||||
# https://docs.astral.sh/ruff/
|
||||
line-length = 120
|
||||
fix = true
|
||||
unsafe-fixes = true
|
||||
extend-exclude = [".venv"]
|
||||
show-fixes = true
|
||||
|
||||
[tool.ruff.lint]
|
||||
# https://docs.astral.sh/ruff/linter/
|
||||
preview = true
|
||||
select = ["ALL"]
|
||||
ignore = [
|
||||
"CPY001", # Checks for the absence of copyright notices within Python files.
|
||||
"D100", # Checks for undocumented public module definitions.
|
||||
"FIX002", # Checks for "TODO" comments.
|
||||
"D104", # Checks for undocumented public package definitions.
|
||||
# https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules
|
||||
"W191",
|
||||
"E111",
|
||||
"E114",
|
||||
"E117",
|
||||
"D206",
|
||||
"D300",
|
||||
"Q000",
|
||||
"Q001",
|
||||
"Q002",
|
||||
"Q003",
|
||||
"COM812",
|
||||
"COM819",
|
||||
"ISC001",
|
||||
"ISC002",
|
||||
]
|
||||
pydocstyle.convention = "google"
|
||||
isort.required-imports = ["from __future__ import annotations"]
|
||||
pycodestyle.ignore-overlong-task-comments = true
|
||||
|
||||
[tool.ruff.format]
|
||||
# https://docs.astral.sh/ruff/formatter/
|
||||
docstring-code-format = true
|
||||
docstring-code-line-length = 20
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"tests/**/*.py" = ["S101", "ARG", "FBT"]
|
66
settings.go
66
settings.go
@ -1,66 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Config holds the configuration parameters
|
||||
type Config struct {
|
||||
DiscordToken string `json:"discord_token"`
|
||||
OpenAIToken string `json:"openai_token"`
|
||||
}
|
||||
|
||||
// Load reads configuration from settings.json or environment variables
|
||||
func Load() (*Config, error) {
|
||||
// Try reading from settings.json file first
|
||||
config, err := loadFromJSONFile("settings.json")
|
||||
if err != nil {
|
||||
// If reading from file fails, try reading from environment variables
|
||||
config, err = loadFromEnvironment()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// loadFromJSONFile reads configuration from a JSON file
|
||||
func loadFromJSONFile(filename string) (*Config, error) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open settings file: %v", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
config := &Config{}
|
||||
decoder := json.NewDecoder(file)
|
||||
err = decoder.Decode(config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode settings file: %v", err)
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// loadFromEnvironment reads configuration from environment variables
|
||||
func loadFromEnvironment() (*Config, error) {
|
||||
discordToken := os.Getenv("DISCORD_TOKEN")
|
||||
if discordToken == "" {
|
||||
return nil, fmt.Errorf("DISCORD_TOKEN environment variable not set or empty. Also tried reading from settings.json file")
|
||||
}
|
||||
|
||||
openAIToken := os.Getenv("OPENAI_TOKEN")
|
||||
if openAIToken == "" {
|
||||
return nil, fmt.Errorf("OPENAI_TOKEN environment variable not set or empty. Also tried reading from settings.json file")
|
||||
}
|
||||
|
||||
config := &Config{
|
||||
DiscordToken: discordToken,
|
||||
OpenAIToken: openAIToken,
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
{
|
||||
"discord_token": "",
|
||||
"openai_token": ""
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Returns a Config object with DiscordToken set when DISCORD_TOKEN environment variable is set
|
||||
func TestLoadFromEnvironment_DiscordTokenSet(t *testing.T) {
|
||||
os.Setenv("DISCORD_TOKEN", "test_token")
|
||||
os.Setenv("OPENAI_TOKEN", "test_token2")
|
||||
defer os.Unsetenv("DISCORD_TOKEN")
|
||||
defer os.Unsetenv("OPENAI_TOKEN")
|
||||
|
||||
config, err := loadFromEnvironment()
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, but got: %v", err)
|
||||
}
|
||||
|
||||
expected := &Config{
|
||||
DiscordToken: "test_token",
|
||||
OpenAIToken: "test_token2",
|
||||
}
|
||||
if !reflect.DeepEqual(config, expected) {
|
||||
t.Errorf("Expected config to be %v, but got %v", expected, config)
|
||||
}
|
||||
}
|
||||
|
||||
// Returns an error when DISCORD_TOKEN environment variable is empty
|
||||
func TestLoadFromEnvironment_EmptyDiscordToken(t *testing.T) {
|
||||
os.Setenv("DISCORD_TOKEN", "")
|
||||
defer os.Unsetenv("DISCORD_TOKEN")
|
||||
|
||||
_, err := loadFromEnvironment()
|
||||
if err == nil {
|
||||
t.Error("Expected an error, but got nil")
|
||||
}
|
||||
|
||||
expected := "DISCORD_TOKEN environment variable not set or empty. Also tried reading from settings.json file"
|
||||
if err.Error() != expected {
|
||||
t.Errorf("Expected error message to be '%s', but got '%s'", expected, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Returns an error when DISCORD_TOKEN environment variable is not set and settings.json file is not present
|
||||
func TestLoadFromEnvironment_NoDiscordTokenNoSettingsFile(t *testing.T) {
|
||||
os.Unsetenv("DISCORD_TOKEN")
|
||||
|
||||
_, err := loadFromEnvironment()
|
||||
if err == nil {
|
||||
t.Error("Expected an error, but got nil")
|
||||
}
|
||||
|
||||
expected := "DISCORD_TOKEN environment variable not set or empty. Also tried reading from settings.json file"
|
||||
if err.Error() != expected {
|
||||
t.Errorf("Expected error message to be '%s', but got '%s'", expected, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Returns an error when settings.json file is present but DiscordToken is not set
|
||||
func TestLoadFromEnvironment_SettingsFileNoDiscordToken(t *testing.T) {
|
||||
os.Setenv("DISCORD_TOKEN", "")
|
||||
defer os.Unsetenv("DISCORD_TOKEN")
|
||||
|
||||
_, err := loadFromEnvironment()
|
||||
if err == nil {
|
||||
t.Error("Expected an error, but got nil")
|
||||
}
|
||||
|
||||
expected := "DISCORD_TOKEN environment variable not set or empty. Also tried reading from settings.json file"
|
||||
if err.Error() != expected {
|
||||
t.Errorf("Expected error message to be '%s', but got '%s'", expected, err.Error())
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user