Repository navigation
EN UO Topic http
Українська · Русский · English · Deutsch · Français · Italiano · Español · 繁體中文 · 日本語 · 한국어
Home · C# · IDE · UO API · A–Z · Examples · Languages
HTTP-queries and reading the response. The status code, header, and response body are separate data.
| Topic | Commands and explanations |
|---|---|
| HTTP_Get | UO.HTTP_Get |
| HTTP_Post | UO.HTTP_Post |
| HTTP_Header | UO.HTTP_Header |
| HTTP_Body | UO.HTTP_Body |
| External messages | UO.Messenger - Telegram, Discord and Viber |
UO.HTTP_Get · UO.HTTP_Post · UO.HTTP_Header · UO.HTTP_Body
Each command card has several independent scenarios: direct code, functions and classes. Below are selected short scenarios; restrictions and full analysis are in the cards.
Parameters, result and all scenarios
UO.HTTP_Get("https://example.com/csharp-config.json");
try
{
string body = Convert.ToString(UO.HTTP_Body()) ?? "";
using (var document = System.Text.Json.JsonDocument.Parse(body))
{
if (document.RootElement.TryGetProperty("minMana", out var value) &&
value.TryGetInt32(out int threshold) && threshold is >= 0 and <= 100)
return threshold;
}
}
catch (System.Text.Json.JsonException)
{
UO.Print("Response is not a valid configuration; using the default.");
}
return 30;Parameters, result and all scenarios
UO.HTTP_Get("https://example.com/C#-permission");
var permission = TrimText(UO.HTTP_Body());
return Truth(permission == "allow");
bool Truth(object? value) => value is bool flag ? flag : value is string text ? !string.IsNullOrEmpty(text) : value != null && Convert.ToDouble(value) != 0;
string TrimText(object? value) => (Convert.ToString(value) ?? "").Trim();Українська · Русский · English · Deutsch · Français · Italiano · Español · 繁體中文 · 日本語 · 한국어
dynamic UO.HTTP_Body();Reads the text of the last HTTP-response of this script. For example, after HTTP_Get you can parse JSON with settings or compare the response of your service with the expected string. The reading itself does not go to the network.
There are no parameters.
string — saved text. Before the first request in this runtime, after GET in the stream and after the network error - an empty line. A normal answer also has an empty body, for example HTTP 204. Do not compare the result with true/false as an indication of delivery.
- The last response is stored only in the current runtime of the script. The next GET/POST of this runtime replaces it; other simultaneously running scripts do not change it. The answer is not saved to the profile. If you need it after the next request, copy the body and headers into variables in advance.
- GET/POST wait for a response in the running script thread. The total time for requesting and reading the body is 20 seconds. Stopping the script cancels the wait; HTTP_Body/HTTP_Header expect nothing. The entire body is loaded into memory even when FileStream is transferred; This is not a streaming download of large files.
- The text is decoded according to the charset of the response, in the absence/unknown encoding - UTF-8. Answers HTTP 4xx/5xx are also read. When there is a network error or timeout, the client writes an error, the body and headers become empty; Try/Catch does not replace checking for the expected response. Script cancellation extends further. A local file write or invalid argument error can be caught by Try/Catch.
- HTTP_Body only reads the last buffer: it does not repeat the request, does not clear the response, and does not return data from another script. After binary GET, use MemoryStream.ToArray() or a stored file, not this text buffer.
Each example can be saved as a separate file .csx and run in IDE. The UO object and wait functions provide the host of the current game session. Replace demo serial names, coordinates and addresses with your own.
Run this code as a separate .csx file. Arguments appear directly in the call; replace demonstration names, serials and addresses with your own.
The result of one call is printed in the journal. A number is not converted to bool; an array is displayed as its element count.
UO.HTTP_Get("https://example.com/");
object? result = UO.HTTP_Body();
UO.Print(result is Array values ? "Count: " + values.Length : Convert.ToString(result));Parameters and execution flow:
- The service must return allow. Another answer, including an empty one, gives false. The example just calculates the resolution; does not trigger movement or combat.
- The example.com addresses are illustrative and do not provide such a service. Replace them with your own; running the example makes a real HTTP-query. The example does not perform game actions.
UO.HTTP_Get("https://example.com/C#-permission");
var permission = TrimText(UO.HTTP_Body());
return Truth(permission == "allow");
bool Truth(object? value) => value is bool flag ? flag : value is string text ? !string.IsNullOrEmpty(text) : value != null && Convert.ToDouble(value) != 0;
string TrimText(object? value) => (Convert.ToString(value) ?? "").Trim();Parameters and execution flow:
- First, the route name is stored, then a description is requested. Without routeName, the second GET would replace the first response.
- The example.com addresses are illustrative and do not provide such a service. Replace them with your own; running the example makes a real HTTP-query. The example does not perform game actions.
return RunScenario();
object? RunScenario()
{
UO.HTTP_Get("https://example.com/route-name");
var routeName = UO.HTTP_Body();
UO.HTTP_Get("https://example.com/route-description");
return routeName + ": " + UO.HTTP_Body();
return null;
}Parameters and execution flow:
- After GET with System.IO.MemoryStream the bytes are in the stream and HTTP_Body is empty. This rule also applies to a correct binary answer; empty text does not mean no bytes.
return new ScriptScenario(UO, WaitAsync, Args, Cancellation, Checkpoint).RunScenario();
class ScriptScenario
{
private readonly global::ClassicUO.CSharp.CSharpGameApi UO;
private readonly Func<int, Task> WaitAsync;
private readonly string[] Args;
private readonly CancellationToken Cancellation;
private readonly Action Checkpoint;
public ScriptScenario(global::ClassicUO.CSharp.CSharpGameApi api, Func<int, Task> wait, string[] args, CancellationToken cancellation, Action checkpoint)
{
UO = api;
WaitAsync = wait;
Args = args;
Cancellation = cancellation;
Checkpoint = checkpoint;
}
public object? RunScenario()
{
using (var data = new System.IO.MemoryStream())
{
UO.HTTP_Get("https://example.com/route-data.bin", data);
string body = Convert.ToString(UO.HTTP_Body()) ?? "";
UO.Print($"Binary bytes: {data.Length}; text-body characters: {body.Length}");
return body;
}
return null;
}
}C# is case sensitive. Use the exact UO name. Compare numeric flags with 0; use bool only where the signature returns bool.
Parameters: No parameters.
Result: dynamic / Any.
During a call:
Internal implementation code; this is not a standalone script.
case "http_body": return new InjectionValue(httpBody);Українська · Русский · English · Deutsch · Français · Italiano · Español · 繁體中文 · 日本語 · 한국어
void UO.HTTP_Get(object? url);
void UO.HTTP_Get(object? url, object? lStream);Loads data on HTTP/HTTPS. Suitable for reading settings from your service or receiving a file; does not manage the game connection or run downloaded code.
-
url— Required string: full address http:// or https://. Replace example.com with the address of your service. The game and game server are not needed for this request; The normal network connection of the process is used, and not the game proxy profile. -
lStreamis an optional standard .NET System.IO.Stream, writable: for example new System.IO.MemoryStream() or System.IO.File.Create(path). Without it or with 0/null the text reads HTTP_Body(). The thread receives the original bytes. For a seekable stream, the previous content is reset to zero, and the position before and after the write becomes 0; The non-seekable thread receives a write to the current position. Creating FileStream via File.Create nullifies the file immediately; the parent directory must exist. An inappropriate argument or read-only stream is rejected before the request.
void - no return value. Call on a separate line; the result cannot be used as a bool, ID, or number of bytes. The text after a normal request is read by HTTP_Body(), the binary data is read from the transferred stream.
- The last response is stored only in the current runtime of the script. The next GET/POST of this runtime replaces it; other simultaneously running scripts do not change it. The answer is not saved to the profile. If you need it after the next request, copy the body and headers into variables in advance.
- GET/POST wait for a response in the running script thread. The total time for requesting and reading the body is 20 seconds. Stopping the script cancels the wait; HTTP_Body/HTTP_Header expect nothing. The entire body is loaded into memory even when FileStream is transferred; This is not a streaming download of large files.
- The text is decoded according to the charset of the response, in the absence/unknown encoding - UTF-8. Answers HTTP 4xx/5xx are also read. When there is a network error or timeout, the client writes an error, the body and headers become empty; Try/Catch does not replace checking for the expected response. Script cancellation extends further. A local file write or invalid argument error can be caught by Try/Catch.
- GET without thread updates text and headings. GET with stream writes bytes, clears HTTP_Body and saves headers. Even an empty response/network error replaces the contents of the stream with empty data: to save the old file, first download to MemoryStream and check the data.
Each example can be saved as a separate file .csx and run in IDE. The UO object and wait functions provide the host of the current game session. Replace demo serial names, coordinates and addresses with your own.
Run this code as a separate .csx file. Arguments appear directly in the call; replace demonstration names, serials and addresses with your own.
The command is a separate statement. The message before it marks the start of the call; it does not confirm a server action.
UO.Print("Calling UO.HTTP_Get");
UO.HTTP_Get("https://example.com/");Parameters and execution flow:
- example.com - demo address; replace it with yours. System.Text.Json reads the response body and only accepts the integer minMana from 0 to 100. An empty/invalid JSON gives the default value 30.
UO.HTTP_Get("https://example.com/csharp-config.json");
try
{
string body = Convert.ToString(UO.HTTP_Body()) ?? "";
using (var document = System.Text.Json.JsonDocument.Parse(body))
{
if (document.RootElement.TryGetProperty("minMana", out var value) &&
value.TryGetInt32(out int threshold) && threshold is >= 0 and <= 100)
return threshold;
}
}
catch (System.Text.Json.JsonException)
{
UO.Print("Response is not a valid configuration; using the default.");
}
return 30;Parameters and execution flow:
- The standard System.IO.MemoryStream is transmitted. It receives the original bytes of the response; HTTP_Body becomes empty. The existing download.bin is replaced only after receiving non-empty data. Checking the length does not prove the content is correct.
return RunScenario();
object? RunScenario()
{
using (var data = new System.IO.MemoryStream())
{
UO.HTTP_Get("https://example.com/route-data.bin", data);
if (data.Length == 0)
{
UO.Print("The response is empty; no file was written.");
return false;
}
System.IO.File.WriteAllBytes("download.bin", data.ToArray());
return true;
}
return null;
}Parameters and execution flow:
- System.IO.File.Create creates or immediately nulls a file; GET writes the original bytes. If the response is empty, the file will remain empty. To save the previous version, first use MemoryStream as in the previous script. using closes the stream.
return new ScriptScenario(UO, WaitAsync, Args, Cancellation, Checkpoint).RunScenario();
class ScriptScenario
{
private readonly global::ClassicUO.CSharp.CSharpGameApi UO;
private readonly Func<int, Task> WaitAsync;
private readonly string[] Args;
private readonly CancellationToken Cancellation;
private readonly Action Checkpoint;
public ScriptScenario(global::ClassicUO.CSharp.CSharpGameApi api, Func<int, Task> wait, string[] args, CancellationToken cancellation, Action checkpoint)
{
UO = api;
WaitAsync = wait;
Args = args;
Cancellation = cancellation;
Checkpoint = checkpoint;
}
public object? RunScenario()
{
using (var output = System.IO.File.Create("http-download.bin"))
{
UO.HTTP_Get("https://example.com/route-data.bin", output);
}
return UO.HTTP_Header();
return null;
}
}C# is case sensitive. Use the exact UO name. Compare numeric flags with 0; use bool only where the signature returns bool.
Parameters: url (Any)
Result: void / Unit.
During a call:
-
InjectionScript.Runtime.InjectionApiUO.ExecuteClientApiCompatibility. -
ClassicUO.Game.Managers.ClassicUOInjectionApiBridge.HttpRequest:bridge.HttpRequest("GET", Text(0), string.Empty). -
InjectionScript.Runtime.InjectionApiUO.ExecuteClientApiCompatibility::Text:Text(0). -
InjectionScript.Runtime.ObjectTypes.MemoryStreamObject.ReplaceBytes:memoryStream.ReplaceBytes(raw).
Internal implementation code; this is not a standalone script.
case "http_get":
{
bool hasStream = arguments.Length > 1
&& !(arguments[1].Kind == InjectionValueKind.Integer && arguments[1].Integer == 0)
&& arguments[1].Kind != InjectionValueKind.Unit;
// Validate before network side effects and before changing the last response.
if (hasStream && !(arguments[1].Object is MemoryStreamObject))
throw new NativeSubrutineException("UO.HTTP_Get: LStream must be a writable System.IO.Stream, 0, or omitted.");
string[] response = bridge.HttpRequest("GET", Text(0), string.Empty);
httpHeader = response.Length > 1 ? response[1] : string.Empty;
httpBody = hasStream ? string.Empty : response.Length > 0 ? response[0] : string.Empty;
if (!hasStream)
{
return InjectionValue.Unit;
}
byte[] raw = response.Length > 3 && !string.IsNullOrEmpty(response[3])
? Convert.FromBase64String(response[3])
: Array.Empty<byte>();
if (arguments[1].Object is MemoryStreamObject memoryStream)
memoryStream.ReplaceBytes(raw);
else
throw new NativeSubrutineException("UO.HTTP_Get: LStream must be a writable System.IO.Stream.");
// Stream overloads keep the response as bytes, not cached text.
return InjectionValue.Unit;
}Parameters: url (Any), lStream (Any)
Result: void / Unit.
During a call:
-
ClassicUO.CSharp.CSharpScriptGlobals.CSharpScriptGlobals. -
InjectionScript.Runtime.InjectionApiUO.ExecuteClientApiCompatibility. -
ClassicUO.Game.Managers.ClassicUOInjectionApiBridge.HttpRequest:bridge.HttpRequest("GET", Text(0), string.Empty). -
InjectionScript.Runtime.InjectionApiUO.ExecuteClientApiCompatibility::Text:Text(0). -
InjectionScript.Runtime.ObjectTypes.MemoryStreamObject.ReplaceBytes:memoryStream.ReplaceBytes(raw).
Internal implementation code; this is not a standalone script.
if (name == "HTTP_Get" && arguments.Length == 2 && arguments[1] is Stream stream)
{
if (!stream.CanWrite) throw new ArgumentException("HTTP_Get requires a writable .NET stream.");
// Adapt the existing binary response path without fetching the
// response twice or converting its bytes through text encoding.
var type = typeof(InjectionValue).Assembly.GetType("InjectionScript.Runtime.ObjectTypes.MemoryStreamObject", throwOnError: true)!;
using var native = (IDisposable)Activator.CreateInstance(type, nonPublic: true)!;
value = Invoke(name, [arguments[0], native]);
var bytes = (InjectionValue)type.GetMethod("ToArray")!.Invoke(native, null)!;
byte[] payload = bytes.Array.Select(item => checked((byte)item.Integer)).ToArray();
if (stream.CanSeek) { stream.SetLength(0); stream.Position = 0; }
stream.Write(payload);
if (stream.CanSeek) stream.Position = 0;
}
else value = Invoke(name, arguments);Internal implementation code; this is not a standalone script.
case "http_get":
{
bool hasStream = arguments.Length > 1
&& !(arguments[1].Kind == InjectionValueKind.Integer && arguments[1].Integer == 0)
&& arguments[1].Kind != InjectionValueKind.Unit;
// Validate before network side effects and before changing the last response.
if (hasStream && !(arguments[1].Object is MemoryStreamObject))
throw new NativeSubrutineException("UO.HTTP_Get: LStream must be a writable System.IO.Stream, 0, or omitted.");
string[] response = bridge.HttpRequest("GET", Text(0), string.Empty);
httpHeader = response.Length > 1 ? response[1] : string.Empty;
httpBody = hasStream ? string.Empty : response.Length > 0 ? response[0] : string.Empty;
if (!hasStream)
{
return InjectionValue.Unit;
}
byte[] raw = response.Length > 3 && !string.IsNullOrEmpty(response[3])
? Convert.FromBase64String(response[3])
: Array.Empty<byte>();
if (arguments[1].Object is MemoryStreamObject memoryStream)
memoryStream.ReplaceBytes(raw);
else
throw new NativeSubrutineException("UO.HTTP_Get: LStream must be a writable System.IO.Stream.");
// Stream overloads keep the response as bytes, not cached text.
return InjectionValue.Unit;
}Українська · Русский · English · Deutsch · Français · Italiano · Español · 繁體中文 · 日本語 · 한국어
dynamic UO.HTTP_Header();Reads the headers of the last HTTP response of this script: for example, Content-Type, to find out the declared data format. This is not the response body, request headers, or numeric code HTTP.
There are no parameters.
string — strings of the form Name: value, separated by CRLF. Before the first request and after the network error - an empty line. The lines HTTP/1.1 200 OK are not here; The status code is not represented by a separate command. The order and case of names is not guaranteed.
- The last response is stored only in the current runtime of the script. The next GET/POST of this runtime replaces it; other simultaneously running scripts do not change it. The answer is not saved to the profile. If you need it after the next request, copy the body and headers into variables in advance.
- GET/POST wait for a response in the running script thread. The total time for requesting and reading the body is 20 seconds. Stopping the script cancels the wait; HTTP_Body/HTTP_Header expect nothing. The entire body is loaded into memory even when FileStream is transferred; This is not a streaming download of large files.
- The text is decoded according to the charset of the response, in the absence/unknown encoding - UTF-8. Answers HTTP 4xx/5xx are also read. When there is a network error or timeout, the client writes an error, the body and headers become empty; Try/Catch does not replace checking for the expected response. Script cancellation extends further. A local file write or invalid argument error can be caught by Try/Catch.
- HTTP_Header combines the headers of the response and its content. After binary GET they are also available. The name should be checked in a case-insensitive manner. Content-Type is a server statement, not a content check; don't execute the downloaded text as code.
Each example can be saved as a separate file .csx and run in IDE. The UO object and wait functions provide the host of the current game session. Replace demo serial names, coordinates and addresses with your own.
Run this code as a separate .csx file. Arguments appear directly in the call; replace demonstration names, serials and addresses with your own.
The result of one call is printed in the journal. A number is not converted to bool; an array is displayed as its element count.
UO.HTTP_Get("https://example.com/");
object? result = UO.HTTP_Header();
UO.Print(result is Array values ? "Count: " + values.Length : Convert.ToString(result));Parameters and execution flow:
- Headings are parsed line by line. The Content-Type name is compared case-insensitively; if there is no string, an empty string is returned.
UO.HTTP_Get("https://example.com/csharp-status");
return ReadHeader(Convert.ToString(UO.HTTP_Header()) ?? "", "Content-Type");
string ReadHeader(string headers, string name)
{
foreach (string line in headers.Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries))
{
int separator = line.IndexOf(':');
if (separator > 0 && line[..separator].Trim().Equals(name, StringComparison.OrdinalIgnoreCase))
return line[(separator + 1)..].Trim();
}
return "";
}Parameters and execution flow:
- The locally saved header text is checked. Content-Type itself does not guarantee the correctness of JSON; check the contents separately System.Text.Json.
return RunScenario();
object? RunScenario()
{
UO.HTTP_Get("https://example.com/csharp-status");
string headers = Convert.ToString(UO.HTTP_Header()) ?? "";
return headers.Contains("application/json", StringComparison.OrdinalIgnoreCase);
return null;
}Parameters and execution flow:
- GET forwards the source bytes to System.IO.MemoryStream. The last response headers still read HTTP_Header; text HTTP_Body for this form is empty. Only the headers are saved to the file.
return new ScriptScenario(UO, WaitAsync, Args, Cancellation, Checkpoint).RunScenario();
class ScriptScenario
{
private readonly global::ClassicUO.CSharp.CSharpGameApi UO;
private readonly Func<int, Task> WaitAsync;
private readonly string[] Args;
private readonly CancellationToken Cancellation;
private readonly Action Checkpoint;
public ScriptScenario(global::ClassicUO.CSharp.CSharpGameApi api, Func<int, Task> wait, string[] args, CancellationToken cancellation, Action checkpoint)
{
UO = api;
WaitAsync = wait;
Args = args;
Cancellation = cancellation;
Checkpoint = checkpoint;
}
public object? RunScenario()
{
using (var data = new System.IO.MemoryStream())
{
UO.HTTP_Get("https://example.com/route-data.bin", data);
string headers = Convert.ToString(UO.HTTP_Header()) ?? "";
System.IO.File.WriteAllText("http-response-headers.txt", headers);
UO.Print($"Saved response headers; downloaded {data.Length} binary bytes.");
return headers;
}
return null;
}
}C# is case sensitive. Use the exact UO name. Compare numeric flags with 0; use bool only where the signature returns bool.
Parameters: No parameters.
Result: dynamic / Any.
During a call:
Internal implementation code; this is not a standalone script.
case "http_header": return new InjectionValue(httpHeader);Українська · Русский · English · Deutsch · Français · Italiano · Español · 繁體中文 · 日本語 · 한국어
dynamic UO.HTTP_Post(object? url, object? postData);Sends a text to its HTTP service using the POST method and returns the response text. For example, a script can report a route stop. This is not a ready-made Telegram/Discord command: their protocol, authorization and request format require separate support.
-
url— Required string: full address http:// or https://. Replace example.com with the address of your service. The game and game server are not needed for this request; The normal network connection of the process is used, and not the game proxy profile. -
postData— Mandatory string with request body. Sent by UTF-8 with Content-Type: text/plain; charset=utf-8. There is no automatic form encoding and switching to application/json. System.Text.Json.JsonSerializer.Serialize only generates text: the example server should accept JSON exactly as text/plain. You cannot configure custom headers using these commands.
string — response text; it is also accessible via HTTP_Body(). This is not bool and not the code HTTP. An empty string does not indicate an error, but a non-empty string does not indicate success of the operation on the server.
- The last response is stored only in the current runtime of the script. The next GET/POST of this runtime replaces it; other simultaneously running scripts do not change it. The answer is not saved to the profile. If you need it after the next request, copy the body and headers into variables in advance.
- GET/POST wait for a response in the running script thread. The total time for requesting and reading the body is 20 seconds. Stopping the script cancels the wait; HTTP_Body/HTTP_Header expect nothing. The entire body is loaded into memory even when FileStream is transferred; This is not a streaming download of large files.
- The text is decoded according to the charset of the response, in the absence/unknown encoding - UTF-8. Answers HTTP 4xx/5xx are also read. When there is a network error or timeout, the client writes an error, the body and headers become empty; Try/Catch does not replace checking for the expected response. Script cancellation extends further. A local file write or invalid argument error can be caught by Try/Catch.
- POST updates the body and headers and returns the same body as a string. Don't repeat POST automatically just because the response is empty: the server may have already performed the action. The accepted/status check in the examples is an agreement with your service, not a built-in sign of HTTP-success.
Each example can be saved as a separate file .csx and run in IDE. The UO object and wait functions provide the host of the current game session. Replace demo serial names, coordinates and addresses with your own.
Run this code as a separate .csx file. Arguments appear directly in the call; replace demonstration names, serials and addresses with your own.
The result of one call is printed in the journal. A number is not converted to bool; an array is displayed as its element count.
object? result = UO.HTTP_Post("https://example.com/", "Hello from C#");
UO.Print(result is Array values ? "Count: " + values.Length : Convert.ToString(result));Parameters and execution flow:
- The service accepts the text Route stopped and, by agreement, responds with accepted. Main returns true only with this text; This is a check of your service's protocol, not a check of the HTTP code.
- The example.com addresses are illustrative and do not provide such a service. Replace them with your own; running the example makes a real HTTP-query. The example does not perform game actions.
var reply = UO.HTTP_Post("https://example.com/C#-event", "Route stopped");
return Truth(TrimText(reply) == "accepted");
bool Truth(object? value) => value is bool flag ? flag : value is string text ? !string.IsNullOrEmpty(text) : value != null && Convert.ToDouble(value) != 0;
string TrimText(object? value) => (Convert.ToString(value) ?? "").Trim();Parameters and execution flow:
- System.Text.Json.JsonSerializer escapes dictionary strings. The status value is read after checking JSON; Replace the demo address with yours.
return RunScenario();
object? RunScenario()
{
var report = new Dictionary<string, string>
{
["event"] = "route-stopped",
["text"] = "line one\nline two"
};
string payload = System.Text.Json.JsonSerializer.Serialize(report);
string reply = Convert.ToString(UO.HTTP_Post("https://example.com/csharp-report", payload)) ?? "";
try
{
using (var document = System.Text.Json.JsonDocument.Parse(reply))
{
return document.RootElement.TryGetProperty("status", out var value) ? value.GetString() : "unknown";
}
}
catch (System.Text.Json.JsonException)
{
return "invalid response";
}
return null;
}Parameters and execution flow:
- firstReply and firstHeaders store the answer POST. The subsequent GET replaces the internal buffers, but the local variables still contain the first response.
- The example.com addresses are illustrative and do not provide such a service. Replace them with your own; running the example makes a real HTTP-query. The example does not perform game actions.
return new ScriptScenario(UO, WaitAsync, Args, Cancellation, Checkpoint).RunScenario();
class ScriptScenario
{
private readonly global::ClassicUO.CSharp.CSharpGameApi UO;
private readonly Func<int, Task> WaitAsync;
private readonly string[] Args;
private readonly CancellationToken Cancellation;
private readonly Action Checkpoint;
public ScriptScenario(global::ClassicUO.CSharp.CSharpGameApi api, Func<int, Task> wait, string[] args, CancellationToken cancellation, Action checkpoint)
{
UO = api;
WaitAsync = wait;
Args = args;
Cancellation = cancellation;
Checkpoint = checkpoint;
}
public object? RunScenario()
{
var firstReply = UO.HTTP_Post("https://example.com/C#-event", "Route stopped");
var firstHeaders = UO.HTTP_Header();
UO.HTTP_Get("https://example.com/C#-status");
return firstReply + "|" + firstHeaders;
return null;
}
}C# is case sensitive. Use the exact UO name. Compare numeric flags with 0; use bool only where the signature returns bool.
Parameters: url (Any), postData (Any)
Result: dynamic / Any.
During a call:
-
InjectionScript.Runtime.InjectionApiUO.ExecuteClientApiCompatibility. -
ClassicUO.Game.Managers.ClassicUOInjectionApiBridge.HttpRequest:bridge.HttpRequest("POST", Text(0), Text(1)). -
InjectionScript.Runtime.InjectionApiUO.ExecuteClientApiCompatibility::Text:Text(0).
Internal implementation code; this is not a standalone script.
case "http_post":
{
string[] response = bridge.HttpRequest("POST", Text(0), Text(1));
httpBody = response.Length > 0 ? response[0] : string.Empty;
httpHeader = response.Length > 1 ? response[1] : string.Empty;
return new InjectionValue(httpBody);
}Українська · Русский · English · Deutsch · Français · Italiano · Español · 繁體中文 · 日本語 · 한국어
dynamic UO.Messenger(object? provider);Creates an object for text notifications and receiving Telegram, Discord or Viber messages. For example, the script could signal the end of resource collection or check for a stop request from a pre-specified user. Creating an object does not yet connect the bot and does not send anything.
-
provider— string: "telegram", "discord" or "viber"; case is not important. Other values cause an error.
Object Messenger. This is not bool and not ID. ID messages, senders and chats in this object are always string: compare them to strings, do not convert via Convert.ToInt32/Convert.ToDouble.
-
Provider() → string;Connected() → 1/0(true/false): provider and result of last authorization check. Connected does not guarantee Internet availability at this second. -
Connect(token) → null: checks the bot token;ConnectFromFile(path) → null: Reads UTF-8 token from file up to 1024 bytes. The relative path is calculated from the current script folder. Reconnection requires Disconnect. Errors can be caught by Try/Catch. -
Disconnect() → null: stops local reception, clears token and cursors; the object can be connected again.Dispose() → null: closes the object completely. -
SendMessage(text, recipient) → string: ID received message. Parameters - text and string chat ID Telegram, channel ID Discord or ID Viber subscriber. Length limits: 4096/2000/7000 respectively; also Viber JSON ≤30000 bytes. Blank text is prohibited. Discord does not create mentions of everyone/roles. -
WatchChannel(channelId) → null: Discord, remembers the current last message and reads only subsequent ones.WatchChannel(channelId, afterMessageId) → null: starts after the given string ID; "0" includes history. Up to 32 channels per object. -
Receive(timeoutSeconds) → Array;Receive(): 0 timeout. Range 0..20 seconds; an empty array means there are no text messages available. Telegram uses long polling, Discord polls selected channels, Viber reads the webhook queue. The network request may take up to 30 seconds. - Each MessengerMessage:
Id(),SenderId(),SenderName(),ChatId(),Text()→ string. A Telegram post from a channel may not have SenderId. Attachments, non-text events, and Discord messages from bots will not be returned. -
RetryAfter() → int: how many seconds to wait after limiting the service. Before this period expires, new HTTP-requests are not executed. Sending after a failure is not repeated automatically: the server may have already received the message. -
StartReceiver(localPort, publicHttpsUrl) → int: Viber only; port 1..65535, returns the busy local port. The public HTTPS-address must end with /viber/ and forward POST to 127.0.0.1:port/viber/ with the original body and signature header in advance. The method registers a webhook with Viber. - You need a bot token, not a personal account password. Telegram with an existing webhook is rejected without removing it. Discord requires channel read/send permissions and MESSAGE_CONTENT for accessible text; Viber sends to subscribed users.
- Receive saves the cursor only after parsing the entire stack. Telegram/Discord returns up to 100 entries per request; call Receive again for subsequent packets. Discord without the second argument WatchChannel skips the old story.
- Viber checks the HMAC-SHA256 of the source body. Queue - 100 messages, repeated ID are stored for the last 2048 deliveries; overflow gives HTTP 503 for retry by service. You need a HTTPS proxy with Content-Length; chunked requests on local input are rejected.
- The token is not recorded in the Connect profile, error text, or argument history. Use ConnectFromFile to avoid storing it in script variables visible to the debugger. Do not publish token files.
- Create an object inside a procedure. No more than 32 at the same time; Using/Dispose frees it earlier. When a script completes, fails, or stops, all its messenger objects are closed. Closing the Viber receiver does not delete the webhook from the service.
- This is a text interface: it does not execute the received text as code and does not connect game accounts. The conditions for trusting the sender are set by the script itself.
Each example can be saved as a separate file .csx and run in IDE. The UO object and wait functions provide the host of the current game session. Replace demo serial names, coordinates and addresses with your own.
Run this code as a separate .csx file. Arguments appear directly in the call; replace demonstration names, serials and addresses with your own.
dynamic messenger = UO.Messenger("telegram");
UO.Print(messenger.Provider());
messenger.Dispose();Parameters and execution flow:
- Place telegram-token.txt next to the script, replace 12345678 with your chat ID. The example sends one notification and returns the string ID; receiving a server response does not confirm human reading.
- You need your own bot settings. Examples are tested with local test answers; they were not sent to real users.
var bot = UO.Messenger("telegram");
using (var resource = bot)
{
bot.ConnectFromFile("telegram-token.txt");
var messageId = bot.SendMessage("Harvest finished", "12345678");
return messageId;
}Parameters and execution flow:
- Enter your channel and ID allowed sender. true means that this check received the text stop from him; the example itself does not stop the movement. This result can be used in your own duty cycle condition.
- You need your own bot settings. Examples are tested with local test answers; they were not sent to real users.
return RunScenario();
object? RunScenario()
{
var bot = UO.Messenger("discord");
using (var resource = bot)
{
bot.ConnectFromFile("discord-token.txt");
bot.WatchChannel("123456789012345678");
var messages = bot.Receive(5);
foreach (var message in messages)
{
if (Truth(message.SenderId() == "987654321098765432") && Truth(Convert.ToString(TrimText(message.Text())) == "stop"))
{
return true;
}
}
return false;
}
return null;
}
bool Truth(object? value) => value is bool flag ? flag : value is string text ? !string.IsNullOrEmpty(text) : value != null && Convert.ToDouble(value) != 0;
string TrimText(object? value) => (Convert.ToString(value) ?? "").Trim();Parameters and execution flow:
- System.Text.Json reads local port/publicUrl. The token is stored in a separate local file; actual dispatches are not performed by this example. Trusted SenderId is checked, not arbitrary incoming text.
return new ScriptScenario(UO, WaitAsync, Args, Cancellation, Checkpoint).RunScenario();
class ScriptScenario
{
private readonly global::ClassicUO.CSharp.CSharpGameApi UO;
private readonly Func<int, Task> WaitAsync;
private readonly string[] Args;
private readonly CancellationToken Cancellation;
private readonly Action Checkpoint;
public ScriptScenario(global::ClassicUO.CSharp.CSharpGameApi api, Func<int, Task> wait, string[] args, CancellationToken cancellation, Action checkpoint)
{
UO = api;
WaitAsync = wait;
Args = args;
Cancellation = cancellation;
Checkpoint = checkpoint;
}
public object? RunScenario()
{
var settingsDocument = System.Text.Json.JsonDocument.Parse(System.IO.File.ReadAllText("viber-settings.json"));
using (settingsDocument)
{
var settings = settingsDocument.RootElement;
var bot = UO.Messenger("viber");
using (var resource = bot)
{
bot.ConnectFromFile("viber-token.txt");
bot.StartReceiver(settings.GetProperty("port").GetInt32(), settings.GetProperty("publicUrl").GetString());
var messages = bot.Receive(5);
foreach (var message in messages)
{
if (Truth(message.SenderId() == "trusted-subscriber-id"))
{
return message.Text();
}
}
return "";
}
}
return null;
}
bool Truth(object? value) => value is bool flag ? flag : value is string text ? !string.IsNullOrEmpty(text) : value != null && Convert.ToDouble(value) != 0;
}C# is case sensitive. Use the exact UO name. Compare numeric flags with 0; use bool only where the signature returns bool.
Parameters: provider (String)
Result: dynamic / Object.
During a call:
Internal implementation code; this is not a standalone script.
private InjectionObject CreateMessenger(string provider)
{
var owner=NativeScriptResourceScope.Current??throw new StatementFailedException("Create messenger objects inside an active C# script execution.");owner.Checkpoint();
var client=MessengerFactory!=null?MessengerFactory(provider,()=>owner.Cancellation,owner.Checkpoint):new Messaging.MessengerClient(provider,()=>owner.Cancellation,owner.Checkpoint);
var resource=new MessengerObject(client,owner.Forget,file=>Path.GetFullPath(Path.Combine(paths.ScriptPath??string.Empty,file)));owner.Track(resource);return resource;
}ClassicUO — Bad Newbie & C# Scripts IDE