短いURLでおねだりしよう

# 背景

- amazonの商品ページをチャットでやりとりする機会があり、長いURLがウザかった。メールに貼りつけると折り返されてURLが途切れてしまうこともあるかと（いまどき無い!?）

例
この時計いいよね:thumbsup:
https://www.amazon.co.jp/%E3%82%BF%E3%82%B0%E3%83%9B%E3%82%A4%E3%83%A4%E3%83%BC-CAR2A1L-BA0688-%E3%82%AD%E3%83%A3%E3%83%AA%E3%83%90%E3%83%BC%E3%83%9B%E3%82%A4%E3%83%A4%E3%83%BC01-%E3%82%A2%E3%82%A4%E3%83%AB%E3%83%88%E3%83%B3%E3%83%BB%E3%82%BB%E3%83%8A-%E4%B8%A6%E8%A1%8C%E8%BC%B8%E5%85%A5%E5%93%81/dp/B07P6VVPLV/ref=sr_1_3?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&keywords=%E3%82%A2%E3%82%A4%E3%83%AB%E3%83%88%E3%83%B3%E3%82%BB%E3%83%8A+%E3%82%BF%E3%82%B0%E3%83%9B%E3%82%A4%E3%83%A4%E3%83%BC&qid=1563237513&s=gateway&sr=8-3

これもいいよね:blush:
https://www.amazon.co.jp/%E3%82%BF%E3%82%B0%E3%83%BB%E3%83%9B%E3%82%A4%E3%83%A4%E3%83%BC-%E3%83%A1%E3%83%B3%E3%82%BA%E8%85%95%E6%99%82%E8%A8%88-%E3%82%AD%E3%83%A3%E3%83%AA%E3%83%90%E3%83%BC%E3%83%9B%E3%82%A4%E3%83%A4%E3%83%BC01-%E3%82%A2%E3%82%A4%E3%83%AB%E3%83%88%E3%83%B3%E3%83%BB%E3%82%BB%E3%83%8A%E9%99%90%E5%AE%9A-CAR2A1L-BA0688/dp/B077YCQ265/ref=sr_1_4?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&keywords=%E3%82%A2%E3%82%A4%E3%83%AB%E3%83%88%E3%83%B3%E3%82%BB%E3%83%8A+%E3%82%BF%E3%82%B0%E3%83%9B%E3%82%A4%E3%83%A4%E3%83%BC&qid=1563237513&s=gateway&sr=8-4


- wikiを引用するときもURLが長い

例

アイルトンセナ
https://ja.wikipedia.org/wiki/%E3%82%A2%E3%82%A4%E3%83%AB%E3%83%88%E3%83%B3%E3%83%BB%E3%82%BB%E3%83%8A

### amazon, wiki のURLは短くできる。（≠短縮URL）

amazonは商品コードで表示できる
https://www.amazon.co.jp/dp/B07P6VVPLV
https://www.amazon.co.jp/dp/B077YCQ265

wikiは記事のidで表示できる
https://ja.wikipedia.org/?curid=4698

短縮URLはサービス経由で短くするので、サービス終了になると使えなくなってしまう。
以下はアイルトンセナのwikiのURLを短くしたもの
https://bit.ly/30tuVIC

amazon, wiki のURLを短くするツールはあったけどめんどくさい

### chrome extension で短いURLをサクっとコピーできるようにしよう

3年ぶりくらいにchrome extension に着手。下記ブレストも参照

# 仕様

- 右クリメニューでクリップボードにコピー
- アイコンでクリップボードにコピー
- 対象外のサイトでは右クリメニューを出さない
- 対象外のサイトではアイコンを無効化 ← 実現できず@TODO 

### ブレスト

- 右クリメニューはサクっと使えるので決まり。以前、作ったこともある。
 
- 貼り付けが目的なので、クリップボードにコピーしたい。→調べたら可能だったので採用
 
- アイコンは使用しないつもりが、勝手に表示されるので、押したらコピーできるようにした。
 
- 右クリメニューを押したときコピーされたか分からないので、メッセージを出そうと思ったが、それもウザいので、対象のサイトのみ右クリメニューが出るようにした。
 
- コピーできてないことがある(wikiのみ)ので、アイコンにコピー状況を表示するようにした。（原因は、contensとbackgroundの通信失敗）
 
- 対象外のサイトでアイコン無効化ができなかった。エクステンションは他にも使用しており、アイコンは表示しないつもりなのでOK(^^;
 
- アイコンでコピーもわりと便利。

- スマホでも使いたい→無理でした

- IEでも使いたい→しらん

# ファイル構成

root_folder
│  manifest.json
│
├─html
│      options.html
│
├─img
│      icon-128x128.png
│      icon-16x16.png
│      icon-48x48.png
│
├─js
│      background.min.js
│      content.min.js
│
└─_locales
    ├─en
    │      messages.json
    └─ja
            messages.json

# ソース

- manifest.json 

permissions はトップドメインを*にできないため、https://www.amazon.＊/＊ はエラーとなる。

```manifest.json
{
  "name": "short url for amazon, wiki",
  "short_name": "short url",
  "version": "1.0.0",
  "manifest_version": 2,
  "description": "short url to clipboard",
  "author": "ctrlzr",
  "default_locale": "ja",
  "permissions": [
    "https://www.amazon.co.jp/*",
    "https://www.amazon.com/*",
    "https://www.amazon.it/*",
    "https://www.amazon.fr/*",
    "https://www.amazon.de/*",
    "https://www.amazon.es/*",
    "https://*.wikipedia.org/wiki/*",
    "activeTab",
    "background",
    "contextMenus",
    "clipboardWrite"
  ],
  "content_scripts": [{
    "matches": [
    "https://www.amazon.co.jp/*",
    "https://www.amazon.com/*",
    "https://www.amazon.it/*",
    "https://www.amazon.fr/*",
    "https://www.amazon.de/*",
    "https://www.amazon.es/*",
    "https://*.wikipedia.org/wiki/*"
],
    "js": ["js/content.min.js"]
  }],
  "background": {
    "scripts": ["js/background.min.js"]
  },
  "browser_action": {
    "default_icon": "img/icon-16x16.png",
    "default_title": "short url"
  },
  "options_ui": {
    "page": "html/options.html",
    "chrome_style": true
  },
  "icons": {
    "128": "img/icon-128x128.png",
    "48": "img/icon-48x48.png",
    "16": "img/icon-16x16.png"
  }
}
```

- content.js

chrome.runtime.onMessage.addListener で background のメッセージを受け取り、wikiの記事idをhtml内から取得する。

```content.js
chrome.runtime.onMessage.addListener(function(msg, sender, sendResponse) {
  var res = null;
  
  if (!msg.target) {
    // 
  } else {
    if ('wiki' === msg.target) {
      res = get_wgArticleId();
    } else {
      console.log(msg.target+' does not supprted');
    }
  }

  sendResponse(res);
});

function get_wgArticleId() {

  let scripts = document.querySelectorAll('script');
  for (let i=0; i<scripts.length; i++) {
    let script = scripts[i];
    let result = script.innerHTML.match(/"wgArticleId":(\d+)/);
    if (result) {
      return result[1];
    }
  }

    return null;
}
```

- background.js

cm_click でasyncしてるのは、wikiでhtmlを捜索するのに、contents_script と通信するため。
どのイベントが走るか console.log で確認して必要なイベントを見極めた。
　chrome.tabs.onActivated：タブを切り替えたとき
　chrome.tabs.onUpdated：画面遷移、再読み込みしたとき
　chrome.browserAction.onClicked：アイコンをクリックしたとき

以下は非同期処理
　chrome.tabs.sendMessage
　chrome.tabs.getSelected

```background.js

var cmid = null;

var cm_click = async function(data, tab) {

  var color = [0, 0xaa, 0, 100];

  let txt = await get_short_url[data.menuItemId].call(this, data.pageUrl, tab.id);
  if (!txt) {
    txt = data.pageUrl;
    color = [0xff, 0, 0, 100];
  }
  
  let textArea = document.createElement('textarea');
  textArea.value = txt;
  document.body.appendChild(textArea);
  textArea.select();
  document.execCommand('copy');
  document.body.removeChild(textArea);
  
  chrome.browserAction.setBadgeText({text:"copy", tabId: tab.id});
  chrome.browserAction.setBadgeBackgroundColor({color: color});
};

var icon_click = async function(tab) {
  
  var data = {
    menuItemId: "wiki",
    pageUrl: tab.url
  };
  
  cm_click(data, tab);
};


var get_short_url = {

  amazon: function(url) {
    let pos = url.indexOf('/dp/');
    if (-1 === pos) {
      return null;
    }
    let pos2 = url.indexOf('/', pos+4);
    if (-1 === pos2) {
      pos2 = url.indexOf('?', pos+4);
    }

    let id = null;
    if (-1 < pos2) {
      id = url.substring(pos+4, pos2);
    } else {  
      id = url.substring(pos+4);
    }

    let path = getBaseUrl(url)+'/dp/'+id;
    
    return path; 
  },
  wiki: function(url, tab_id) {
    return new Promise(function(resolve, reject) {
      chrome.tabs.sendMessage(tab_id, {target: "wiki"}, function(id) {
        if (id) {
          let path = getBaseUrl(url)+'/?curid='+id;
          resolve(path);
        } else {
          resolve(null);
        }
      });
    });
  }
};

function getBaseUrl(url) {
  let pos = url.indexOf('/', 8); 
  return url.substring(0,pos);
}

var cm = function() {
  chrome.tabs.getSelected(null,function(tab){
    let target = null;
    if (!tab.url) {
    } else if (tab.url.match(/^https:\/\/www\.amazon\.[^/]+\/([^\/]*\/|)dp\//)) {
      target = 'amazon';
    } else if (tab.url.match(/^https:\/\/.*\.wikipedia.org\//)) {
      target = 'wiki';
    } else {
//
    }
    
    if (cmid) {
      chrome.contextMenus.remove(cmid);
      cmid = null;
    }

    if (target) {
      let options = {
        id: target,
        title: "short url to clipboard",
        contexts: ['page'],
        onclick: cm_click
      };
      cmid = chrome.contextMenus.create(options);
    }
  });
}

chrome.tabs.onActivated.addListener(function (tabId) {
  cm();
});

chrome.tabs.onUpdated.addListener(function (tabId) {
  cm();
});

chrome.browserAction.onClicked.addListener(function(tab){
  if (!cmid) {
    return;
  }
  cm_click({menuItemId: cmid, pageUrl: tab.url}, tab);
});
```

- その他のソースはほぼ使用していないので省略


# 注意点

### 公開は有料（5$）

限定公開は無料という情報もあったが、現在(2019年7月)は、有料。
20本まで5$

### manifest.json の permissions に \<all_urls\> , tab を使用しない

chrome extension の公開には審査があり、\<all_urls\> にすると審査が長くなる。tab は activeTab を推奨される。権限ありありでどんなことができるかは、こちらを参照
[ブラウザ拡張の権限でどこまで（悪いことを）できるのか？とその対策【デモあり】](https://qiita.com/tksugimoto/items/0e9ada7efc3b6570e10c)

### permissions は適切に！

manifst.json内のpermissionsは必要最小限にすること。
紆余曲折は[こちら](https://qiita.com/ctrlzr/items/80bd6e9c4e1e73ca8c41)
拡張外の何かを読み込む処理は、プライバシーポリシーに抵触するおそれあり。
localStorageは問題なし([たぶん](https://ctrlzr.sakura.ne.jp/img/ameblo/tabun2.gif)）

### minifyは不要

PCでは通信量なんて気にするな、ということらしい。紆余曲折は[こちら](https://qiita.com/ctrlzr/items/80bd6e9c4e1e73ca8c41)
手間暇かけて、というほどでもないけど、minifiyで拒否られるくらいなら、何もしないほうがいい。

### document は content_scripts のみ

manifest.json の content_scripts に指定したjsのみ、documentを読める。
backend で document.getElementById('honya') としても想定した値は返らない。

### tabs は background のみ

content_scripts 側で、chrome.tabs がエラーになってハマった。

### 非同期を前提に

普通にjsで書いていたら非同期通信を余儀なくされ、構成が変わってしまい、aync await で回避する羽目に。

### 自動更新は使えない

[2017年12月以降](https://blog.chromium.org/2016/08/from-chrome-apps-to-web.html)、自動更新する機能は制限された。
以前は、manifest.json に update_url を記載すれば自動更新できた。らしい。

# 公開

思ったより審査に手間取ったので別にしました

[chrome拡張の審査がとおらない](https://qiita.com/ctrlzr/items/80bd6e9c4e1e73ca8c41)

ようやく公開されました。まだ検索しても出ないのでurlを載せておきます。

[short url for amazon, wiki](https://chrome.google.com/webstore/detail/short-url-for-amazon-wiki/mioankphbildkdhofdpdgddamfjmmfmj)