mirror of
https://github.com/oliverbooth/VPLink
synced 2024-10-18 04:36: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