Google Music Beta: Thumbs Up and Down Now Playing using Javascript - javascript

I am working on my first extension for Google Chrome. I want to be able to hit the "Thumbs Up" button on the Google Music Beta page using my extension. For some reason, the thumbs up button seems to be much more complicated than shuffle, repeat, play, next, and previous. For all of those, the following code works:
chrome.tabs.executeScript(tab_id,
{
code: "location.assign('javascript:SJBpost(\"" + command +
"\");void 0');",
allFrames: true
});
where command="playPause", "nextSong", "prevSong", "toggleShuffle", "togglePlay", etc.
I figured a lot of those out using the developer tools to follow the stack trace and see the arguments given to SJBpost. Trying SJBpost with "thumbsUp" returns an error.
Obviously this question is going to be restricted to a smaller crowd since not everyone is going to be able to view the source of Google Music, but if you can help me out, I would greatly appreciate it.
The div for the thumbs up on the Google Music page looks like this:
<div id="thumbsUpPlayer" class="thumbsUp" title="Thumbs up"></div>
Now, I've tried doing this using jQuery:
$("#thumbsUpPlayer").click()
But I get TypeError, undefined_method message in the javascript console.
Any help would be greatly appreciated. I am a huge beginner to javascript and plugins, and all of this stuff, and I'm really excited to get these last pieces of the extension together.
Thank you!

It seems that Google Music Beta doesn't actually listen on the click() event per se, rather, it is based on the events which usually precede the actual click event: mouseover, mousedown and mouseup.
I'm not a jQuery expert, so I can't exactly figure out why $("#thumbsUpPlayer").mouseover().mousedown().mouseup() doesn't work (neither does doing .trigger).
Anyway, here's some (tested on June 21) working javascript code (no dependencies).
function triggerMouseEvent(element, eventname){
var event = document.createEvent('MouseEvents');
event.initMouseEvent(eventname, true, true, document.defaultView, 1, 0, 0, 0, 0, false, false, false, false, 0, element);
element.dispatchEvent(event);
}
function replicateClick(element){
triggerMouseEvent(element, 'mouseover');
triggerMouseEvent(element, 'mousedown');
triggerMouseEvent(element, 'mouseup');
}
function thumbsUp(){
replicateClick(document.getElementById('thumbsUpPlayer'));
}
function thumbsDown(){
replicateClick(document.getElementById('thumbsDownPlayer'));
}
It should be probably fairly easy to use, just call thumbsUp() if you want a thumbs up or call thumbsDown() if you want to thumbs down.

I've been looking into this today and found that you can trigger a lot of the commands using SJBpost from the console.
http://hints.macworld.com/article.php?story=20110622061755509
SJBpost('thumbsUpPlayer');
Commands I've tried and can confirm working:
playPause
thumbsUpPlayer
thumbsDownPlayer
prevSong
nextSong
I'm still looking into commands which let you retrieve current artist / track info. Let me know if you find out what those are.

The following snippet from the 'music plus for google' chrome extension might be of interest:
function playback_action(type, callback) {
var $button;
if (type == 'playPause') {
$button = $('button[data-id="play-pause"]');
}
else if (type == 'nextSong') {
$button = $('button[data-id="forward"]');
}
else if (type == 'prevSong') {
$button = $('button[data-id="rewind"]');
}
else if (type == 'currently_playing') {
$button = $('button[data-id="play-pause"]');
}
if ($('button[data-id="play-pause"]').attr('disabled')) {
$instant_mix = $('li[data-type="rd"]').click();
setTimeout(function() {
$('div[data-type="im"] .radio-icon').first().click();
}, 1000);
}
else {
$button.click();
}
callback();
}
When the above snippet stops working, check the link again and see if maybe the author updated the extension to something that works.

Related

Post comments on Facebook page via console

Like in the image, the Facebook comment box has no submit button, when you write something and press Enter button, the comment posted.
I want to submit the comment via JavaScript that running in console, but I tried to trigger Enter event, submit event of the DOM. Could not make it work.
The current comment boxes aren't a traditional <textarea> inside of a <form>. They're using the contenteditable attribute on a div. In order to submit in this scenario, you'd want to listen to one of the keyboard events (keydown, keypress, keyup) and look for the Enter key which is keycode 13.
Looks like FB is listening to the keydown evt in this case, so when I ran this code I was able to fake submit a comment:
function fireEvent(type, element) {
var evt;
if(document.createEvent) {
evt = document.createEvent("HTMLEvents");
evt.initEvent(type, true, true);
} else {
evt = document.createEventObject();
evt.eventType = type;
}
evt.eventName = type;
evt.keyCode = 13;
evt.which = 13;
if(document.createEvent) {
element.dispatchEvent(evt);
} else {
element.fireEvent("on" + evt.eventType, evt);
}
}
fireEvent('keydown', document.querySelector('[role="combobox"]._54-z span span'));
A couple of things to note about this. The class ._54-z was a class they just happened to use on my page. Your mileage may vary. Use dev tools to make sure you grab the right element (it should have the aria role "combobox"). Also, if you're looking to support older browsers, you're going to have to tweak the fireEvent function code above. I only tested the above example in the latest Chrome.
Finally, to complicate matters on your end, Facebook is using React which creates a virtual DOM representation of the current page. If you're manually typing in the characters into the combobox and then run the code above, it'll work as expected. But you will not be able to set the combobox's innermost <span>'s innerHTML to what you're looking to do and then trigger keydown. You'll likely need to trigger the change event on the combobox to ensure your message persists to the Virtual DOM.
That should get you started! Hope that helps!
Some years after, this post remains relevant and is actually the only one I found regarding this, whilst I was toying around trying to post to FB groups through JS code (a task similar to the original question).
At long last I cracked it - tested and works:
setTimeout(() => {
document.querySelector('[placeholder^="Write something"]').click();
setTimeout(() => {
let postText = "I'm a Facebook post from Javascript!";
let dataDiv = document.querySelector('[contenteditable] [data-offset-key]');
let dataKey = dataDiv.attributes["data-offset-key"].value;
//Better to construct the span structure exactly in the form FB does it
let spanHTML = `<span data-offset-key="${dataKey}"><span data-text="true">${postText}</span></span>`;
dataDiv.innerHTML = spanHTML;
let eventType = "input";
//This can probably be optimized, no need to fire events for so many elements
let div = document.querySelectorAll('div[role=presentation]')[1].parentElement.parentElement;
let collection = div.getElementsByTagName("*");
[...collection].forEach(elem => {
let evt = document.createEvent("HTMLEvents");
evt.initEvent(eventType, true, true); //second "true" is for bubbling - might be important
elem.dispatchEvent(evt);
});
//Clicking the post button
setTimeout(()=>{
document.querySelector('.rfloat button[type=submit][value="1"]').click();
},2000);
}, 4000);
}, 7000);
So here's the story, as I've learned from previous comments in this post and from digging into FB's code. FB uses React, thus changes to the DOM would not "catch on" as React uses virtual DOM. If you were to click "Post" after changing the DOM from JS, the text would not be posted. That's why you'd have to fire the events manually as was suggested here.
However - firing the right event for the right element is tricky business and has almost prevented me from succeeding. After some long hours I found that this code works, probably because it targets multiple elements, starting from a parent element of the group post, and drilling down to all child elements and firing the event for each one of them (this is the [...collection].forEach(elem => { bit). As written this can be obviously be optimized to find the one right element that needs to fire the event.
As for which event to fire, as was discussed here, I've experimented with several, and found "input" to be the one. Also, the code started working after I changed the second argument of initEvent to true - i.e. evt.initEvent(eventType, true, true). Not sure if this made a difference but I've had enough hours fiddling with this, if it works, that enough for me. BTW the setTimeouts can be played around with, of course.
(Unsuccessfully) Digging into FB's React Data Structure
Another note about a different path I tried to go and ended up being fruitless: using React Dev Tools Chrome extension, you're able to access the components themselves and all their props and states using $r. Surprisingly, this also works outside of the console, so using something like TamperMonkey to run JS code also works. I actually found where FB keeps the post text in the state. For reference, it's in a component called ComposerStatusAttachmentMentionsInputContainer that's in charge of the editor part of the post, and below is the code to access it.
$r actually provides access to a lot of React stuff, like setState. Theoritically I believed I could use that to set the state of the post text in React (if you know React, you'd agree that setState would be the right way to trigger a change that would stick).
However, after some long hours I found that this is VERY hard to do, since FB uses a framework on top of React called Draft.js, which handles all posts. This framework has it's own methods, classes, data structures and what not, and it's very hard to operate on those from "outside" without the source code.
I also tried manually firing the onchange functions attached to the components, which didn't work because I didn't have the right parameters, which are objects in the likes of editorContent and selectionContent from Draft.Js, which need to be carefully constructed using methods like Modifier from Draft.js that I didn't have access to (how the hell do you externally access a static method from a library entangled in the source code?? I didn't manage to).
Anyway, the code for accessing the state variable where the text is stored, provided you have React dev tools and you've highlighted ComposerStatusAttachmentMentionsInputContainer:
let blockMap = $r["state"].activeEditorState["$1"].currentContent.blockMap;
let innerObj = JSON.parse(JSON.stringify(blockMap)); //this is needed to get the next property as it's not static or something
let id = Object.keys(innerObj)[0]; //get the id from the obj property
console.log(innerObj[id].text); //this is it!
But as I wrote, this is pretty much useless :-)
as I wasn't able to post comments through the "normal" facebook page, I remembered that they also have the mobile version, which is on m.facebook. com, there, they still have the submit Button, so depending on your needs, this may be a good option
so, you could go to the mobile facebook post (eg https://m.facebook.com/${author}/posts/${postId}) and do
// Find the input element that saves the message to be posted
document.querySelector("input[name='comment_text']").value='MESSAGE TO POST';
// find the submit button, enable it and click it
const submitButton = document.querySelector("button[name='submit']");
submitButton.disabled = false;
submitButton.click();
Here is a working solution after 3 weeks of experimenting (using #Benjamin Solum's fireEvent function):
this version posts a comment only for the first post on the page (by using querySelector method)
this version can be used only on your personal wall (unless you change the query selectors)
function fireEvent(type, element, keyCode) {
var evt;
if(document.createEvent) {
evt = document.createEvent("HTMLEvents");
evt.initEvent(type, true, true);
} else {
evt = document.createEventObject();
evt.eventType = type;
}
evt.eventName = type;
if (keyCode !== undefined){
evt.keyCode = keyCode;
evt.which = keyCode;
}
if(document.createEvent) {
element.dispatchEvent(evt);
} else {
element.fireEvent("on" + evt.eventType, evt);
}
}
// clicking the comment link - it reveals the combobox
document.querySelector(".fbTimelineSection .comment_link").click();
setTimeout(function(){
var combobox = document.querySelector(".fbTimelineSection [role='combobox']");
var spanWrapper = document.querySelector(".fbTimelineSection [role='combobox'] span");
// add text to the combobox
spanWrapper.innerHTML = "<span data-text='true'>Thank you!</span>";
var spanElement = document.querySelector(".fbTimelineSection [role='combobox'] span span");
fireEvent("blur", combobox);
fireEvent("focus", combobox);
fireEvent("input", combobox);
fireEvent("keydown", spanElement, 13); // pushing enter
},2000);
function fireEvent(type, element) {
var evt;
if(document.createEvent) {
evt = document.createEvent("HTMLEvents");
evt.initEvent(type, true, true);
} else {
evt = document.createEventObject();
evt.eventType = type;
}
evt.eventName = type;
evt.keyCode = 13;
evt.which = 13;
if(document.createEvent) {
element.dispatchEvent(evt);
} else {
element.fireEvent("on" + evt.eventType, evt);
}
}
fireEvent('keydown', document.
to solve your question may you see this link, there is a example how to "Auto comment on a facebook post using JavaScript"
"Below are the steps:
Go to facebook page using m.facebook.com
Sign in and open any post.
Open developer mode in Chrome by pressing Ctrl+Shift+I
Navigate to the console.
Now, run the below script."
var count = 100;
var message = "Hi";
var loop = setInterval(function(){
var input = document.getElementsByName("comment_text")[0];
var submit = document.querySelector('button[type="submit"]');
submit.disabled = false;
input.value = message;
submit.click();
count -= 1;
if(count == 0)
{
clearInterval(loop);
}
}, 10000);
Kind regards
ref.: source page

Embedding a SoundCloud recording right away?

Basically I want the user to record a sound with the SoundCloud recorder and once they click save, the sound they just recorded will be embedded on to my webpage.
I use the SC.record() method to get the recording. This is my function for where I save the recordings... Right now nothing is embedded when I try to run it
$('#save a').click(function(e) {
var currentURL ="";
e.preventDefault();
SC.connect({
connected: function() {
$('.status').html('Uploading...');
SC.recordUpload({
track: {
title: 'whatever',
sharing: 'public'
}
}, function(track) {
currentURL = '"'+track.permalink_url+'"';
$('#SCtracks').append('<li id='+currentURL+'>'+currentURL+'</li>');
SC.oEmbed(currentURL, {color: "ff0066"}, document.getElementById(currentURL));
});
}
});
});
But if I go in and call SC.oEmbed with a URL to a recording I made earlier it works fine.
So I think I might be trying to embed the recording before it is fully uploaded, but I don't know where else I could put that statement.
Thanks
This answered my question. I guess you have to just keep checking the state until it is for sure done. If someone know a quicker way please answer

Youtube Javascript API: not working in IE

I have an embedded Youtube video, and I want to capture events when the user pauses the video, skips around, etc. Following the examples at Google's documentation (http://developers.google.com/youtube/js_api_reference), I do the following:
var video = (my video ID here);
var params = { allowScriptAccess: "always" };
var atts = { id: "youtubeplayer" };
var x = 854;
var y = 480;
swfobject.embedSWF("http://www.youtube.com/v/" + video + "?enablejsapi=1&playerapiid=ytplayer&version=3", "bobina", x, y, "8", null, null, params, atts);
function onYouTubePlayerReady(playerId) {
youtubeplayer = document.getElementById('youtubeplayer');
youtubeplayer.addEventListener("onStateChange", "onytplayerStateChange");
youtubeplayer.setPlaybackQuality('large');
}
function onytplayerStateChange(newState) {
//Make it autoplay on page load
if (newState == -1) {
youtubeplayer.playVideo();
}
var tiempo=youtubeplayer.getCurrentTime();
//Rounds to 2 decimals
var tiempo = Math.round(tiempo*100)/100;
alert(tiempo);
}
Now, all this works perfectly in Firefox and Safari, but there's no way to make it work in IE8 (by which I mean: the video loads, but events aren't captured: the event handler never gets called). According to javascript addEventListener onStateChange not working in IE , IE doesn't support addEventListener(), so I tried replacing that line with:
youtubeplayer.attachEvent("onStateChange", onytplayerStateChange);
But it doesn't work either. (The author of that question says that he finally solved it by putting "onComplete in my colorbox and put the swfobject in that", but I'm not using a colorbox here (I don't even know what that is), so I don't understand what he means.
Using alerts() to debug, I see that the first function (onYoutubePlayerReady()) is indeed called, but I can't even tell whether the event listener isn't being registered or whether it's the getElementById() that isn't working; I tried debugging by adding a:
alert(typeof youtubeplayer);
Right after it, but it doesn't even pop up.
Oh, and one more thing: the video starts automatically in Firefox and Safari, but in IE it doesn't either. (And yes, I'm running this on a server, not on a local page).
Anyone has any ideas? I don't really know what else to try.
Okay, found the solution. Guess what it was? Just write is as:
var youtubeplayer = document.getElementById('youtubeplayer');
And it works perfectly in IE 8. All it needed was the "var" keyword.
Just in case it's useful for someone else with the same problem...

Sound Cloud custom player - "id property is null" error

I'm having some issues working with the Sound Cloud custom html5 player (http://developers.soundcloud.com/docs/custom-player) specifically on internet explorer (tested on version 7, 8 and 9).
It seems whenever the player is created (right now this is dynamically through JS) it is running into an error with a function in the "sc-player.js" file (provided by sound cloud here).
The error is "Unable to get the value or property 'id': object null or undefined". This is the code that messes up: line 640 character 9
// selecting tracks in the playlist
$('.sc-trackslist li').live('click', function(event) {
var $track = $(this),
$player = $track.closest('.sc-player'),
trackId = $track.data('sc-track').id,
play = $player.is(':not(.playing)') || $track.is(':not(.active)');
if (play) {
onPlay($player, trackId);
}else{
onPause($player);
}
$track.addClass('active').siblings('li').removeClass('active');
$('.artworks li', $player).each(function(index) {
$(this).toggleClass('active', index === trackId);
});
return false;
});
I understand this is trying to find the id of a track on the generated list, however I have hidden this list on the page, so really shouldn't that click event should not even fire? I have tried removing the click event all together but that seems to cause a number of other issues with the player.
Just wondering if anyone else has experienced this or knows how to get around it?
EDIT -
I manually attached "test" data the track. And this seems to solve that first error, but of course brings up another. This time the same error is spat out for this line (in the sc-player.js file):
getPlayerData = function(node) {
return players[$(node).data('sc-player').id];
}
This is the code I use to call the player, the track argument being the link to a music tracks soundcloud page, and #player being the empty div its being created on top of:
function init_player(track,title) {
$('.open #player').scPlayer({
links: [{url: track, title: title}],
beforeRender : function(tracksData) {
$scplayer = $(this);
$('.sc-trackslist li, .sc-trackslist li a').data({ id: track});
}
}
When the user wants to change to another section of music, I then have another function which empties the container div, appends #player to it (because it was overwritten by the soundcloud player), and re runs the above function.
My issue is on the second time the init_player function is run, I get the error.
Sorry for the delayed response!
This line is wrong:
trackId = $track.data('sc-track').id,
I can't help you more then that because I need the rest of the code...

Help making userscript work in chrome

I've written a userscript for Gmail : Pimp.my.Gmail & i'd like it to be compatible with Google Chrome too.
Now i have tried a couple of things, to the best of my Javascript knowledge (which is very weak) & have been successful up-to a certain extent, though im not sure if it's the right way.
Here's what i tried, to make it work in Chrome:
The very first thing i found is that contentWindow.document doesn't work in chrome, so i tried contentDocument, which works.
BUT i noticed one thing, checking the console messages in Firefox and Chrome, i saw that the script gets executed multiple times in Firefox whereas in Chrome it just executes once!
So i had to abandon the window.addEventListener('load', init, false); line and replace it with window.setTimeout(init, 5000); and i'm not sure if this is a good idea.
The other thing i tried is keeping the window.addEventListener('load', init, false); line and using window.setTimeout(init, 1000); inside init() in case the canvasframe is not found.
So please do lemme know what would be the best way to make this script cross-browser compatible.
Oh and im all ears for making this script better/efficient code wise (which im sure is possible)
edit: no help...? :'(
edit 28-Apr:
i re-wrote the code a little and now it looks something like this.:
if(document.location != top.location) return;
(function() {
var interval = window.setInterval(waitforiframe, 3000);
var canvas;
function waitforiframe() {
console.log("Finding canvas frame");
canvas = document.getElementById("canvas_frame");
if (canvas && canvas.contentDocument) {
console.log("Found canvas frame");
pimpmygmail();
}
}
function pimpmygmail() {
gmail = canvas.contentDocument;
if(!gmail) return;
window.clearInterval(interval);
console.log("Lets PIMP.MY.GMAIL!!!");
......rest of the code......
})();
This works perfectly fine in Firefox, but in Chrome, it gives me a top is undefined error.
Another thing i noticed is that if i remove the first line if(document.location != top.location) return; , the waitforiframe() method keeps getting called over and over again. (ie i see the "Finding canvas frame" error in the console)
can someone tell me what does the first line do? i mean what does it achieve & why does the waitforiframe() method run forever if i remove that line??
A BIG THANK YOU TO ALL WHO HELPED! -_- meh
btw, this was all i needed at the beginning of the script:
try { if(top.location.href != window.location.href) { return; } }
catch(e) { return; }

Categories

Resources