r/ListenToThis에서 좋아하는 음악을 강조하는 브라우저 확장
너는 이곳에서 온전한 원본 코드를 찾을 수 있다: https://github.com/alexadam/ListenToThis-Highlight
브라우저 확장을 만드는 방법
음악 하이라이트라는 폴더를 만듭니다.확장된 루트 폴더가 됩니다.
mkdir music-highlight
cd music-highlight
각 브라우저 확장자에는 작성자 이름, 설명, 권한, 라이센스, 스크립트 등의 메타데이터가 들어 있는 목록 파일이 있어야 합니다. 자세한 내용은 https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json파일 생성
manifest.json
및 붙여넣기:{
"manifest_version": 2,
"name": "music-highlight",
"version": "1",
"author": "Your Name",
"homepage_url": "https://www.reddit.com/r/listentothis/",
"description": "Highlight your favorite music genres on r/ListenToThis",
"content_scripts": [{
"matches": ["*://*.reddit.com/r/listentothis*"],
"js": ["colors.js"]
}],
"permissions": [
"activeTab",
"https://www.reddit.com/r/listentothis/",
"https://old.reddit.com/r/listentothis/",
"storage"
]
}
컨텐트 스크립트 섹션에서는 Chrome이나 Firefox에 색상을 실행하라고 알려 줍니다.URL이 정규 표현식 모드와 일치하는 웹 페이지에서 js를 사용합니다. 우리의 예에는 신구reddit가 있습니다.색을 주입하다.reddit 페이지에서 표준 DOM API(색상 변경, 새 HTML 요소 추가 등)를 사용하여 컨텐트에 액세스하고 수정할 수 있습니다.컨텐츠 스크립트에 대한 자세한 내용: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_scripts
얼마나 예쁜 색깔인데.js 작품:
파일
colors.js
을 만들고 모든 게시물을 반환하는 함수를 추가합니다.const getAllPosts = () => {
// old reddit
const allPosts = document.getElementsByClassName('thing');
if (allPosts.length === 0) {
// new reddit
return document.getElementsByClassName('scrollerItem');
}
return allPosts
}
그런 다음 추출 머리글의 함수를 만듭니다.const getTitle = (post) => {
// old reddit
const titleElem = post.querySelector('a.title');
// new reddit
if (!titleElem) {
return post.querySelector('h3');
}
return titleElem
}
제목 형식은 Band - Song [genre1/genre2, genre3] (2020)
과 비슷하며 대괄호 안의 내용만 필요합니다.const getGenresAsString = (titleElem) => {
const text = titleElem.innerText.toLowerCase()
// Extract the genres from the title
const genresRegex = /\[([^\]]+)\]/g
const match = genresRegex.exec(text)
// Skip over posts that are not properly formatted
if (!match) {
return null
}
return match[0]
}
가장 좋아하는 유형과 색상을 나열하는 목록을 추가합니다.const favoriteGenres = {
'ambient': '#fa8335',
'blues': '#0df9f9',
'country': '#fad337',
'chill': '#a8f830',
'funk': '#F2EF0C',
'jazz': '#fba19d',
'soul': '#aca2bb',
}
모든 fav를 옮겨다니는 함수를 만듭니다.위 목록에 정의된 유형입니다.getGenresAsString
이 반환하는 문자열에서 어떤 종류가 언급되면 함수는 그 색을 반환합니다. (일치하는 항목이 여러 개 있으면 마지막 종류의 색을 반환합니다.)const getBGColor = (allGenresStr, favGenres) => {
let bgColor = null
// Test if the post contains any of our fav. genres
for (const genre of Object.keys(favGenres)) {
const genreRegex = new RegExp('.*' + genre + '.*', "i")
if (!genreRegex.test(allGenresStr)) {
continue
}
bgColor = 'background-color: ' + favGenres[genre] + ' !important'
}
return bgColor
}
시도하기 전에 우리는 또 다른 문제를 해결해야 한다...새 레딧은 동적으로 컨텐트를 로드하고 로드할 때 색상을 추가합니다.js가 페이지에서 실행되고 유용한 데이터가 없습니다.또한 아래로 스크롤할 때 새로운 내용을 추가합니다. 논리를 다시 적용하고 색을 업데이트해야 합니다.따라서 코드를 트리거하는 새로운 컨텐츠 모니터가 필요합니다.const observeDOM = (() => {
const MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
const eventListenerSupported = window.addEventListener;
return (obj, callback) => {
if (MutationObserver) {
const obs = new MutationObserver((mutations, observer) => {
if (mutations[0].addedNodes.length)
callback(mutations[0].addedNodes);
});
obs.observe(obj, {
childList: true,
subtree: true
});
} else if (eventListenerSupported) {
obj.addEventListener('DOMNodeInserted', callback, false);
obj.addEventListener('DOMNodeRemoved', callback, false);
}
};
})();
// detect if on the new Reddit before the content is loaded
const targetElem = document.getElementById('2x-container')
if (targetElem) {
observeDOM(targetElem, (addedNodes) => {
// whenever new content is added
addColorsOnSongs(favoriteGenres);
});
}
우리가 모든 물건을 한데 놓은 후에 색깔.js는 다음과 같습니다.const favoriteGenres = {
'ambient': '#fa8335',
'blues': '#0df9f9',
'country': '#fad337',
'chill': '#a8f830',
'funk': '#F2EF0C',
'jazz': '#fba19d',
'soul': '#aca2bb',
}
const getAllPosts = () => {
// old reddit
const allPosts = document.getElementsByClassName('thing');
if (allPosts.length === 0) {
// new reddit
return document.getElementsByClassName('scrollerItem');
}
return allPosts
}
const getTitle = (post) => {
// old reddit
const titleElem = post.querySelector('a.title');
// new reddit
if (!titleElem) {
return post.querySelector('h3');
}
return titleElem
}
const getGenresAsString = (titleElem) => {
const text = titleElem.innerText.toLowerCase()
// Extract the genres from the title
const genresRegex = /\[([^\]]+)\]/g
const match = genresRegex.exec(text)
// Skip over posts that are not properly formatted
if (!match) {
return null
}
return match[0]
}
const getBGColor = (allGenresStr, favGenres) => {
let bgColor = null
// Test if the post contains any of our fav. genres
for (const genre of Object.keys(favGenres)) {
const genreRegex = new RegExp('.*' + genre + '.*', "i")
if (!genreRegex.test(allGenresStr)) {
continue
}
bgColor = 'background-color: ' + favGenres[genre] + ' !important'
}
return bgColor
}
const changePostColor = (post, bgColor) => {
post.setAttribute('style', bgColor);
for (let child of post.children) {
child.setAttribute('style', bgColor);
for (let child2 of child.children) {
child2.setAttribute('style', bgColor);
}
}
}
const addColorsOnSongs = (colorData) => {
const allPosts = getAllPosts();
for (const post of allPosts) {
const titleElem = getTitle(post)
if (!titleElem) continue
const genresStr = getGenresAsString(titleElem)
const bgColor = getBGColor(genresStr, favoriteGenres)
if (!bgColor) continue
// Change the post's & its children bg color
changePostColor(post, bgColor)
}
}
addColorsOnSongs(favoriteGenres)
const observeDOM = (() => {
const MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
const eventListenerSupported = window.addEventListener;
return (obj, callback) => {
if (MutationObserver) {
const obs = new MutationObserver((mutations, observer) => {
if (mutations[0].addedNodes.length)
callback(mutations[0].addedNodes);
});
obs.observe(obj, {
childList: true,
subtree: true
});
} else if (eventListenerSupported) {
obj.addEventListener('DOMNodeInserted', callback, false);
obj.addEventListener('DOMNodeRemoved', callback, false);
}
};
})();
// detect if on the new Reddit before the content is loaded
const targetElem = document.getElementById('2x-container')
if (targetElem) {
observeDOM(targetElem, (addedNodes) => {
addColorsOnSongs(favoriteGenres);
});
}
우리 한번 테스트해 보자...Chrome:
Firefox:
그리고 https://old.reddit.com/r/listentothis/을 방문하면 다음을 볼 수 있습니다.
https://www.reddit.com/r/listentothis/
사용자 경험 문제 해결
다른 색깔의 하이라이트 디스플레이를 사용하면 목록에서 재미있는 음악을 쉽게 찾을 수 있지만, 우리는 여전히 그것을 개선할 수 있다.더 큰 글꼴로 유파를 표시하는 요소를 추가하고 싶습니다. 유파를 클릭하면 레드디트 평론 페이지를 열지 않고 링크된 노래로 바로 들어갑니다.
그래서 한 편의 댓글에 적어도 나의 최애를 포함한다면.장르:
const getSongURL = (titleElem, post) => {
// old reddit
let href = titleElem.href
// new reddit
if (!href) {
const extLink = post.querySelector('a.styled-outbound-link')
if (extLink) {
return extLink.href
}
}
return href
}
createSongLink에서 HTML 요소를 만들려면 다음과 같이 하십시오.const createSongLink = (titleElem, post, genresText) => {
post.style.position = 'relative'
let linkElem = document.createElement('a')
linkElem.className = "favGenresLink"
linkElem.style.position = 'absolute'
linkElem.style.right = '20px'
linkElem.style.bottom = '0'
linkElem.style.height = '50px'
linkElem.style.color = 'black'
linkElem.style.fontSize = '50px'
linkElem.style.zIndex = '999999'
linkElem.innerText = genresText
linkElem.href = getSongURL(titleElem, post)
return linkElem
}
getBGcolor를 수정하여 색상과 일치하는 모든 유형을 텍스트로 반환합니다.const getBGColor = (allGenresStr, favGenres) => {
let bgColor = null
let favGenresStr = ''
// Test if the post contains any of our fav. genres
for (const genre of Object.keys(favGenres)) {
const genreRegex = new RegExp('.*' + genre + '.*', "i")
if (!genreRegex.test(allGenresStr)) {
continue
}
bgColor = 'background-color: ' + favGenres[genre] + ' !important'
favGenresStr += genre + ' '
}
return {bgColor, favGenresStr}
}
addColorsOnSongs 업데이트:const addColorsOnSongs = (colorData) => {
const allPosts = getAllPosts();
for (const post of allPosts) {
// Ingnore this post if it already
// contains a favGenresLink
let colorObj = post.querySelector('a.favGenresLink');
if (colorObj) continue
const titleElem = getTitle(post)
if (!titleElem) continue
const genresStr = getGenresAsString(titleElem)
const {bgColor, favGenresStr} = getBGColor(genresStr, favoriteGenres)
if (!bgColor) continue
// Change the post's & its children bg color
changePostColor(post, bgColor)
// Create the genres link and add it to the post
const linkElem = createSongLink(titleElem, post, favGenresStr)
post.insertAdjacentElement('afterbegin', linkElem)
}
}
새로 추가한 모든 HTML 요소는 이전에 만든 새 컨텐트 탐지기를 트리거하여 동적 컨텐트의 색상을 업데이트합니다.무한 순환 방지 - 새 콘텐츠 탐지기 -> 색상 추가 () -> 유파 링크 만들기 및 추가 -> 새 콘텐츠 탐지기 트리거 - 조건을 추가해야 합니다:if (targetElem) {
observeDOM(targetElem, (addedNodes) => {
// ignore favGenresLink to avoid an infinite loop
for (let addedNode of addedNodes) {
if (addedNode.classList.contains('favGenresLink')) {
return
}
}
// whenever new content is added
addColorsOnSongs(favoriteGenres);
});
}
이게 컬러예요.업데이트된 js 파일을 모두 포함합니다.
const favoriteGenres = {
'ambient': '#fa8335',
'blues': '#0df9f9',
'country': '#fad337',
'chill': '#a8f830',
'funk': '#F2EF0C',
'jazz': '#fba19d',
'soul': '#aca2bb',
}
const getAllPosts = () => {
// old reddit
const allPosts = document.getElementsByClassName('thing');
if (allPosts.length === 0) {
// new reddit
return document.getElementsByClassName('scrollerItem');
}
return allPosts
}
const getTitle = (post) => {
// old reddit
const titleElem = post.querySelector('a.title');
// new reddit
if (!titleElem) {
return post.querySelector('h3');
}
return titleElem
}
const getGenresAsString = (titleElem) => {
const text = titleElem.innerText.toLowerCase()
// Extract the genres from the title
const genresRegex = /\[([^\]]+)\]/g
const match = genresRegex.exec(text)
// Skip over posts that are not properly formatted
if (!match) {
return null
}
return match[0]
}
const getBGColor = (allGenresStr, favGenres) => {
let bgColor = null
let favGenresStr = ''
// Test if the post contains any of our fav. genres
for (const genre of Object.keys(favGenres)) {
const genreRegex = new RegExp('.*' + genre + '.*', "i")
if (!genreRegex.test(allGenresStr)) {
continue
}
bgColor = 'background-color: ' + favGenres[genre] + ' !important'
favGenresStr += genre + ' '
}
return {bgColor, favGenresStr}
}
const changePostColor = (post, bgColor) => {
post.setAttribute('style', bgColor);
for (let child of post.children) {
child.setAttribute('style', bgColor);
for (let child2 of child.children) {
child2.setAttribute('style', bgColor);
}
}
}
const getSongURL = (titleElem, post) => {
// old reddit
let href = titleElem.href
// new reddit
if (!href) {
const extLink = post.querySelector('a.styled-outbound-link')
if (extLink) {
return extLink.href
}
}
return href
}
const createSongLink = (titleElem, post, genresText) => {
post.style.position = 'relative'
let linkElem = document.createElement('a')
linkElem.className = "favGenresLink"
linkElem.style.position = 'absolute'
linkElem.style.right = '20px'
linkElem.style.bottom = '0'
linkElem.style.height = '50px'
linkElem.style.color = 'black'
linkElem.style.fontSize = '50px'
linkElem.style.zIndex = '999999'
linkElem.innerText = genresText
linkElem.href = getSongURL(titleElem, post)
return linkElem
}
const addColorsOnSongs = (colorData) => {
const allPosts = getAllPosts();
for (const post of allPosts) {
// ignore
let colorObj = post.querySelector('a.favGenresLink');
if (colorObj) continue //TODO
const titleElem = getTitle(post)
if (!titleElem) continue
const genresStr = getGenresAsString(titleElem)
const {bgColor, favGenresStr} = getBGColor(genresStr, favoriteGenres)
if (!bgColor) continue
// Change the post's & its children bg color
changePostColor(post, bgColor)
// Create the genres link and add it to the post
const linkElem = createSongLink(titleElem, post, favGenresStr)
post.insertAdjacentElement('afterbegin', linkElem)
}
}
addColorsOnSongs(favoriteGenres)
const observeDOM = (() => {
const MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
const eventListenerSupported = window.addEventListener;
return (obj, callback) => {
if (MutationObserver) {
const obs = new MutationObserver((mutations, observer) => {
if (mutations[0].addedNodes.length)
callback(mutations[0].addedNodes);
});
obs.observe(obj, {
childList: true,
subtree: true
});
} else if (eventListenerSupported) {
obj.addEventListener('DOMNodeInserted', callback, false);
obj.addEventListener('DOMNodeRemoved', callback, false);
}
};
})();
// detect if on the new Reddit before the content is loaded
const targetElem = document.getElementById('2x-container')
if (targetElem) {
observeDOM(targetElem, (addedNodes) => {
// ignore favGenresLink to avoid an infinite loop
for (let addedNode of addedNodes) {
if (addedNode.classList.contains('favGenresLink')) {
return
}
}
// whenever new content is added
addColorsOnSongs(favoriteGenres);
});
}
사용자 정의 스타일 및 색상
현재 가장 좋아하는 유형은 코드 형식으로 목록에 저장되어 있다.이 설정을 변경하고 원하는 유형을 정의할 수 있는 설정 페이지를 만듭니다.
먼저
manifest.json
을 업데이트하고 옵션 페이지를 지정해야 합니다....
"options_page": "options.html",
"options_ui": {
"page": "options.html"
}
...
파일 생성 옵션.html:<html>
<head></head>
<body>
<h1>Music Highlight Options</h1>
<div id="root">
<div id="container">
</div>
<button id="add" class="button" type="button" name="button">Add genre</button>
<hr />
<div id="buttons">
<button id="save" class="button" type="button" name="button">Save</button>
<div id="status"></div>
</div>
</div>
<script src="options.js"></script>
</body>
</html>
및 를 선택합니다.js...const defaultGenres = {
'ambient': '#fa8335',
'blues': '#0df9f9',
'country': '#fad337',
'chill': '#a8f830',
'funk': '#F2EF0C',
'jazz': '#fba19d',
'soul': '#aca2bb',
}
const restoreOptions = () => {
chrome.storage.local.get('colors', (data) => {
if (!data
|| Object.keys(data).length === 0
|| Object.keys(data.colors).length === 0) {
createColorsUI(defaultGenres);
} else {
createColorsUI(data.colors);
}
});
}
document.addEventListener('DOMContentLoaded', restoreOptions);
다음 장르 색상 입력을 만드는 함수를 추가합니다.const createColorInput = (genre, color, id) => {
let genreInputLabel = document.createElement('span')
genreInputLabel.innerText = 'Genre:'
genreInputLabel.className = 'genreNameLabel'
let genreInput = document.createElement('input')
genreInput.className = 'genreName'
genreInput.type = 'text'
genreInput.value = genre
let colorInputLabel = document.createElement('span')
colorInputLabel.innerText = 'Color:'
colorInputLabel.className = 'colorNameLabel'
let colorInput = document.createElement('input')
colorInput.className = 'colorName'
colorInput.type = 'color'
colorInput.value = color
let removeButton = document.createElement('button')
removeButton.innerText = 'Remove'
removeButton.className = 'removeButton button'
removeButton.addEventListener('click', ((e) => {
let tmpElem = document.getElementById(e.target.parentElement.id)
if (tmpElem && tmpElem.parentElement) {
tmpElem.parentElement.removeChild(tmpElem)
}
}))
let group = document.createElement('div')
group.id = 'data' + id
group.className = 'genreColorGroup'
group.appendChild(genreInputLabel)
group.appendChild(genreInput)
group.appendChild(colorInputLabel)
group.appendChild(colorInput)
group.appendChild(removeButton)
let container = document.getElementById('container')
container.appendChild(group)
}
다음 함수createColorsUIconst createColorsUI = (data) => {
let index = 0
for (let variable in data) {
if (data.hasOwnProperty(variable)) {
createColorInput(variable, data[variable], index)
index++
}
}
}
및 addOption 기능:const addOption = () => {
let index = Math.floor(Math.random() * 1000000)
createColorInput('misc', '#000000', index)
}
document.getElementById('add').addEventListener('click', addOption);
변경 사항을 크롬에 저장합니다.저장실.지역:const saveOptions = () => {
let allGenreNames = document.getElementsByClassName('genreName')
let allColorNames = document.getElementsByClassName('colorName')
let data = {}
for (let i = 0; i < allGenreNames.length; i++) {
let name = allGenreNames[i].value
let color = allColorNames[i].value
data[name] = color
}
chrome.storage.local.set({
colors: data
}, () => {
let status = document.getElementById('status');
status.textContent = 'Options saved.';
setTimeout(() => {
status.textContent = '';
}, 2750);
});
}
document.getElementById('save').addEventListener('click', saveOptions);
Chrome에서 옵션 페이지를 여는 방법:Firefox:
마지막 부분
그런 다음 작업을 수행하려면 저장된 옵션을 강조 표시된 코드에 링크해야 합니다.
열린 색상.js 및 바꾸기:
addColorsOnSongs(favoriteGenres)
및:chrome.storage.local.get('colors', (data) => {
if (!data || Object.keys(data).length === 0 || Object.keys(data.colors).length === 0) {
addColorsOnSongs(favoriteGenres);
} else {
console.log(data)
addColorsOnSongs(data.colors);
}
});
및 업데이트:// detect if on the new Reddit before the content is loaded
const targetElem = document.getElementById('2x-container')
if (targetElem) {
observeDOM(targetElem, (addedNodes) => {
// ignore favGenresLink to avoid an infinite loop
for (let addedNode of addedNodes) {
if (addedNode.classList.contains('favGenresLink')) {
return
}
}
// whenever new content is added
//addColorsOnSongs(favoriteGenres);
chrome.storage.local.get('colors', (data) => {
if (!data || Object.keys(data).length === 0 || Object.keys(data.colors).length === 0) {
addColorsOnSongs(favoriteGenres);
} else {
console.log(data)
addColorsOnSongs(data.colors);
}
});
});
}
브라우저 작업 및 아이콘 만들기
도구막대 메뉴의 확장 단추에 아이콘을 추가합니다. (r/ListenToThis 아이콘을 다시 사용합니다.)
"icons": {
"48": "icons/logo.png"
},
"browser_action": {
"default_icon": {
"48": "icons/logo.png"
},
"default_title": "Music-Highlight",
"browser_style": true,
"default_popup": "action.html"
}
옵션 페이지에 들어가려면 여러 단계가 필요하므로 옵션 버튼이 있는 메뉴를 만들고, 클릭만 하면 페이지를 열 수 있습니다.행동을 창조하다.html:
<html>
<body>
<button id="openOptions">Options...</button>
<script src="action.js" charset="utf-8"></script>
</body>
</html>
그리고 행동.jsdocument.getElementById('openOptions').addEventListener('click', (e) => {
let optionsUrl = chrome.extension.getURL("./options.html");
chrome.tabs.create({
url: optionsUrl
});
})
현재 확장된 도구막대 아이콘을 누르면 메뉴가 팝업됩니다. 그 중 하나의 단추가 옵션 페이지를 엽니다.너는 이곳에서 온전한 원본 코드를 찾을 수 있다: https://github.com/alexadam/ListenToThis-Highlight
Reference
이 문제에 관하여(r/ListenToThis에서 좋아하는 음악을 강조하는 브라우저 확장), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/alexadam/a-browser-extension-to-highlight-your-favorite-music-on-r-listentothis-1dg5텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)