TwitterでMarkdownを使えるようにした

## はじめに

[クソアプリ Advent Calendar 2018](https://qiita.com/advent-calendar/2018/kuso-app) の3日目です。

## 概要

Twitterでツイートしようと思った時にふと「Markdownで書きたい」と思ったことはないでしょうか？　ここに訪れている方であれば誰もがあるはずです。僕も時々そう思っていたので今回TwitterでMarkdownを使えるようにしてみました。

## とりあえず完成したもの

例えば下記のようなツイートを行うと…

![Twitterでmarkdownを入力している画面](https://qiita-image-store.s3.amazonaws.com/0/136506/96f3c91f-8397-e630-7b87-4a35b9d7e9c8.png)

こんな感じで表示されます。

![ツイートのmarkdownがきちんと変換されている画像](https://qiita-image-store.s3.amazonaws.com/0/136506/f6a2d653-24f1-2653-72bf-14f0b89b2136.png)


リンクや画像も、コードまでも思いのままです。さあ、皆さん思う存分Twitter上でMarkdownライフをエンジョイしてください！

…と、すみません、技術解説や使い方の説明が抜けていました。

## 技術解説

何はともあれツイート文に書かれているMarkdownを変換する必要がありますので、変換用ライブラリと、コードハイライトのライブラリを導入します。あとCSSも一緒に入れておきます。

```shell
yarn add marked highlight.js github-markdown-css
```

markedを初期化します。コード部分はhighlight.jsを使うように設定します。

```javascript
const marked = require("marked");
import * as hljs from "highlight.js";
require("highlight.js/styles/atom-one-dark.css");
const renderer = new marked.Renderer();
renderer.code = (code, language) => {
  return (
    "<pre" +
    '><code class="hljs">' +
    hljs.highlightAuto(code).value +
    "</code></pre>"
  );
};
marked.setOptions({
  renderer: renderer
});
```

各ツイートはjs-tweet-text-containerというクラスの要素に含まれているため、これでループしていきます。

```javascript
  document.querySelectorAll(".js-tweet-text-container").forEach(element => {
    let body = element.innerHTML;
    if (body.match(/<!--marked-->/)) {
      return;
    }
    const pStartRegex = /<p[^>]+>/;
    const pStart = body.match(pStartRegex);
    body = body.replace(pStartRegex, "");
    body = body.replace(/<\/p>/, "");

    body = stripUrlTags(body);
    body = stripDollarHashLinks(body);

    body = body.trim();
    const markdown = marked(body).replace(/<p> +/, pStart[0]);
    const divStart = pStart[0]
      .replace(/<p/, "<div")
      .replace(/class="/, 'class="markdown-body ');
    element.innerHTML = `<!--marked-->${divStart}${markdown}</div>`;
  }
```

各要素を監視して実行してるため、同じ要素で何度も実行してしまわないように`<!--marked-->`というコメントを入れてそれがあれば無視するようにしています。

あと`p`とか`div`とかをあれこれしていますが、ツイート本文は`p`タグで囲まれているため、Markdownを変換後は色々なタグがその`p`タグの子要素になってしまい正常ではなくなるため、`div`に変更してあります。ついでにCSSが反映されるように`markdown-body`クラスを追加しています。

### URLの自動リンクを削除

ツイートにURLを書くと自動的にリンクされてしまいます。そのためMarkdownのリンク最初からリンクされている状態になってしまうため正常に変換できません。そのため予め自動的に付与されたリンクを削除しておきます。

```javascript
function stripUrlTags(body) {
  const regex = /\[([^\]]*)\]\((<a.+?twitter-timeline-link.+?>.+?<\/a>)[ …]*\)/;
  let match = body.match(regex);
  while (match) {
    const url = match[2]
      .replace(/<[^>]+>/g, "")
      .replace(/[ …]/g, "")
      .replace(/&nbsp;/g, "")
      .trim();
    console.log(url);
    const replaced = `[${match[1]}](${url})`;
    body = body.replace(match[0], replaced);
    match = body.match(regex);
  }
  return body;
}
```

### ハッシュタグの自動リンクを削除

ハッシュタグと、あと`$`で始まるキャッシュタグも自動リンクされてしまいます。PHPの変数を含めたコードを書きたい場合などに支障が出るためこちらも予め削除しておきます。

```javascript
function stripDollarHashLinks(body) {
  const lines = body.split(/\n/);
  const regex = /<a.+?twitter-(c|h)ashtag.+?>.+?<\/a>/g;
  let isCode = false;
  lines.forEach((line, i) => {
    if (line.substr(0, 3) == "```") {
      isCode = !isCode;
      return;
    }
    if (!isCode) {
      return;
    }

    const matches = line.match(regex);
    if (!matches) {
      return;
    }
    matches.forEach(match => {
      const replaced = match.replace(/<[^>]+>/g, "");
      body = body.replace(match, replaced);
    });
    lines[i] = line;
  });
  return body;
}
```

## 使い方

さて、次に使い方ですが、下記のChrome拡張をインストールするだけです。これでMarkdownライフがエンジョイできます。

[Twitter Markdown](https://chrome.google.com/webstore/detail/twitter-markdown/cnffgdmmphddooilldbedaejhdndnihp)

## 注意点

Chrome拡張なので当然インストールしているブラウザでしか変換されません。他の人が見るとこんな感じになっています…。

![ツイートのmarkdownが変換されずそのまま表示されている様子](https://qiita-image-store.s3.amazonaws.com/0/136506/5f4e5f6c-9e99-7862-83d7-bf7e830a98d5.png)

ということで全員がこのChrome拡張のインストールをお願いします！！！　（あとMarkdown変換に対応したスマホ用のTwitterクライアントの開発もどなたかお願いします）


## 関連リンク

* [Twitterから「いいね」を消し去るChrome拡張を作る](https://qiita.com/hal1437/items/59d7a55124027d2ff492)
* [samuelsimoes/chrome-extension-webpack-boilerplate](https://github.com/samuelsimoes/chrome-extension-webpack-boilerplate)