Task Module call from Ms Teams in Bot Framework - module

I am looking to open a task module (Pop up - iframe with audio/video) in my bot that is connected to Teams channel. I am having issues following the sample code provided on the GitHub page.
I have tried to follow the sample and incorporate to my code by did not succeed.
In my bot.cs file I am creating card action of invoke type:
card.Buttons.Add(new CardAction("invoke", TaskModuleUIConstants.YouTube.ButtonTitle, null,null,null,
new Teams.Samples.TaskModule.Web.Models.BotFrameworkCardValue<string>()
{
Data = TaskModuleUIConstants.YouTube.Id
}));
In my BotController.cs that inherits from Controller
[HttpPost]
public async Task PostAsync()
{
// Delegate the processing of the HTTP POST to the adapter.
// The adapter will invoke the bot.
await _adapter.ProcessAsync(Request, Response, _bot);
}
public async Task<HttpResponseMessage> Post([FromBody] Activity activity)
{
if (activity.Type == ActivityTypes.Invoke)
{
return HandleInvokeMessages(activity);
}
return new HttpResponseMessage(HttpStatusCode.Accepted);
}
private HttpResponseMessage HandleInvokeMessages (Activity activity)
{
var activityValue = activity.Value.ToString();
if (activity.Name == "task/fetch")
{
var action = Newtonsoft.Json.JsonConvert.DeserializeObject<Teams.Samples.TaskModule.Web.Models.BotFrameworkCardValue<string>>(activityValue);
Teams.Samples.TaskModule.Web.Models.TaskInfo taskInfo = GetTaskInfo(action.Data);
Teams.Samples.TaskModule.Web.Models.TaskEnvelope taskEnvelope = new Teams.Samples.TaskModule.Web.Models.TaskEnvelope
{
Task = new Teams.Samples.TaskModule.Web.Models.Task()
{
Type = Teams.Samples.TaskModule.Web.Models.TaskType.Continue,
TaskInfo = taskInfo
}
};
return msg;
}
return new HttpResponseMessage(HttpStatusCode.Accepted);
}
There is more code as per the GitHub sample but I won't paste it here. Can someone point me into the correct direction ?
I have got to the stage that it is displaying a pop up window but the content and title comes from manifest file instead of creating actual iframe also no video is rendering. My goal is to render video within my teams using iframe container.

The important part from the sample:
This sample is deployed on Microsoft Azure and you can try it yourself by uploading Task Module CSharp.zip to one of your teams and/or as a personal app. (Sideloading must be enabled for your tenant; see step 6 here.) The app is running on the free Azure tier, so it may take a while to load if you haven't used it recently and it goes back to sleep quickly if it's not being used, but once it's loaded it's pretty snappy.
So,
Your Teams Admin MUST enable sideloading
Your bot MUST be sideloaded into Teams
The easiest way to do this would be download the sample manifest, open it in App Studio, then edit your bot information in. You then need to make sure Domains and permissions > Valid Domains are set for your bot. Also ensure you change the Tabs URLs to your own.
You also need to make sure that in your Tasks, the URLs they call ALL use https and not http. If anywhere in the chain is using http (like if you're using ngrok and http://localhost), it won't work.

Related

How to use YouTube Data API

I tried using YouTube Data API.
I really took a good look at everything I found on the internet. The code itself isn't the problem, but I did not find out, where to use this code. Do I simply create a python file (in Visual Studio Code for example) and run it there? Because it didn't work when I tried this...
I also saw many people using the API with the commander only, others used something in chrome (localhost:8888...). So I don`t really know what's the way to go or what I should do.
Thanks for any help :)
Best regards!
I'm not a python developer but as a guess you could start here:
https://developers.google.com/youtube/v3/quickstart/python
using pip to install the dependencies you need.
You should be able to create a simple python file that authenticates with the API and then calls a method on the on the google api client and then output it. There are some examples here:
https://github.com/youtube/api-samples/blob/master/python/
using System;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Services;
using Google.Apis.Upload;
using Google.Apis.Util.Store;
using Google.Apis.YouTube.v3;
using Google.Apis.YouTube.v3.Data;
namespace Google.Apis.YouTube.Samples
{
/// <summary>
/// YouTube Data API v3 sample: upload a video.
/// Relies on the Google APIs Client Library for .NET, v1.7.0 or higher.
/// See https://code.google.com/p/google-api-dotnet-client/wiki/GettingStarted
/// </summary>
internal class UploadVideo
{
[STAThread]
static void Main(string[] args)
{
Console.WriteLine("YouTube Data API: Upload Video");
Console.WriteLine("==============================");
try
{
new UploadVideo().Run().Wait();
}
catch (AggregateException ex)
{
foreach (var e in ex.InnerExceptions)
{
Console.WriteLine("Error: " + e.Message);
}
}
Console.WriteLine("Press any key to continue...");
Console.ReadKey();
}
private async Task Run()
{
UserCredential credential;
using (var stream = new FileStream("client_secrets.json", FileMode.Open, FileAccess.Read))
{
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
// This OAuth 2.0 access scope allows an application to upload files to the
// authenticated user's YouTube channel, but doesn't allow other types of access.
new[] { YouTubeService.Scope.YoutubeUpload },
"user",
CancellationToken.None
);
}
var youtubeService = new YouTubeService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = Assembly.GetExecutingAssembly().GetName().Name
});
var video = new Video();
video.Snippet = new VideoSnippet();
video.Snippet.Title = "Default Video Title";
video.Snippet.Description = "Default Video Description";
video.Snippet.Tags = new string[] { "tag1", "tag2" };
video.Snippet.CategoryId = "22"; // See https://developers.google.com/youtube/v3/docs/videoCategories/list
video.Status = new VideoStatus();
video.Status.PrivacyStatus = "unlisted"; // or "private" or "public"
var filePath = #"REPLACE_ME.mp4"; // Replace with path to actual movie file.
using (var fileStream = new FileStream(filePath, FileMode.Open))
{
var videosInsertRequest = youtubeService.Videos.Insert(video, "snippet,status", fileStream, "video/*");
videosInsertRequest.ProgressChanged += videosInsertRequest_ProgressChanged;
videosInsertRequest.ResponseReceived += videosInsertRequest_ResponseReceived;
await videosInsertRequest.UploadAsync();
}
}
void videosInsertRequest_ProgressChanged(Google.Apis.Upload.IUploadProgress progress)
{
switch (progress.Status)
{
case UploadStatus.Uploading:
Console.WriteLine("{0} bytes sent.", progress.BytesSent);
break;
case UploadStatus.Failed:
Console.WriteLine("An error prevented the upload from completing.\n{0}", progress.Exception);
break;
}
}
void videosInsertRequest_ResponseReceived(Video video)
{
Console.WriteLine("Video id '{0}' was successfully uploaded.", video.Id);
}
}
}
Make sure you have python installed on your PC
Create a project: Google’s APIs and Services dashboard
Enable the Youtube v3 API: API Library
Create credentials: Credentials wizard
Now you need to get an access token and a refresh token using the credentials you created
Find an authentication example in one of the following libraries:
https://github.com/googleapis/google-api-python-client
https://github.com/omarryhan/aiogoogle (for the async version)
Copy and paste the client ID and client secret you got from step 4 and paste them in the authentication example you found in step 6 (Should search for an OAuth2 example), this step should provide with an access token and a refresh token
Copy and paste a Youtube example from either:
https://github.com/googleapis/google-api-python-client
https://github.com/omarryhan/aiogoogle (for the async version)
Replace the access token and refresh token fields with the ones you got.
Now you should be able to run the file from any terminal by typing:
python3 yourfile.py
[EDIT]
The API key is not the same as the access token. There are 2 main ways to authenticate with Google APIs:
Access and refresh token
API_KEY.
API key won't work with personal info. You need to get an access and refresh token for that (method 1).
Once you get an access token, it acts in a similar fashion to the API_KEY you got. Getting an access token is a bit more complicated than only working with an API_KEY.
A refresh token is a token you get with the access token upon authentication. Access tokens expire after 3600 seconds. When they expire, your authentication library asks Google's servers for a new access token with the refresh token. The refresh token has a very long lifetime (often indefinite), so make sure you store it securely.
To get an access token and a refresh token (user credentials), you must first create client credentials. Which should consists of 1. a client ID and 2. a client secret. These are just normal strings.
You should also, set a redirect URL in your Google app console in order to properly perform the OAuth2 flow. The OAuth2 flow is the authentication protocol that many APIs rely on to allow them to act on a user's account with the consent of the user. (e.g. when an app asks you to post on your behalf or control your account on your behalf, it typically will use this protocol.)
Aiogoogle's docs does a decent job in explaining the authentication flow(s) available by Google.
https://aiogoogle.readthedocs.io/en/latest/
But this is an async Python library. If you're not familiar with the async syntax, you can read the docs just to get a general idea of how the authentication system works and then apply it to Google's sync Python library.
About point no.6. The links I posted with Aiogoogle being one of them, are just client libraries that help you access Google's API quicker and with less boilerplate. Both libraries have documentation, where they have links to examples on how to use them. So, open the documentation, read it, search for the examples posted, try to understand how the code in the example(s) work. Then maybe download it and run it on your own machine.
I recommend that your read the docs. Hope that helps.

Sense/net using content query in web application

I try to use content query in web application but it throw an exception " Lucene.Net.Store.AlreadyClosedException: this IndexReader is closed". Please give help me resolve that problem.
var startSettings = new RepositoryStartSettings
{
Console = Console.Out,
StartLuceneManager = true, // <-- this is necessary
IsWebContext = false,
PluginsPath = AppDomain.CurrentDomain.BaseDirectory,
};
using (Repository.Start(startSettings))
{
var resultQuery = ContentQuery.Query("+InTree:#0 + DisplayName:*#1*", null, folderPath, q);
}
The recommended way to connect to Sense/Net from a different application (app domain) is through the REST API. It is much easier to maintain and involves less configuration (the only exception is where you are working inside the Sense/Net application itself, or you only have a single application and you do not want to access Sense/Net from anywhere else, and you are willing to deal with a local index of Sense/Net and all the config values it needs, etc).
Connecting through the REST API does not mean you have to send HTTP requests manually (although that is also not complicated at all): there is a .Net client library which does that for you. You can access all content metadata or binaries through the client, you can upload files, query content, manage permissions, etc.
// loading a content
dynamic content = await Content.LoadAsync(id);
DateTime date = content.BirthDate;
// querying
var results = await Content.QueryAsync(queryText);
Install: https://www.nuget.org/packages/SenseNet.Client
Source and examples: https://github.com/SenseNet/sn-client-dotnet
To use it in a web application, you have to do the following:
initialize the client context once, at the beginning of the application life cycle (e.g. app start)
if you need to make requests to Sense/Net in the name of the currently logged in user (e.g. because you want to query for documents accessible by her), than you have to create a new ServerContext object for every user with the username/password of that user, and provide this object to any client call (e.g. load or save content methods).
var sc = new ServerContext
{
Url = "http://example.com",
Username = "user1",
Password = "asdf"
};
var content = await Content.LoadAsync(id, sc);

Prevent getting old updates from Telegram Bot API using a web hook

I'm writing a Telegram bot and I'm using the official bot API. I've got a webhook server that handles requests and sends a 200 OK response for every request.
Before the server stops, the webhook is detached so Telegram does not send updates anymore. However, whenever I turn the bot on and set the webhook URL again, Telegram starts flooding the webhook server with old updates.
Is there any way I can prevent this without requesting /getUpdates repeatedly until I reach the last update?
Here's a heavily simplified version of how my code looks like:
var http = require('http'),
unirest = require('unirest'),
token = '***';
// Attach the webhook
unirest.post('https://api.telegram.org/bot' + token + '/setWebhook')
.field('url', 'https://example.com/api/update')
.end();
process.on('exit', function() {
// Detach the webhook
unirest.post('https://api.telegram.org/bot' + token + '/setWebhook')
.field('url', '')
.end();
});
// Handle requests
var server = http.createServer(function(req, res) {
res.writeHead(200, { 'Content-Type': 'text/plain' })
res.end('Thanks!');
});
server.listen(80);
Thanks in advance.
The best way is to use update_id which is a specific number that increases on every new request (i.e. update). How to implement it?
First off, let's start with the following anonymous class (using PHP7):
$lastUpdateId = new class()
{
const FILE_PATH = "last-update-id.txt";
private $value = 1;
public function __construct()
{
$this->ensureFileExists();
$this->value = filesize(self::FILE_PATH) == 0
? 0 : (int)(file_get_contents(self::FILE_PATH));
}
public function set(int $lastUpdateId)
{
$this->ensureFileExists();
file_put_contents(self::FILE_PATH, $lastUpdateId);
$this->value = $lastUpdateId;
}
public function get(): int
{
return $this->value;
}
public function isNewRequest(int $updateId): bool
{
return $updateId > $this->value;
}
private function ensureFileExists()
{
if (!file_exists(self::FILE_PATH)) {
touch(self::FILE_PATH);
}
}
};
What the class does is clear: Handling the last update_id via a plain file.
Note: The class is tried to be as short as possible. It does not provide error-checking. Use your custom implementation (e.g. use SplFileObject instead of file_{get|put}_contents() functions) instead.
Now, there are two methods of getting updates: Long Polling xor WebHooks (check Telegram bot API for more details on each methods and all JSON properties). The above code (or similar) should be used in both cases.
Note: Currently, it is impossible to use both methods at the same time.
Long Polling Method (default)
This way, you send HTTPS requests to Telegram bot API, and you'd get updates as response in a JSON-formatted object. So, the following work can be done to get new updates (API, why using offset):
$botToken = "<token>";
$updates = json_decode(file_get_contents("https://api.telegram.org/bot{$botToken}/getUpdates?offset={$lastUpdateId->get()}"), true);
// Split updates from each other in $updates
// It is considered that one sample update is stored in $update
// See the section below
parseUpdate($update);
WebHook Method (preferred)
Requiring support for HTTPS POST method from your server, the best way of getting updates at-the-moment.
Initially, you must enable WebHooks for your bot, using the following request (more details):
https://api.telegram.org/bot<token>/setWebhook?url=<file>
Replace <token> with you bot token, and <file> with the address of your file which is going to accept new requests. Again, it must be HTTPS.
OK, the last step is creating your file at the specified URL:
// The update is sent
$update = $_POST;
// See the section below
parseUpdate($update);
From now, all requests and updates your bot will be directly sent to the file.
Implementation of parseUpdate()
Its implementation is totally up to you. However, to show how to use the class above in the implementation, this is a sample and short implementation for it:
function parseUpdate($update)
{
// Validate $update, first
// Actually, you should have a validation class for it
// Here, we suppose that: $update["update_id"] !== null
if ($lastUpdateId->isNewRequest($update["update_id"])) {
$lastUpdateId->set($update["update_id"]);
// New request, go on
} else {
// Old request (or possible file error)
// You may throw exceptions here
}
}
Enjoy!
Edit: Thanks to #Amir for suggesting editions made this answer more complete and useful.
When you server starts up you can record the timestamp and then use this to compare against incoming message date values. If the date is >= the timestamp when you started...the message is ok to be processed.
I am not sure if there is a way you can tell Telegram you are only interested in new updates, their retry mechanism is a feature so that messages aren't missed...even if your bot is offline.
In the webhook mode, Telegram servers send updates every minute until receives an OK response from the webhook program.
so I recommend these steps:
Check your webhook program that you specified its address as url parameter of the setWebhook method. Call its address in a browser. It does not produce an output to view, but clears that probably there is no error in your program.
Include a command that produces a '200 OK Status' header output in your program to assure that the program sends this header to the Telegram server.
I have the same issue, then I tried to reset the default webhook with
https://api.telegram.org/bot[mybotuniqueID]/setWebhook?url=
after that, i verified the current getUpdates query were the same old updates but I sent new requests through the telegram's bot chat
https://api.telegram.org/bot[mybotuniqueID]/getUpdates
when I set up my webhook again the webhook read the same old updates. Maybe the getUpdates method is not refreshing the JSON content.
NOTE:
in my case, it was working fine until I decided to change /set privacy bot settings from botfather

How do i use Behat with Mink and WebApiContext?

The project i'm working on has a api behind a login.
I'm using behat with mink to login:
Scenario: Login
Given I am on "/login/"
And I should see "Login"
When I fill in "_username" with "test"
And I fill in "_password" with "test"
And I press "_submit"
Then I should be on "/"
This works..
However, the login session is not stored whenever i want to do the following using the WebApiContext:
Scenario: Getting the list of pages
When I send a GET request to "/api/pages.json"
Then print response
I'm using both scenarios in the same feature. My FeatureContext class looks something like this:
class FeatureContext extends MinkContext
{
public function __construct(array $parameters)
{
$context = new WebApiContext($parameters['base_url']);
$context->getBrowser()->getClient()->setCookieJar(new \Buzz\Util\CookieJar());
$this->useContext('web', $context);
}
}
I added the cookiejar idea from this issue without success.. When i print the response i just see the HTML page from the login screen..
Does anyone have any idea if i'm going at this totally the wrong way or am i somewhat in the right direction?
I am successfully using the same method. I don't think there's a standard way of doing this. As far as you understand the cookie basics you should be able to implement the solution.
In a common scenario, a client sends an authentication request to the server with some credentials, the servers validates it, starts an authenticated session and sends back a cookie with that session id. All following requests contain that id, so the server can recognise the callee. A specific header can be used instead of the cookie, or a database can be used instead of the session, but the principle is the same and you can (relatively) easily simulate it with Mink.
/**
* Start a test session, set the authenticated user and set the client's cookie.
*
* #Given /^I am signed in$/
*/
signIn()
{
session_start();
$_SESSION['user'] = 'jos';
$this->getSession()->getDriver()->setCookie(session_name(), session_id());
session_commit();
}
The above step definition (Behat 3) is the basics of it, you manually create the authenticated session and set to the client it's id. That must be also what the other example illustrates.
PHP's sessions can be problematic when you start doing more complex things and there are a couple of big underwater rocks with this solution. If you want to run assertions from both perspectives (the client and the server) you might often need to have your sessions synced. This can be done by updating the cookie before all Mink steps and reloading the session after.
/**
* #beforeStep
* #param BeforeStepScope $scope
*/
public function synchroniseClientSession(BeforeStepScope $scope)
{
// Setup session id and Xdebug cookies to synchronise / enable both.
$driver = $this->getSession()->getDriver();
// Cookie must be set for a particular domain.
if ($driver instanceof Selenium2Driver && $driver->getCurrentUrl() === 'data:,') {
$driver->visit($this->getMinkParameter('base_url'));
}
// Also enables the debugging support.
$driver->setCookie(session_name(), session_id());
$driver->setCookie('XDEBUG_SESSION', 'PHPSTORM');
}
/**
* #afterStep
* #param AfterStepScope $scope
*/
public function synchroniseServerSession(AfterStepScope $scope)
{
$driver = $this->getSession()->getDriver();
// Only browser kit driver, only initiated requests, only not repeating requests.
if (!$driver instanceof BrowserKitDriver) {
return;
} elseif (($request = $driver->getClient()->getRequest()) === null) {
return;
} elseif ($request === self::$request) {
return;
}
// Your logic for reloading the session.
self::$request = $request;
}
The biggest problem I had was the session reloading. This might be due to my framework of choice, which I doubt. The very first code snippet has session_commit(), which saves and closes the session. In theory in the following step definitions you must be able to session_id(/* session id from the cookie… */); and session_start();, but in practice that didn't work and no session data was actually loaded from the file, though the session did start. To solve this I created a custom session manager with reload() method using session save handler.
Second problem is where you cannot simply close the session without either writing it or destroying it (the support is added in PHP 5.6) on which relies the reloading itself. I reinvented the wheel with a flag for the session manager which tells it whether to write or just to close it.
:)

URL rewrite in ASP.NET 4.5 and Web API

We've got a long-running ASP.NET web-forms application that was born in the .NET 1.1/IIS6 days. We're now on .NET4.5/IIS7 but we've done nothing with MVC.
We provide a catalog to customers and give them a URL they can use:
www.ourhost.com/customername
Using a custom IHttpModule we developed we pull 'customername' out of the URL to find the customer in the database. That customer's ID is then stored in the page's context* and used by virtually all the pages on the site to customize content for that customer. After this process, the above URL would be rewritten and processed as
www.ourhost.com/index.aspx
with index.aspx having access to the customer's ID via its context and it can do its thing.
This works great and we support several thousand customers with it. the rewriting logic is fairly complex because it validates customer accounts, redirects to a 'uh oh' page if the customer is invalid and to a different 'find a dealer' page if the customer has not paid, etc. etc.
Now I'd like to build some Web API controllers and MVC-style rewriting has me worried. I see many examples where rewriting happens to make URL's like this work:
www.ourhost.com/api/{controller}
but I still need these web api 'calls' to happen in the context of a customer. Our pages are getting more sophisticated with JSON/AJAX async calls but in answering those calls I still need customer context. I would like the URL's to be
www.ourhost.com/customername/api/{controller}
But I am stumped as to how to configure routing to do this and have it play nicely with our IHttpModule.
Is this even possible?
*UPDATE: When I say 'stored in the page context' I mean the HttpContext associated with each web request that includes a dictionary where I can store some page/request-specific data.
There are two parts of the answer to your issue that I can see.
Maintaining the User Info across multiple requests
Generally an MVC API application will be stateless, that is you do not retain the current users session state between requests. Well that is what I have learned or been preached many times when writing RESTFul APIs.
That been said, you can enable session state in MVC Web API by adding the following to your global.asax.cs
protected void Application_PostAuthorizeRequest()
{
// To enable session state in the WebAPI.
System.Web.HttpContext.Current.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Required);
}
Authorising A Customer in the Request
As you have shown in the Request URL you could add the customer name, then capture that and pass it to the same routine that your current http module calls to authorise on request. You could do this with an MVC Filter.
First do a similar URL Pattern to capture your customers name in the WebApiConfig.cs, something like so;
config.Routes.MapHttpRoute(
name: "WithCustomerApi",
routeTemplate: "api/{customername}/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Then add an ActionFilter to your API Controller which processes each request, checks current session info and if needed calls your authorise/customer lookup code and then saves to session state for later use. Or if no good info from customer can send to a new MVC route
So you will add an attribute something like so;
[WebApiAuthentication]
public class BaseApiController : ApiController
{
}
Then create an action filter that might look like this (note I have not tested this, just done for a pattern of how to).
public class WebApiAuthenticationAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var routeData = actionContext.ControllerContext.Request.GetRouteData();
var currentContext = HttpContext.Current;
if (routeData.Route.RouteTemplate.Contains("customername"))
{
try
{
var authenticated = currentContext.Request.IsAuthenticated;
if (!authenticated)
{
var customer = routeData.Values["customername"];
// do something with customer here and then put into session or cache
currentContext.Session.Add("CustomerName", customer);
}
}
catch (Exception exception)
{
var error = exception.Message;
// We dont like the request
actionContext.Response = new HttpResponseMessage(HttpStatusCode.BadRequest);
}
}
else
{
// No customer name specified, send bad request, not found, what have you ... you *could* potentially redirect but we are in API so it probably a service request rather than a user
actionContext.Response = new HttpResponseMessage(HttpStatusCode.NotFound);
}
}
}
If you create a new MVC 5 Web API Application and add in these extras and put the filter on the default values controller like so you should be able to see this running as demo of a possible solution.
This will echo the customer name back if all works ok.
[WebApiAuthentication]
public class ValuesController : ApiController
{
// GET api/values
public IEnumerable<string> Get()
{
var session = HttpContext.Current.Session;
if (session != null)
{
return new string[] {"session is present", "customer is", session["CustomerName"].ToString()};
}
return new string[] { "value1", "value2" };
}
}
I offer this up as a possible solution as I say, there are religious arguments about storing session and authorising in an API but those are not the question.
Hope that helps,
Steve