diff --git a/.gitignore b/.gitignore index 13035f8..3708d9a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +chrome/build public/jspm_packages public/views node_modules diff --git a/chrome/icon-blue-128.png b/chrome/icon-blue-128.png new file mode 100644 index 0000000..8c81336 Binary files /dev/null and b/chrome/icon-blue-128.png differ diff --git a/chrome/icon-blue-16.png b/chrome/icon-blue-16.png new file mode 100644 index 0000000..010e6e9 Binary files /dev/null and b/chrome/icon-blue-16.png differ diff --git a/chrome/icon-blue-48.png b/chrome/icon-blue-48.png new file mode 100644 index 0000000..d905a01 Binary files /dev/null and b/chrome/icon-blue-48.png differ diff --git a/chrome/manifest.json b/chrome/manifest.json index dc19088..3548fdf 100644 --- a/chrome/manifest.json +++ b/chrome/manifest.json @@ -1,19 +1,31 @@ { "name" : "Match Audio", - "version" : "0.2.2", + "version" : "0.3.1", "description" : "Match Audio makes sharing from music services better.", "background" : { "persistent": false, - "scripts": [ "background.js" ] + "scripts": [ + "src/background.js" + ] }, + "content_scripts": [ + { + "matches": ["https://play.google.com/music/*"], + "js": ["src/lib/selector-listener.js", "src/google.js"] + } + ], "page_action" : { "default_icon": { "16": "icon-16.png", "48": "icon-48.png", "128": "icon-128.png" - } + }, + "default_title": "Find matches for this music on Match Audio" }, - "permissions" : [ "declarativeContent", "https://match.audio/" ], + "permissions" : [ + "declarativeContent", + "https://play.google.com/music" + ], "icons": { "16": "icon-16.png", "48": "icon-48.png", diff --git a/chrome/src/background.js b/chrome/src/background.js index 6523f54..713b3db 100644 --- a/chrome/src/background.js +++ b/chrome/src/background.js @@ -1,45 +1,36 @@ -"use strict"; +const apiUrl = 'https://match.audio'; -chrome.runtime.onInstalled.addListener(function() { - chrome.declarativeContent.onPageChanged.removeRules(undefined, function() { +chrome.runtime.onInstalled.addListener(() => { + chrome.declarativeContent.onPageChanged.removeRules(undefined, () => { chrome.declarativeContent.onPageChanged.addRules([ { conditions: [ new chrome.declarativeContent.PageStateMatcher({ - pageUrl: { hostSuffix: 'beatsmusic.com', pathPrefix: "/album" }, + pageUrl: { hostEquals: 'www.deezer.com', pathPrefix: '/album' }, }), new chrome.declarativeContent.PageStateMatcher({ - pageUrl: { hostSuffix: 'beatsmusic.com', pathPrefix: "/track" }, + pageUrl: { hostEquals: 'www.deezer.com', pathPrefix: '/track' }, }), new chrome.declarativeContent.PageStateMatcher({ - pageUrl: { hostSuffix: 'deezer.com', pathPrefix: "/album" }, - }), - new chrome.declarativeContent.PageStateMatcher({ - pageUrl: { hostSuffix: 'deezer.com', pathPrefix: "/track" }, - }), - new chrome.declarativeContent.PageStateMatcher({ - pageUrl: { hostEquals: 'play.google.com', pathPrefix: "/music" }, + pageUrl: { hostEquals: 'play.google.com', pathPrefix: '/music' }, }), new chrome.declarativeContent.PageStateMatcher({ - pageUrl: { hostEquals: 'itunes.apple.com', pathPrefix: "/music" }, + pageUrl: { hostEquals: 'itunes.apple.com', pathPrefix: '/music' }, }), new chrome.declarativeContent.PageStateMatcher({ pageUrl: { hostSuffix: 'rdio.com' }, }), new chrome.declarativeContent.PageStateMatcher({ - pageUrl: { hostSuffix: 'rd.io' }, + pageUrl: { hostSuffix: 'spotify.com', pathPrefix: '/album' }, }), new chrome.declarativeContent.PageStateMatcher({ - pageUrl: { hostSuffix: 'spotify.com', pathPrefix: "/album" }, + pageUrl: { hostSuffix: 'spotify.com', pathPrefix: '/track' }, }), new chrome.declarativeContent.PageStateMatcher({ - pageUrl: { hostSuffix: 'spotify.com', pathPrefix: "/track" }, + pageUrl: { hostEquals: 'music.microsoft.com', pathPrefix: '/track' }, }), new chrome.declarativeContent.PageStateMatcher({ - pageUrl: { hostSuffix: 'music.xbox.com', pathPrefix: "/track" }, - }), - new chrome.declarativeContent.PageStateMatcher({ - pageUrl: { hostSuffix: 'music.xbox.com', pathPrefix: "/album" }, + pageUrl: { hostEquals: 'music.microsoft.com', pathPrefix: '/album' }, }), new chrome.declarativeContent.PageStateMatcher({ pageUrl: { hostSuffix: 'youtube.com' }, @@ -53,17 +44,18 @@ chrome.runtime.onInstalled.addListener(function() { }); chrome.pageAction.onClicked.addListener(function(tab) { - var params = "url=" + encodeURI(tab.url); - var xhr = new XMLHttpRequest(); - xhr.open("POST", "https://match.audio/search", true); - xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); - xhr.onreadystatechange = function() { - if (xhr.readyState == 4) { - var match = JSON.parse(xhr.response); - if (match.id) { - chrome.tabs.create({ url: "https://match.audio/" + match.service + "/" + match.type + "/" + match.id}); - } - } - } - xhr.send(params); + chrome.pageAction.setIcon({tabId: tab.id, path: 'icon-blue-128.png'}, () => { + const headers = new Headers(); + headers.append('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8'); + fetch(apiUrl + '/search', {method: 'POST', mode: 'cors', headers, body: 'url=' + encodeURI(tab.url)}).then((response) => { + response.json().then((match) => { + chrome.pageAction.setIcon({tabId: tab.id, path: 'icon-128.png'}); + if (match.id) { + chrome.tabs.create({ url: apiUrl + '/' + match.service + '/' + match.type + '/' + match.id}); + } + }); + }).catch(() => { + chrome.pageAction.setIcon({tabId: tab.id, path: 'icon-128.png'}); + }); + }); }); diff --git a/chrome/src/google.js b/chrome/src/google.js new file mode 100644 index 0000000..7e50639 --- /dev/null +++ b/chrome/src/google.js @@ -0,0 +1,51 @@ +const apiUrl = 'https://match.audio'; + +var button = document.createElement('button'); +button.className = 'share-button'; +button.setAttribute('aria-label', 'Share to Match Audio'); + +var buttonContent = document.createElement('div'); +buttonContent.className = 'button-content'; +button.appendChild(buttonContent); + +var paperRipple = document.createElement('paper-ripple'); +paperRipple.class = 'circle'; +buttonContent.appendChild(paperRipple); + +var background = document.createElement('div'); +background.id = 'background'; +background.className = 'style-scope paper-ripple'; +paperRipple.appendChild(background); + +var waves = document.createElement('div'); +waves.id = 'waves'; +waves.className = 'style-scope paper-ripple'; +paperRipple.appendChild(waves); + +var img = document.createElement('img'); +img.src = 'https://match.audio/images/logo-128.png'; +img.height = 48; +buttonContent.appendChild(img) + +var buttonLabel = document.createElement('div'); +buttonLabel.className = 'button-label'; +buttonLabel.setAttribute('aria-hidden', true); +buttonLabel.innerText = 'Match Audio'; +buttonContent.appendChild(buttonLabel); + +// select the target node +document.addSelectorListener('.share-buttons', (event) => { + var input = event.target.parentElement.querySelector('paper-input input'); + // Check that it's an album or track, and not a playlist + const match = input.value.match(/https:\/\/play\.google\.com\/music\/m\/([a-zA-Z0-9]+)/); + if (match) { + event.target.insertBefore(button, event.target.firstChild); + button.addEventListener('click', (event) => { + if (match[1].startsWith('T')) { // Track + window.open(apiUrl + '/google/track/' + match[1], '_blank'); + } else if (match[1].startsWith('B')) { // Album + window.open(apiUrl + '/google/album/' + match[1], '_blank'); + } + }); + } +}); diff --git a/chrome/src/lib/selector-listener.js b/chrome/src/lib/selector-listener.js new file mode 100644 index 0000000..4a6dd91 --- /dev/null +++ b/chrome/src/lib/selector-listener.js @@ -0,0 +1,77 @@ +var events = {}, + selectors = {}, + styles = document.createElement('style'), + keyframes = document.createElement('style'), + head = document.getElementsByTagName('head')[0], + startNames = ['animationstart', 'oAnimationStart', 'MSAnimationStart', 'webkitAnimationStart'], + startEvent = function(event){ + event.selector = (events[event.animationName] || {}).selector; + ((this.selectorListeners || {})[event.animationName] || []).forEach(function(fn){ + fn.call(this, event); + }, this); + }, + prefix = (function() { + var duration = 'animation-duration: 0.001s;', + name = 'animation-name: SelectorListener !important;', + computed = window.getComputedStyle(document.documentElement, ''), + pre = (Array.prototype.slice.call(computed).join('').match(/moz|webkit|ms/)||(computed.OLink===''&&['o']))[0]; + return { + css: '-' + pre + '-', + properties: '{' + duration + name + '-' + pre + '-' + duration + '-' + pre + '-' + name + '}', + keyframes: !!(window.CSSKeyframesRule || window[('WebKit|Moz|MS|O').match(new RegExp('(' + pre + ')', 'i'))[1] + 'CSSKeyframesRule']) + }; + })(); + +styles.type = keyframes.type = 'text/css'; +head.appendChild(styles); +head.appendChild(keyframes); + +HTMLDocument.prototype.addSelectorListener = HTMLElement.prototype.addSelectorListener = function(selector, fn){ + var key = selectors[selector], + listeners = this.selectorListeners = this.selectorListeners || {}; + + if (key) events[key].count++; + else { + key = selectors[selector] = 'SelectorListener-' + new Date().getTime(); + var node = document.createTextNode('@' + (prefix.keyframes ? prefix.css : '') + 'keyframes ' + key + ' {' + +'from { outline-color: #fff; } to { outline-color: #000; }' + + '}'); + keyframes.appendChild(node); + styles.sheet.insertRule(selector + prefix.properties.replace(/SelectorListener/g, key), 0); + events[key] = { count: 1, selector: selector, keyframe: node, rule: styles.sheet.cssRules[0] }; + } + + if (listeners.count) listeners.count++; + else { + listeners.count = 1; + startNames.forEach(function(name){ + this.addEventListener(name, startEvent, false); + }, this); + } + + (listeners[key] = listeners[key] || []).push(fn); +}; + +HTMLDocument.prototype.removeSelectorListener = HTMLElement.prototype.removeSelectorListener = function(selector, fn){ + var listeners = this.selectorListeners || {}, + key = selectors[selector], + listener = listeners[key] || [], + index = listener.indexOf(fn); + + if (index > -1){ + var event = events[selectors[selector]]; + event.count--; + if (!event.count){ + styles.sheet.deleteRule(styles.sheet.cssRules.item(event.rule)); + keyframes.removeChild(event.keyframe); + delete events[key]; + delete selectors[selector]; + } + + listeners.count--; + listener.splice(index, 1); + if (!listeners.count) startNames.forEach(function(name){ + this.removeEventListener(name, startEvent, false); + }, this); + } +}; diff --git a/public/images/xbox.png b/public/images/xbox.png old mode 100755 new mode 100644 index ac50f95..e1f097a Binary files a/public/images/xbox.png and b/public/images/xbox.png differ