I'm building a small website in asp.net which let the user login via facebook and then the app
can post to user's timeline, i'm using scope="publish_actions,manage_pages" but when user login, app never asks the permission to post on the wall, it only gets the permission to access public profile, here is my code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.Mvc;
using System.IO;
using Facebook;
namespace EadProject.Controllers
{
public class LoginController : Controller
{
//
// GET: /Login/
public ActionResult Index()
{
return View();
}
public ActionResult FacebookLogin()
{
string app_id = "1415837275360274";
string app_secret = "911b08ff5ae3cdaf74e512ce1c312c65";
string scope = "publish_actions,manage_pages";
if (Request["code"] == null)
{
Response.Redirect(string.Format("https://graph.facebook.com/oauth/authorize?client_id={0}&redirect_uri={1}&scope={2}",app_id,Request.Url.AbsoluteUri,scope));
}
else
{
Dictionary<string, string> tokens = new Dictionary<string, string>();
string url = string.Format("https://graph.facebook.com/oauth/access_token?client_id={0}&redirect_uri={1}&scope={2}&code={3}&client_secret={4}", app_id, Request.Url.AbsoluteUri, scope,Request["code"].ToString(),app_secret);
HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
{
StreamReader reader = new StreamReader(response.GetResponseStream());
string vals = reader.ReadToEnd();
foreach (string token in vals.Split('&'))
{
tokens.Add(token.Substring(0,token.IndexOf("=")),token.Substring(token.IndexOf("=")+1,token.Length-token.IndexOf("=")-1));
}
}
string access_token = tokens["access_token"];
var client = new FacebookClient(access_token);
client.Post("/me/feed", new { message = "I am using QUiz Contest Pucit App developed by AHmed ALI :)" });
StreamWriter sw = new StreamWriter(new FileStream("C:\tokens.txt",FileMode.OpenOrCreate));
sw.WriteLine(access_token);
}
return View();
}
}
}
Please explain what is the problem?
Related
I have created an HTTP trigger azure function, which holds the code (below) to upload a video to YouTube automatically. Source: (https://developers.google.com/youtube/v3/docs/videos/insert).
When I try to run the app locally using visual studio, I am getting the following error:
Executed 'Function1' (Failed, Id=d601d64a-2f2c-4f8a-8053-a2f33ca21dbc)
System.Private.CoreLib: Exception while executing function: Function1.
Google.Apis.Auth: At least one client secrets (Installed or Web)
should be set
It looks like a Google Authentication error, but I am unsure as to how to get this fixed and I see that YouTube API does not support Service account? How can this issue be fixed, is there a get around to this? Thanks in advance.
C# Code:
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Upload;
using Google.Apis.YouTube.v3.Data;
using System.Reflection;
using Google.Apis.YouTube.v3;
using Google.Apis.Services;
using System.Threading;
namespace UploadVideo
{
public static class Function1
{
[FunctionName("Function1")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
log.LogInformation("YouTube Data API: Upload Video");
log.LogInformation("==============================");
try
{
await Run();
}
catch (AggregateException ex)
{
foreach (var e in ex.InnerExceptions)
{
log.LogInformation("Error: " + e.Message);
}
}
return new OkObjectResult($"Video Processed..");
}
private static 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 = #"C:\Users\Peter\Desktop\audio\test.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();
}
}
private static 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;
}
}
private static void videosInsertRequest_ResponseReceived(Video video)
{
Console.WriteLine("Video id '{0}' was successfully uploaded.", video.Id);
}
}
}
It looks like you were trying to use the service account to do the OAuth2 web server flow, which wont work. The correct code form creating a service account credential is as follows.
GoogleCredential credential;
using (var stream = new FileStream(serviceAccountCredentialFilePath, FileMode.Open, FileAccess.Read))
{
credential = GoogleCredential.FromStream(stream)
.CreateScoped(scopes);
}
Note
as i have mentioned in your other questions the YouTube API does NOT support service account authentication. You must use Oauth2 and i am not convinced this can be done inside of azure functions. As there is no way to spawn the web browser window to request authorization of the user.
I am trying to make a web API Post method call as follows but it not working as expected,xmlcontent seems OK but somehow the formatting seems messed up when the request is being sent and the response throws an error ,I double checked the XML from python and it works,is there a better way to create and send the XML?what am I doing wrong?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Http;
namespace WebApiXML
{
public class Program
{
static void Main(string[] args)
{
testWCF2(); //Or whatever
Console.ReadLine();
}
public static async Task testWCF2()
{
string xmlcontent = #"<SoftwareProductBuild>
<BuildSource>DATASOURCE</BuildSource>
<BuiltBy>username1</BuiltBy>
<CreatedBy>username1</CreatedBy>
<Name>username1_1959965_1969310_524f7fef-5b37-11e7-b4ee-f0921c133f10_UL.AB.1.2_test2</Name>
<Status>Approved</Status>
<BuiltOn>2017-06-27T06:20:30.275690</BuiltOn>
<Tag>username1_1959965_1969310_524f7fef-5b37-11e7-b4ee-f0921c133f10_test2</Tag>
<Keywords>
<KeywordInfo>
<Name>subystem</Name>
</KeywordInfo>
</Keywords>
<SoftwareImageBuilds>
<SoftwareImageBuild>
<Type>LA</Type>
<Name>username1_1959965_1969310_524f7fef-5b37-11e7-b4ee-f0921c133f10_UL.AB.1.2_test2</Name>
<Location>\\location1\data1\PRECOMMIT_OS_DEF</Location>
<Variant>PRECOMMIT_OS_DEF</Variant>
<LoadType>Direct</LoadType>
<Target>msm8998</Target>
<SoftwareImages>
<SoftwareImage>
<Name>UL.AB.1.2</Name>
</SoftwareImage>
</SoftwareImages>
</SoftwareImageBuild>
</SoftwareImageBuilds>
</SoftwareProductBuild>";
#region using
using (var client = new System.Net.Http.HttpClient())
{
var response = await client.PostAsXmlAsync("http://server:8100/api/SoftwareProductBuild", xmlcontent);
if (!response.IsSuccessStatusCode)
{
//throw new InvalidUriException("Some error with details.");
Console.WriteLine(response);
}
Console.WriteLine("Printing DEV Pool Response\n");
}
#endregion
//return null;
}
}
}
PostAsXmlAsync will try to serialize the object passed to it. So you have a string that contains XML and then try to post the string as XML(Double serialization).
Use StringContent, giving it the XML string value and set the content type to appropriate media type, then post it. i.e. client.PostAsync(url, content)
using (var client = new System.Net.Http.HttpClient()) {
var url = "http://server:8100/api/SoftwareProductBuild";
var content = new StringContent(xmlcontent, Encoding.UTF8, "application/xml");
var response = await client.PostAsync(url, content);
if (response.IsSuccessStatusCode) {
var responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine("Printing DEV Pool Response\n");
Console.WriteLine(responseBody);
} else {
Console.WriteLine(string.Format("Bad Response {0} \n", response.StatusCode.ToString()));
}
}
I'm literally going crazy trying to fix this but nothing I do seems to make a difference. When I navigate to localhost:3978/api/callback it throws
<Error>
<Message>
No HTTP resource was found that matches the request URI 'http://localhost:3978/api/callback'.
</Message>
<MessageDetail>
No action was found on the controller 'Callback' that matches the request.
</MessageDetail>
</Error>
This is the controller in my Controllers/CallbackController.cs
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
using System;
using System.Configuration;
using System.Net;
using System.Net.Http;
using System.Web;
using System.Web.Http;
using Autofac;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs.Internals;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
namespace VSTF_RD_Bot.Controllers
{
public class CallbackController : ApiController
{
[HttpGet]
[Route("api/Callback")]
public async Task<HttpResponseMessage> Callback([FromUri] string state, [FromUri] string code)
{
//parse out the userId and convoId from states parameter
string[] states = state.Split(new[] { "," }, StringSplitOptions.None);
string userId = states[0];
string conversationId = states[1];
// Check if the bot is running against emulator
var connectorType = HttpContext.Current.Request.IsLocal ? ConnectorType.Emulator : ConnectorType.Cloud;
// Exchange the Facebook Auth code with Access toekn
var token = await AdHelpers.ExchangeCodeForAccessToken(userId, conversationId, code, "redirect_uri");
// Create the message that is send to conversation to resume the login flow
var msg = new Message
{
Text = $"token:{token}",
From = new ChannelAccount { Id = userId },
To = new ChannelAccount { Id = Constants.botId },
ConversationId = conversationId
};
var reply = await Conversation.ResumeAsync(Constants.botId, userId, conversationId, msg, connectorType: connectorType);
// Remove the pending message because login flow is complete
IBotData dataBag = new JObjectBotData(reply);
PendingMessage pending;
if (dataBag.PerUserInConversationData.TryGetValue("pendingMessage", out pending))
{
dataBag.PerUserInConversationData.RemoveValue("pendingMessage");
var pendingMessage = pending.GetMessage();
reply.To = pendingMessage.From;
reply.From = pendingMessage.To;
// Send the login success asynchronously to user
var client = Conversation.ResumeContainer.Resolve<IConnectorClient>(TypedParameter.From(connectorType));
await client.Messages.SendMessageAsync(reply);
return Request.CreateResponse("You are now logged in! Continue talking to the bot.");
}
else
{
// Callback is called with no pending message as a result the login flow cannot be resumed.
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, new InvalidOperationException("Cannot resume!"));
}
}
}
}
What am I missing here?
This is my webApiconfig.cs
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
namespace VSTF_RD_Bot
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Json settings
config.Formatters.JsonFormatter.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
config.Formatters.JsonFormatter.SerializerSettings.Formatting = Formatting.Indented;
JsonConvert.DefaultSettings = () => new JsonSerializerSettings()
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
Formatting = Newtonsoft.Json.Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore,
};
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
}
Make your controller
[HttpGet]
[Route("api/Callback/{state}/{code}")]
public async Task<HttpResponseMessage> Callback(string state, string code)
{
and request url
"localhost:3978/api/Callback/samplestate/samplecode"
The sendPhoto command require an argument photo defined as InputFile or String.
The API doc tells:
Photo to send. You can either pass a file_id as String to resend a photo
that is already on the Telegram servers, or upload a new photo using
multipart/form-data.
And
InputFile
This object represents the contents of a file to be uploaded. Must be
posted using multipart/form-data in the usual way that files are
uploaded via the browser.
I'm not a C# Developer but I generated this code using Postman, it uses RestSharp lib
var client = new RestClient("https://api.telegram.org/bot%3Ctoken%3E/sendPhoto");
var request = new RestRequest(Method.POST);
request.AddHeader("postman-token", "7bb24813-8e63-0e5a-aa55-420a7d89a82c");
request.AddHeader("cache-control", "no-cache");
request.AddHeader("content-type", "multipart/form-data; boundary=---011000010111000001101001");
request.AddParameter("multipart/form-data; boundary=---011000010111000001101001", "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"photo\"; filename=\"[object Object]\"\r\nContent-Type: false\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"chat_id\"\r\n\r\n2314123\r\n-----011000010111000001101001--", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
Just tweak it and it should work.
here is a working, parametrized code sample:
using System.Linq;
using System.IO;
using System.Text;
using System.Net.Http;
using System.Threading.Tasks;
namespace ConsoleApplication
{
public class Program
{
public static void Main(string[] args)
{
SendPhoto(args[0], args[1], args[2]).Wait();
}
public async static Task SendPhoto(string chatId, string filePath, string token)
{
var url = string.Format("https://api.telegram.org/bot{0}/sendPhoto", token);
var fileName = filePath.Split('\\').Last();
using (var form = new MultipartFormDataContent())
{
form.Add(new StringContent(chatId.ToString(), Encoding.UTF8), "chat_id");
using (FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
form.Add(new StreamContent(fileStream), "photo", fileName);
using (var client = new HttpClient())
{
await client.PostAsync(url, form);
}
}
}
}
}
}
There is a auction website in, the requirement is that everytime user make a bidding on an auction. That action will be posted on their facebook wall if the user allow us to post on their behalf. Is this possible to do and what i have to know to be able to do this. I don't know much about facebook application development.
I don't have knowledge of facebook-c#-sdk (as you tagged) but for this need to follow these steps
Authenticate user using facebook OAuth 2.0 with user_status permission
and the you need to call status api with required param
after googling I found a small solution to update status using facebook-c#-sdk
FacebookClient fbClient = new FacebookClient(accessToken);
parameters = new Dictionary<string, object> {
{ "message", "this is my test message" }
};
fbClient.Post("me/feed", parameters);
The answer above is a possible solution, but a bit clunky.
Leveraging Open Graph Actions would be better.
Your starting point would bd to read:
https://developers.facebook.com/docs/opengraph/
It's not quite as easy as FB suggest of course and the documentation is sketchy, but for an automated, 'frictionless' action as per your requirement, this is the route to follow.
I created an video tutorial and sample source code for doing exactly this.
Video/Code:
http://www.markhagan.me/Samples/Grant-Access-And-Post-As-Facebook-User-ASPNet
If you don't feel like going to my website, here is the source code:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Facebook;
namespace FBO
{
public partial class facebooksync : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
CheckAuthorization();
}
private void CheckAuthorization()
{
string app_id = "374961455917802";
string app_secret = "9153b340ee604f7917fd57c7ab08b3fa";
string scope = "publish_stream,manage_pages";
if (Request["code"] == null)
{
Response.Redirect(string.Format(
"https://graph.facebook.com/oauth/authorize?client_id={0}&redirect_uri={1}&scope={2}",
app_id, Request.Url.AbsoluteUri, scope));
}
else
{
Dictionary<string, string> tokens = new Dictionary<string, string>();
string url = string.Format("https://graph.facebook.com/oauth/access_token?client_id={0}&redirect_uri={1}&scope={2}&code={3}&client_secret={4}",
app_id, Request.Url.AbsoluteUri, scope, Request["code"].ToString(), app_secret);
HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
{
StreamReader reader = new StreamReader(response.GetResponseStream());
string vals = reader.ReadToEnd();
foreach (string token in vals.Split('&'))
{
//meh.aspx?token1=steve&token2=jake&...
tokens.Add(token.Substring(0, token.IndexOf("=")),
token.Substring(token.IndexOf("=") + 1, token.Length - token.IndexOf("=") - 1));
}
}
string access_token = tokens["access_token"];
var client = new FacebookClient(access_token);
client.Post("/me/feed", new { message = "markhagan.me video tutorial" });
}
}
}
}