Implement plugin system (#1020)

* Implement basic plugin system

* The dawn of new era

* Add plugins warning

* Move more members to PublicAPI

* Open commands for the plugins

* Add IBotHackNewChat

* Run plugin events in parallel

* Use properties in IPlugin

* Hook our custom plugin into CI to ensure it compiles

* Fix dotnet brain damage

* Add IBotsComparer

* Add code documentation

* Add IBotTradeOffer

* Add IBotTradeOffer example

* Add IBotTradeOfferResults

* Final bulletproofing

* Final renaming
This commit is contained in:
Łukasz Domeradzki
2019-01-10 22:33:07 +01:00
committed by GitHub
parent 62a770479e
commit 0f2a816b92
49 changed files with 3025 additions and 1653 deletions

View File

@@ -29,10 +29,11 @@ using System.Xml;
using ArchiSteamFarm.Localization;
using ArchiSteamFarm.NLog;
using HtmlAgilityPack;
using JetBrains.Annotations;
using Newtonsoft.Json;
namespace ArchiSteamFarm {
internal sealed class WebBrowser : IDisposable {
public sealed class WebBrowser : IDisposable {
internal const byte MaxConnections = 10; // Defines maximum number of connections per ServicePoint. Be careful, as it also defines maximum number of sockets in CLOSE_WAIT state
internal const byte MaxTries = 5; // Defines maximum number of recommended tries for a single request
@@ -73,6 +74,200 @@ namespace ArchiSteamFarm {
HttpClientHandler.Dispose();
}
[PublicAPI]
public async Task<HtmlDocumentResponse> UrlGetToHtmlDocument(string request, string referer = null, byte maxTries = MaxTries) {
if (string.IsNullOrEmpty(request) || (maxTries == 0)) {
ArchiLogger.LogNullError(nameof(request) + " || " + nameof(maxTries));
return null;
}
StringResponse response = await UrlGetToString(request, referer, maxTries).ConfigureAwait(false);
return response != null ? new HtmlDocumentResponse(response) : null;
}
[PublicAPI]
public async Task<ObjectResponse<T>> UrlGetToJsonObject<T>(string request, string referer = null, byte maxTries = MaxTries) where T : class {
if (string.IsNullOrEmpty(request) || (maxTries == 0)) {
ArchiLogger.LogNullError(nameof(request) + " || " + nameof(maxTries));
return null;
}
for (byte i = 0; i < maxTries; i++) {
StringResponse response = await UrlGetToString(request, referer, 1).ConfigureAwait(false);
if (string.IsNullOrEmpty(response?.Content)) {
continue;
}
T obj;
try {
obj = JsonConvert.DeserializeObject<T>(response.Content);
} catch (JsonException e) {
ArchiLogger.LogGenericWarningException(e);
if (Debugging.IsUserDebugging) {
ArchiLogger.LogGenericDebug(string.Format(Strings.Content, response.Content));
}
continue;
}
return new ObjectResponse<T>(response, obj);
}
if (maxTries > 1) {
ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, maxTries));
ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, request));
}
return null;
}
[PublicAPI]
public async Task<XmlDocumentResponse> UrlGetToXmlDocument(string request, string referer = null, byte maxTries = MaxTries) {
if (string.IsNullOrEmpty(request) || (maxTries == 0)) {
ArchiLogger.LogNullError(nameof(request) + " || " + nameof(maxTries));
return null;
}
for (byte i = 0; i < maxTries; i++) {
StringResponse response = await UrlGetToString(request, referer, 1).ConfigureAwait(false);
if (string.IsNullOrEmpty(response?.Content)) {
continue;
}
XmlDocument xmlDocument = new XmlDocument();
try {
xmlDocument.LoadXml(response.Content);
} catch (XmlException e) {
ArchiLogger.LogGenericWarningException(e);
continue;
}
return new XmlDocumentResponse(response, xmlDocument);
}
if (maxTries > 1) {
ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, maxTries));
ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, request));
}
return null;
}
[PublicAPI]
public async Task<BasicResponse> UrlHead(string request, string referer = null, byte maxTries = MaxTries) {
if (string.IsNullOrEmpty(request) || (maxTries == 0)) {
ArchiLogger.LogNullError(nameof(request) + " || " + nameof(maxTries));
return null;
}
for (byte i = 0; i < maxTries; i++) {
using (HttpResponseMessage response = await InternalHead(request, referer).ConfigureAwait(false)) {
if (response == null) {
continue;
}
return new BasicResponse(response);
}
}
if (maxTries > 1) {
ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, maxTries));
ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, request));
}
return null;
}
[PublicAPI]
public async Task<BasicResponse> UrlPost(string request, IReadOnlyCollection<KeyValuePair<string, string>> data = null, string referer = null, byte maxTries = MaxTries) {
if (string.IsNullOrEmpty(request) || (maxTries == 0)) {
ArchiLogger.LogNullError(nameof(request) + " || " + nameof(maxTries));
return null;
}
for (byte i = 0; i < maxTries; i++) {
using (HttpResponseMessage response = await InternalPost(request, data, referer).ConfigureAwait(false)) {
if (response == null) {
continue;
}
return new BasicResponse(response);
}
}
if (maxTries > 1) {
ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, maxTries));
ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, request));
}
return null;
}
[PublicAPI]
public async Task<HtmlDocumentResponse> UrlPostToHtmlDocument(string request, IReadOnlyCollection<KeyValuePair<string, string>> data = null, string referer = null, byte maxTries = MaxTries) {
if (string.IsNullOrEmpty(request) || (maxTries == 0)) {
ArchiLogger.LogNullError(nameof(request) + " || " + nameof(maxTries));
return null;
}
StringResponse response = await UrlPostToString(request, data, referer, maxTries).ConfigureAwait(false);
return response != null ? new HtmlDocumentResponse(response) : null;
}
[PublicAPI]
public async Task<ObjectResponse<T>> UrlPostToJsonObject<T>(string request, IReadOnlyCollection<KeyValuePair<string, string>> data = null, string referer = null, byte maxTries = MaxTries) where T : class {
if (string.IsNullOrEmpty(request) || (maxTries == 0)) {
ArchiLogger.LogNullError(nameof(request) + " || " + nameof(maxTries));
return null;
}
for (byte i = 0; i < maxTries; i++) {
StringResponse response = await UrlPostToString(request, data, referer, maxTries).ConfigureAwait(false);
if (string.IsNullOrEmpty(response?.Content)) {
continue;
}
T obj;
try {
obj = JsonConvert.DeserializeObject<T>(response.Content);
} catch (JsonException e) {
ArchiLogger.LogGenericWarningException(e);
if (Debugging.IsUserDebugging) {
ArchiLogger.LogGenericDebug(string.Format(Strings.Content, response.Content));
}
continue;
}
return new ObjectResponse<T>(response, obj);
}
if (maxTries > 1) {
ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, maxTries));
ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, request));
}
return null;
}
internal HttpClient GenerateDisposableHttpClient(bool extendedTimeout = false) {
HttpClient result = new HttpClient(HttpClientHandler) {
Timeout = TimeSpan.FromSeconds(extendedTimeout ? ExtendedTimeoutMultiplier * Program.GlobalConfig.ConnectionTimeout : Program.GlobalConfig.ConnectionTimeout)
@@ -185,57 +380,6 @@ namespace ArchiSteamFarm {
return null;
}
internal async Task<HtmlDocumentResponse> UrlGetToHtmlDocument(string request, string referer = null, byte maxTries = MaxTries) {
if (string.IsNullOrEmpty(request) || (maxTries == 0)) {
ArchiLogger.LogNullError(nameof(request) + " || " + nameof(maxTries));
return null;
}
StringResponse response = await UrlGetToString(request, referer, maxTries).ConfigureAwait(false);
return response != null ? new HtmlDocumentResponse(response) : null;
}
internal async Task<ObjectResponse<T>> UrlGetToJsonObject<T>(string request, string referer = null, byte maxTries = MaxTries) where T : class {
if (string.IsNullOrEmpty(request) || (maxTries == 0)) {
ArchiLogger.LogNullError(nameof(request) + " || " + nameof(maxTries));
return null;
}
for (byte i = 0; i < maxTries; i++) {
StringResponse response = await UrlGetToString(request, referer, 1).ConfigureAwait(false);
if (string.IsNullOrEmpty(response?.Content)) {
continue;
}
T obj;
try {
obj = JsonConvert.DeserializeObject<T>(response.Content);
} catch (JsonException e) {
ArchiLogger.LogGenericWarningException(e);
if (Debugging.IsUserDebugging) {
ArchiLogger.LogGenericDebug(string.Format(Strings.Content, response.Content));
}
continue;
}
return new ObjectResponse<T>(response, obj);
}
if (maxTries > 1) {
ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, maxTries));
ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, request));
}
return null;
}
internal async Task<StringResponse> UrlGetToString(string request, string referer = null, byte maxTries = MaxTries) {
if (string.IsNullOrEmpty(request) || (maxTries == 0)) {
ArchiLogger.LogNullError(nameof(request) + " || " + nameof(maxTries));
@@ -261,142 +405,6 @@ namespace ArchiSteamFarm {
return null;
}
internal async Task<XmlDocumentResponse> UrlGetToXmlDocument(string request, string referer = null, byte maxTries = MaxTries) {
if (string.IsNullOrEmpty(request) || (maxTries == 0)) {
ArchiLogger.LogNullError(nameof(request) + " || " + nameof(maxTries));
return null;
}
for (byte i = 0; i < maxTries; i++) {
StringResponse response = await UrlGetToString(request, referer, 1).ConfigureAwait(false);
if (string.IsNullOrEmpty(response?.Content)) {
continue;
}
XmlDocument xmlDocument = new XmlDocument();
try {
xmlDocument.LoadXml(response.Content);
} catch (XmlException e) {
ArchiLogger.LogGenericWarningException(e);
continue;
}
return new XmlDocumentResponse(response, xmlDocument);
}
if (maxTries > 1) {
ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, maxTries));
ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, request));
}
return null;
}
internal async Task<BasicResponse> UrlHead(string request, string referer = null, byte maxTries = MaxTries) {
if (string.IsNullOrEmpty(request) || (maxTries == 0)) {
ArchiLogger.LogNullError(nameof(request) + " || " + nameof(maxTries));
return null;
}
for (byte i = 0; i < maxTries; i++) {
using (HttpResponseMessage response = await InternalHead(request, referer).ConfigureAwait(false)) {
if (response == null) {
continue;
}
return new BasicResponse(response);
}
}
if (maxTries > 1) {
ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, maxTries));
ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, request));
}
return null;
}
internal async Task<BasicResponse> UrlPost(string request, IReadOnlyCollection<KeyValuePair<string, string>> data = null, string referer = null, byte maxTries = MaxTries) {
if (string.IsNullOrEmpty(request) || (maxTries == 0)) {
ArchiLogger.LogNullError(nameof(request) + " || " + nameof(maxTries));
return null;
}
for (byte i = 0; i < maxTries; i++) {
using (HttpResponseMessage response = await InternalPost(request, data, referer).ConfigureAwait(false)) {
if (response == null) {
continue;
}
return new BasicResponse(response);
}
}
if (maxTries > 1) {
ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, maxTries));
ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, request));
}
return null;
}
internal async Task<HtmlDocumentResponse> UrlPostToHtmlDocument(string request, IReadOnlyCollection<KeyValuePair<string, string>> data = null, string referer = null, byte maxTries = MaxTries) {
if (string.IsNullOrEmpty(request) || (maxTries == 0)) {
ArchiLogger.LogNullError(nameof(request) + " || " + nameof(maxTries));
return null;
}
StringResponse response = await UrlPostToString(request, data, referer, maxTries).ConfigureAwait(false);
return response != null ? new HtmlDocumentResponse(response) : null;
}
internal async Task<ObjectResponse<T>> UrlPostToJsonObject<T>(string request, IReadOnlyCollection<KeyValuePair<string, string>> data = null, string referer = null, byte maxTries = MaxTries) where T : class {
if (string.IsNullOrEmpty(request) || (maxTries == 0)) {
ArchiLogger.LogNullError(nameof(request) + " || " + nameof(maxTries));
return null;
}
for (byte i = 0; i < maxTries; i++) {
StringResponse response = await UrlPostToString(request, data, referer, maxTries).ConfigureAwait(false);
if (string.IsNullOrEmpty(response?.Content)) {
continue;
}
T obj;
try {
obj = JsonConvert.DeserializeObject<T>(response.Content);
} catch (JsonException e) {
ArchiLogger.LogGenericWarningException(e);
if (Debugging.IsUserDebugging) {
ArchiLogger.LogGenericDebug(string.Format(Strings.Content, response.Content));
}
continue;
}
return new ObjectResponse<T>(response, obj);
}
if (maxTries > 1) {
ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, maxTries));
ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, request));
}
return null;
}
private async Task<HttpResponseMessage> InternalGet(string request, string referer = null, HttpCompletionOption httpCompletionOptions = HttpCompletionOption.ResponseContentRead) {
if (string.IsNullOrEmpty(request)) {
ArchiLogger.LogNullError(nameof(request));
@@ -563,7 +571,7 @@ namespace ArchiSteamFarm {
return null;
}
internal class BasicResponse {
public class BasicResponse {
internal readonly Uri FinalUri;
internal BasicResponse(HttpResponseMessage httpResponseMessage) {
@@ -583,20 +591,9 @@ namespace ArchiSteamFarm {
}
}
internal sealed class BinaryResponse : BasicResponse {
internal readonly byte[] Content;
internal BinaryResponse(HttpResponseMessage httpResponseMessage, byte[] content) : base(httpResponseMessage) {
if ((httpResponseMessage == null) || (content == null)) {
throw new ArgumentNullException(nameof(httpResponseMessage) + " || " + nameof(content));
}
Content = content;
}
}
internal sealed class HtmlDocumentResponse : BasicResponse {
internal readonly HtmlDocument Content;
public sealed class HtmlDocumentResponse : BasicResponse {
[PublicAPI]
public readonly HtmlDocument Content;
internal HtmlDocumentResponse(StringResponse stringResponse) : base(stringResponse) {
if (stringResponse == null) {
@@ -607,8 +604,9 @@ namespace ArchiSteamFarm {
}
}
internal sealed class ObjectResponse<T> : BasicResponse {
internal readonly T Content;
public sealed class ObjectResponse<T> : BasicResponse {
[PublicAPI]
public readonly T Content;
internal ObjectResponse(StringResponse stringResponse, T content) : base(stringResponse) {
if (stringResponse == null) {
@@ -619,6 +617,31 @@ namespace ArchiSteamFarm {
}
}
public sealed class XmlDocumentResponse : BasicResponse {
[PublicAPI]
public readonly XmlDocument Content;
internal XmlDocumentResponse(StringResponse stringResponse, XmlDocument content) : base(stringResponse) {
if (stringResponse == null) {
throw new ArgumentNullException(nameof(stringResponse));
}
Content = content;
}
}
internal sealed class BinaryResponse : BasicResponse {
internal readonly byte[] Content;
internal BinaryResponse(HttpResponseMessage httpResponseMessage, byte[] content) : base(httpResponseMessage) {
if ((httpResponseMessage == null) || (content == null)) {
throw new ArgumentNullException(nameof(httpResponseMessage) + " || " + nameof(content));
}
Content = content;
}
}
internal sealed class StringResponse : BasicResponse {
internal readonly string Content;
@@ -630,17 +653,5 @@ namespace ArchiSteamFarm {
Content = content;
}
}
internal sealed class XmlDocumentResponse : BasicResponse {
internal readonly XmlDocument Content;
internal XmlDocumentResponse(StringResponse stringResponse, XmlDocument content) : base(stringResponse) {
if (stringResponse == null) {
throw new ArgumentNullException(nameof(stringResponse));
}
Content = content;
}
}
}
}