Given a url, I need to extract some meta content. For instance, say I need to get the content of the tag <meta property="og:site_name" ... content="some name"/> so that I can persist it inside a ParseObject before I save. So the general structure of my cloud code would look like
Parse.Cloud.beforeSave(“Food”,function(request,response){
if(!request.user || !request.user.authenticated()){
response.error("Need an authenticated user");
}else if(!request.params.link || request.params.link==""){
esponse.error("Need a link");
}else{
Parse.Cloud.httpRequest({
url:link,
success:function(httpResponse){
//TODO I need to extract meta content here: for site_name
},
error:function(httpResponse){
response.error("request failed with response code "+httpResponse.status);
}
});
}
});
Here is my question: What do I do with httpResponse in order to extract the data?
For instance if I had a DOM object, I could simply do
function getMetaContent() {
var metas = document.getElementsByTagName('meta');
for (i=0; i<metas.length; i++) {
if (metas[i].getAttribute("property") == “og:site_name”) {
var content= metas[i].getAttribute("content");
…
}
}
return "";
}
Thanks for any help, and if the question is too simple it's because I am a backend guy needing to code javascript for Parse Cloud Code.
Related
In our Android project (download manager) we need to show built-in web browser so we able to catch downloads there with the all data (headers, cookies, post data) so we can handle them properly.
Unfortunately, WebView control we use does not provide any way to access POST data of the requests it makes.
So we use a hacky way to get this data. We inject this javascript code in the each html code the browser loads:
<script language="JavaScript">
HTMLFormElement.prototype._submit = HTMLFormElement.prototype.submit;
HTMLFormElement.prototype.submit = formSubmitMonitor;
window.addEventListener('submit', function(e) {
formSubmitMonitor(e);
}, true);
function formSubmitMonitor(e) {
var frm = e ? e.target : this;
formSubmitMonitor_onsubmit(frm);
frm._submit();
}
function formSubmitMonitor_onsubmit(f) {
var data = "";
for (i = 0; i < f.elements.length; i++) {
var name = f.elements[i].name;
var value = f.elements[i].value;
//var type = f.elements[i].type;
if (name)
{
if (data !== "")
data += '&';
data += encodeURIComponent(name) + '=' + encodeURIComponent(value);
}
}
postDataMonitor.onBeforeSendPostData(
f.attributes['method'] === undefined ? null : f.attributes['method'].nodeValue,
new URL(f.action, document.baseURI).href,
data,
f.attributes['enctype'] === undefined ? null : f.attributes['enctype'].nodeValue);
}
XMLHttpRequest.prototype.origOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
// these will be the key to retrieve the payload
this.recordedMethod = method;
this.recordedUrl = url;
this.origOpen(method, url, async, user, password);
};
XMLHttpRequest.prototype.origSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function(body) {
if (body)
{
postDataMonitor.onBeforeSendPostData(
this.recordedMethod,
this.recordedUrl,
body,
null);
}
this.origSend(body);
};
const origFetch = window.fetch;
window.fetch = function()
{
postDataMonitor.onBeforeSendPostData(
"POST",
"test",
"TEST",
null);
return origFetch.apply(this, arguments);
}
</script>
Generally, it works fine.
But in Google Mail web interface, it's not working for some unknown reason. E.g. when the user enters his login name and presses Next. I thought it's using Fetch API, so I've added interception for it too. But this did not help. Please note, that we do not need to intercept the user credentials, but we need to be able to intercept all, or nothing. Unfortunately, this is the way the whole system works there...
Addition #1.
I've found another way: don't override shouldInterceptRequest, but override onPageStarted instead and call evaluateJavascript there. That way it works even on Google Mail web site! But why the first method is not working then? We break HTML code somehow?
I have a richtext content control named firstname in a Word document. I am trying to access its content but am not able retrieve it.
This is a sample method given in msdn. Using it I am able to get the control's id and its type but not the data. Please let me know whether any way to access the same?
function bindContentControl() {
Office.context.document.bindings.addFromNamedItemAsync(
'FirstName', Office.BindingType.Text, {id:'firstName'},
function (result) {
if (result.status === Office.AsyncResultStatus.Succeeded) {
write('Control bound. Binding.id: ' + result.value.id + ' Binding.type: ' + result.value.type); }
else {
write('Error:', result.error.message);
}
});
}
// Function that writes to a div with id='message' on the page.
Funktion write(message){
document.getElementById('message').innerText += message; }
The sample code you provided creates a binding to an object with the name 'FirstName'.
You will want to use context.document.contentControls.getByTitle() to retrieve the content control of a given name. Here's my sample code.
await Word.run(async (context) => {
let controls = context.document.contentControls.getByTitle("FirstName");
controls.load();
await context.sync();
//assuming there's only one para.
controls.items[0].paragraphs.load();
await context.sync();
console.log(controls.items[0].paragraphs.items[0].text);
});
For SEO purposes I wanted to copy this blog post design this website
the specific part I want to copy is the div with id="enter-posts" which has the title, the author, date, labels of the post
I tried to put tags like:
<div class='author-profile' itemprop='author' itemscope='itemscope' itemtype='http://schema.org/Person'>
<meta expr:content='data:post.authorProfileUrl' itemprop='url'/>
<b:if cond='data:post.authorPhoto.url'><a expr:href='data:post.authorProfileUrl' itemprop='url'>
<div class='auhtor-image'><img expr:src='data:post.authorPhoto.url' itemprop='image'/></div></a></b:if>
<div><span class='author-about'>About </span><a class='g-profile' expr:href='data:post.authorProfileUrl' rel='author' title='author profile'><span itemprop='name'><data:post.author/></span></a></div><p class='description' itemprop='description'><data:post.authorAboutMe/></p></div>
for author and so on for the other elements I want to add but the problem is:
Nothing is displayed, BUT when I put these tags inside the post body before the title or at the end of the post they appear normally. what should I do?
Though this is an old post, may I post some clarification for those looking for an answer. Fact is, there is no data:post.authorPhoto.url provided by blogger.
Most template writers take the data from /feeds/posts/default and parse the JSON and update the HTML after page load. Then they obfuscate the js code, making new comers think it was all fetched from blogger. Along with that fetch, who knows what else was fetched.
For example, for the above blog, you can fetch the JSON from http://www.th3professional.com/feeds/posts/default?alt=json-in-script
My implementation
Image is fetched directly from the feed
Author description is fetched from 'Introduction' part of user profile. Since that's CORS, I'm using a CORS proxy (https://github.com/Rob--W/cors-anywhere/) for that purpose.
-
var HttpClient = function() {
this.get = function(aUrl, aCallback) {
var anHttpRequest = new XMLHttpRequest();
anHttpRequest.onreadystatechange = function() {
if (anHttpRequest.readyState == 4 && anHttpRequest.status == 200)
aCallback(anHttpRequest.responseText);
}
anHttpRequest.open("GET", "https://cors-anywhere.herokuapp.com/"+aUrl, true);
anHttpRequest.send(null);
}
}
$(document).ready(function($){
// This is one way to get post id, other ways are possible too
var post_id = $("div[itemprop='articleBody']").attr('id');
post_id = post_id.match(/.*post-body-(\d+).*/);
post_id = ((post_id!==null && post_id.length>0) ? post_id[1] : null);
if(post_id!==null) {
$.ajax({
url: "/feeds/posts/default/"+post_id+"?alt=json-in-script",
type: 'get',
dataType: "jsonp",
success: function(data) {
var image = ((data.entry.author[0]["gd$image"].src!==null) ? data.entry.author[0]["gd$image"].src : false);
if(image!=false) { //site correct class here
$('.author-image').attr('src', image);
}
var description = false;
var client = new HttpClient();
client.get((data.entry.author[0].uri["$t"]).replace("http", "https"), function(response) {
var doc = document.documentElement.cloneNode();
doc.innerHTML = response;
$content = $(doc.querySelectorAll('tr'));
for(var lk=0; lk<$content.find('th.item-key').length; lk++) {
if($content.find('th.item-key')[lk].innerText=="Introduction" && $content[lk].querySelectorAll('td')[0]!==null) {
description = $content[lk].querySelectorAll('td')[0].textContent;
break;
}
}
if(description!=false) { //site correct class here
$('.description').text(description);
}
});
}
}); } });
Here is the code:
<!DOCTYPE html>
<html>
<head>
<title>Google Fitness API</title>
<meta charset='utf-8' />
</head>
<body>
<p>Get your step counts using the Google Fitness API.</p>
<!--Add buttons to initiate auth sequence and sign out-->
<button id="authorize-button" style="display: none;">Authorize</button>
<button id="signout-button" style="display: none;">Sign Out</button>
<div id="content"></div>
<script type="text/javascript">
// Enter an API key from the Google API Console:
// https://console.developers.google.com/apis/credentials?project=_
var apiKey = 'API_key';
// Enter a client ID for a web application from the Google API Console:
var clientId = 'XYZ';
// Enter one or more authorization scopes. Refer to the documentation for
// the API or https://developers.google.com/identity/protocols/googlescopes
// for details.
var scopes = 'https://www.googleapis.com/auth/fitness.activity.read';
var auth2; // The Sign-In object.
var authorizeButton = document.getElementById('authorize-button');
var signoutButton = document.getElementById('signout-button');
function handleClientLoad() {
// Load the API client and auth library
gapi.load('client:auth2', initAuth);
}
function initAuth() {
gapi.client.setApiKey(apiKey);
gapi.auth2.init({
client_id: clientId,
scope: scopes
}).then(function () {
auth2 = gapi.auth2.getAuthInstance();
// Listen for sign-in state changes.
auth2.isSignedIn.listen(updateSigninStatus);
// Handle the initial sign-in state.
updateSigninStatus(auth2.isSignedIn.get());
authorizeButton.onclick = handleAuthClick;
signoutButton.onclick = handleSignoutClick;
});
}
function updateSigninStatus(isSignedIn) {
if (isSignedIn) {
authorizeButton.style.display = 'none';
signoutButton.style.display = 'block';
makeApiCall();
} else {
authorizeButton.style.display = 'block';
signoutButton.style.display = 'none';
}
}
function handleAuthClick(event) {
auth2.signIn();
}
function handleSignoutClick(event) {
auth2.signOut();
}
// Load the API and make an API call.
function makeApiCall() {
gapi.client.load('fitness', 'v1', function() {
var request = gapi.client.fitness.users.dataSources.datasets.get({
userId: 'me',
dataSourceId: 'com.google.step_count.delta',
datasetId: '1476092378000000-' + new Date().getTime() + '000000',
});
request.execute(function(resp) {
console.log(resp);
});
});
console.log(auth2.currentUser.get().getBasicProfile().getGivenName());
}
</script>
<script src="https://apis.google.com/js/api.js?onload=handleClientLoad"></script>
</body>
</html>
And here is what I get from the console:
Object {minStartTimeNs: "1476092378000000", maxEndTimeNs: "1476461775789000000", dataSourceId: "com.google.step_count.delta", point: Array[0], result: Object}
dataSourceId: "com.google.step_count.delta"
maxEndTimeNs: "1476461775789000000"
minStartTimeNs: "1476092378000000"
point: Array[0]
Other than that I don't get error messages in the console. Am I not supposed to get a series of values? If not, how could I do it? I must admit I'm fairly new to APIs :) Many thanks for your help!
Well, make sure that you use valid values for the parameters that you put in your request.
According to the documentation, The dataSources resource includes the data type (and a list of its fields) for each data source. You can specify one of these data types when you create data sources, and you can obtain the name of the data type and a list of its fields when you retrieve a data source from the fitness store.
I also found out that the datasetId is a dataset identifier that is a composite of the minimum data point start time and maximum data point end time represented as nanoseconds from the epoch. The ID is formatted like: "startTime-endTime" where startTime and endTime are 64 bit integers.
For more information, check these related SO questions:
How do I retrieve step count data from Google Fitness REST api?
How to get step count from Google Fit REST API like Google Fit app?
****Update*****
The line:
dataSourceId: 'com.google.step_count.delta'
in the code above was wrong, as 'com.google.step_count.delta' is a dataTypeName, not a dataSourceId
Works much better with an actual dataSourceId ;) Such as:
dataSourceId: 'derived:com.google.step_count.delta:com.google.android.gms:estimated_steps'
I've used Umbraco 7.3 in my project. I created a custom data type but when I want to call a Surfacecontroller in here is HelloSurfaceController or Hello2SurfaceController, I got an error in umbraco backoffice that said Request error: The URL returned a 404 (not found):
I studied some articles about routing but I couldn't solve my problem. I don't know that where I did wrong.
How can I solve this problem?
Reply.controller.js:
angular.module("umbraco")
.controller("Reply.controller", function ($scope, $http) {
$scope.SendReply = function () {
var sendTo = $("#Email").val();
var textMessage = $("#TextMessage").val();
$scope.xxx = "I'm here!";
var data = { SendTo: sendTo, TextMessage: textMessage };
// ~/Hello2Surface/ReplyMessage ---> Cannot find this URL
$http.post("~/App_Plugins/Reply/HelloSurface/ReplyMessage") // Can not find this URL
.then(function (response) {
alert("YES!");
//TODO:
});
}
});
SurfaceController
namespace Jahan.Nuts.Web.Mvc.UmbracoCms.App.App_Plugins.Reply
{
public class HelloSurfaceController : SurfaceController
{
[HttpPost][ChildActionOnly]
public ActionResult ReplyMessage()
{
//TODO: how should be write this method that be proper for getting data from angularjs?
return null;
}
}
}
package.manifest
{
propertyEditors: [
{
alias: "Send.Reply",
name: "Send Reply",
editor:{
view:"~/App_Plugins/Reply/Reply.html"
},
}
]
,
javascript:[
'~/App_Plugins/Reply/Reply.controller.js'
]
}
Reply.html
<div ng-controller="Reply.controller">
<div style="width: 100%;">
<input type="button" value="Send Reply" title="SendReply" name="Send Reply" ng-click="SendReply()" />
</div>
<div>
<input type="text" ng-model="xxx" name="message" />
</div>
Error in umbraco backoffice:
Take a closer look at the documentation - in particular the Plugin-based SurfaceControllers section:
https://our.umbraco.org/documentation/Reference/Routing/surface-controllers
try doing this (note the PluginController attribute):
namespace Jahan.Nuts.Web.Mvc.UmbracoCms.App.App_Plugins.Reply
{
[PluginController("Reply")]
public class HelloSurfaceController : SurfaceController
{
[HttpPost][ChildActionOnly]
public ActionResult ReplyMessage()
{
//TODO: how should be write this method that be proper for getting data from angularjs?
return null;
}
}
}
Other Notes:
You don't need to include "Surface" in the controller name anymore - simply calling it HelloController is enough.
Don't use a SurfaceController for Api calls if you're using it with AngularJS - Better to use an UmbracoApiController instead. Check out https://our.umbraco.org/documentation/Reference/Routing/WebApi/ for more information (including notes on where to expect the Api Endpoint to be)
You might also want to re-locate your controller so it's in a more conventional spot. There's no problem with putting it in the ~/Controllers directory even if it is a Plugin Controller.
Edit: Added "correct" way to do this:
As noted above, to implement an UmbracoApiController, you want a class looking like this - note you can use UmbracoApiController if you don't need to worry about authorization:
namespace Jahan.Nuts.Web.Mvc.UmbracoCms.App.App_Plugins.Reply
{
[PluginController("Reply")]
public class HelloApiController : UmbracoAuthorizedApiController
{
public void PostReplyMessage(string to, string message)
{
// TODO: process your message and then return something (if you want to).
}
}
}
Then in AngularJS set up a resource like this:
function replyResource($q, $http, umbDataFormatter, umbRequestHelper) {
var replyResource = {
sendMessage: function (sendTo, msg) {
return umbRequestHelper.resourcePromise(
$http.post("Backoffice/Reply/HelloApi/PostReplyMessage?" +
umbRequestHelper.dictionaryToQueryString(
[{ to: sendTo }, { message: msg }])),
'Failed to send message to ' + sendTo + ': ' + msg);
}
};
return replyResource;
}
angular.module('umbraco.resources').factory('replyResource', replyResource);
and finally your actual view controller can use this as follows:
angular.module("umbraco")
.controller("Reply.controller", function ($scope, $http, $injector) {
// Get a reference to our resource - this is why we need the $injector specified above
replyResource = $injector.get('replyResource');
$scope.SendReply = function () {
// You really shouldn't use jQuery here - learn to use AngularJS Bindings instead and bind your model properly.
var sendTo = $("#Email").val();
var textMessage = $("#TextMessage").val();
replyResource.sendMessage(sendTo, textMessage)
.then(function (response) {
// Success
}, function (err) {
// Failure
});
}
};
});
It's possible there's some errors in there; I did it mostly from memory - in particular, you may need to look into the best way to post data to the ApiController - it's not likely that it'll just accept the two parameters like that.
For a more complete example, consider reviewing the code of the Umbraco MemberListView plugin: https://github.com/robertjf/umbMemberListView
Also, you really should read up on the ASP.Net MVC fundamentals and the Umbraco Documentation for SurfaceControllers and APIControllers I've listed above already.
remove the "Surface" from the URL and include "backoffice":
angular.module("umbraco")
.controller("Reply.controller", function ($scope, $http) {
$scope.SendReply = function () {
var sendTo = $("#Email").val();
var textMessage = $("#TextMessage").val();
$scope.xxx = "I'm here!";
var data = { SendTo: sendTo, TextMessage: textMessage };
// ~/Hello2Surface/ReplyMessage ---> Cannot find this URL
$http.post("backoffice/Reply/Hello/ReplyMessage") // Can not find this URL
.then(function (response) {
alert("YES!");
//TODO:
});
}
});
Also, I'd recommend using UmbracoAuthorizedController not a surface controller as this is being used in the back end by logged in users it'll be wise to keep it secure.
So instead your controller should look something like this:
[PluginController("Reply")]
namespace Jahan.Nuts.Web.Mvc.UmbracoCms.App.App_Plugins.Reply
{
public class HelloApiController : UmbracoAuthorizedJsonController
{
public [Model-to-be-returned-to-angular] ReplyMessage()
{
//sql query etc to populate model
//return model
}
}
}