mirror of
https://github.com/oliverbooth/VPLink
synced 2024-10-18 06:06:10 +00:00
feat: initial commit
This commit is contained in:
commit
5d3a5a173d
25
.dockerignore
Normal file
25
.dockerignore
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
**/.dockerignore
|
||||||
|
**/.env
|
||||||
|
**/.git
|
||||||
|
**/.gitignore
|
||||||
|
**/.project
|
||||||
|
**/.settings
|
||||||
|
**/.toolstarget
|
||||||
|
**/.vs
|
||||||
|
**/.vscode
|
||||||
|
**/.idea
|
||||||
|
**/*.*proj.user
|
||||||
|
**/*.dbmdl
|
||||||
|
**/*.jfm
|
||||||
|
**/azds.yaml
|
||||||
|
**/bin
|
||||||
|
**/charts
|
||||||
|
**/docker-compose*
|
||||||
|
**/Dockerfile*
|
||||||
|
**/node_modules
|
||||||
|
**/npm-debug.log
|
||||||
|
**/obj
|
||||||
|
**/secrets.dev.yaml
|
||||||
|
**/values.dev.yaml
|
||||||
|
LICENSE
|
||||||
|
README.md
|
133
.github/CODE_OF_CONDUCT.md
vendored
Normal file
133
.github/CODE_OF_CONDUCT.md
vendored
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
We as members, contributors, and leaders pledge to make participation in our
|
||||||
|
community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||||
|
identity and expression, level of experience, education, socio-economic status,
|
||||||
|
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||||
|
identity and orientation.
|
||||||
|
|
||||||
|
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||||
|
diverse, inclusive, and healthy community.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to a positive environment for our
|
||||||
|
community include:
|
||||||
|
|
||||||
|
* Demonstrating empathy and kindness toward other people
|
||||||
|
* Being respectful of differing opinions, viewpoints, and experiences
|
||||||
|
* Giving and gracefully accepting constructive feedback
|
||||||
|
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||||
|
and learning from the experience
|
||||||
|
* Focusing on what is best not just for us as individuals, but for the overall
|
||||||
|
community
|
||||||
|
|
||||||
|
Examples of unacceptable behavior include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery, and sexual attention or advances of
|
||||||
|
any kind
|
||||||
|
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or email address,
|
||||||
|
without their explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Enforcement Responsibilities
|
||||||
|
|
||||||
|
Community leaders are responsible for clarifying and enforcing our standards of
|
||||||
|
acceptable behavior and will take appropriate and fair corrective action in
|
||||||
|
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||||
|
or harmful.
|
||||||
|
|
||||||
|
Community leaders have the right and responsibility to remove, edit, or reject
|
||||||
|
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||||
|
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||||
|
decisions when appropriate.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies within all community spaces, and also applies when
|
||||||
|
an individual is officially representing the community in public spaces.
|
||||||
|
Examples of representing our community include using an official e-mail address,
|
||||||
|
posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported to the community leaders responsible for enforcement at
|
||||||
|
yiliansource@gmail.com or me@olivr.me.
|
||||||
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
|
All community leaders are obligated to respect the privacy and security of the
|
||||||
|
reporter of any incident.
|
||||||
|
|
||||||
|
## Enforcement Guidelines
|
||||||
|
|
||||||
|
Community leaders will follow these Community Impact Guidelines in determining
|
||||||
|
the consequences for any action they deem in violation of this Code of Conduct:
|
||||||
|
|
||||||
|
### 1. Correction
|
||||||
|
|
||||||
|
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||||
|
unprofessional or unwelcome in the community.
|
||||||
|
|
||||||
|
**Consequence**: A private, written warning from community leaders, providing
|
||||||
|
clarity around the nature of the violation and an explanation of why the
|
||||||
|
behavior was inappropriate. A public apology may be requested.
|
||||||
|
|
||||||
|
### 2. Warning
|
||||||
|
|
||||||
|
**Community Impact**: A violation through a single incident or series of
|
||||||
|
actions.
|
||||||
|
|
||||||
|
**Consequence**: A warning with consequences for continued behavior. No
|
||||||
|
interaction with the people involved, including unsolicited interaction with
|
||||||
|
those enforcing the Code of Conduct, for a specified period of time. This
|
||||||
|
includes avoiding interactions in community spaces as well as external channels
|
||||||
|
like social media. Violating these terms may lead to a temporary or permanent
|
||||||
|
ban.
|
||||||
|
|
||||||
|
### 3. Temporary Ban
|
||||||
|
|
||||||
|
**Community Impact**: A serious violation of community standards, including
|
||||||
|
sustained inappropriate behavior.
|
||||||
|
|
||||||
|
**Consequence**: A temporary ban from any sort of interaction or public
|
||||||
|
communication with the community for a specified period of time. No public or
|
||||||
|
private interaction with the people involved, including unsolicited interaction
|
||||||
|
with those enforcing the Code of Conduct, is allowed during this period.
|
||||||
|
Violating these terms may lead to a permanent ban.
|
||||||
|
|
||||||
|
### 4. Permanent Ban
|
||||||
|
|
||||||
|
**Community Impact**: Demonstrating a pattern of violation of community
|
||||||
|
standards, including sustained inappropriate behavior, harassment of an
|
||||||
|
individual, or aggression toward or disparagement of classes of individuals.
|
||||||
|
|
||||||
|
**Consequence**: A permanent ban from any sort of public interaction within the
|
||||||
|
community.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||||
|
version 2.1, available at
|
||||||
|
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||||
|
|
||||||
|
Community Impact Guidelines were inspired by
|
||||||
|
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||||
|
|
||||||
|
For answers to common questions about this code of conduct, see the FAQ at
|
||||||
|
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
||||||
|
[https://www.contributor-covenant.org/translations][translations].
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||||
|
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||||
|
[FAQ]: https://www.contributor-covenant.org/faq
|
||||||
|
[translations]: https://www.contributor-covenant.org/translations
|
||||||
|
|
2
.github/FUNDING.yml
vendored
Normal file
2
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
ko_fi: oliverbooth
|
||||||
|
custom: ['https://buymeacoffee.com/oliverbooth']
|
42
.github/workflows/docker-release.yml
vendored
Normal file
42
.github/workflows/docker-release.yml
vendored
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
name: Docker Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v[0-9]+.[0-9]+.[0-9]+"
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: ghcr.io
|
||||||
|
IMAGE_NAME: ${{ github.repository }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-push-image:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Log in to the Container registry
|
||||||
|
uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Extract metadata (tags, labels) for Docker
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
|
|
||||||
|
- name: Build and push Docker image
|
||||||
|
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
25
.github/workflows/dotnet.yml
vendored
Normal file
25
.github/workflows/dotnet.yml
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
name: .NET
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: "Build & Test"
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Setup .NET
|
||||||
|
uses: actions/setup-dotnet@v3
|
||||||
|
with:
|
||||||
|
dotnet-version: 7.0.x
|
||||||
|
- name: Add NuGet source
|
||||||
|
run: dotnet nuget add source --username oliverbooth --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/oliverbooth/index.json"
|
||||||
|
- name: Restore dependencies
|
||||||
|
run: dotnet restore
|
||||||
|
- name: Build
|
||||||
|
run: dotnet build --no-restore --configuration Release
|
||||||
|
- name: Test
|
||||||
|
run: dotnet test --no-build --verbosity normal
|
35
.github/workflows/prerelease.yml
vendored
Normal file
35
.github/workflows/prerelease.yml
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
name: Tagged Pre-Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v[0-9]+.[0-9]+.[0-9]+-*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
prerelease:
|
||||||
|
name: "Tagged Pre-Release"
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Setup .NET
|
||||||
|
uses: actions/setup-dotnet@v3
|
||||||
|
with:
|
||||||
|
dotnet-version: 7.0.x
|
||||||
|
|
||||||
|
- name: Add GitHub NuGet source
|
||||||
|
run: dotnet nuget add source --username oliverbooth --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/oliverbooth/index.json"
|
||||||
|
|
||||||
|
- name: Restore dependencies
|
||||||
|
run: dotnet restore
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: dotnet build -c Release
|
||||||
|
|
||||||
|
- name: Create Release
|
||||||
|
uses: "marvinpinto/action-automatic-releases@latest"
|
||||||
|
with:
|
||||||
|
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
|
prerelease: true
|
35
.github/workflows/release.yml
vendored
Normal file
35
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
name: Tagged Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v[0-9]+.[0-9]+.[0-9]+"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
name: "Tagged Release"
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Setup .NET
|
||||||
|
uses: actions/setup-dotnet@v3
|
||||||
|
with:
|
||||||
|
dotnet-version: 7.0.x
|
||||||
|
|
||||||
|
- name: Add GitHub NuGet source
|
||||||
|
run: dotnet nuget add source --username oliverbooth --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/oliverbooth/index.json"
|
||||||
|
|
||||||
|
- name: Restore dependencies
|
||||||
|
run: dotnet restore
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: dotnet build -c Release
|
||||||
|
|
||||||
|
- name: Create Release
|
||||||
|
uses: "marvinpinto/action-automatic-releases@latest"
|
||||||
|
with:
|
||||||
|
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
|
prerelease: false
|
169
.gitignore
vendored
Normal file
169
.gitignore
vendored
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
*.swp
|
||||||
|
*.*~
|
||||||
|
project.lock.json
|
||||||
|
.DS_Store
|
||||||
|
*.pyc
|
||||||
|
nupkg/
|
||||||
|
tmp/
|
||||||
|
|
||||||
|
# Visual Studio Code
|
||||||
|
.vscode
|
||||||
|
|
||||||
|
# Rider
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
[Dd]ebug/
|
||||||
|
[Dd]ebugPublic/
|
||||||
|
[Rr]elease/
|
||||||
|
[Rr]eleases/
|
||||||
|
x64/
|
||||||
|
x86/
|
||||||
|
build/
|
||||||
|
bld/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
[Oo]ut/
|
||||||
|
msbuild.log
|
||||||
|
msbuild.err
|
||||||
|
msbuild.wrn
|
||||||
|
|
||||||
|
# Visual Studio 2015
|
||||||
|
.vs/
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# vuepress v2.x temp and cache directory
|
||||||
|
.temp
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# Docusaurus cache and generated files
|
||||||
|
.docusaurus
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
3
README.md
Normal file
3
README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# VpBridge
|
||||||
|
|
||||||
|
VpBridge is a simple bot for both Discord and Virtual Paradise which bridges chat messages from a designated Discord channel, to a world in Virtual Paradise.
|
16
VpBridge.sln
Normal file
16
VpBridge.sln
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VpBridge", "VpBridge\VpBridge.csproj", "{CD488A1E-0232-4EB5-A381-38A42B267B11}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{CD488A1E-0232-4EB5-A381-38A42B267B11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{CD488A1E-0232-4EB5-A381-38A42B267B11}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{CD488A1E-0232-4EB5-A381-38A42B267B11}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{CD488A1E-0232-4EB5-A381-38A42B267B11}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
20
VpBridge/Dockerfile
Normal file
20
VpBridge/Dockerfile
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
|
||||||
|
WORKDIR /app
|
||||||
|
EXPOSE 80
|
||||||
|
EXPOSE 443
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
|
||||||
|
WORKDIR /src
|
||||||
|
COPY ["VpBridge/VpBridge.csproj", "VpBridge/"]
|
||||||
|
RUN dotnet restore "VpBridge/VpBridge.csproj"
|
||||||
|
COPY . .
|
||||||
|
WORKDIR "/src/VpBridge"
|
||||||
|
RUN dotnet build "VpBridge.csproj" -c Release -o /app/build
|
||||||
|
|
||||||
|
FROM build AS publish
|
||||||
|
RUN dotnet publish "VpBridge.csproj" -c Release -o /app/publish /p:UseAppHost=false
|
||||||
|
|
||||||
|
FROM base AS final
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=publish /app/publish .
|
||||||
|
ENTRYPOINT ["dotnet", "VpBridge.dll"]
|
36
VpBridge/Program.cs
Normal file
36
VpBridge/Program.cs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
using Discord;
|
||||||
|
using Discord.WebSocket;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Serilog;
|
||||||
|
using Tomlyn.Extensions.Configuration;
|
||||||
|
using VpBridge.Services;
|
||||||
|
using VpSharp;
|
||||||
|
using X10D.Hosting.DependencyInjection;
|
||||||
|
|
||||||
|
Log.Logger = new LoggerConfiguration()
|
||||||
|
.WriteTo.Console()
|
||||||
|
.WriteTo.File("logs/latest.log", rollingInterval: RollingInterval.Day)
|
||||||
|
#if DEBUG
|
||||||
|
.MinimumLevel.Debug()
|
||||||
|
#endif
|
||||||
|
.CreateLogger();
|
||||||
|
|
||||||
|
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
|
||||||
|
builder.Configuration.AddTomlFile("data/config.toml", true, true);
|
||||||
|
|
||||||
|
builder.Logging.ClearProviders();
|
||||||
|
builder.Logging.AddSerilog();
|
||||||
|
|
||||||
|
builder.Services.AddSingleton<VirtualParadiseClient>();
|
||||||
|
builder.Services.AddSingleton(new DiscordSocketClient(new DiscordSocketConfig
|
||||||
|
{
|
||||||
|
GatewayIntents = GatewayIntents.AllUnprivileged | GatewayIntents.MessageContent
|
||||||
|
}));
|
||||||
|
|
||||||
|
builder.Services.AddHostedSingleton<IVirtualParadiseService, VirtualParadiseService>();
|
||||||
|
builder.Services.AddHostedSingleton<IDiscordService, DiscordService>();
|
||||||
|
builder.Services.AddHostedSingleton<RelayService>();
|
||||||
|
|
||||||
|
await builder.Build().RunAsync();
|
105
VpBridge/Services/DiscordService.cs
Normal file
105
VpBridge/Services/DiscordService.cs
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
using System.Reactive.Linq;
|
||||||
|
using System.Reactive.Subjects;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using Discord;
|
||||||
|
using Discord.WebSocket;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using VpSharp.Entities;
|
||||||
|
|
||||||
|
namespace VpBridge.Services;
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IDiscordService" />
|
||||||
|
internal sealed partial class DiscordService : BackgroundService, IDiscordService
|
||||||
|
{
|
||||||
|
private static readonly Regex UnescapeRegex = GetUnescapeRegex();
|
||||||
|
private static readonly Regex EscapeRegex = GetEscapeRegex();
|
||||||
|
|
||||||
|
private readonly ILogger<DiscordService> _logger;
|
||||||
|
private readonly IConfiguration _configuration;
|
||||||
|
private readonly DiscordSocketClient _discordClient;
|
||||||
|
private readonly Subject<IUserMessage> _messageReceived = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="DiscordService" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="logger">The logger.</param>
|
||||||
|
/// <param name="configuration">The configuration.</param>
|
||||||
|
/// <param name="discordClient">The Discord client.</param>
|
||||||
|
public DiscordService(ILogger<DiscordService> logger,
|
||||||
|
IConfiguration configuration,
|
||||||
|
DiscordSocketClient discordClient)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_configuration = configuration;
|
||||||
|
_discordClient = discordClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IObservable<IUserMessage> OnMessageReceived => _messageReceived.AsObservable();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Establishing relay");
|
||||||
|
_discordClient.MessageReceived += arg =>
|
||||||
|
{
|
||||||
|
if (arg is not IUserMessage message)
|
||||||
|
return Task.CompletedTask;
|
||||||
|
|
||||||
|
if (message.Channel.Id != _configuration.GetSection("Discord:ChannelId").Get<ulong>())
|
||||||
|
return Task.CompletedTask;
|
||||||
|
|
||||||
|
_messageReceived.OnNext(message);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
string token = _configuration.GetSection("Discord:Token").Value ??
|
||||||
|
throw new InvalidOperationException("Token is not set.");
|
||||||
|
|
||||||
|
_logger.LogDebug("Connecting to Discord");
|
||||||
|
await _discordClient.LoginAsync(TokenType.Bot, token);
|
||||||
|
await _discordClient.StartAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task SendMessageAsync(VirtualParadiseMessage message)
|
||||||
|
{
|
||||||
|
if (message is null) throw new ArgumentNullException(nameof(message));
|
||||||
|
if (string.IsNullOrWhiteSpace(message.Content)) return Task.CompletedTask;
|
||||||
|
|
||||||
|
if (message.Author is not { } author)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Received message without author, ignoring message");
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (author.IsBot && !_configuration.GetSection("Bot:RelayBotMessages").Get<bool>())
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Bot messages are disabled, ignoring message");
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Message by {Author}: {Content}", author, message.Content);
|
||||||
|
|
||||||
|
var channelId = _configuration.GetSection("Discord:ChannelId").Get<ulong>();
|
||||||
|
if (_discordClient.GetChannel(channelId) is not ITextChannel channel)
|
||||||
|
{
|
||||||
|
_logger.LogError("Channel {ChannelId} does not exist", channelId);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
string unescaped = UnescapeRegex.Replace(message.Content, "$1");
|
||||||
|
string escaped = EscapeRegex.Replace(unescaped, "\\$1");
|
||||||
|
|
||||||
|
string displayName = author.Name;
|
||||||
|
return channel.SendMessageAsync($"{displayName}: {escaped}");
|
||||||
|
}
|
||||||
|
|
||||||
|
[GeneratedRegex(@"\\(\*|_|`|~|\\)", RegexOptions.Compiled)]
|
||||||
|
private static partial Regex GetUnescapeRegex();
|
||||||
|
|
||||||
|
[GeneratedRegex(@"(\*|_|`|~|\\)", RegexOptions.Compiled)]
|
||||||
|
private static partial Regex GetEscapeRegex();
|
||||||
|
}
|
23
VpBridge/Services/IDiscordService.cs
Normal file
23
VpBridge/Services/IDiscordService.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using Discord;
|
||||||
|
using VpSharp.Entities;
|
||||||
|
|
||||||
|
namespace VpBridge.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a service that sends messages to the Discord channel.
|
||||||
|
/// </summary>
|
||||||
|
public interface IDiscordService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an observable that is triggered when a message is received from the Discord channel.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>An observable that is triggered when a message is received from the Discord channel.</value>
|
||||||
|
IObservable<IUserMessage> OnMessageReceived { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a message to the Discord channel.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message to send.</param>
|
||||||
|
/// <returns>A <see cref="Task" /> representing the asynchronous operation.</returns>
|
||||||
|
Task SendMessageAsync(VirtualParadiseMessage message);
|
||||||
|
}
|
25
VpBridge/Services/IVirtualParadiseService.cs
Normal file
25
VpBridge/Services/IVirtualParadiseService.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
using Discord;
|
||||||
|
using VpSharp.Entities;
|
||||||
|
|
||||||
|
namespace VpBridge.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a service that sends messages to the Virtual Paradise world server.
|
||||||
|
/// </summary>
|
||||||
|
public interface IVirtualParadiseService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an observable that is triggered when a message is received from the Virtual Paradise world server.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// An observable that is triggered when a message is received from the Virtual Paradise world server.
|
||||||
|
/// </value>
|
||||||
|
IObservable<VirtualParadiseMessage> OnMessageReceived { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a message to the Virtual Paradise world server.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The Discord message to send.</param>
|
||||||
|
/// <returns>A <see cref="Task" /> representing the asynchronous operation.</returns>
|
||||||
|
Task SendMessageAsync(IUserMessage message);
|
||||||
|
}
|
54
VpBridge/Services/RelayService.cs
Normal file
54
VpBridge/Services/RelayService.cs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
using System.Reactive.Linq;
|
||||||
|
using Discord.WebSocket;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using VpSharp;
|
||||||
|
using VpSharp.Extensions;
|
||||||
|
|
||||||
|
namespace VpBridge.Services;
|
||||||
|
|
||||||
|
internal sealed class RelayService : BackgroundService
|
||||||
|
{
|
||||||
|
private readonly ILogger<RelayService> _logger;
|
||||||
|
private readonly IDiscordService _discordService;
|
||||||
|
private readonly IVirtualParadiseService _virtualParadiseService;
|
||||||
|
private readonly DiscordSocketClient _discordClient;
|
||||||
|
private readonly VirtualParadiseClient _virtualParadiseClient;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="RelayService" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="logger">The logger.</param>
|
||||||
|
/// <param name="discordService">The Discord service.</param>
|
||||||
|
/// <param name="virtualParadiseService">The Virtual Paradise service.</param>
|
||||||
|
/// <param name="discordClient">The Discord client.</param>
|
||||||
|
/// <param name="virtualParadiseClient">The Virtual Paradise client.</param>
|
||||||
|
public RelayService(ILogger<RelayService> logger,
|
||||||
|
IDiscordService discordService,
|
||||||
|
IVirtualParadiseService virtualParadiseService,
|
||||||
|
DiscordSocketClient discordClient,
|
||||||
|
VirtualParadiseClient virtualParadiseClient)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_discordService = discordService;
|
||||||
|
_virtualParadiseService = virtualParadiseService;
|
||||||
|
_discordClient = discordClient;
|
||||||
|
_virtualParadiseClient = virtualParadiseClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Establishing relay");
|
||||||
|
|
||||||
|
_discordService.OnMessageReceived
|
||||||
|
.Where(m => m.Author != _discordClient.CurrentUser)
|
||||||
|
.SubscribeAsync(_virtualParadiseService.SendMessageAsync);
|
||||||
|
|
||||||
|
_virtualParadiseService.OnMessageReceived
|
||||||
|
.Where(m => m.Author != _virtualParadiseClient.CurrentAvatar)
|
||||||
|
.SubscribeAsync(_discordService.SendMessageAsync);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
79
VpBridge/Services/VirtualParadiseService.cs
Normal file
79
VpBridge/Services/VirtualParadiseService.cs
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
using System.Reactive.Linq;
|
||||||
|
using System.Reactive.Subjects;
|
||||||
|
using Discord;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using VpSharp;
|
||||||
|
using VpSharp.Entities;
|
||||||
|
using Color = System.Drawing.Color;
|
||||||
|
|
||||||
|
namespace VpBridge.Services;
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IVirtualParadiseService" />
|
||||||
|
internal sealed class VirtualParadiseService : BackgroundService, IVirtualParadiseService
|
||||||
|
{
|
||||||
|
private readonly ILogger<VirtualParadiseService> _logger;
|
||||||
|
private readonly IConfiguration _configuration;
|
||||||
|
private readonly VirtualParadiseClient _virtualParadiseClient;
|
||||||
|
private readonly Subject<VirtualParadiseMessage> _messageReceived = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="VirtualParadiseService" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="logger">The logger.</param>
|
||||||
|
/// <param name="configuration">The configuration.</param>
|
||||||
|
/// <param name="virtualParadiseClient">The Virtual Paradise client.</param>
|
||||||
|
public VirtualParadiseService(ILogger<VirtualParadiseService> logger,
|
||||||
|
IConfiguration configuration,
|
||||||
|
VirtualParadiseClient virtualParadiseClient)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_configuration = configuration;
|
||||||
|
_virtualParadiseClient = virtualParadiseClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IObservable<VirtualParadiseMessage> OnMessageReceived => _messageReceived.AsObservable();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task SendMessageAsync(IUserMessage message)
|
||||||
|
{
|
||||||
|
if (message is null) throw new ArgumentNullException(nameof(message));
|
||||||
|
if (string.IsNullOrWhiteSpace(message.Content)) return Task.CompletedTask;
|
||||||
|
|
||||||
|
if (message.Author.IsBot && !_configuration.GetSection("Bot:RelayBotMessages").Get<bool>())
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Bot messages are disabled, ignoring message");
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Message by {Author}: {Content}", message.Author, message.Content);
|
||||||
|
|
||||||
|
string displayName = message.Author.GlobalName ?? message.Author.Username;
|
||||||
|
return _virtualParadiseClient.SendMessageAsync(displayName, message.Content, FontStyle.Bold, Color.MidnightBlue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Establishing relay");
|
||||||
|
_virtualParadiseClient.MessageReceived.Subscribe(_messageReceived);
|
||||||
|
|
||||||
|
string username = _configuration.GetSection("VirtualParadise:Username").Value ??
|
||||||
|
throw new InvalidOperationException("Username is not set.");
|
||||||
|
string password = _configuration.GetSection("VirtualParadise:Password").Value ??
|
||||||
|
throw new InvalidOperationException("Password is not set.");
|
||||||
|
string world = _configuration.GetSection("VirtualParadise:World").Value ??
|
||||||
|
throw new InvalidOperationException("World is not set.");
|
||||||
|
string botName = _configuration.GetSection("VirtualParadise:BotName").Value ??
|
||||||
|
throw new InvalidOperationException("Bot name is not set.");
|
||||||
|
|
||||||
|
_logger.LogDebug("Connecting to Virtual Paradise");
|
||||||
|
await _virtualParadiseClient.ConnectAsync().ConfigureAwait(false);
|
||||||
|
await _virtualParadiseClient.LoginAsync(username, password, botName).ConfigureAwait(false);
|
||||||
|
|
||||||
|
_logger.LogInformation("Entering world {World}", world);
|
||||||
|
await _virtualParadiseClient.EnterAsync(world).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
57
VpBridge/VpBridge.csproj
Normal file
57
VpBridge/VpBridge.csproj
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||||
|
<Authors>Oliver Booth</Authors>
|
||||||
|
<RepositoryUrl>https://github.com/oliverbooth/VpBridge</RepositoryUrl>
|
||||||
|
<RepositoryType>git</RepositoryType>
|
||||||
|
<VersionPrefix>1.0.0</VersionPrefix>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
|
||||||
|
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(VersionSuffix)' != '' And '$(BuildNumber)' == ''">
|
||||||
|
<Version>$(VersionPrefix)-$(VersionSuffix)</Version>
|
||||||
|
<AssemblyVersion>$(VersionPrefix).0</AssemblyVersion>
|
||||||
|
<FileVersion>$(VersionPrefix).0</FileVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(VersionSuffix)' != '' And '$(BuildNumber)' != ''">
|
||||||
|
<Version>$(VersionPrefix)-$(VersionSuffix).$(BuildNumber)</Version>
|
||||||
|
<AssemblyVersion>$(VersionPrefix).$(BuildNumber)</AssemblyVersion>
|
||||||
|
<FileVersion>$(VersionPrefix).$(BuildNumber)</FileVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(VersionSuffix)' == ''">
|
||||||
|
<Version>$(VersionPrefix)</Version>
|
||||||
|
<AssemblyVersion>$(VersionPrefix).0</AssemblyVersion>
|
||||||
|
<FileVersion>$(VersionPrefix).0</FileVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="..\.dockerignore">
|
||||||
|
<Link>.dockerignore</Link>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Discord.Net" Version="3.12.0"/>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1"/>
|
||||||
|
<PackageReference Include="Serilog" Version="3.0.1"/>
|
||||||
|
<PackageReference Include="Serilog.Extensions.Hosting" Version="7.0.0"/>
|
||||||
|
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0"/>
|
||||||
|
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0"/>
|
||||||
|
<PackageReference Include="System.Reactive" Version="6.0.0"/>
|
||||||
|
<PackageReference Include="Tomlyn.Extensions.Configuration" Version="1.0.5"/>
|
||||||
|
<PackageReference Include="VpSharp" Version="0.1.0-nightly.43"/>
|
||||||
|
<PackageReference Include="X10D" Version="3.3.1"/>
|
||||||
|
<PackageReference Include="X10D.Hosting" Version="3.3.1"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
16
docker-compose.yml
Normal file
16
docker-compose.yml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
version: '3.9'
|
||||||
|
services:
|
||||||
|
vpbridge:
|
||||||
|
container_name: VpBridge
|
||||||
|
pull_policy: build
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: VpBridge/Dockerfile
|
||||||
|
volumes:
|
||||||
|
- type: bind
|
||||||
|
source: /var/log/vp/vp-bridge
|
||||||
|
target: /app/logs
|
||||||
|
- type: bind
|
||||||
|
source: /etc/vp/vp-bridge
|
||||||
|
target: /app/data
|
||||||
|
restart: always
|
7
global.json
Normal file
7
global.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"sdk": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"rollForward": "latestMinor",
|
||||||
|
"allowPrerelease": false
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user