Send request to websocket server using JSON data - c#

Basically, I have OBS running on a computer and I need to interact with it through a second computer.
I am using an OBS-WebSocket plugin which is creating a websocket server on OBS to send remote commands.
My goal is to set visible / invisible a source on OBS whenever I press a key on the second (remote) computer.
I have no issue with that key press part. My issue is that I have no idea how to correctly send a request/command to the websocket plugin (it requires JSON format as this is how data is formatted).
Main code (C#)
static void Main(string[] args)
{
using (var ws = new WebSocket("ws://192.168.1.1:1122"))
{
ws.Connect();
ws.OnMessage += (sender, e) =>
Console.WriteLine(e.Data);
ws.Send("REPLACE WITH JSON REQUEST DATA");
Console.ReadKey(true);
}
}
Down below is an example of data received from the websocket whenever I turn on or off a source in OBS.
You can clearly see that the 'visibility' is either false or true.
*This 'item-visible' is the setting I need to mess with to tell OBS to hide or show the source.*
↓ Source is visible in OBS ↓
{
"item-id": 10,
"item-name": "map",
"item-visible": true,
"scene-name": "ONHOLD",
"update-type": "SceneItemVisibilityChanged"
}
↓ Source is hidden in OBS ↓
{
"item-id": 10,
"item-name": "map",
"item-visible": false,
"scene-name": "ONHOLD",
"update-type": "SceneItemVisibilityChanged"
}

You can see example test usages in the official Github repo.
If you look there, you'd see that that e.Data should be parsed. Then, you can assign it as needed, for example:
public string SetIsVisible(MessageEventArgs e, bool isVisible)
{
JObject body = JObject.Parse(e.Data);
body["item-visible"] = isVisible;
return body.ToString();
}
... and then just send the returned string into your websocket:
ws.Send(SetIsVisible(e, isVisible)); // isVisible is boolean you should assign
P.S. If you'd like to work with a static-type DTO, simply generate one (e.g. here):
public partial class OBSSource
{
[JsonProperty("item-id")]
public long ItemId { get; set; }
[JsonProperty("item-name")]
public string ItemName { get; set; }
[JsonProperty("item-visible")]
public bool ItemVisible { get; set; }
[JsonProperty("scene-name")]
public string SceneName { get; set; }
[JsonProperty("update-type")]
public string UpdateType { get; set; }
}
... and then:
public string SetIsVisible(MessageEventArgs e, bool isVisible)
{
OBSSource obsSource = JsonConvert.DeserializeObject<OBSSource>(e.Data);
obsSource.ItemVisible = isVisible;
return JsonConvert.SerializeObject(obsSource);
}
... and then send it serialized into the websocket as above.

Related

ServiceStack.JsonServiceClient.HttpLog is not populating

I'm having trouble enabling logging for a ServiceStack.JsonServiceClient. I'm working from the documentation Capture HTTP Headers in .NET Service Clients but I must be missing something becasue I'm only ever getting an empty string.
Here's my code:
public class Client
{
private bool Logging { get; }
public Client(bool logging = false)
{
Api = new("https://api.service.com/");
Logging = logging;
if (Logging) Api.CaptureHttp(true, true);
}
public string GetInfo(string name)
{
if (Logging) Api.HttpLog.Clear();
var response = Api.Get(new Requests.GetInfo(name));
Debug.WriteLine(Api.HttpLog.ToString());
return response.Result;
}
}
[Route("/v1/getinfo/{name}")]
[DataContract]
public class GetInfo : Base, IReturn<Responses.GetInfo>
{
public GetInfo(string name) => Name = name;
[DataMember(Name = "name")]
public string Name { get; init; }
}
[TestMethod]
public void Client_GetInfo()
{
var client = new Client(true);
client.GetInfo()
}
Because I have Api.CaptureHttp(true, true) it is logging the info to the test log but I want to be able to capture the httpLog in code and like I said, it's only ever an empty string there.
CaptureHttp has 3 flags:
public void CaptureHttp(bool print = false, bool log = false, bool clear = true)
You're only setting Api.CaptureHttp(print:true,log:true) which should print to the console and log to your logger if IsDebugEnabled, your call only sets the first 2 flags but you're code is relying on capturing the log to print it out in which case you wanted to specify that it shouldn't clear the HttpLog after each request with:
Api.CaptureHttp(clear:false)

How to retrieve a message based on its' index number?

My question is about retrieving the message type after saving the message index in PlayerPrefs in Unity.
I'm using a message system in my client / server system, based on this tutorial set
For example message "CreateAccount":
The NetOP script hold the index
public static class NetOP
{
public const int None = 0;
public const int CreateAccount = 1;
public const int LoginRequest = 2;
}
The NetMsg class manages the messages
public abstract class NetMsg
{
public byte OP { set; get; } //OP = operation code, a single number to know what is this message
public NetMsg()
{
OP = NetOP.None;
}
}
There's a class for this message:
[System.Serializable]
public class Net_CreateAccount : NetMsg
{
public Net_CreateAccount()
{
OP = NetOP.CreateAccount;
}
public string UserName { set; get; }
public string Password { set; get; }
public string Email { set; get; }
}
When the client receive the message, on Client.cs script:
public void MessageReceived(NetMsg msg)
{
PlayerPrefs.SetInt("msg",msg.OP); //save the msg index in PlayerPrefs
print(msg.OP); //check the message index is correct
}
So far so good, now the Client need to retrieve the received message since it got disconnected (by mistake), so on Client.cs
...case NetworkEventType.ConnectEvent:
isConnected = true;
Debug.Log("We have connected to the server ");
if (PlayerPrefs.GetInt("msg") > 0) //we have a msg in PlayerPref
{
//ISSUE HERE:
communicationManager.CheckMessageStatus(connectionId,PlayerPrefs.GetInt("msg"));
}
break;
....
the communicationManager.CheckMessageStatus(int connectionID, NetMsg msg) - takes an int, and a NetMsg (not the NetMsg.OP which is the index).
I know how to find the index of a NetMsg, but I don't know how to trace the NetMsg back based on its index.
Is it possible (from the script snippets I've posted) to trace the message based on the index?
I'm still learning C# so please help.

Setting private set fields from a post request on backend server

I am making my way through various todo list tutorials while learning react and entity framework. As some background I have made my way though Microsoft's todo list todo tutorial; although I have replaced the front end part of that with my own front end. It was all working fine, until I've tried to extend it and hit the issue I will outline below.
I have updated the EF model to include private set fields for the added benefits (becoming read only after it is initialised etc). This is shown in the code below.
public class TodoItem
{
public long id { get; private set; }
public string title { get; private set; }
public bool IsComplete { get; private set; }
// Define constructor
public TodoItem(long newId, string newTitle)
{
id = newId;
title = newTitle;
IsComplete = false;
}
public void ToggleComplete()
{
IsComplete = !IsComplete;
}
}
The post action from the controller is shown below. I have included some debug printouts as these show where the field is already showing the title as null.
I believe this is the section of code I am struggling with and would like to know what mistakes I am making or what the best practices are!
[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem item)
{
// returns null if model field set to private
System.Diagnostics.Debug.WriteLine("item title: " + item.title);
// Create new item passing in arguments for constructor
TodoItem newItem = new TodoItem(item.id, item.title);
_context.TodoItems.Add(newItem);
await _context.SaveChangesAsync();
return CreatedAtAction(nameof(GetTodoItem), new { id = newItem.id }, newItem);
}
The frontend method (js) where the post request is made is shown below:
const addTodoMethod = (title) => {
// Create new item
const item = {
title: title,
id: Date.now(),
isComplete: false,
}
// Update state
const newTodos = [...todos, item];
setTodos(newTodos);
// Can use POST requiest to add to db
axios.post('https://localhost:44371/api/todo/',
item)
.then(res=> {
console.log("Added item. Title: ", title);
})
.catch(function (error) {
console.log(error);
})}
I hope I've explained the problem well enough. Let me know if there is anything else needed!
I have updated the EF model to include private set fields for the added benefits (becoming read only after it is initialised etc).
There are two problems in what you did. The first one is that the Models must have a parameter-less constructor, and the second one that the properties must be public, both getter and setter.
The best you can do right now is to stop using your database entity for user input and create a ViewModel class:
public class TodoItemViewModel
{
public long id { get; set; }
public string title { get; set; }
public bool IsComplete { get; set; }
}
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItemViewModel model)
{
var item = new TodoItem(item.id, item.title);
...
}

Howto Cancel Async Download of one file witihn mutiple file downloads?

i use some code to download mutiple files, which works good.
while downloading a lot of files, i would like to be able to cancel them.
Besides the UI with a cancel button i use as global variables:
private WebClient client = null;
private CancellationToken cts = new CancellationToken();
Within the download function i use:
// The Object SourceId is only used to be able
// to pass the ID (index) of datasource to callback functions
var current = new SourceId();
current.Id = sourceList.IndexOf(current);
cts = new CancellationToken();
//i tried to use (cancellationToken.Register(() => webClient.CancelAsync())
using (client = new WebClient())
using (cts.Register(() => wc_Cancel()))
{
client.DownloadProgressChanged += wc_DownloadProgressChanged;
client.DownloadFileCompleted += wc_DownloadFileCompleted;
client.DownloadFileAsync(new Uri(driver.Filelink), targetFile, current);
}
"current" is an object, which contains the source ID of the original Datasource, which i use within wc_DownloadProgressChanged and wc_DownloadFileCompleted to determine the correct Data from my datasource.
Within the function wc_DownloadFileCompleted i use if (e.Cancelled) to determine if the download was canceled.
I try to cancel a specific Download. The cancelDownload Function is called by a button after getting the corresponding source Id.
How can i cancel one specificed async download, which means how to pass the needed argument for cancel function ??
private void cancelDownload(int id)
{
// Here i would like to send the ID to cancel to specific download
// and wait for it to finish. How can i do this ?
wc_Cancel();
}
public virtual void wc_Cancel()
{
if (this.client != null)
this.client.CancelAsync();
}
How can i achieve that ?
finally i found a solution for this. For those with the same problem here is my solution.
The use of the CancellationToken is not necessary to get this work. Also the functions cancelDownload and wc_Cancel are not needed. I use a download class, with all needed informations like FileLink, Name etc. I did expand that class with these properties:
// HelperClass to pass for callbacks
public class DownId
{
public int Id { get; set; }
}
// DownloadClass with all needed properties. Should be expanded for your needs.
public class Downloads
{
public Driver()
{
}
public string Title { get; set; }
public string Filelink { get; set; }
public string FileName { get; set; }
public bool IsCanceling { get; set; }
public WebClient Client { get; set; }
public int Retry { get; set; }
public int Progress { get; set; }
public long valBytesRec { get; set; }
public long valBytesTotal { get; set; }
}
The Class now contains a property for the WebClient and bool parameter for canceling. A global Variable downloadlist of type List<Downloads> is used to access the needed properties within all functions.
In the download function I use a parameter currentdriver, which is the indexnumber of the current download within the list (used as paramter for download):
DownId currentSourceId = new DownId();
currentSourceId.Id = currentdriver;
using (downloadlist[current].Client = new WebClient())
{
downloadlist[current].Client.DownloadProgressChanged += wc_DownloadProgressChanged;
downloadlist[current].Client.DownloadFileCompleted += wc_DownloadFileCompleted;
downloadlist[current].Client.DownloadFileAsync(new Uri(driver.Filelink), targetFile, currentSourceId);
}
To be able to pass the currentdrive to the DownloadFileAsync, it must be used within an object. for this purpose is the DownId Class is used.
This way every download will use its own WebClient, which can be access quite easy.
For canceling it is only necessary to set the IsCanceling property within the button_Click event handler:
driverlist[currentdriver].IsCanceling = true;
In the wc_DownloadProgressChanged, I use this code to determine, if Canceling is necessary:
var currentDriver = e.UserState as DownId;
if (downloadlist[current].IsCanceling)
{
downloadlist[current].IsCanceling = false;
downloadlist[current].Client.CancelAsync();
}
else
{
//Update progress etc.
// beware of not using UI Elements here, caused UI Lags
// and can be a problem within threads
// if necessary use Invoke(new MethodInvoker(delegate { ...}));
}
Hope this helps.. I did search for quite a while to find a solution.

print custom List in JavaScript

I have a custom list earthquakes which contains a list of earthquakes. How do I parse this in JavaScript in order to add it to innerHtml and display on the screen. The problem is that I cannot get this to display on the screen in a div. When it parses I get no result because my javascript is wrong and if I try just printing the result i get [Object object]
So the flow goes input from textbox -> web service -> list to javascript
earthquakes class:
public class earthquakes
{
public string eqid { get; set; }
public double magnitude { get; set; }
public double lng { get; set; }
public string source { get; set; }
public DateTime date { get; set; }
public int depth { get; set; }
public double lat { get; set; }
}
dataEarthquakes class
public class dataPostalCodes
{
public List<postalCodes> postalCodes { get; set; }
}
WebService:
public static dataEarthQuakes getEarthquakes(dataPostalCodes postalCodes)
{
double lat = postalCodes.postalCodes[0].lat;
double lng = postalCodes.postalCodes[0].lng;
Uri address = new Uri(String.Format(FindEarthquakes, lat, 0, lng, 0));
WebClient client = new WebClient();
string jsonResponse = string.Empty;
jsonResponse = client.DownloadString(address.AbsoluteUri);
var results = JsonConvert.DeserializeObject<dataEarthQuakes>(jsonResponse);
return results;
}
Javascript:
function OnLookupComplete(e) {
var result = e;
var weatherData = new Sys.StringBuilder();
var line;
for (var property in result.dataPostalCodes) {
line = String.format("<b>{0}:</b> {1}<br/>",
property, result.dataPostalCodes[property]);
weatherData.append(line);
}
$get('divResult').innerHTML = weatherData.toString();
}
Json string:
{"earthquakes":[{"eqid":"2010utc5","magnitude":7.7,"lng":97.1315,"src":"us","datetime":"2010-04-06 20:15:02","depth":31,"lat":2.3602}, {"eqid":"2009kdb2","magnitude":7.6,"lng":92.9226,"src":"us","datetime":"2009-08-10 17:55:39","depth":33.1,"lat":14.0129},{"eqid":"2010zbca","magnitude":7.6,"lng":123.533,"src":"us","datetime":"2010-07-23 20:51:11","depth":576.3,"lat":6.4939},{"eqid":"2010xkbv","magnitude":7.5,"lng":91.9379,"src":"us","datetime":"2010-06-12 17:26:50","depth":35,"lat":7.7477},{"eqid":"c0000rxc","magnitude":7.4,"lng":143.7392,"src":"us","datetime":"2010-12-21 16:19:41","depth":14.9,"lat":26.8656},{"eqid":"2010zbcd","magnitude":7.4,"lng":123.2677,"src":"us","datetime":"2010-07-23 21:15:08","depth":616.7,"lat":6.7489},{"eqid":"2010wbaq","magnitude":7.4,"lng":96.0805,"src":"us","datetime":"2010-05-09 03:59:44","depth":61.4,"lat":3.7284},{"eqid":"2007hvbq","magnitude":7.4,"lng":142.6846,"src":"us","datetime":"2007-09-28 11:38:58","depth":261.3,"lat":21.98},{"eqid":"2010zbbz","magnitude":7.3,"lng":123.4788,"src":"us","datetime":"2010-07-23 20:08:11","depth":604.5,"lat":6.7079},{"eqid":"2007xvam","magnitude":7.3,"lng":126.292,"src":"us","datetime":"2007-01-21 10:27:42","depth":10,"lat":1.2071}]}
As no.good.at.coding said in the comment, if your weatherData object contains the correct data, then it might be as simple as:
$('#divResult').html(weatherData.toString());
Another option may be to call parseJSON on your json object and then use jquery's each function to iterate through the results:
var results = $.parseJSON(e);
$(results).each(function (i, val) {
$('#divResult').append('<p>' + val.eqid + '<p>'); // can add markup here for magnitude and other properties
});
If you aren't sure what your objects are in javascript, firebug is a great tool for debugging (or you could use the Developer Tools that are built in to Chrome).
You didn't state an exact problem, but hopefully this will help get you on the right track.
Here's what I might do:
$.get('my-webservice-url',
function(data) {
OnLookupComplete(data['earthquakes']);
},
'json');
function OnLookupComplete(e) {
var weatherData = new Sys.StringBuilder();
for(var i=0;i<e.length;i++) {
var line;
for (var property in e[i].dataPostalCodes) {
line = String.format("<b>{0}:</b> {1}<br/>",
property, e[i].dataPostalCodes[property]);
weatherData.append(line);
}
}
$('#divResult').html(weatherData.toString());
}
The idea here is that you make your call to your web service and indicate to jQuery that the response expected is JSON (this is useful in case you aren't setting the content-type header correctly in the response from the server).
Once the GET request completes, jQuery will call your callback function (the anonymous function you can see in the call to $.get(). From your JSON example, I see that you expect an object earthquakes which is an array of objects of earthquake details.
The function then calls OnLookupComplete() with each the earthquakes array.
OnLookupComplete() then iterates over each earthquake, builds the right string and appends it to the StringBuilder. Finally, once all earthquakes have been dealt with, it appends the complete set of formatted lines to the div with the id divResult.

Categories