6
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ZOZOAdvent Calendar 2022

Day 19

Twitterの文字数をカウントする拡張機能

Last updated at Posted at 2022-12-19

TL;DR

Tweetの文字数をカウントできる拡張機能を開発しました。

背景

弊社で運営しているFashion Tech Newsでは、記事公開のタイミングに合わせてZapierからTweetを実行しています。Tweetの一部の文章は、記事の管理も行っているmicroCMSに登録し、RSSを利用してZapierに渡しています。Tweetの文字数がオーバーした場合、Zapierでエラーが出て中断されます。そのためmicroCMSに文章を登録する際に、文字数がオーバーしないように気を付ける必要があり、従来は手動で様々な項目をTwitterのウェブアプリにコピペして文字数の確認を行っていました。

そこで、記事を管理しているページを開くと自動的にTweetの文字数を表示する拡張機能を作成しました。

Tweetの例:

ポイント

  1. SPAの画面遷移に対応する
  2. 文字数のカウント方法はTwitterのルールをそのまま活用する(全角の換算方法URLなどの換算方法など
  3. CMS内の複数項目をTweetに含めて、変更ごとにカウントをし直して表示する

手順・実装

スタートガイドなどで紹介される、基本的なcontent scriptを利用すると、SPAの画面遷移を検知できません。そのため今回はbackground scriptを利用して画面遷移を検知し、content scriptで画面への描画を行うという方法を採用しました。

フォルダ構造は以下です。
今回Twitterの文字数カウントをそのまま活用するために、twitter-textというNPMパッケージを利用しました。そのためBrowserifyを利用し、src/内のファイルをBuildしてscripts/に生成するようにしました。

├── scripts/
├── images/
├── node_modules/
├── scripts/
├── src
│   ├── background.js
│   └── content.js
├── README.md
├── manifest.json
├── package-lock.json
└── package.json

manifest.jsonは以下で、background scriptの読み込みを行っています。その後許可するホストと、許可する行為を指定しています。

manifest.json
{
  "manifest_version": 3,
  "name": "Tweet Counter Extension",
  "description": "Displays the number of tweet characters",
  "version": "1.0",
  "icons": {
    "16": "images/icon-16.png",
    "32": "images/icon-32.png",
    "48": "images/icon-48.png",
    "128": "images/icon-128.png"
  },
  "background": {
    "service_worker": "scripts/background.js"
  },
  "host_permissions": ["https://CMSのURL/*"],
  "permissions": ["tabs", "scripting"]
}
参考までにpackage.jsonも載せておきます。
package.json
{
  "name": "tweet-counter-extension",
  "version": "1.0.0",
  "description": "Displays the number of tweet characters",
  "scripts": {
    "build": "browserify src/content.js -o scripts/content.js && browserify src/background.js -o scripts/background.js"
  },
  "license": "ISC",
  "devDependencies": {
    "browserify": "^17.0.0",
    "twitter-text": "^3.1.0"
  }
}

background scriptはbackground.jsとしました。chrome.tabs.onUpdatedで画面遷移を検知し、Tweetの文字数を表示させたい、記事を管理するページの場合のみ、content.jsを実行するというものです。こうすることでSPAの画面遷移でも、ページごとにcontent.jsを実行することができます。

background.js
chrome.tabs.onUpdated.addListener(function (tabId, info, tab) {
  const urlConditions = tab.url.indexOf("https://記事を管理するページ/") !== -1
  if (info.status === 'complete' && urlConditions) {
    chrome.scripting.executeScript({
      target: { tabId: tabId },
      files: ["scripts/content.js"]
    },);
  }
});

実行されるcontent scriptのcontent.jsは以下です。
twitter-textのパッケージを読み込みます。
②Zapierで毎回追加される、【Fashion Tech News】の見出しやURLの一例を設定します。
③Tweetに追加される、タイトル・本文をクラス名の前方一致で取得します。(前方一致で取得する理由は、ツールなどによりBuildごとに異なる文字列などが付加されるためです。)
④Tweetの文字数を表示する場所に隣接する要素を取得すると共に、その新たに追加する要素に設定するidを決めます。
updateBadgeの関数では、tweetの変数に実際のTweetの内容を定義し文字数のカウントを行います。文字数がオーバーしている場合には警告の意味を込めて赤背景・白抜き文字で表示します。
⑥SPAの場合読み込みに少し時間がかかることがあるため、上記のupdateBadgeなどの処理は目的の要素が読み込まれてから実行します。
⑦読み込まれるまでsetIntevalを活用して1秒ごとに実行を試みて、読み込まれた後にclearIntevalで繰り返し実行をキャンセルするようにします。
⑧初期実行が終わった後は、フォームなどの変更に応じて描画し直せるように、Event Listner追加します。

content.js
const main = () => {
  const jsLoaded = () => {
    // ①パッケージの読み込み
    const twitter = require("twitter-text")
    // ②Tweetに毎回記載されている部分を定義
    const tweetHeading = "【Fashion Tech News】"
    const exampleUrl = "https://fashiontechnews.zozo.com/" // URLs are shortened and converted to 12 characters in full-width

    // ③Tweetに追加される要素を取得
    const articleTitle = document.querySelector("[class^='タイトルのフォームのクラス名一部']")
    const tweetForm = document.querySelector("[class^='Tweet本文のフォームのクラス名の一部']")

    // ④文字数を表示する要素に隣接する要素の取得と、idの設定
    const insertingArea = document.querySelector("[class^='contentActions_buttons']")
    const badgeId = "tweet-count"

    // ⑤文字数のカウントと描画を担当する関数
    const updateBadge = () => {
      const tweet = `${tweetHeading}\n${articleTitle.value}\n\n${tweetForm.value}\n${exampleUrl}`
      const parseData = twitter.parseTweet(tweet)
      const characterCountInFullWidth = Math.ceil(parseData.weightedLength / 2)
      const isOvered = characterCountInFullWidth > 140

      const updatingBadge = document.querySelector(`#${badgeId}`)
      updatingBadge.textContent = `Tweet: ${characterCountInFullWidth}文字`
      updatingBadge.style.color = isOvered ? "#fff" : "#21213b"
      updatingBadge.style.backgroundColor = isOvered ? "#c00" : "#fff"
    }

    // ⑥目的の要素が読み込まれてから実行
    if (insertingArea != null && articleTitle != null && tweetForm != null) {
      // ⑦目的の要素が読み込まれた後は繰り返し実行をキャンセル
      clearInterval(jsInitCheckTimer)

      const badge = document.createElement("p")
      badge.id = badgeId
      badge.style.padding = "8px"
      badge.style.marginRight = "16px"
      badge.style.textAlign = "right"
      badge.style.fontWeight = "700"
      insertingArea.insertAdjacentElement("afterbegin", badge)
      updateBadge()

      // ⑧フォームにEvent Listnerを追加し変更に応じて文字数の描画し直す
      articleTitle.addEventListener("input", updateBadge)
      tweetForm.addEventListener("input", updateBadge)
    }
  }

  // Iterate until the target DOM is ready
  const jsInitCheckTimer = setInterval(jsLoaded, 1000)
}

main()

文字数を表示させた様子(赤背景の部分)
スクリーンショット 2022-12-19 15.41.46.png

注意点

画面遷移を検知するのがbackground.js、実際に描画するのがcontent.jsという実装を行いました。その結果、上記⑦のclearIntervalが実行される前に画面遷移が行われると、content.jsではそれを検知できず、updateBadgeが複数実行され、文字数の表示が複数になってしまう問題があります。今回は社内ツールということもあり、問題が発生するページを除外するという対応策にとどめました。

参考

6
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?