1
0
mirror of https://github.com/oliverbooth/VPLink synced 2024-10-18 04:36:10 +00:00

feat: initial commit

This commit is contained in:
Oliver Booth 2023-08-22 14:57:18 +01:00
commit 5d3a5a173d
Signed by: oliverbooth
GPG Key ID: B89D139977693FED
20 changed files with 907 additions and 0 deletions

25
.dockerignore Normal file
View 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
View 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
View File

@ -0,0 +1,2 @@
ko_fi: oliverbooth
custom: ['https://buymeacoffee.com/oliverbooth']

42
.github/workflows/docker-release.yml vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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();

View 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();
}

View 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);
}

View 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);
}

View 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;
}
}

View 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
View 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
View 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
View File

@ -0,0 +1,7 @@
{
"sdk": {
"version": "7.0.0",
"rollForward": "latestMinor",
"allowPrerelease": false
}
}