From 5cdab275c352119377641ace62bd26303080c9b3 Mon Sep 17 00:00:00 2001
From: barelyprofessional
<150058423+barelyprofessional@users.noreply.github.com>
Date: Sun, 16 Jun 2024 12:18:56 +0800
Subject: [PATCH] 3xpl websocket client in case anyone wanted one. Don't bother
using it though, their websocket service is a piece of shit that's totally
broken which I only found out after wasting a day on it.
---
KfChatDotNet.sln | 12 +
ThreeXplCliClient/NLog.config | 15 +
ThreeXplCliClient/NLog.xsd | 3483 ++++++++++++++++++
ThreeXplCliClient/Program.cs | 18 +
ThreeXplCliClient/ThreeXplCliClient.csproj | 30 +
ThreeXplCliClient/ThreeXplClient.cs | 38 +
ThreeXplWsClient/Events/EventHandlers.cs | 22 +
ThreeXplWsClient/Events/EventModels.cs | 110 +
ThreeXplWsClient/Models/JwtResponseModels.cs | 17 +
ThreeXplWsClient/Models/RequestModels.cs | 31 +
ThreeXplWsClient/ThreeXplWsClient.cs | 235 ++
ThreeXplWsClient/ThreeXplWsClient.csproj | 15 +
12 files changed, 4026 insertions(+)
create mode 100644 ThreeXplCliClient/NLog.config
create mode 100644 ThreeXplCliClient/NLog.xsd
create mode 100644 ThreeXplCliClient/Program.cs
create mode 100644 ThreeXplCliClient/ThreeXplCliClient.csproj
create mode 100644 ThreeXplCliClient/ThreeXplClient.cs
create mode 100644 ThreeXplWsClient/Events/EventHandlers.cs
create mode 100644 ThreeXplWsClient/Events/EventModels.cs
create mode 100644 ThreeXplWsClient/Models/JwtResponseModels.cs
create mode 100644 ThreeXplWsClient/Models/RequestModels.cs
create mode 100644 ThreeXplWsClient/ThreeXplWsClient.cs
create mode 100644 ThreeXplWsClient/ThreeXplWsClient.csproj
diff --git a/KfChatDotNet.sln b/KfChatDotNet.sln
index ee16e3e..33a8819 100644
--- a/KfChatDotNet.sln
+++ b/KfChatDotNet.sln
@@ -10,6 +10,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KickWsClient", "KickWsClien
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KfChatDotNetKickBot", "KfChatDotNetKickBot\KfChatDotNetKickBot.csproj", "{4734E0A4-150E-4915-B905-928BB4BE3FF6}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ThreeXplWsClient", "ThreeXplWsClient\ThreeXplWsClient.csproj", "{3D72D70A-48AD-4EE8-89DC-C78153EEA879}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ThreeXplCliClient", "ThreeXplCliClient\ThreeXplCliClient.csproj", "{D098E281-5535-4A07-9514-57AF78704B0C}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -36,5 +40,13 @@ Global
{4734E0A4-150E-4915-B905-928BB4BE3FF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4734E0A4-150E-4915-B905-928BB4BE3FF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4734E0A4-150E-4915-B905-928BB4BE3FF6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3D72D70A-48AD-4EE8-89DC-C78153EEA879}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3D72D70A-48AD-4EE8-89DC-C78153EEA879}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3D72D70A-48AD-4EE8-89DC-C78153EEA879}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3D72D70A-48AD-4EE8-89DC-C78153EEA879}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D098E281-5535-4A07-9514-57AF78704B0C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D098E281-5535-4A07-9514-57AF78704B0C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D098E281-5535-4A07-9514-57AF78704B0C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D098E281-5535-4A07-9514-57AF78704B0C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
diff --git a/ThreeXplCliClient/NLog.config b/ThreeXplCliClient/NLog.config
new file mode 100644
index 0000000..ab4e010
--- /dev/null
+++ b/ThreeXplCliClient/NLog.config
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/ThreeXplCliClient/NLog.xsd b/ThreeXplCliClient/NLog.xsd
new file mode 100644
index 0000000..e2b7858
--- /dev/null
+++ b/ThreeXplCliClient/NLog.xsd
@@ -0,0 +1,3483 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Watch config file for changes and reload automatically.
+
+
+
+
+ Print internal NLog messages to the console. Default value is: false
+
+
+
+
+ Print internal NLog messages to the console error output. Default value is: false
+
+
+
+
+ Write internal NLog messages to the specified file.
+
+
+
+
+ Log level threshold for internal log messages. Default value is: Info.
+
+
+
+
+ Global log level threshold for application log messages. Messages below this level won't be logged.
+
+
+
+
+ Throw an exception when there is an internal error. Default value is: false. Not recommend to set to true in production!
+
+
+
+
+ Throw an exception when there is a configuration error. If not set, determined by throwExceptions.
+
+
+
+
+ Gets or sets a value indicating whether Variables should be kept on configuration reload. Default value is: false.
+
+
+
+
+ Write internal NLog messages to the System.Diagnostics.Trace. Default value is: false.
+
+
+
+
+ Write timestamps for internal NLog messages. Default value is: true.
+
+
+
+
+ Use InvariantCulture as default culture instead of CurrentCulture. Default value is: false.
+
+
+
+
+ Perform message template parsing and formatting of LogEvent messages (true = Always, false = Never, empty = Auto Detect). Default value is: empty.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Make all targets within this section asynchronous (creates additional threads but the calling thread isn't blocked by any target writes).
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Prefix for targets/layout renderers/filters/conditions loaded from this assembly.
+
+
+
+
+ Load NLog extensions from the specified file (*.dll)
+
+
+
+
+ Load NLog extensions from the specified assembly. Assembly name should be fully qualified.
+
+
+
+
+
+
+
+
+
+ Filter on the name of the logger. May include wildcard characters ('*' or '?').
+
+
+
+
+ Comma separated list of levels that this rule matches.
+
+
+
+
+ Minimum level that this rule matches.
+
+
+
+
+ Maximum level that this rule matches.
+
+
+
+
+ Level that this rule matches.
+
+
+
+
+ Comma separated list of target names.
+
+
+
+
+ Ignore further rules if this one matches.
+
+
+
+
+ Enable this rule. Note: disabled rules aren't available from the API.
+
+
+
+
+ Rule identifier to allow rule lookup with Configuration.FindRuleByName and Configuration.RemoveRuleByName.
+
+
+
+
+ Loggers matching will be restricted to specified minimum level for following rules.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Default action if none of the filters match.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the file to be included. You could use * wildcard. The name is relative to the name of the current config file.
+
+
+
+
+ Ignore any errors in the include file.
+
+
+
+
+
+
+
+ Variable value. Note, the 'value' attribute has precedence over this one.
+
+
+
+
+
+ Variable name.
+
+
+
+
+ Variable value.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Action to be taken when the lazy writer thread request queue count exceeds the set limit.
+
+
+
+
+ Limit on the number of requests in the lazy writer thread request queue.
+
+
+
+
+ Number of log events that should be processed in a batch by the lazy writer thread.
+
+
+
+
+ Whether to use the locking queue, instead of a lock-free concurrent queue
+
+
+
+
+ Number of batches of P:NLog.Targets.Wrappers.AsyncTargetWrapper.BatchSize to write before yielding into P:NLog.Targets.Wrappers.AsyncTargetWrapper.TimeToSleepBetweenBatches
+
+
+
+
+ Time in milliseconds to sleep between batches. (1 or less means trigger on new activity)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Delay the flush until the LogEvent has been confirmed as written
+
+
+
+
+ Condition expression. Log events who meet this condition will cause a flush on the wrapped target.
+
+
+
+
+ Only flush when LogEvent matches condition. Ignore explicit-flush, config-reload-flush and shutdown-flush
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Number of log events to be buffered.
+
+
+
+
+ Action to take if the buffer overflows.
+
+
+
+
+ Timeout (in milliseconds) after which the contents of buffer will be flushed if there's no write in the specified period of time. Use -1 to disable timed flushes.
+
+
+
+
+ Indicates whether to use sliding timeout.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Separator for T:NLog.ScopeContext operation-states-stack.
+
+
+
+
+ Stack separator for log4j:NDC in output from T:NLog.ScopeContext nested context.
+
+
+
+
+ Renderer for log4j:event logger-xml-attribute (Default ${logger})
+
+
+
+
+ Whether to include the contents of the T:NLog.ScopeContext properties-dictionary.
+
+
+
+
+ Whether to include log4j:NDC in output from T:NLog.ScopeContext nested context.
+
+
+
+
+ Indicates whether to include source info (file name and line number) in the information sent over the network.
+
+
+
+
+ Whether to include log4j:NDC in output from T:NLog.ScopeContext nested context.
+
+
+
+
+ Option to include all properties from the log events
+
+
+
+
+ Indicates whether to include call site (class and method name) in the information sent over the network.
+
+
+
+
+ AppInfo field. By default it's the friendly name of the current AppDomain.
+
+
+
+
+ Instance of T:NLog.Layouts.Log4JXmlEventLayout that is used to format log messages.
+
+
+
+
+ Indicates whether to include NLog-specific extensions to log4j schema.
+
+
+
+
+ Action that should be taken, when more connections than P:NLog.Targets.NetworkTarget.MaxConnections.
+
+
+
+
+ SSL/TLS protocols. Default no SSL/TLS is used. Currently only implemented for TCP.
+
+
+
+
+ Action that should be taken, when more pending messages than P:NLog.Targets.NetworkTarget.MaxQueueSize.
+
+
+
+
+ Action that should be taken if the message is larger than P:NLog.Targets.NetworkTarget.MaxMessageSize
+
+
+
+
+ Maximum queue size for a single connection. Requires P:NLog.Targets.NetworkTarget.KeepConnection = true
+
+
+
+
+ Network address.
+
+
+
+
+ Indicates whether to keep connection open whenever possible.
+
+
+
+
+ The number of seconds a connection will remain idle before the first keep-alive probe is sent
+
+
+
+
+ Size of the connection cache (number of connections which are kept alive). Requires P:NLog.Targets.NetworkTarget.KeepConnection = true
+
+
+
+
+ Maximum simultaneous connections. Requires P:NLog.Targets.NetworkTarget.KeepConnection = false
+
+
+
+
+ Type of compression for protocol payload. Useful for UDP where datagram max-size is 8192 bytes.
+
+
+
+
+ Skip compression when protocol payload is below limit to reduce overhead in cpu-usage and additional headers
+
+
+
+
+ Maximum message size in bytes. On limit breach then P:NLog.Targets.NetworkTarget.OnOverflow action is activated.
+
+
+
+
+ Encoding to be used.
+
+
+
+
+ End of line value if a newline is appended at the end of log message P:NLog.Targets.NetworkTarget.NewLine.
+
+
+
+
+ Indicates whether to append newline at the end of log message.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Viewer parameter name.
+
+
+
+
+ Layout that should be use to calculate the value for the parameter.
+
+
+
+
+ Whether an attribute with empty value should be included in the output
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Text to be rendered.
+
+
+
+
+ Header.
+
+
+
+
+ Footer.
+
+
+
+
+ Indicates whether to auto-check if the console is available. - Disables console writing if Environment.UserInteractive = False (Windows Service) - Disables console writing if Console Standard Input is not available (Non-Console-App)
+
+
+
+
+ Enables output using ANSI Color Codes
+
+
+
+
+ The encoding for writing messages to the T:System.Console.
+
+
+
+
+ Indicates whether to send the log messages to the standard error instead of the standard output.
+
+
+
+
+ Indicates whether to auto-flush after M:System.Console.WriteLine
+
+
+
+
+ Indicates whether to auto-check if the console has been redirected to file - Disables coloring logic when System.Console.IsOutputRedirected = true
+
+
+
+
+ Indicates whether to use default row highlighting rules.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Background color.
+
+
+
+
+ Condition that must be met in order to set the specified foreground and background color.
+
+
+
+
+ Foreground color.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Background color.
+
+
+
+
+ Compile the P:NLog.Targets.ConsoleWordHighlightingRule.Regex? This can improve the performance, but at the costs of more memory usage. If false, the Regex Cache is used.
+
+
+
+
+ Condition that must be met before scanning the row for highlight of words
+
+
+
+
+ Foreground color.
+
+
+
+
+ Indicates whether to ignore case when comparing texts.
+
+
+
+
+ Regular expression to be matched. You must specify either text or regex.
+
+
+
+
+ Text to be matched. You must specify either text or regex.
+
+
+
+
+ Indicates whether to match whole words only.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Text to be rendered.
+
+
+
+
+ Header.
+
+
+
+
+ Footer.
+
+
+
+
+ Indicates whether to auto-flush after M:System.Console.WriteLine
+
+
+
+
+ Indicates whether to auto-check if the console is available - Disables console writing if Environment.UserInteractive = False (Windows Service) - Disables console writing if Console Standard Input is not available (Non-Console-App)
+
+
+
+
+ The encoding for writing messages to the T:System.Console.
+
+
+
+
+ Indicates whether to send the log messages to the standard error instead of the standard output.
+
+
+
+
+ Whether to activate internal buffering to allow batch writing, instead of using M:System.Console.WriteLine
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Database user name. If the ConnectionString is not provided this value will be used to construct the "User ID=" part of the connection string.
+
+
+
+
+ Database password. If the ConnectionString is not provided this value will be used to construct the "Password=" part of the connection string.
+
+
+
+
+ Database name. If the ConnectionString is not provided this value will be used to construct the "Database=" part of the connection string.
+
+
+
+
+ Name of the connection string (as specified in <connectionStrings> configuration section.
+
+
+
+
+ Database host name. If the ConnectionString is not provided this value will be used to construct the "Server=" part of the connection string.
+
+
+
+
+ Indicates whether to keep the database connection open between the log events.
+
+
+
+
+ Name of the database provider.
+
+
+
+
+ Connection string. When provided, it overrides the values specified in DBHost, DBUserName, DBPassword, DBDatabase.
+
+
+
+
+ Connection string using for installation and uninstallation. If not provided, regular ConnectionString is being used.
+
+
+
+
+ Configures isolated transaction batch writing. If supported by the database, then it will improve insert performance.
+
+
+
+
+ Text of the SQL command to be run on each log level.
+
+
+
+
+ Type of the SQL command to be run on each log level.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Convert format of the property value
+
+
+
+
+ Culture used for parsing property string-value for type-conversion
+
+
+
+
+ Value to assign on the object-property
+
+
+
+
+ Name for the object-property
+
+
+
+
+ Type of the object-property
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Type of the command.
+
+
+
+
+ Connection string to run the command against. If not provided, connection string from the target is used.
+
+
+
+
+ Indicates whether to ignore failures.
+
+
+
+
+ Command text.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Database parameter name.
+
+
+
+
+ Layout that should be use to calculate the value for the parameter.
+
+
+
+
+ Database parameter DbType.
+
+
+
+
+ Database parameter size.
+
+
+
+
+ Database parameter precision.
+
+
+
+
+ Database parameter scale.
+
+
+
+
+ Type of the parameter.
+
+
+
+
+ Fallback value when result value is not available
+
+
+
+
+ Convert format of the database parameter value.
+
+
+
+
+ Culture used for parsing parameter string-value for type-conversion
+
+
+
+
+ Whether empty value should translate into DbNull. Requires database column to allow NULL values.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Text to be rendered.
+
+
+
+
+ Header.
+
+
+
+
+ Footer.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Text to be rendered.
+
+
+
+
+ Header.
+
+
+
+
+ Footer.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Layout used to format log messages.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Layout used to format log messages.
+
+
+
+
+ Layout that renders event Category.
+
+
+
+
+ Optional entry type. When not set, or when not convertible to T:System.Diagnostics.EventLogEntryType then determined by T:NLog.LogLevel
+
+
+
+
+ Layout that renders event ID.
+
+
+
+
+ Name of the Event Log to write to. This can be System, Application or any user-defined name.
+
+
+
+
+ Name of the machine on which Event Log service is running.
+
+
+
+
+ Maximum Event log size in kilobytes.
+
+
+
+
+ Message length limit to write to the Event Log.
+
+
+
+
+ Value to be used as the event Source.
+
+
+
+
+ Action to take if the message is larger than the P:NLog.Targets.EventLogTarget.MaxMessageLength option.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Indicates whether to return to the first target after any successful write.
+
+
+
+
+ Whether to enable batching, but fallback will be handled individually
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Name of the file to write to.
+
+
+
+
+ Text to be rendered.
+
+
+
+
+ Header.
+
+
+
+
+ Footer.
+
+
+
+
+ Indicates whether the footer should be written only when the file is archived.
+
+
+
+
+ Maximum number of archive files that should be kept.
+
+
+
+
+ Maximum days of archive files that should be kept.
+
+
+
+
+ Value of the file size threshold to archive old log file on startup.
+
+
+
+
+ Indicates whether to archive old log file on startup.
+
+
+
+
+ Indicates whether to compress archive files into the zip archive format.
+
+
+
+
+ Name of the file to be used for an archive.
+
+
+
+
+ Is the P:NLog.Targets.FileTarget.ArchiveFileName an absolute or relative path?
+
+
+
+
+ Indicates whether to automatically archive log files every time the specified time passes.
+
+
+
+
+ Value specifying the date format to use when archiving files.
+
+
+
+
+ Size in bytes above which log files will be automatically archived.
+
+
+
+
+ Way file archives are numbered.
+
+
+
+
+ Indicates whether to create directories if they do not exist.
+
+
+
+
+ Indicates whether file creation calls should be synchronized by a system global mutex.
+
+
+
+
+ Gets or set a value indicating whether a managed file stream is forced, instead of using the native implementation.
+
+
+
+
+ Is the P:NLog.Targets.FileTarget.FileName an absolute or relative path?
+
+
+
+
+ File attributes (Windows only).
+
+
+
+
+ Cleanup invalid values in a filename, e.g. slashes in a filename. If set to true, this can impact the performance of massive writes. If set to false, nothing gets written when the filename is wrong.
+
+
+
+
+ Indicates whether to write BOM (byte order mark) in created files. Defaults to true for UTF-16 and UTF-32
+
+
+
+
+ Indicates whether to enable log file(s) to be deleted.
+
+
+
+
+ Indicates whether to delete old log file on startup.
+
+
+
+
+ File encoding.
+
+
+
+
+ Indicates whether to replace file contents on each write instead of appending log message at the end.
+
+
+
+
+ Line ending mode.
+
+
+
+
+ Number of times the write is appended on the file before NLog discards the log message.
+
+
+
+
+ Delay in milliseconds to wait before attempting to write to the file again.
+
+
+
+
+ Maximum number of seconds before open files are flushed. Zero or negative means disabled.
+
+
+
+
+ Maximum number of seconds that files are kept open. Zero or negative means disabled.
+
+
+
+
+ Indicates whether concurrent writes to the log file by multiple processes on different network hosts.
+
+
+
+
+ Log file buffer size in bytes.
+
+
+
+
+ Indicates whether to automatically flush the file buffers after each log message.
+
+
+
+
+ Indicates whether to keep log file open instead of opening and closing it on each logging event.
+
+
+
+
+ Indicates whether concurrent writes to the log file by multiple processes on the same host.
+
+
+
+
+ Whether or not this target should just discard all data that its asked to write. Mostly used for when testing NLog Stack except final write
+
+
+
+
+ Number of files to be kept open. Setting this to a higher value may improve performance in a situation where a single File target is writing to many files (such as splitting by level or by logger).
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Condition expression. Log events who meet this condition will be forwarded to the wrapped target.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Identifier to perform group-by
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Windows domain name to change context to.
+
+
+
+
+ Required impersonation level.
+
+
+
+
+ Type of the logon provider.
+
+
+
+
+ Logon Type.
+
+
+
+
+ User account password.
+
+
+
+
+ Indicates whether to revert to the credentials of the process instead of impersonating another user.
+
+
+
+
+ Username to change context to.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Interval in which messages will be written up to the P:NLog.Targets.Wrappers.LimitingTargetWrapper.MessageLimit number of messages.
+
+
+
+
+ Maximum allowed number of messages written per P:NLog.Targets.Wrappers.LimitingTargetWrapper.Interval.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Text to be rendered.
+
+
+
+
+ Header.
+
+
+
+
+ Footer.
+
+
+
+
+ Indicates whether NewLine characters in the body should be replaced with tags.
+
+
+
+
+ Priority used for sending mails.
+
+
+
+
+ Encoding to be used for sending e-mail.
+
+
+
+
+ BCC email addresses separated by semicolons (e.g. john@domain.com;jane@domain.com).
+
+
+
+
+ CC email addresses separated by semicolons (e.g. john@domain.com;jane@domain.com).
+
+
+
+
+ Indicates whether to add new lines between log entries.
+
+
+
+
+ Indicates whether to send message as HTML instead of plain text.
+
+
+
+
+ Sender's email address (e.g. joe@domain.com).
+
+
+
+
+ Mail message body (repeated for each log message send in one mail).
+
+
+
+
+ Mail subject.
+
+
+
+
+ Recipients' email addresses separated by semicolons (e.g. john@domain.com;jane@domain.com).
+
+
+
+
+ Specifies how outgoing email messages will be handled.
+
+
+
+
+ SMTP Server to be used for sending.
+
+
+
+
+ SMTP Authentication mode.
+
+
+
+
+ Username used to connect to SMTP server (used when SmtpAuthentication is set to "basic").
+
+
+
+
+ Password used to authenticate against SMTP server (used when SmtpAuthentication is set to "basic").
+
+
+
+
+ Indicates whether SSL (secure sockets layer) should be used when communicating with SMTP server.
+
+
+
+
+ Port number that SMTP Server is listening on.
+
+
+
+
+ Indicates whether the default Settings from System.Net.MailSettings should be used.
+
+
+
+
+ Folder where applications save mail messages to be processed by the local SMTP server.
+
+
+
+
+ Indicates the SMTP client timeout.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Text to be rendered.
+
+
+
+
+ Header.
+
+
+
+
+ Footer.
+
+
+
+
+ Max number of items to have in memory
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Class name.
+
+
+
+
+ Method name. The method must be public and static. Use the AssemblyQualifiedName , https://msdn.microsoft.com/en-us/library/system.type.assemblyqualifiedname(v=vs.110).aspx e.g.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the parameter.
+
+
+
+
+ Layout that should be use to calculate the value for the parameter.
+
+
+
+
+ Fallback value when result value is not available
+
+
+
+
+ Type of the parameter.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Layout used to format log messages.
+
+
+
+
+ SSL/TLS protocols. Default no SSL/TLS is used. Currently only implemented for TCP.
+
+
+
+
+ Action that should be taken, when more pending messages than P:NLog.Targets.NetworkTarget.MaxQueueSize.
+
+
+
+
+ Action that should be taken if the message is larger than P:NLog.Targets.NetworkTarget.MaxMessageSize
+
+
+
+
+ Maximum queue size for a single connection. Requires P:NLog.Targets.NetworkTarget.KeepConnection = true
+
+
+
+
+ Action that should be taken, when more connections than P:NLog.Targets.NetworkTarget.MaxConnections.
+
+
+
+
+ Indicates whether to keep connection open whenever possible.
+
+
+
+
+ The number of seconds a connection will remain idle before the first keep-alive probe is sent
+
+
+
+
+ Size of the connection cache (number of connections which are kept alive). Requires P:NLog.Targets.NetworkTarget.KeepConnection = true
+
+
+
+
+ Network address.
+
+
+
+
+ Maximum simultaneous connections. Requires P:NLog.Targets.NetworkTarget.KeepConnection = false
+
+
+
+
+ Type of compression for protocol payload. Useful for UDP where datagram max-size is 8192 bytes.
+
+
+
+
+ Skip compression when protocol payload is below limit to reduce overhead in cpu-usage and additional headers
+
+
+
+
+ Maximum message size in bytes. On limit breach then P:NLog.Targets.NetworkTarget.OnOverflow action is activated.
+
+
+
+
+ Encoding to be used.
+
+
+
+
+ End of line value if a newline is appended at the end of log message P:NLog.Targets.NetworkTarget.NewLine.
+
+
+
+
+ Indicates whether to append newline at the end of log message.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Separator for T:NLog.ScopeContext operation-states-stack.
+
+
+
+
+ Stack separator for log4j:NDC in output from T:NLog.ScopeContext nested context.
+
+
+
+
+ Renderer for log4j:event logger-xml-attribute (Default ${logger})
+
+
+
+
+ Whether to include the contents of the T:NLog.ScopeContext properties-dictionary.
+
+
+
+
+ Whether to include log4j:NDC in output from T:NLog.ScopeContext nested context.
+
+
+
+
+ Indicates whether to include source info (file name and line number) in the information sent over the network.
+
+
+
+
+ Whether to include log4j:NDC in output from T:NLog.ScopeContext nested context.
+
+
+
+
+ Option to include all properties from the log events
+
+
+
+
+ Indicates whether to include call site (class and method name) in the information sent over the network.
+
+
+
+
+ AppInfo field. By default it's the friendly name of the current AppDomain.
+
+
+
+
+ Instance of T:NLog.Layouts.Log4JXmlEventLayout that is used to format log messages.
+
+
+
+
+ Indicates whether to include NLog-specific extensions to log4j schema.
+
+
+
+
+ Action that should be taken, when more connections than P:NLog.Targets.NetworkTarget.MaxConnections.
+
+
+
+
+ SSL/TLS protocols. Default no SSL/TLS is used. Currently only implemented for TCP.
+
+
+
+
+ Action that should be taken, when more pending messages than P:NLog.Targets.NetworkTarget.MaxQueueSize.
+
+
+
+
+ Action that should be taken if the message is larger than P:NLog.Targets.NetworkTarget.MaxMessageSize
+
+
+
+
+ Maximum queue size for a single connection. Requires P:NLog.Targets.NetworkTarget.KeepConnection = true
+
+
+
+
+ Network address.
+
+
+
+
+ Indicates whether to keep connection open whenever possible.
+
+
+
+
+ The number of seconds a connection will remain idle before the first keep-alive probe is sent
+
+
+
+
+ Size of the connection cache (number of connections which are kept alive). Requires P:NLog.Targets.NetworkTarget.KeepConnection = true
+
+
+
+
+ Maximum simultaneous connections. Requires P:NLog.Targets.NetworkTarget.KeepConnection = false
+
+
+
+
+ Type of compression for protocol payload. Useful for UDP where datagram max-size is 8192 bytes.
+
+
+
+
+ Skip compression when protocol payload is below limit to reduce overhead in cpu-usage and additional headers
+
+
+
+
+ Maximum message size in bytes. On limit breach then P:NLog.Targets.NetworkTarget.OnOverflow action is activated.
+
+
+
+
+ Encoding to be used.
+
+
+
+
+ End of line value if a newline is appended at the end of log message P:NLog.Targets.NetworkTarget.NewLine.
+
+
+
+
+ Indicates whether to append newline at the end of log message.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Layout used to format log messages.
+
+
+
+
+ Indicates whether to perform layout calculation.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Default filter to be applied when no specific rule matches.
+
+
+
+
+
+
+
+
+
+
+
+
+ Condition to be tested.
+
+
+
+
+ Resulting filter to be applied when the condition matches.
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Number of times to repeat each log message.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Whether to enable batching, and only apply single delay when a whole batch fails
+
+
+
+
+ Number of retries that should be attempted on the wrapped target in case of a failure.
+
+
+
+
+ Time to wait between retries in milliseconds.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Text to be rendered.
+
+
+
+
+ Header.
+
+
+
+
+ Footer.
+
+
+
+
+ Forward F:NLog.LogLevel.Fatal to M:System.Diagnostics.Trace.Fail(System.String) (Instead of M:System.Diagnostics.Trace.TraceError(System.String))
+
+
+
+
+ Force use M:System.Diagnostics.Trace.WriteLine(System.String) independent of T:NLog.LogLevel
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Indicates whether to pre-authenticate the HttpWebRequest (Requires 'Authorization' in P:NLog.Targets.WebServiceTarget.Headers parameters)
+
+
+
+
+ Value whether escaping be done according to Rfc3986 (Supports Internationalized Resource Identifiers - IRIs)
+
+
+
+
+ Value whether escaping be done according to the old NLog style (Very non-standard)
+
+
+
+
+ Value of the User-agent HTTP header.
+
+
+
+
+ Web service URL.
+
+
+
+
+ Proxy configuration when calling web service
+
+
+
+
+ Custom proxy address, include port separated by a colon
+
+
+
+
+ Protocol to be used when calling web service.
+
+
+
+
+ Web service namespace. Only used with Soap.
+
+
+
+
+ Web service method name. Only used with Soap.
+
+
+
+
+ Should we include the BOM (Byte-order-mark) for UTF? Influences the P:NLog.Targets.WebServiceTarget.Encoding property. This will only work for UTF-8.
+
+
+
+
+ Encoding.
+
+
+
+
+ Name of the root XML element, if POST of XML document chosen. If so, this property must not be null. (see P:NLog.Targets.WebServiceTarget.Protocol and F:NLog.Targets.WebServiceProtocol.XmlPost).
+
+
+
+
+ (optional) root namespace of the XML document, if POST of XML document chosen. (see P:NLog.Targets.WebServiceTarget.Protocol and F:NLog.Targets.WebServiceProtocol.XmlPost).
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Custom column delimiter value (valid when ColumnDelimiter is set to 'Custom').
+
+
+
+
+ Column delimiter.
+
+
+
+
+ Footer layout.
+
+
+
+
+ Header layout.
+
+
+
+
+ Body layout (can be repeated multiple times).
+
+
+
+
+ Quote Character.
+
+
+
+
+ Quoting mode.
+
+
+
+
+ Indicates whether CVS should include header.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the column.
+
+
+
+
+ Layout of the column.
+
+
+
+
+ Override of Quoting mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Option to render the empty object value {}
+
+
+
+
+ Option to suppress the extra spaces in the output json
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Option to include all properties from the log event (as JSON)
+
+
+
+
+ Indicates whether to include contents of the T:NLog.GlobalDiagnosticsContext dictionary.
+
+
+
+
+ Whether to include the contents of the T:NLog.ScopeContext dictionary.
+
+
+
+
+ Should forward slashes be escaped? If true, / will be converted to \/
+
+
+
+
+ Option to exclude null/empty properties from the log event (as JSON)
+
+
+
+
+ List of property names to exclude when P:NLog.Layouts.JsonLayout.IncludeAllProperties is true
+
+
+
+
+ How far should the JSON serializer follow object references before backing off
+
+
+
+
+ Option to render the empty object value {}
+
+
+
+
+ Option to suppress the extra spaces in the output json
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the attribute.
+
+
+
+
+ Layout that will be rendered as the attribute's value.
+
+
+
+
+ Fallback value when result value is not available
+
+
+
+
+ Determines whether or not this attribute will be Json encoded.
+
+
+
+
+ Should forward slashes be escaped? If true, / will be converted to \/
+
+
+
+
+ Indicates whether to escape non-ascii characters
+
+
+
+
+ Whether an attribute with empty value should be included in the output
+
+
+
+
+ Result value type, for conversion of layout rendering output
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Footer layout.
+
+
+
+
+ Header layout.
+
+
+
+
+ Body layout (can be repeated multiple times).
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Option to include all properties from the log events
+
+
+
+
+ Whether to include log4j:NDC in output from T:NLog.ScopeContext nested context.
+
+
+
+
+ Whether to include log4j:NDC in output from T:NLog.ScopeContext nested context.
+
+
+
+
+ Whether to include the contents of the T:NLog.ScopeContext properties-dictionary.
+
+
+
+
+ AppInfo field. By default it's the friendly name of the current AppDomain.
+
+
+
+
+ Indicates whether to include call site (class and method name) in the information sent over the network.
+
+
+
+
+ Indicates whether to include source info (file name and line number) in the information sent over the network.
+
+
+
+
+ Log4j:event logger-xml-attribute (Default ${logger})
+
+
+
+
+ Whether the log4j:throwable xml-element should be written as CDATA
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Layout text.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the root XML element
+
+
+
+
+ Value inside the root XML element
+
+
+
+
+ Whether to include the contents of the T:NLog.ScopeContext dictionary.
+
+
+
+
+ Determines whether or not this attribute will be Xml encoded.
+
+
+
+
+ List of property names to exclude when P:NLog.Layouts.XmlElementBase.IncludeAllProperties is true
+
+
+
+
+ Whether a ElementValue with empty value should be included in the output
+
+
+
+
+ Auto indent and create new lines
+
+
+
+
+ How far should the XML serializer follow object references before backing off
+
+
+
+
+ XML element name to use for rendering IList-collections items
+
+
+
+
+ XML attribute name to use when rendering property-key When null (or empty) then key-attribute is not included
+
+
+
+
+ XML element name to use when rendering properties
+
+
+
+
+ XML attribute name to use when rendering property-value When null (or empty) then value-attribute is not included and value is formatted as XML-element-value
+
+
+
+
+ Option to include all properties from the log event (as XML)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the attribute.
+
+
+
+
+ Layout that will be rendered as the attribute's value.
+
+
+
+
+ Fallback value when result value is not available
+
+
+
+
+ Determines whether or not this attribute will be Xml encoded.
+
+
+
+
+ Whether an attribute with empty value should be included in the output
+
+
+
+
+ Result value type, for conversion of layout rendering output
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the element
+
+
+
+
+ Whether to include the contents of the T:NLog.ScopeContext dictionary.
+
+
+
+
+ Value inside the element
+
+
+
+
+ Determines whether or not this attribute will be Xml encoded.
+
+
+
+
+ List of property names to exclude when P:NLog.Layouts.XmlElementBase.IncludeAllProperties is true
+
+
+
+
+ Whether a ElementValue with empty value should be included in the output
+
+
+
+
+ Auto indent and create new lines
+
+
+
+
+ How far should the XML serializer follow object references before backing off
+
+
+
+
+ XML element name to use for rendering IList-collections items
+
+
+
+
+ XML attribute name to use when rendering property-key When null (or empty) then key-attribute is not included
+
+
+
+
+ XML element name to use when rendering properties
+
+
+
+
+ XML attribute name to use when rendering property-value When null (or empty) then value-attribute is not included and value is formatted as XML-element-value
+
+
+
+
+ Option to include all properties from the log event (as XML)
+
+
+
+
+
+
+
+
+
+
+
+
+ Action to be taken when filter matches.
+
+
+
+
+ Condition expression.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Action to be taken when filter matches.
+
+
+
+
+ Indicates whether to ignore case when comparing strings.
+
+
+
+
+ Layout to be used to filter log messages.
+
+
+
+
+ Substring to be matched.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Action to be taken when filter matches.
+
+
+
+
+ String to compare the layout to.
+
+
+
+
+ Indicates whether to ignore case when comparing strings.
+
+
+
+
+ Layout to be used to filter log messages.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Action to be taken when filter matches.
+
+
+
+
+ Indicates whether to ignore case when comparing strings.
+
+
+
+
+ Layout to be used to filter log messages.
+
+
+
+
+ Substring to be matched.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Action to be taken when filter matches.
+
+
+
+
+ String to compare the layout to.
+
+
+
+
+ Indicates whether to ignore case when comparing strings.
+
+
+
+
+ Layout to be used to filter log messages.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Action to be taken when filter matches.
+
+
+
+
+ Append FilterCount to the P:NLog.LogEventInfo.Message when an event is no longer filtered
+
+
+
+
+ Insert FilterCount value into P:NLog.LogEventInfo.Properties when an event is no longer filtered
+
+
+
+
+ Applies the configured action to the initial logevent that starts the timeout period. Used to configure that it should ignore all events until timeout.
+
+
+
+
+ Layout to be used to filter log messages.
+
+
+
+
+ Max length of filter values, will truncate if above limit
+
+
+
+
+ How long before a filter expires, and logging is accepted again
+
+
+
+
+ Default number of unique filter values to expect, will automatically increase if needed
+
+
+
+
+ Max number of unique filter values to expect simultaneously
+
+
+
+
+ Default buffer size for the internal buffers
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ThreeXplCliClient/Program.cs b/ThreeXplCliClient/Program.cs
new file mode 100644
index 0000000..2b773c5
--- /dev/null
+++ b/ThreeXplCliClient/Program.cs
@@ -0,0 +1,18 @@
+using System.Net;
+using System.Text;
+using Spectre.Console;
+using ThreeXplWsClient.Events;
+
+namespace ThreeXplCliClient
+{
+ public class Program
+ {
+ static async Task Main(string[] args)
+ {
+ Console.OutputEncoding = Encoding.UTF8;
+ AnsiConsole.MarkupLine("[green]3xpl test client started[/]");
+ var cliClient = new ThreeXplClient();
+ await cliClient.Start();
+ }
+ }
+}
\ No newline at end of file
diff --git a/ThreeXplCliClient/ThreeXplCliClient.csproj b/ThreeXplCliClient/ThreeXplCliClient.csproj
new file mode 100644
index 0000000..16aef83
--- /dev/null
+++ b/ThreeXplCliClient/ThreeXplCliClient.csproj
@@ -0,0 +1,30 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Always
+
+
+
+ Always
+
+
+
+
diff --git a/ThreeXplCliClient/ThreeXplClient.cs b/ThreeXplCliClient/ThreeXplClient.cs
new file mode 100644
index 0000000..150a373
--- /dev/null
+++ b/ThreeXplCliClient/ThreeXplClient.cs
@@ -0,0 +1,38 @@
+using System.Text.Json;
+using Spectre.Console;
+using ThreeXplWsClient.Events;
+
+namespace ThreeXplCliClient;
+
+public class ThreeXplClient
+{
+ private List _addresses =
+ [
+ "MC8TiBEsnQVjxbvLtTsUXjTBZTQaR8fe8X",
+ "ltc1qks2m7hvmhs3c20zrfvptv9pvk82p8g70sgw5mk"
+ ];
+ public async Task Start()
+ {
+ var client = new ThreeXplWsClient.ThreeXplWsClient();
+ client.OnThreeXplPush += OnThreeXplEvent;
+ await client.StartWsClient();
+ while (true)
+ {
+ var prompt = AnsiConsole.Ask("Channel: ");
+ client.SendSubscribeRequest(prompt);
+ }
+ }
+
+ private void OnThreeXplEvent(object sender, ThreeXplPushModel e, int connectionId)
+ {
+ AnsiConsole.MarkupLine("[blue]Received event from 3xpl[/]");
+ foreach (var txn in e.Pub.Data.Data)
+ {
+ if (txn.Address == null) return;
+ if (_addresses.Contains(txn.Address))
+ {
+ AnsiConsole.MarkupLine($"[green]Saw txn I'm interested in: {txn.Address}, effect {txn.Effect}, currency {txn.Currency}[/]");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ThreeXplWsClient/Events/EventHandlers.cs b/ThreeXplWsClient/Events/EventHandlers.cs
new file mode 100644
index 0000000..a360196
--- /dev/null
+++ b/ThreeXplWsClient/Events/EventHandlers.cs
@@ -0,0 +1,22 @@
+using Websocket.Client;
+
+namespace ThreeXplWsClient.Events;
+
+public class EventHandlers
+{
+ public delegate void OnThreeXplPing(object sender, int connectionId);
+
+ public delegate void OnThreeXplPush(object sender, ThreeXplPushModel e, int connectionId);
+
+ public delegate void OnWsDisconnectionEventHandler(object sender, DisconnectionInfo e, int connectionId);
+
+ public delegate void OnWsReconnectEventHandler(object sender, ReconnectionInfo e, int connectionId);
+
+ public delegate void OnWsMessageReceivedEventHandler(object sender, ResponseMessage e, int connectionId);
+
+ public delegate void OnThreeXplConnect(object sender, ThreeXplConnectDataModel e, int connectionId);
+
+ public delegate void OnThreeXplError(object sender, ThreeXplErrorModel e, int connectionId);
+
+ public delegate void OnThreeXplSubscribe(object sender, ThreeXplSubscribeModel e, int connectionId);
+}
\ No newline at end of file
diff --git a/ThreeXplWsClient/Events/EventModels.cs b/ThreeXplWsClient/Events/EventModels.cs
new file mode 100644
index 0000000..f18a953
--- /dev/null
+++ b/ThreeXplWsClient/Events/EventModels.cs
@@ -0,0 +1,110 @@
+using System.Text.Json.Serialization;
+
+namespace ThreeXplWsClient.Events;
+
+public class BaseThreeXplPacketModel
+{
+ [JsonPropertyName("connect")]
+ public ThreeXplConnectDataModel? Connect { get; set; }
+ [JsonPropertyName("id")]
+ public int? Id { get; set; }
+ [JsonPropertyName("error")]
+ public ThreeXplErrorModel? Error { get; set; }
+ [JsonPropertyName("subscribe")]
+ public ThreeXplSubscribeModel? Subscribe { get; set; }
+ [JsonPropertyName("push")]
+ public ThreeXplPushModel? Push { get; set; }
+
+}
+
+public class ThreeXplDataModel
+{
+ [JsonPropertyName("blockchain")]
+ public string? Blockchain { get; set; }
+ [JsonPropertyName("module")]
+ public string? Module { get; set; }
+ [JsonPropertyName("block")]
+ public int? Block { get; set; }
+ [JsonPropertyName("transaction")]
+ public string? Transaction { get; set; }
+ [JsonPropertyName("sort_key")]
+ public int? SortKey { get; set; }
+ [JsonPropertyName("time")]
+ public DateTimeOffset? Time { get; set; }
+ [JsonPropertyName("currency")]
+ public string? Currency { get; set; }
+ [JsonPropertyName("effect")]
+ public string? Effect { get; set; }
+ [JsonPropertyName("failed")]
+ public bool? Failed { get; set; }
+ [JsonPropertyName("extra")]
+ public object? Extra { get; set; }
+ [JsonPropertyName("extra_indexed")]
+ public object? ExtraIndexed { get; set; }
+ [JsonPropertyName("address")]
+ public string? Address { get; set; }
+}
+
+public class ThreeXplContextModel
+{
+ // "time":"0.21778600 1718465848"
+ [JsonPropertyName("time")]
+ public string? Time { get; set; }
+}
+
+public class ThreeXplConnectDataModel
+{
+ [JsonPropertyName("client")]
+ public string? Client { get; set; }
+ [JsonPropertyName("version")]
+ public string? Version { get; set; }
+ [JsonPropertyName("ping")]
+ public int? Ping { get; set; }
+ [JsonPropertyName("pong")]
+ public bool? Pong { get; set; }
+}
+
+public class ThreeXplErrorModel
+{
+ [JsonPropertyName("code")]
+ public int? Code { get; set; }
+ [JsonPropertyName("Message")]
+ public string? Message { get; set; }
+ [JsonPropertyName("temporary")]
+ public bool? Temporary { get; set; }
+}
+
+public class ThreeXplSubscribeModel
+{
+ [JsonPropertyName("recoverable")]
+ public bool? Recoverable { get; set; }
+ [JsonPropertyName("epoch")]
+ public string? Epoch { get; set; }
+ [JsonPropertyName("positioned")]
+ public bool? Positioned { get; set; }
+}
+
+public class ThreeXplPushModel
+{
+ [JsonPropertyName("channel")]
+ public required string Channel { get; set; }
+ [JsonPropertyName("pub")]
+ public required ThreeXplPushPubModel Pub { get; set; }
+ [JsonPropertyName("offset")]
+ public int? Offset { get; set; }
+
+}
+
+public class ThreeXplPushPubModel
+{
+ [JsonPropertyName("data")]
+ public required ThreeXplPushDataModel Data { get; set; }
+}
+
+public class ThreeXplPushDataModel
+{
+ [JsonPropertyName("data")]
+ public required List Data { get; set; }
+ [JsonPropertyName("context")]
+ public required ThreeXplContextModel Context { get; set; }
+}
\ No newline at end of file
diff --git a/ThreeXplWsClient/Models/JwtResponseModels.cs b/ThreeXplWsClient/Models/JwtResponseModels.cs
new file mode 100644
index 0000000..5b61075
--- /dev/null
+++ b/ThreeXplWsClient/Models/JwtResponseModels.cs
@@ -0,0 +1,17 @@
+using System.Text.Json.Serialization;
+
+namespace ThreeXplWsClient.Models;
+
+public class GetWebsocketTokenModel
+{
+ [JsonPropertyName("data")]
+ public required string Data { get; set; }
+ [JsonPropertyName("context")]
+ public GetWebSocketTokenContextModel? Context { get; set; }
+}
+
+public class GetWebSocketTokenContextModel
+{
+ [JsonPropertyName("code")]
+ public int? Code { get; set; }
+}
\ No newline at end of file
diff --git a/ThreeXplWsClient/Models/RequestModels.cs b/ThreeXplWsClient/Models/RequestModels.cs
new file mode 100644
index 0000000..11691f1
--- /dev/null
+++ b/ThreeXplWsClient/Models/RequestModels.cs
@@ -0,0 +1,31 @@
+using System.Text.Json.Serialization;
+
+namespace ThreeXplWsClient.Models;
+
+public class ConnectRequestModel
+{
+ [JsonPropertyName("connect")]
+ public required ConnectRequestTokenModel Connect { get; set; }
+ [JsonPropertyName("id")]
+ public int Id { get; set; } = 1;
+}
+
+public class ConnectRequestTokenModel
+{
+ [JsonPropertyName("token")]
+ public required string Token { get; set; }
+}
+
+public class SubscribeRequestModel
+{
+ [JsonPropertyName("subscribe")]
+ public required SubscribeRequestChannelModel Subscribe { get; set; }
+ [JsonPropertyName("id")]
+ public int Id { get; set; } = 2;
+}
+
+public class SubscribeRequestChannelModel
+{
+ [JsonPropertyName("channel")]
+ public required string Channel { get; set; }
+}
\ No newline at end of file
diff --git a/ThreeXplWsClient/ThreeXplWsClient.cs b/ThreeXplWsClient/ThreeXplWsClient.cs
new file mode 100644
index 0000000..ae3a4ad
--- /dev/null
+++ b/ThreeXplWsClient/ThreeXplWsClient.cs
@@ -0,0 +1,235 @@
+using System.Net;
+using System.Net.Http.Json;
+using System.Net.WebSockets;
+using System.Text.Json;
+using NLog;
+using ThreeXplWsClient.Events;
+using ThreeXplWsClient.Models;
+using Websocket.Client;
+
+namespace ThreeXplWsClient;
+
+public class ThreeXplWsClient
+{
+ public event EventHandlers.OnWsMessageReceivedEventHandler OnWsMessageReceived;
+ public event EventHandlers.OnWsDisconnectionEventHandler OnWsDisconnection;
+ public event EventHandlers.OnWsReconnectEventHandler OnWsReconnect;
+ public event EventHandlers.OnThreeXplPing OnThreeXplPing;
+ public event EventHandlers.OnThreeXplPush OnThreeXplPush;
+ public event EventHandlers.OnThreeXplConnect OnThreeXplConnect;
+ public event EventHandlers.OnThreeXplError OnThreeXplError;
+ public event EventHandlers.OnThreeXplSubscribe OnThreeXplSubscribe;
+
+ private WebsocketClient _wsClient;
+ private readonly Logger _logger = LogManager.GetCurrentClassLogger();
+ private string? _wsJwt;
+ private DateTime _wsJwtLastRetrieved = DateTime.Now;
+ private int _wsJwtValidityPeriodSeconds;
+ private string? _proxy;
+ private int _reconnectTimeout;
+ private Uri _wsUri;
+ // Basically they have a limit of 10 subscriptions per connection and I have more than 10 addresses to monitor, so
+ // I give each connection an ID number as that way I know what addresses need to be resubscribed in the event of a
+ // connection drop. This ID is included with every event fired and set when the class is constructed.
+ private int _connectionId;
+
+ ///
+ /// Client for the 3xpl WebSocket API
+ ///
+ /// URI for the websocket API, published at https://3xpl.com/data/websocket-api
+ /// Web proxy to use for the WebSocket connection
+ /// Reconnect timeout, defaults to 30 seconds as 3xpl tells us to expect a ping every 25 seconds
+ /// How long the JWT is valid for. Set to int.MaxValue if you've manually provided a non-expiring token
+ /// Manually provide a JWT if you have access to create your own
+ /// ID that can be used to differentiate multiple 3xpl connections
+ public ThreeXplWsClient(string threeXplWsUri = "wss://stream.3xpl.net", string? proxy = null,
+ int reconnectTimeout = 30, int jwtValidityPeriodSeconds = 600, string? jwtApiToken = null, int connectionId = 0)
+ {
+ _wsUri = new Uri(threeXplWsUri);
+ _proxy = proxy;
+ _reconnectTimeout = reconnectTimeout;
+ _wsJwtValidityPeriodSeconds = jwtValidityPeriodSeconds;
+ _wsJwt = jwtApiToken;
+ _connectionId = connectionId;
+ }
+
+ private async Task RefreshApiToken()
+ {
+ _logger.Debug("Refreshing the API token");
+ if (_wsJwtValidityPeriodSeconds == int.MaxValue)
+ {
+ _logger.Debug($"Token is non expiring as it is set to {int.MaxValue}");
+ return;
+ }
+ if (_wsJwt != null && _wsJwtLastRetrieved.AddSeconds(_wsJwtValidityPeriodSeconds) >= DateTime.Now)
+ {
+ _logger.Debug(
+ $"Token has not yet expired. Its expiration date is {_wsJwtLastRetrieved.AddSeconds(_wsJwtValidityPeriodSeconds):yyyy-MM-dd HH:mm:ss}");
+ return;
+ }
+
+ var handler = new HttpClientHandler { AutomaticDecompression = DecompressionMethods.All };
+ if (_proxy != null)
+ {
+ handler.Proxy = new WebProxy(_proxy);
+ handler.UseProxy = true;
+ }
+
+ using var client = new HttpClient(handler);
+ var token = await client.GetFromJsonAsync("https://3xpl.com/get-websockets-token");
+ if (token == null)
+ {
+ _logger.Error("Caught a null when retrieving a WebSocket JWT from 3xpl");
+ throw new InvalidOperationException("Caught a null when retrieving a WebSocket JWT from 3xp");
+ }
+
+ _wsJwt = token.Data;
+ _wsJwtLastRetrieved = DateTime.Now;
+ }
+
+ public async Task StartWsClient()
+ {
+ _logger.Debug("StartWsClient() called, creating client");
+ await CreateWsClient();
+ }
+
+ private async Task CreateWsClient()
+ {
+ var factory = new Func(() =>
+ {
+ var clientWs = new ClientWebSocket();
+ if (_proxy == null) return clientWs;
+ clientWs.Options.Proxy = new WebProxy(_proxy);
+ return clientWs;
+ });
+ var client = new WebsocketClient(_wsUri, factory)
+ {
+ ReconnectTimeout = TimeSpan.FromSeconds(_reconnectTimeout)
+ };
+ _wsClient = client;
+
+ client.ReconnectionHappened.Subscribe(WsReconnection);
+ client.MessageReceived.Subscribe(WsMessageReceived);
+ client.DisconnectionHappened.Subscribe(WsDisconnection);
+
+ _logger.Debug("Websocket client has been built, about to start");
+ await client.Start();
+ _logger.Debug("Websocket client started!");
+ }
+
+ public bool IsConnected()
+ {
+ return _wsClient is { IsRunning: true };
+ }
+
+ private void WsDisconnection(DisconnectionInfo disconnectionInfo)
+ {
+ _logger.Error($"Client disconnected from the chat (or never successfully connected). Type is {disconnectionInfo.Type}");
+ _logger.Error(disconnectionInfo.Exception);
+ OnWsDisconnection?.Invoke(this, disconnectionInfo, _connectionId);
+ }
+
+ private void SendConnectRequest()
+ {
+ if (_wsJwt == null)
+ {
+ _logger.Error("JWT was null.");
+ throw new InvalidOperationException("JWT was null");
+ }
+
+ var data = new ConnectRequestModel { Connect = new ConnectRequestTokenModel { Token = _wsJwt } };
+ var payload = JsonSerializer.Serialize(data);
+ _logger.Debug("Sending the following payload to 3xpl");
+ _logger.Debug(payload);
+ _wsClient.Send(payload);
+ }
+
+ private void WsReconnection(ReconnectionInfo reconnectionInfo)
+ {
+ _logger.Error($"Websocket connection dropped and reconnected. Reconnection type is {reconnectionInfo.Type}");
+ _logger.Info("Refreshing JWT");
+ RefreshApiToken().Wait();
+ _logger.Info("Sending connect request");
+ SendConnectRequest();
+ OnWsReconnect?.Invoke(this, reconnectionInfo, _connectionId);
+ }
+
+ public void SendSubscribeRequest(string channel)
+ {
+ var data = new SubscribeRequestModel { Subscribe = new SubscribeRequestChannelModel { Channel = channel }};
+ var payload = JsonSerializer.Serialize(data);
+ _logger.Debug("Sending the following subscription payload to 3xpl");
+ _logger.Debug(payload);
+ _wsClient.Send(payload);
+ }
+
+ private void WsMessageReceived(ResponseMessage message)
+ {
+ OnWsMessageReceived?.Invoke(this, message, _connectionId);
+ _logger.Debug("Received JSON from 3xpl");
+ _logger.Debug(message.Text);
+
+ if (message.Text == null)
+ {
+ _logger.Info("Websocket message was null, ignoring packet");
+ return;
+ }
+
+ if (message.Text == "{}")
+ {
+ _logger.Debug("Received ping from 3xpl. Sending back a pong and invoking event");
+ _wsClient.Send("{}");
+ OnThreeXplPing?.Invoke(this, _connectionId);
+ return;
+ }
+
+ BaseThreeXplPacketModel threeXplPacket;
+ try
+ {
+ threeXplPacket = JsonSerializer.Deserialize(message.Text) ??
+ throw new InvalidOperationException();
+ }
+ catch (Exception e)
+ {
+ _logger.Error("Failed to parse 3xpl payload. Exception follows:");
+ _logger.Error(e);
+ _logger.Error("--- Message from 3xpl follows ---");
+ _logger.Error(message.Text);
+ _logger.Error("--- /end of message ---");
+ return;
+ }
+
+ if (threeXplPacket.Connect != null)
+ {
+ _logger.Debug("Received connect packet from 3xpl, invoking event");
+ OnThreeXplConnect?.Invoke(this, threeXplPacket.Connect, _connectionId);
+ return;
+ }
+
+ if (threeXplPacket.Push != null)
+ {
+ _logger.Debug("Received data event from 3xpl");
+ OnThreeXplPush?.Invoke(this, threeXplPacket.Push, _connectionId);
+ return;
+ }
+
+ if (threeXplPacket.Error != null)
+ {
+ _logger.Debug("Received error packet from 3xpl");
+ OnThreeXplError?.Invoke(this, threeXplPacket.Error, _connectionId);
+ return;
+ }
+
+ if (threeXplPacket.Subscribe != null)
+ {
+ _logger.Debug("Received subscribe packet from 3xpl");
+ OnThreeXplSubscribe?.Invoke(this, threeXplPacket.Subscribe, _connectionId);
+ return;
+ }
+
+ _logger.Error("Failed to handle 3xpl packet");
+ _logger.Error("--- Message from 3xpl follows ---");
+ _logger.Error(message.Text);
+ _logger.Error("--- /end of message ---");
+ }
+}
\ No newline at end of file
diff --git a/ThreeXplWsClient/ThreeXplWsClient.csproj b/ThreeXplWsClient/ThreeXplWsClient.csproj
new file mode 100644
index 0000000..c9ea1c5
--- /dev/null
+++ b/ThreeXplWsClient/ThreeXplWsClient.csproj
@@ -0,0 +1,15 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+