📜 ⬆️ ⬇️

We write the extension for Chrome "download audio recordings from Vkontakte", part 2

We continue to write our extension for Chrome, which adds a “Download” link for each audio recording on VKontakte.
Last time, we changed our section My Audio Records so.
It was originally:Became with the extension:
OriginalResult


But, at that time, our expansion had a significant drawback: it did not work when going from page to page.
If you go to the main page, then go to My Audio Records, then the links from the songs did not appear.
I recall, VKontakte when moving from page to page does not update the page in the classical sense, but programmatically changes the page layout, and updates the address bar. This is not a classic browser transition to a new page, and therefore our extension has not been updated.
Let's fix it.

As before, our extension will consist of three files - a description file (manifest.json), an injected js script (vk_inject.js), and an inline style file (vk_styles.css).

Here is the main file extension: manifest.json. It contains the extension descriptor and links to the embedded files.
manifest.json
{ "manifest_version": 2, "name": "   ", "description": "       .", "version": "2.0", "content_scripts": [{ "matches": ["*://vk.com/*"], "js": ["vk_inject.js"], "css": ["vk_styles.css"] }] } 


The “content_scripts” tag in the manifest determines which js and css files will be embedded in the page.
Our extension will embed vk_inject.js and vk_styles.css files on every VKontakte page - http://vk.com/* or https://vk.com/*.
')
The style file (vk_styles.css) contains styles for the embedded link. The link will have a css downloadLink class.
Be sure to ensure that the class does not overlap with the styles of the original page.
Let's make a border for our link and highlight when hovering. Unlike the first version, we will make our link smaller,
so that it is more often placed in the space defined for the song.


You can, of course, override styles if you like.
vk_styles.css
 .downloadLink { float: right; cursor: copy; border: 1px dotted #CED8DB; border-radius: 2px; padding: 0 4px; } .downloadLink:hover { background-color: #d0e6ff; border-color: #9DA5AE; } 


All the main actions of the extension will occur in the embedded code vk_inject.js.
So, what shall we do:

For each song in the list of audio recordings we will implement the link “Download”.

We will search for items on the page with id 'pad_playlist', 'pad_search_list', 'initial_list', 'search_list', 'choose_audio_rows'.
It is in them are lists of audio recordings. But, each of the elements may initially be present on the page, and
dynamically created / deleted. Therefore, we need to monitor the addition of elements to the DOM page.

Our embedded script is executed in a separate virtual machine, and can not interact with the script on the village.
Therefore, we cannot override the source functions or otherwise intercept the js code on the source page.
But, both of these scripts share a DOM tree. So we will keep track of DOM updates on the list items with MutationObserver.
vk_inject.js
 (function (){ //     ,      //  observer          var trackObserver = new MutationObserver(listModified); // , ,         var list_ids = ['pad_playlist', 'pad_search_list', 'initial_list', 'search_list', 'choose_audio_rows']; for (var i= 0 ; i < list_ids.length; i++) { var list = document.getElementById(list_ids[i]); if (list) { //   ""   ,        trackObserver listFound(list); } } //     ,      css  list = document.getElementById('results'); if (list && list.classList.contains('audio_results')) { listFound(list); } //  observer         var listObserver = new MutationObserver(elementAdded); //    body,      listObserver.observe(document.body, {childList: true, subtree: true}); //     DOM  function elementAdded(mutations) { for (var i = 0; i < mutations.length; i++) { var added = mutations[i].addedNodes; //        for (var j = 0; j < added.length; j++) { findAudioLists(added[j]); } } } //             function findAudioLists(node) { if (node.id) //     id { for (var i = 0; i < list_ids.length; i++) // ,   id   { if (list_ids[i] == node.id) { listFound(node); return; //        } } if (node.id == 'results') //    '#results.audio_results' -   { if (node.classList.contains('audio_results')) { listFound(node); return; } } } //      var child = node.firstElementChild; while (child) { findAudioLists(child); //       child = child.nextElementSibling; } } //    ,     function listFound(listNode) { if (listNode.children.length) //       { for (var j = 0; j < listNode.children.length; j++) { addDownloadLink(listNode.children[j]); //      "" } } trackObserver.observe(listNode, {childList: true}); //      -> listModified() } // ,      ( )  function listModified(mutations) { for (var i = 0; i < mutations.length; i++) { var mut = mutations[i]; //     for (var j = 0; j < mut.addedNodes.length; j++) { addDownloadLink(mut.addedNodes[j]); } //   - mut.removedNodes  } } //   ""    function addDownloadLink(row) { //  -    ,    ,   if (!row.classList.contains('audio')) { // ,     " " row = row.querySelector('div.audio'); //    'div.audio',      if (!row) { return; } } var titleNode = row.querySelector('div.title_wrap'); //   +  if (!titleNode) //     -  (,   ?) { return; } // ,    ?  ,          if (titleNode.querySelector('a.downloadLink')) { return; //      } var input = row.querySelector('div.play_btn > input'); //  input,    url if (!input) { input = row.querySelector('div.play_btn_wrap + input'); //     if (!input) { return; //    } } var ref = input.getAttribute('value'); //  URL ref = ref.substr(0, ref.indexOf('?')); //    '?',      mp3 var link = document.createElement('a'); link.className = 'downloadLink'; //   'downloadLink'    link.textContent = "^"; link.setAttribute('title', ""); link.setAttribute('download', titleNode.textContent + '.mp3'); //     link.setAttribute('href', ref); link.addEventListener('click', function(event){ //     ,    event.stopPropagation(); }); titleNode.appendChild(link); } })(); 


Install the extension


So, our three files are ready.
You can copy them from the post or download the archive .
In chrome, go to the settings page, select the Extensions tab (or just type “chrome: // extensions” in the address bar).
Turn on Developer Mode . Then click Download unpacked extension ....

Extensions

Select the folder where you saved these three files. In my case, this is D: \ Droopy \ work \ habr \ plugin.
The extension should appear in the list. Turn it on.



Let's check how it works. To do this, let's go to VKontakte, select the Music section in the top panel.



Hurray, links "Download" appeared! Moreover, if we start searching for audio recordings on the same page, then for each song found there will also be a download link. The extension works.

But, as I said in the last post, there is one difficulty with the name of the song being downloaded. When you click on the “Download” link, in the file saving dialog you will be offered not the file name that was specified in the “download” attribute, but the file name on the server. The fact is that VKontakte stores audio recordings on a separate domain, and chrome for this case will use the file name on the server instead of the one suggested in the link.
In the chrome bug tracker, it says that in this case you need to select the Save link as item in the context menu. Then we will be offered the normal name of the audio.



Our extension is ready. For each audio recording, a download link appears.

Since it is unpacked (that is, in development mode), each time the browser is restarted, it will be suggested to disable it.
In principle, it's better to do it this way, and turn it on as needed when you want to download songs. Or you can download it in Chrome webstore to use all the time.

Source: https://habr.com/ru/post/254979/


All Articles