Is there a way I can make a sort of One way vc bot thing - discord-jda

Hello is there a way to make it so you can kinda have a 1 way vc where (how ive set it up so far) 2 accounts are somehow conected (ive made 2 be able to run from one jda project)
then have one of the bots in one vc and the other one in the other vc.
The first bot hears what is being said tn that vc then the second bot shound shares that in a second vc
so the people in the first vc cant hear the people in the second vc but the people in the second vc can hear the people in the first vc
Btw this is how i have connected the 2 bots in one jda project
Main.java:
public class Main {
public static void main(String[] args){
Bot bot = new Bot("token for bot 1");
Bot2 bot2 = new Bot2("token for bot 2");
bot.start();
bot2.start();
}
}
Bot.java:
import net.dv8tion.jda.api.JDABuilder;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import javax.security.auth.login.LoginException;
public class Bot {
ListenerAdapter[] listenerAdapters= new ListenerAdapter[]{new Help()};
String token;
public Bot(String token) {
this.token = token;
}
public void start() {
JDABuilder jdaBuilder = JDABuilder.createDefault(token);
jdaBuilder.addEventListeners(listenerAdapters);
jdaBuilder.setActivity(Activity.watching("1!help"));
try {
JDA jda = jdaBuilder.build();
jda.awaitReady();
} catch (LoginException | InterruptedException e){
e.printStackTrace();
}
}
}
Bot2.java:
import commands2.*;
import net.dv8tion.jda.api.entities.Activity;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.JDABuilder;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import javax.security.auth.login.LoginException;
public class Bot2 {
ListenerAdapter[] listenerAdapters = new ListenerAdapter[]{new Help2()};
String token;
public Bot2(String token) {
this.token = token;
}
public void start() {
JDABuilder jdaBuilder = JDABuilder.createDefault(token);
jdaBuilder.addEventListeners(listenerAdapters);
jdaBuilder.setActivity(Activity.watching("2!help"));
try {
JDA jda = jdaBuilder.build();
jda.awaitReady();
} catch (LoginException | InterruptedException e){
e.printStackTrace();
}
}
}
Whould this idea be posible?
(btw the way ive set this up works i juts run it in intellij and then both bots work)

I will only show you how to do the "streaming audio from one bot to another" - part.
The implementation is completely up to you (and whether you want to activate it via command or want the bots to join specific channels on startup etc.)
To be able to skip this part I will assume that both bots are already in their dedicated channels.
Before you can start you have to make some changes to your existing code, since you may need them later:
import net.dv8tion.jda.api.JDABuilder;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import javax.security.auth.login.LoginException;
public class Bot {
ListenerAdapter[] listenerAdapters= new ListenerAdapter[]{new Help()};
String token;
// this is new
JDA jda;
public Bot(String token) {
this.token = token;
}
public void start() {
JDABuilder jdaBuilder = JDABuilder.createDefault(token);
jdaBuilder.addEventListeners(listenerAdapters);
jdaBuilder.setActivity(Activity.watching("1!help"));
try {
// and this changed as well
this.jda = jdaBuilder.build();
jda.awaitReady();
} catch (LoginException | InterruptedException e){
e.printStackTrace();
}
}
}
I changed the code to make it possible to access the JDA instance via Bot using Bot.jda. These changes have to be made in Bot2 as well of course.
After making the changes you have to implement a way for the bots to join their dedicated voice channel, but this is up to you.
What you basically want is to plug the received audio of one bot into the sent audio of the other one.
Luckily, JDA offers an example on how to let a bot play back the receiving audio. In there, you can find the class EchoHandler.
public static class EchoHandler implements AudioSendHandler, AudioReceiveHandler
{
/*
All methods in this class are called by JDA threads when resources are available/ready for processing.
The receiver will be provided with the latest 20ms of PCM stereo audio
Note you can receive even while setting yourself to deafened!
The sender will provide 20ms of PCM stereo audio (pass-through) once requested by JDA
When audio is provided JDA will automatically set the bot to speaking!
*/
private final Queue<byte[]> queue = new ConcurrentLinkedQueue<>();
/* Receive Handling */
#Override // combine multiple user audio-streams into a single one
public boolean canReceiveCombined()
{
// limit queue to 10 entries, if that is exceeded we can not receive more until the send system catches up
return queue.size() < 10;
}
#Override
public void handleCombinedAudio(CombinedAudio combinedAudio)
{
// we only want to send data when a user actually sent something, otherwise we would just send silence
if (combinedAudio.getUsers().isEmpty())
return;
byte[] data = combinedAudio.getAudioData(1.0f); // volume at 100% = 1.0 (50% = 0.5 / 55% = 0.55)
queue.add(data);
}
/*
Disable per-user audio since we want to echo the entire channel and not specific users.
#Override // give audio separately for each user that is speaking
public boolean canReceiveUser()
{
// this is not useful if we want to echo the audio of the voice channel, thus disabled for this purpose
return false;
}
#Override
public void handleUserAudio(UserAudio userAudio) {} // per-user is not helpful in an echo system
*/
/* Send Handling */
#Override
public boolean canProvide()
{
// If we have something in our buffer we can provide it to the send system
return !queue.isEmpty();
}
#Override
public ByteBuffer provide20MsAudio()
{
// use what we have in our buffer to send audio as PCM
byte[] data = queue.poll();
return data == null ? null : ByteBuffer.wrap(data); // Wrap this in a java.nio.ByteBuffer
}
#Override
public boolean isOpus()
{
// since we send audio that is received from discord we don't have opus but PCM
return false;
}
}
This class basically does all the hard work for you. You can feed in an AudioReceiveHandler and plug it into an AudioSendHandler with the wanted result.
Only thing left now is to actually do it.
You want the audio from the first bot to be played by the second bot, like this:
(Note: You have to get guild and guild2 from the jda instances of Bot and Bot2.)
/*
example for getting the guilds:
Guild guild = bot.jda.getGuildById(GUILDID);
Guild guild2 = bot2.jda.getGuildById(GUILDID);
*/
AudioManager audioManager = guild.getAudioManager(); // of Bot
AudioManager audioManager2 = guild2.getAudioManager(); // of Bot2
EchoHandler handler = new EchoHandler();
audioManager.setReceivingHandler(handler);
audioManager2.setSendingHandler(handler);

Related

Outlook-Redemption - RDOFolder.Items ItemAdd Event not triggered regular with Exchange in Online-Mode

System-Environment:
Windows 10 Pro - Version: 1909 - OS System Build: 18363.752
Microsoft Outlook 2019 MSO - Version 1808 - 32-Bit
Microsoft Exchange 2016 15.1 Build (Build 1979.3)
-- Microsoft Exchange is installed on Microsoft Server 2016
Outlook Redemption COM-Library - Version 5.22.0.5498
Issue Summary:
The application sends emails via Outlook using the Outlook-Redemption COM-Library. The class "RedemptionHandler" is our Singleton-Class which interacts with the Outlook-Redemption COM-Library. During the construction of the RedemptionHandler we create a RDOSession with a static class named RedemptionLoader and call Logon() on the RDOSession. The RDOSession is used afterwards in Initialize() to retrieve the Folders for Drafts and mails which are sent.
public static class RedemptionLoader
{
public static RDOSession new_RDOSession()
{
return (RDOSession)NewRedemptionObject(new Guid("29AB7A12-B531-450E-8F7A-EA94C2F3C05F"));
}
}
public class RedemptionHandler
{
private static RedemptionHandler instance = null;
private static readonly object padlock = new object();
private RDOSession _rdoSession;
private RDOFolder _rdoSentFolder;
private RDOFolder _rdoDraftsFolder;
private RDOItems _sentItems = null;
public EventHandler<MailGesendetEventArgs> MailSuccessfullySent;
private RedemptionHandler()
{
_rdoSession = RedemptionLoader.new_RDOSession();
_rdoSession.Logon(null, null, false, null, null, null);
Initialize();
}
public static RedemptionHandler Instance
{
get
{
lock (padlock)
{
if (instance == null)
{
instance = new RedemptionHandler();
}
return instance;
}
}
}
private void Initialize()
{
try
{
if (isInitialized) return;
_rdoSentFolder = _rdoSession.GetDefaultFolder(Redemption.rdoDefaultFolders.olFolderSentMail);
_sentItems = _rdoSentFolder.Items;
_sentItems.ItemAdd += MailSent;
_rdoDraftsFolder = _rdoSession.GetDefaultFolder(Redemption.rdoDefaultFolders.olFolderDrafts);
isInitialized = true;
}
catch
{
//TODO
isInitialized = false;
}
}
}
At this point, we have a working instance from our RedemptionHandler. The COM-Object RDOSession is created and referenced within just as the RDOFolder for Drafts and Sent. We have also registrered an event-listener for the Sent-Folder to recognize new Mails in this folder.
In the next steps we want to send an email and recognize this email if its stored in the sent-folder. We use the RDOMail.Fields - Property to store custom data within the RDOMail-Object.
public RDOMail CreateMail(string recipient, string subject, string body, Guid gdSender, string storagePath)
{
RDOMail newMail = _rdoDraftsFolder.Items.Add(Redemption.rdoItemType.olMailItem);
newMail.Recipients.Add(recipient);
newMail.Recipients.ResolveAll();
newMail.Subject = subject;
newMail.HTMLBody = body;
newMail.BodyFormat = (int)rdoBodyFormat.olFormatHTML;
// Here we want to store an identifier in the RDOMail.Fields
int id = newMail.GetIDsFromNames(PropertyGuid, PropertyGdItemId);
newMail.Fields[id] = Guid.NewGuid().ToString();
return newMail;
}
After the mail creation we want to display the mail to the user because we dont want to send data without letting the user know about it.
public void DisplayMail(RDOMail mail, bool modal = false)
{
mail.Display(modal, null);
}
The Outlook window now comes to front and the user checks the mail and clicks on send.
The Mail is now stored in the Sent-Folder.
The MailSent Event gets invoked by the RDOFolder.Items.Add Listener.
private void MailSent(RDOMail mail)
{
var test = mail.Fields[SenderId];
Console.WriteLine(test);
// test value is correct!
}
Difference between Exchange in Online-Mode and Cache-Mode:
If we use the Exchange with Cache-Mode, everything works fine. Everytime we send an email, the MailSent is triggered and we can read data from the RDOMail.Fields-Property. If we switch to Exchange without Cache, the MailSent Event is triggered only once, when the first mail is sent. All emails afterwars are sent but dont trigger the MailSent-Event. If we delete this line of code, everything works also fine without Cache-Mode.
var test = mail.Fields[SenderId];
This is because we think that reading data from the RDOMail.Fields - Property does something special if the cache-mode from exchange is deactivated.
We need to store custom data within the mails to check if new mails in the sent-folder are created by our application or not.
We highly appreciate help and hints.
I tried to fix this issue without success. I've set-up a new Project without any other code:
public partial class RedemptionTest : Form
{
static RDOSession _rdoSession;
static RDOFolder _rdoSentFolder;
static RDOFolder _rdoDraftsFolder;
static RDOItems _draftItems;
static RDOItems _sentItems;
public RedemptionTest()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
_rdoSession = RedemptionLoader.new_RDOSession();
_rdoSession.Logon();
_rdoSentFolder = _rdoSession.GetDefaultFolder(rdoDefaultFolders.olFolderSentMail);
_rdoDraftsFolder = _rdoSession.GetDefaultFolder(rdoDefaultFolders.olFolderDrafts);
_sentItems = _rdoSentFolder.Items;
_draftItems = _rdoDraftsFolder.Items;
_draftItems.ItemAdd += DraftAdd;
_sentItems.ItemAdd += MailSent;
}
private void DraftAdd(RDOMail Item)
{
Console.WriteLine(Item.Subject);
}
private void MailSent(RDOMail Item)
{
Console.WriteLine(Item.Subject);
}
}
The Drafts-Folder Event is fired all the time, the MailSent Event is only fired the first time. I have stored all RDO-Objects in static variables to avoid them from being garbage collected.
The object raising the events (RDOItems) must be alive be able to fire the events. Your code is using multiple dot notation, which means the compiler creates an implicit variable to hold the RDOItems collection. As soon as that variable is released by the Garbage Collector, no events will be fired.
The line
_rdoSentFolder.Items.ItemAdd += MailSent;
must be changed to
RDOItems _sentItems; //global/class variable
..
_sentItems = _rdoSentFolder.Items;
_sentItems .ItemAdd += MailSent;
Have the same issue in Outlook VSTO add-in using Redemption. Happens for both Sent and Inbox folder. The same code works correctly in cached mode but fires events only once in Online mode.
Native Outlook object model Items.ItemAdd works correctly in Online mode for the same folder.
Currently, we were able to do a workaround for this by unsubscribing and resubscribing to event right in the event handler. Like this:
private void SentItems_ItemAdd(RDOMail rdoMail)
{
_sentItems.ItemAdd -= SentItems_ItemAdd;
_sentItems.ItemAdd += SentItems_ItemAdd;
Log.Debug("SentItems.ItemAdd");
SentMailItemAdd?.Invoke(rdoMail);
}

Allow multiple Microsoft App IDs for chat bot

I have a chatbot that works on localhost, and it's working great. I then added a new Bot Channels Registration on Azure for testing, and that works fine too. I did it by taking its Microsoft App ID and password and putting it into my appsettings.json file.
However, I need to add another Bot Channels Registration. When I test it on that registration, my bot returns a 401 unauthorized error. It's because that has a new App ID and password. But I already put the App ID and password from my first registration channel. I need both of them to work.
How can I allow my chatbot to accept multiple App IDs and passwords? Or how do I get rid of that level of security completely (ie. Allow ALL App IDs and passwords)?
The answer, as #Mick suggested, is to create a bot adapter for each channel. You can do something like this if you want it really dynamic:
BotController.cs
[HttpPost, HttpGet]
public async Task PostAsync()
{
var credentialProvider = new SimpleCredentialProvider(YourAppId, YourAppPassword); // for each adapter
Adapter = new BotFrameworkHttpAdapter(credentialProvider); // for each adapter
await Adapter.ProcessAsync(Request, Response, Bot);
}
With a custom ICredentialProvider the appid and password can be retrieved from anywhere:
public class MultiCredentialProvider : ICredentialProvider
{
public Dictionary<string, string> Credentials = new Dictionary<string, string>
{
{ "YOUR_MSAPP_ID_1", "YOUR_MSAPP_PASSWORD_1" },
{ "YOUR_MSAPP_ID_2", "YOUR_MSAPP_PASSWORD_2" }
};
public Task<bool> IsValidAppIdAsync(string appId)
{
return Task.FromResult(this.Credentials.ContainsKey(appId));
}
public Task<string> GetAppPasswordAsync(string appId)
{
return Task.FromResult(this.Credentials.ContainsKey(appId) ? this.Credentials[appId] : null);
}
public Task<bool> IsAuthenticationDisabledAsync()
{
return Task.FromResult(!this.Credentials.Any());
}
}
Then, in Startup.cs:
services.AddSingleton<ICredentialProvider, MultiCredentialProvider>();

IntentService in Xamarin PCL Solution

I am busy writing an application where the user needs to capture a lot of images and then they get packaged together with some text data and then they get uploaded to a local server. I want to implement the uploading on the Android platform through an Intent Service but I cannot find a good Xamarin Forms PCL example to show me how.
This is the method where I initialize the Intent to pass to the IntentService:
public async Task<bool> UploadAsync(Uri serviceAddress,
CaptureEntity capture,
List<ImageEntity> images)
{
try
{
Intent uploadIntent = new Intent();
uploadIntent.PutExtra("serviceAddress", serviceAddress.ToString());
uploadIntent.PutExtra("captureId", capture.WorkflowId.ToString());
StartService(uploadIntent);
return true;
}
catch (Exception exc)
{
App.logger.LogError(DateTime.Now, "Uploader", exc.ToString());
throw exc;
}
}
And this is the IntentService itself.
[Service]
public class ServiceIntent : IntentService
{
public ServiceIntent() : base("ServiceIntent")
{
}
//[return: GeneratedEnum]
public override StartCommandResult OnStartCommand(Intent intent, [GeneratedEnum] StartCommandFlags flags, int startId)
{
return base.OnStartCommand(intent, flags, startId);
}
public override void OnCreate()
{
base.OnCreate();
}
protected override void OnHandleIntent(Intent intent)
{
Uri serviceAddress = new Uri(intent.GetStringExtra("serviceAddress"));
Guid captureId = Guid.Parse(intent.GetStringExtra("captureId"));
CaptureEntity capture = new DatabaseConnection_Android().CreateConnection().Query<CaptureEntity>("SELECT * FROM [CaptureEntity]").Single(c => c.WorkflowId == captureId);
var images = new DatabaseConnection_Android().CreateConnection().Query<ImageEntity>("SELECT * FROM [ImageEntity]").Where(i => i.CaptureEntityId == capture.Id);
try
{
MultipartFormDataContent content = new MultipartFormDataContent();
StringContent strContent = new StringContent(
capture.XmlData,
Encoding.UTF8,
"text/xml");
IImageHandler handler = new ImageHandler_Droid();
HttpRequestMessage request = new HttpRequestMessage();
request.Headers.Add("workflow", capture.WorkflowId.ToString());
request.Method = HttpMethod.Post;
request.RequestUri = serviceAddress;
foreach (var image in images)
{
byte[] imageByte = handler.ReadAllBytes(image.ImagePath);
ByteArrayContent byteContent = new ByteArrayContent(imageByte);
byteContent.Headers.Add("Content-Type", "image/jpeg");
content.Add(byteContent, "file", image.ImageName);
}
content.Add(strContent, "text/xml");
request.Content = content;
using (HttpClient client = new HttpClient())
{
client.Timeout = TimeSpan.FromSeconds(180);
var response = client.SendAsync(
request,
HttpCompletionOption.ResponseContentRead).Result;
var readResponse = response.Content.ReadAsStringAsync().Result;
if (readResponse == "File uploaded.")
MessagingCenter.Send<CaptureEntity, string>(
capture,
"Completed",
"Success");
else if (readResponse.Contains("An error has occurred."))
MessagingCenter.Send<CaptureEntity, string>(
capture,
"Uploader",
String.Format(CultureInfo.InvariantCulture,
"Failed: {0}",
readResponse));
else
MessagingCenter.Send<CaptureEntity, string>(
capture,
"Uploader",
String.Format(CultureInfo.InvariantCulture,
"Failed: {0}",
readResponse));
}
}
catch (WebException webExc)
{
MessagingCenter.Send<string, string>("Uploader", "Failed",
String.Format(CultureInfo.InvariantCulture,
"{0} upload failed.\n{1}",
capture.DisplayName,
webExc.Message));
}
catch (TimeoutException timeExc)
{
MessagingCenter.Send<string, string>("Uploader", "Failed",
String.Format(CultureInfo.InvariantCulture,
"{0} upload failed.\n{1}",
capture.DisplayName,
timeExc.Message));
}
catch (Exception exc)
{
MessagingCenter.Send<string, string>("Uploader", "Failed",
String.Format(CultureInfo.InvariantCulture,
"{0} upload failed.\n{1}",
capture.DisplayName,
exc.Message));
}
}
}
Can anyone tell me what I am doing wrong as I am getting the following error when I want to start the service:
Java.Lang.NullPointerException: Attempt to invoke virtual method 'android.content.ComponentName android.content.Context.startService(android.content.Intent)' on a null object reference
In your Intent declaration you need to tell the service you want to call
Something like this:
var uploadIntent = new Intent(this, typeof(ServiceIntent));
Note: this represents the Context.
Update:
As mentioned in the comments your interface implementation cannot derive from Activity class. In order to have access to the Context to be able to call the StartService method and also create your Intent you can make it in two ways:
Using the Xamarin.Forms.Forms.Context:
public async Task<bool> UploadAsync(Uri serviceAddress,
CaptureEntity capture,
List<ImageEntity> images)
{
try
{
var context = Xamarin.Forms.Forms.Context;
var uploadIntent = new Intent(context, typeof(ServiceIntent));
uploadIntent.PutExtra("serviceAddress", serviceAddress.ToString());
uploadIntent.PutExtra("captureId", capture.WorkflowId.ToString());
context.StartService(uploadIntent);
return true;
}
catch (Exception exc)
{
App.logger.LogError(DateTime.Now, "Uploader", exc.ToString());
throw exc;
}
}
If you are using latest versions of Xamarin.Forms this global context was deprecated and they suggest to you local context instead. You can still use it though but in future updates of XF your app might break.
using CurrentActivity plugin:
public async Task<bool> UploadAsync(Uri serviceAddress,
CaptureEntity capture,
List<ImageEntity> images)
{
try
{
var context = CrossCurrentActivity.Current.Activity;
var uploadIntent = new Intent(context, typeof(ServiceIntent));
uploadIntent.PutExtra("serviceAddress", serviceAddress.ToString());
uploadIntent.PutExtra("captureId", capture.WorkflowId.ToString());
context.StartService(uploadIntent);
return true;
}
catch (Exception exc)
{
App.logger.LogError(DateTime.Now, "Uploader", exc.ToString());
throw exc;
}
}
This plugin can be installed from nugget and the setup is very straight forward. Basically it gives you access to the current activity and you can use it as your context to call the IntentService
Hope this helps.-
Here is the IntentService.
IntentService is a base class for Services that handle asynchronous requests (expressed as Intents) on demand. Clients send requests through startService(Intent) calls; the service is started as needed, handles each Intent in turn using a worker thread, and stops itself when it runs out of work.
In Android, we usually use IntentService to do asynchronous operator. As we all know, thread is also used to do asynchronous operator. The difference between IntentService and Thread is IntentService is Service which belongs to Android Component. So, the priority of IntentService is higher than Thread.
For example, there is a ActivityA which has a IntentService, and there is a ActivityB which has a Thread, both IntentService and Thread are working, and both ActivityA and ActivityB are al background Activity. Now, if your phone's system doesn't have extra resources, your ActivityB will be killed firstly.
About the Exception:
Java.Lang.NullPointerException: Attempt to invoke virtual method 'android.content.ComponentName android.content.Context.startService(android.content.Intent)' on a null object reference
That means you should use android.content.Context to call the StartService method. In Android, there are three kinds of Context. Application, Activity and Service. So you can call the StartService method in these three classes directly. If you are not in these three classes, you need pass the Context to your class, and then use the Context to call StartService.
I added Activity for this class' inheritance.
If you do this, your class will be a Activity, and you need to register it in your manifiest, add layout for your class, and it should have the lifecycle, and etc. It will not be what you want to get class. In Android, Activity is a Component, not normal class, so you can't inherit it unless you want your class to be a Activity.
Demo:
I have made a demo for you,

App.OnResume error in Xamarin forms on Android and IOS devices

We are using xamarin forms. After an Android or IOS device resumes from background, we are making a REST call in .net that is being triggered by a timer. The first attempt on IOS returns a "The Descriptor is not a socket" error and the Android returns a "Connection refused" error. The same code works fine in Windows. Future attempts (every few seconds) in all 3 platforms work fine. Has anyone seen this and have a fix?
Code
//app on resume event
protected async override void OnResume()
{
// Handle when your app resumes
if (MainPage is RootPage)
{
RootPage mainPage = MainPage as RootPage;
if (mainPage.Detail is NavigationPage)
{
NavigationPage nvPage = mainPage.Detail as NavigationPage;
if(nvPage.CurrentPage is ThingsPage)
{
ThingsPage thPage = nvPage.CurrentPage as ThingsPage;
thPage.TurnOnTimer();
}
}
}
}
//code on the page
public void TurnOnTimer()
{
if (viewModel != null)
{
viewModel.ContinueTimer = true;
viewModel.StartAnotherTimer();
}
}
//code in view model
public async void StartAnotherTimer()
{
while (ContinueTimer)
{
try
{
DevicesUpdate devicesUpdate = await DataSource.GetDevices(LocationID, ControllerID, lastDevicesUpdateReceivedAt);
}
catch (Exception ex)
{
}
// Update the UI (because of async/await magic, this is still in the UI thread!)
if (ContinueTimer)
{
await Task.Delay(TimeSpan.FromSeconds(3));
}
}
}
public static async Task<DevicesUpdate> GetDevices(Guid locationID, Guid controllerID, DateTime lastUpdateReceivedAt)
{
DevicesUpdate devicesUpdate = await GetLastUpdatedDevices(controllerID, lastUpdateReceivedAt);
}
//code in view model
public static async Task<DevicesUpdate> GetLastUpdatedDevices(Guid controllerID,
DateTime lastUpdate)
{
System.Net.Http.HttpClient client = new System.Net.Http.HttpClient();
string url = string.Format("http://appname.azurewebsites.net/api/devices?controllerid={1}&lastUpdate={2}"
, Constants.WebServerURL, controllerID, lastUpdate);
System.Net.Http.HttpResponseMessage response = await client.GetAsync(new Uri(url));
string result = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
DevicesUpdate devices = JSONHelper.Deserialize<DevicesUpdate>(result);
return devices;
}
else
{
if (response.ReasonPhrase == "UserException")
{
throw new UserException(result);
}
else
{
//throw error because the response from rest api is not a success
throw new System.Net.Http.HttpRequestException(result);
}
}
}
You might have a few things happening here that's causing problems.
GetDevices doesn't return anything. (I hope you just left out the return for brevity sake)
You are never setting ContinueTimer to false.
What iOS version are you on? In later versions, you HAVE to use HTTPS or explicitly allow non-secure connections. This shouldn't be a problem because Azure has ssl.
If you plan on running this in the background, you need to register your app as a background process.
If you don't plan on running this in the background, you might have issues with previous attempts being ran (or still trying to execute, or just have failed) and then calling more.
What is the reason for calling the 3 second timer for the network calls? What if the call takes more than 3 seconds (then you are making duplicate calls even though the first might succeed).
If you want to make your network calls more robust, check out this Blog Post by Rob Gibbons about resilient network calls.
First thing I would do is remove it from the timer because it seems like the underlying sockets are having issues cross-thread.

How to terminate a Bot conversation (and get client details)?

I have a simple Bot as below:
[Serializable]
[Template(TemplateUsage.NotUnderstood, "I do not understand \"{0}\".", "Try again, I don't get \"{0}\".")]
class MyOrder
{
public string Subject;
public string Description;
public static IForm<MyOrder> BuildForm()
{
return new FormBuilder<MyOrder>()
.Field(nameof(MyOrder.Subject), "What Subject should I use?")
.Field(nameof(MyOrder.Description), "And what Description?")
.AddRemainingFields()
.OnCompletionAsync(MyFormComplete)
.Build();
}
private static async Task MyFormComplete(IDialogContext context, MyOrder order)
{
if (order != null)
{
await context.PostAsync($"Created. Number is 9833");
}
else
{
await context.PostAsync("Form returned empty response!");
}
}
Once the form is completed the MyFormComplete callback is made.
First question - How do I get access to the client details in that function? I need to know the Skype handle so that I can map it to a internal user.
Secondly - After completing the form I can't start a new one. No matter what I enter on the client it just keeps triggering the callback function. There must be a way to terminate the session/conversation so that the next text from the Skype client will start a new conversation/form. Yeah?
Worked out how to get the Skype caller id inside the Dialog's CompletionDelegate. Simply add the message's From details to the message.BotUserData inside the MessageController before building the dialog.
message.BotUserData = JObject.FromObject(message.From)
I can then access this on the context within that callback.

Resources