관련 지식
#chrome #extension #webRequest #synology #download-station #youtube #NAS

개인용 NAS 중에 가장 유명한 시놀로지 제품에는 매우 유용한 기능들이 있습니다. 그 중 제가 가장 자주 쓰는 기능은 다운로드 스테이션으로 토렌트의 마그넷 뿐 아니라 몇 가지 다운로드 형태를 지원합니다.

  • http://, https://
  • ftp://, ftps://, sftp://
  • magnet:, thunder://, flashget://, qqdl://

특히 다운로드 스테이션은 유튜브 URL을 입력하면 유튜브 영상을 바로 저장해주는 기능도 있습니다.(오래된 URL은 안될 수 있습니다.)

유튜브

보통은 위와 같이 다운로드 받고자 하는 URL을 다운로드 스테이션에 직접 붙여넣어야 동작하지만 이 기능을 좀더 편하게 쓰도록 만들어진 크롬 확장 프로그램 Synology Download Station이 있습니다.

확장 프로그램을 설치 후 간단한 환경 설정을 하면 magnet: 주소 등을 클릭했을때 자동으로 다운로드 스테이션에 추가시켜 줍니다. 그런데 아쉽게도 이 프로그램은 유튜브와 같이 http://, https:// 링크는 지원하지 않습니다. 왜냐하면 웹페이지에서 http://, https:// 가 이동할 링크인지 다운로드 링크인지 구분할수 없기 때문입니다.

그래서 이번엔 유튜브 화면에 대해서는 시놀로지 다운로드 스테이션 을 사용할 수 있도록 작업해 볼 것입니다.

흐름 정리

대략 아래와 같은 흐름으로 동작할 것입니다.

  1. 유튜브 링크를 클릭
  2. 유튜브가 재생화면이 보임
  3. 시놀로지 다운로드 스테이션으로 연결할 수 있는 링크가 보임(작업필요)
  4. 링크를 클릭하면 시놀로지 다운로드 스테이션 동작(작업필요)

유튜브 원래의 기능에 영향이 가지 않도록 다운로드 가능한 새로운 링크를 화면에 추가할 것입니다. 이 기능을 추가하기 위해 확장 프로그램을 만들어야 합니다. 4번 항목 또한 작업이 필요한데 이것은 시놀로지 다운로드 스테이션(확장프로그램)을 커스터마이징 해야 합니다.

결과

3번 작업이 마무리 되면 위 이미지와 같이 유튜브 사이트 좌측 상단에 다운로드 링크가 보이게 될겁니다. 그리고 3번 작업은 간단한 스크립트 코딩으로도 가능하지만 번거로움을 최소화 하기 위해 크롬 확장 프로그램으로 만들어보겠습니다.

manifest.json 생성

먼저 manifest.json 파일을 만들어야 합니다. 해당 파일에는 프로그램의 이름, 버전정보 같은것이 들어갑니다.

  1. {
  2. "name": "sample-youtube.link",
  3. "version": "0.0.1",
  4. "manifest_version": 2,
  5. "description": "유튜브 링크생성",
  6. "background": {
  7. "scripts": [
  8. "background.js"
  9. ],
  10. "persistent": true
  11. },
  12. "permissions": [
  13. "background",
  14. "tabs",
  15. "webRequest",
  16. "https://*/*",
  17. "http://*/*"
  18. ]
  19. }

3개의 권한이 필요합니다. 특정 버튼을 클릭했을때 동작하는 것이 아니라 자동으로 실행되기 위한 background 권한, 탭에 접근하는 권한 그리고 유튜브 url을 호출했을때 html, css, js 등 서버로 부터 리소스를 받는 행위를 하는 webRequest에 대한 권한입니다.

background.js 생성

  1. chrome.webRequest.onCompleted.addListener(
  2. function(details) {
  3. },
  4. // filters
  5. {urls: ['https://*/*', 'http://*/*']},
  6. // extraInfoSpec
  7. ['responseHeaders']
  8. );

브라우저에서 http://www.youtube.com/aaa/bbb 라는 링크를 호출할 경우 bbb에 대한 webRequest가 동작을 하고 다운로드가 되었을때 onComplete 이벤트가 발생합니다. 따라서 이벤트가 발생했을때 특별한 처리를 하기 위한 소스입니다.

webRequest 의 이벤트는 유튜브 사이트 뿐 아니라 모든 사이트에서 발생하게 되므로 유튜브 사이트로 동작을 제한하는 처리가 필요합니다.

  1. if(details.url.indexOf("youtube.com/watch") < 0) {
  2. return;
  3. }

그런데 만약 bbb라는 화면에서 3개의 .css 가 있을 경우 css 파일을 다운받기 위한 webRequest가 3번 더 실행됩니다. 이미지, js 등 요청하는 모든 리소스에 대해 webRequest가 실행되므로 우리는 실제 화면에 해당하는 최초의 링크만 구분을 해야 합니다. 콜백함수로 넘어오는 객체의 type 속성은 ResourceType 으로 MDN에서도 확인 가능합니다.

https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest/ResourceType

  1. if(details.type == 'main_frame') {
  2. return;
  3. }

이제 화면이 보일때 response.js를 실행시킬 것입니다.

  1. chrome.tabs.executeScript(details.id, {
  2. file: 'response.js'
  3. });

response.js 생성

response.js 에서 하는 일은 매우 명확합니다. 화면에 <a> 태그를 추가할 것입니다. 특별한 것은 없지만 간단하게 설명하면 <a> 태그를 포함하는 <div> 태그를 왼쪽 상단에 고정하는 속성을 부여하여 <body> 태그에 추가하는 내용입니다.

다만 조금 특이한 것은 <a> 태그의 href 속성에 들어가는 링크가 http://https:// 가 아닌 ttp:?ttps:? 라는 것입니다. 시놀로지 다운로드 스테이션 확장 프로그램에서 http:// 나 https:// 와 같은 기본 URL 포맷을 다운로드 링크로 감지할 경우 웹사이트를 정상적으로 사용하기 힘들기 때문에 존재하지 않는 임의의 URL 프로토콜을 만들려는 것입니다.

  1. (function(global) {
  2. function makeRoot() {
  3. let id = "downloadstation";
  4. let div = document.getElementById(id);
  5. if(!div) {
  6. div = document.createElement('div');
  7. div.id = "downloadstation";
  8. div.style.width = "20px";
  9. div.style.height = "20px";
  10. div.style.position = "fixed";
  11. div.style.left = "0";
  12. div.style.top = "0";
  13. div.style.zIndex = "999999";
  14. }
  15. let anchor = document.createElement('a');
  16. anchor.href = global.location.href.replace("https://", "ttps:?").replace("http://", "ttp:?");
  17. anchor.textContent = '▼▽';
  18. div.innerHTML = '';
  19. div.appendChild(anchor);
  20. return div;
  21. }
  22. let root = makeRoot();
  23. document.body.insertBefore(root, document.body.childNodes[0]);
  24. })(window);

확장프로그램 추가

확장프로그램추가

크롬의 ‘확장 프로그램’ 메뉴에서 ‘압축해제된 확장 프로그램을 로드합니다’ 버튼을 클릭하여 이번에 작성한 파일들이 있는 폴더를 선택하면 등록이 완료 됩니다. js 파일 및 json 3개의 파일이 모두 같은 경로에 있어야 합니다.

정리

여기서 만들려는 확장 프로그램의 기능을 다시 정리해 보겠습니다.

  1. 크롬에서 호출한 URL을 감지한다.
  2. 호출된 URL이 유튜브 화면이라면 document.body 에 앵커 태그를 추가한다.
  3. 앵커태그의 href 속성은 ‘http://www.youtube.com/~‘ 나 ‘https://www.youtube.com/~‘ 형태가 아니라 ‘ttp:?www.youtube.com/~’, ‘ttps:?www.youtube.com/~’ 로 만든다.

이렇게 만들어진 확장 프로그램을 크롬에 추가하고 유튜브 영상을 조회 했을때 좌측상단에 아래와 같은 구조를 가진 태그가 만들어지면 잘 동작하는 것입니다.

https://www.youtube.com/watch?v=ABCDE 호출시)

  1. <div id="downloadstation" style="width: 20px; height: 20px; position: fixed; left: 0px; top: 0px; z-index: 999999;">
  2. <a href="ttps:?www.youtube.com/watch?v=ABCDE">▼▽</a>
  3. </div>

지금 당장은 링크를 눌러도 동작하지 않습니다. 이것을 동작 시키기 위해선 웹스토어에 있는 확장 프로그램을 수정해야 합니다. 다음 글에서 수정하는 방법을 설명 드리겠습니다.

최종 소스

manifest.json)

  1. {
  2. "name": "sample-youtube.link",
  3. "version": "0.0.1",
  4. "manifest_version": 2,
  5. "description": "유튜브 링크생성",
  6. "background": {
  7. "scripts": [
  8. "background.js"
  9. ],
  10. "persistent": true
  11. },
  12. "permissions": [
  13. "background",
  14. "tabs",
  15. "webRequest",
  16. "https://*/*",
  17. "http://*/*"
  18. ]
  19. }

background.js)

  1. chrome.webRequest.onCompleted.addListener(
  2. function(details) {
  3. if(details.url.indexOf("youtube.com/watch") < 0) {
  4. return;
  5. }
  6. if(details.type == 'main_frame') {
  7. return;
  8. }
  9. chrome.tabs.executeScript(details.id, {
  10. file: 'response.js'
  11. });
  12. },
  13. // filters
  14. {urls: ['https://*/*', 'http://*/*']},
  15. // extraInfoSpec
  16. ['responseHeaders']
  17. );

response.js)

  1. (function(global) {
  2. function makeRoot() {
  3. let id = "downloadstation";
  4. let div = document.getElementById(id);
  5. if(!div) {
  6. div = document.createElement('div');
  7. div.id = "downloadstation";
  8. div.style.width = "20px";
  9. div.style.height = "20px";
  10. div.style.position = "fixed";
  11. div.style.left = "0";
  12. div.style.top = "0";
  13. div.style.zIndex = "999999";
  14. }
  15. let anchor = document.createElement('a');
  16. anchor.href = global.location.href.replace("https://", "ttps:?").replace("http://", "ttp:?");
  17. anchor.textContent = '▼▽';
  18. div.innerHTML = '';
  19. div.appendChild(anchor);
  20. return div;
  21. }
  22. let root = makeRoot();
  23. document.body.insertBefore(root, document.body.childNodes[0]);
  24. })(window);