diff --git a/README.md b/README.md index 537a345..4731e74 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,9 @@ -# NCOverlay +# NCOverlay + + + +## 概要 + 動画配信サービスの再生画面にニコニコのコメントを表示する拡張機能です。
今現在はアニメのみ対応。 @@ -11,7 +16,12 @@ コメントは自動で取得・表示されるので何もしなくてOK。
取得したコメント数は拡張機能のアイコンに表示されます。
-ニコニコにログインしているとdアニメストア ニコニコ支店のコメントも取得できます。 +ニコニコにログインしていると、dアニメストア ニコニコ支店のコメントも取得・表示されます。 + +#### ポップアップ +- コメントの表示/非表示 +- 不透明度の設定 +- 表示中のコメントの確認 ## インストール @@ -20,4 +30,10 @@ ### 手動 1. [Releases](https://github.com/Midra429/NCOverlay/releases) から最新バージョンの `extension.zip` をダウンロード -2. ダウンロードしたファイルを `chrome://extensions` (デベロッパー モードON) にドラッグ&ドロップ +2. ダウンロードしたファイルを `chrome://extensions` (デベロッパー モード: ON) にドラッグ&ドロップ + +## 不具合報告・機能提案など + +- [GitHub](https://github.com/Midra429/NCOverlay/issues) + +- [X (@Midra429)](https://x.com/Midra429) に [メンション](https://x.com/intent/tweet?screen_name=Midra429) や [DM](https://x.com/messages/compose?recipient_id=1052566817279864837) diff --git a/assets/icon.png b/assets/icon.png new file mode 100644 index 0000000..8859b26 Binary files /dev/null and b/assets/icon.png differ diff --git a/assets/promo_440x280.png b/assets/promo_440x280.png new file mode 100644 index 0000000..bf78ffc Binary files /dev/null and b/assets/promo_440x280.png differ diff --git a/build.js b/build.js index 32b6631..b182e92 100644 --- a/build.js +++ b/build.js @@ -29,6 +29,7 @@ const build = async () => { fs.mkdirSync(outputDir, { recursive: true }) fs.copySync(`${inputDir}/manifest.json`, `${outputDir}/manifest.json`) + fs.copySync(`${inputDir}/assets`, `${outputDir}/assets`) fs.copySync(`${inputDir}/styles`, `${outputDir}/styles`) fs.copySync(`${inputDir}/popup/index.html`, `${outputDir}/popup/index.html`) diff --git a/src/assets/github-mark.svg b/src/assets/github-mark.svg new file mode 100644 index 0000000..37fa923 --- /dev/null +++ b/src/assets/github-mark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/icon_128.png b/src/assets/images/icon_128.png new file mode 100644 index 0000000..3fe1eb7 Binary files /dev/null and b/src/assets/images/icon_128.png differ diff --git a/src/assets/images/icon_16.png b/src/assets/images/icon_16.png new file mode 100644 index 0000000..dd77665 Binary files /dev/null and b/src/assets/images/icon_16.png differ diff --git a/src/assets/images/icon_32.png b/src/assets/images/icon_32.png new file mode 100644 index 0000000..9585448 Binary files /dev/null and b/src/assets/images/icon_32.png differ diff --git a/src/assets/images/icon_48.png b/src/assets/images/icon_48.png new file mode 100644 index 0000000..8f4d873 Binary files /dev/null and b/src/assets/images/icon_48.png differ diff --git a/src/assets/images/icon_gray_128.png b/src/assets/images/icon_gray_128.png new file mode 100644 index 0000000..e0f4104 Binary files /dev/null and b/src/assets/images/icon_gray_128.png differ diff --git a/src/assets/images/icon_gray_16.png b/src/assets/images/icon_gray_16.png new file mode 100644 index 0000000..9aca54c Binary files /dev/null and b/src/assets/images/icon_gray_16.png differ diff --git a/src/assets/images/icon_gray_32.png b/src/assets/images/icon_gray_32.png new file mode 100644 index 0000000..799d451 Binary files /dev/null and b/src/assets/images/icon_gray_32.png differ diff --git a/src/assets/images/icon_gray_48.png b/src/assets/images/icon_gray_48.png new file mode 100644 index 0000000..07feaa9 Binary files /dev/null and b/src/assets/images/icon_gray_48.png differ diff --git a/src/background/index.ts b/src/background/index.ts index 19dda16..887f6d1 100644 --- a/src/background/index.ts +++ b/src/background/index.ts @@ -3,35 +3,34 @@ import { isChromeMessageSearch, isChromeMessageVideo, isChromeMessageThreads, - isChromeMessageBadge, + isChromeMessageAction, + isChromeMessageActionBadge, + isChromeMessageActionTitle, } from '@/types/chrome/message' +import { + ACTION_ICONS_ENABLE, + ACTION_ICONS_DISABLE, + GITHUB_URL, +} from '@/constants' import { NiconicoApi } from './api/niconico' console.log('[NCOverlay] background.js') -chrome.action.setBadgeBackgroundColor({ - color: '#2389FF', -}) +chrome.action.disable() +chrome.action.setIcon({ path: ACTION_ICONS_DISABLE }) +chrome.action.setBadgeBackgroundColor({ color: '#2389FF' }) +chrome.action.setBadgeTextColor({ color: '#FFF' }) -chrome.action.setBadgeTextColor({ - color: '#ffffff', -}) +chrome.runtime.onInstalled.addListener((details) => { + const { version } = chrome.runtime.getManifest() + + if (details.reason === chrome.runtime.OnInstalledReason.INSTALL) { + chrome.tabs.create({ url: `${GITHUB_URL}/blob/v${version}/README.md` }) + } -chrome.runtime.onInstalled.addListener(() => { - chrome.action.disable() - - chrome.declarativeContent.onPageChanged.removeRules(() => { - chrome.declarativeContent.onPageChanged.addRules([ - { - conditions: [ - new chrome.declarativeContent.PageStateMatcher({ - css: ['html.NCOverlay'], - }), - ], - actions: [new chrome.declarativeContent.ShowAction()], - }, - ]) - }) + if (details.reason === chrome.runtime.OnInstalledReason.UPDATE) { + chrome.tabs.create({ url: `${GITHUB_URL}/releases/tag/v${version}` }) + } }) chrome.runtime.onMessage.addListener( @@ -42,6 +41,7 @@ chrome.runtime.onMessage.addListener( ) => { let promise: Promise | null = null + // ニコニコ 検索 if (isChromeMessageSearch(message)) { const minLength = message.body.duration ? message.body.duration - 30 : -1 const maxLength = message.body.duration ? message.body.duration + 30 : -1 @@ -66,18 +66,43 @@ chrome.runtime.onMessage.addListener( }) } + // ニコニコ 動画情報 if (isChromeMessageVideo(message)) { promise = NiconicoApi.video(message.body.videoId, message.body.guest) } + // ニコニコ コメント if (isChromeMessageThreads(message)) { promise = NiconicoApi.threads(message.body.nvComment) } - if (isChromeMessageBadge(message)) { - promise = chrome.action.setBadgeText({ + // 拡張機能 アクション 有効/無効 + if (isChromeMessageAction(message)) { + if (message.body) { + chrome.action.enable(sender.tab?.id) + } else { + chrome.action.disable(sender.tab?.id) + } + + chrome.action.setIcon({ + tabId: sender.tab?.id, + path: message.body ? ACTION_ICONS_ENABLE : ACTION_ICONS_DISABLE, + }) + } + + // 拡張機能 アクション バッジ + if (isChromeMessageActionBadge(message)) { + chrome.action.setBadgeText({ + tabId: sender.tab?.id, text: message.body.toString(), + }) + } + + // 拡張機能 アクション タイトル (ツールチップ) + if (isChromeMessageActionTitle(message)) { + chrome.action.setTitle({ tabId: sender.tab?.id, + title: message.body ? `${message.body} | NCOverlay` : '', }) } diff --git a/src/constants.ts b/src/constants.ts index 23f93e0..0e96336 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,2 +1,21 @@ +/** アイコン (有効) */ +export const ACTION_ICONS_ENABLE = { + '16': 'assets/images/icon_16.png', + '32': 'assets/images/icon_32.png', + '48': 'assets/images/icon_48.png', + '128': 'assets/images/icon_128.png', +} + +/** アイコン (無効) */ +export const ACTION_ICONS_DISABLE = { + '16': 'assets/images/icon_gray_16.png', + '32': 'assets/images/icon_gray_32.png', + '48': 'assets/images/icon_gray_48.png', + '128': 'assets/images/icon_gray_128.png', +} + +/** GitHub */ +export const GITHUB_URL = 'https://github.com/Midra429/NCOverlay' + /** dアニメストア ニコニコ支店のチャンネルID */ export const DANIME_CHANNEL_ID = 2632720 diff --git a/src/content_script/NCOverlay.ts b/src/content_script/NCOverlay.ts index 388c2e5..a04efc2 100644 --- a/src/content_script/NCOverlay.ts +++ b/src/content_script/NCOverlay.ts @@ -9,7 +9,8 @@ import type { VideoData } from '@/types/niconico/video' import { isChromeMessageGetFromPage } from '@/types/chrome/message' import NiconiComments from '@xpadev-net/niconicomments' import { ChromeStorageApi } from '@/utils/storage' -import { setBadgeText } from '@/content_script/utils/setBadgeText' +import { setActionBadge } from '@/content_script/utils/setActionBadge' +import { setActionTitle } from '@/content_script/utils/setActionTitle' import { sendToPopup } from '@/content_script/utils/sendToPopup' export class NCOverlay { @@ -21,6 +22,7 @@ export class NCOverlay { #commentsData?: InputFormat #commentsFormat?: InputFormatType + #commentsCount: number = 0 #isEmpty: boolean = true #isPlaying: boolean = false @@ -147,14 +149,13 @@ export class NCOverlay { } ) - let commentsCount = 0 if (NiconiComments.typeGuard.v1.threads(this.#commentsData)) { for (const data of this.#commentsData) { - commentsCount += data.comments.length + this.#commentsCount += data.comments.length } } - console.log('[NCOverlay] commentsCount', commentsCount) + console.log('[NCOverlay] commentsCount', this.#commentsCount) this.#update() @@ -163,17 +164,21 @@ export class NCOverlay { } let badgeText = '' - if (0 < commentsCount) { - if (1000 <= commentsCount) { - badgeText = `${Math.round((commentsCount / 1000) * 10) / 10}k` + if (0 < this.#commentsCount) { + if (1000 <= this.#commentsCount) { + badgeText = `${Math.round((this.#commentsCount / 1000) * 10) / 10}k` } else { - badgeText = commentsCount.toString() + badgeText = this.#commentsCount.toString() } } - setBadgeText(badgeText) + setActionBadge(badgeText) + setActionTitle( + `${this.#commentsCount.toLocaleString()}件のコメントを表示中` + ) sendToPopup({ + commentsCount: this.#commentsCount, videoData: this.#videoData, }) } @@ -202,7 +207,10 @@ export class NCOverlay { this.#canvas.remove() - setBadgeText('') + setActionBadge('') + setActionTitle('') + + sendToPopup({}) } clear() { @@ -284,6 +292,7 @@ export class NCOverlay { sendResponse({ type: message.type, result: { + commentsCount: this.#commentsCount, videoData: this.#videoData, }, }) diff --git a/src/content_script/api/niconico/search.ts b/src/content_script/api/niconico/search.ts index a12a12b..d2e84c2 100644 --- a/src/content_script/api/niconico/search.ts +++ b/src/content_script/api/niconico/search.ts @@ -5,9 +5,13 @@ import { optimizeTitleForSearch } from '@/utils/optimizeTitleForSearch' import { isEqualTitle } from '@/utils/isEqualTitle' export const search = async (info: { + /** 検索タイトル */ title: string + /** 検索対象の動画の長さ用 */ duration: number + /** 作品のタイトル (あいまい検索用) */ workTitle?: string + /** エピソードのサブタイトル (あいまい検索用) */ subtitle?: string }): Promise => { const optimizedTitle = optimizeTitleForSearch(info.title) diff --git a/src/content_script/index.ts b/src/content_script/index.ts index 004ceab..bf450b1 100644 --- a/src/content_script/index.ts +++ b/src/content_script/index.ts @@ -1,3 +1,4 @@ +import { setAction } from './utils/setAction' import vodPrimeVideo from './vod/primeVideo' import vodDAnime from './vod/dAnime' import vodAbema from './vod/abema' @@ -8,6 +9,8 @@ chrome.runtime.onMessage.addListener(() => false) const init = () => { document.documentElement.classList.add('NCOverlay') + + setAction(true) } const main = () => { diff --git a/src/content_script/utils/getThreads.ts b/src/content_script/utils/getThreads.ts index 7640544..e3430bb 100644 --- a/src/content_script/utils/getThreads.ts +++ b/src/content_script/utils/getThreads.ts @@ -10,14 +10,14 @@ export const getThreads = async ( const threadsData: ThreadsData[] = [] for (const data of videoData) { - const result = await NiconicoApi.threads({ + const res = await NiconicoApi.threads({ additionals: {}, params: data.comment.nvComment.params, threadKey: data.comment.nvComment.threadKey, }) - if (result) { - threadsData.push(result) + if (res) { + threadsData.push(res) } } diff --git a/src/content_script/utils/getVideoData.ts b/src/content_script/utils/getVideoData.ts index 5afa30d..ee867c2 100644 --- a/src/content_script/utils/getVideoData.ts +++ b/src/content_script/utils/getVideoData.ts @@ -10,10 +10,10 @@ export const getVideoData = async ( const videoData: VideoData[] = [] for (const id of contentIds) { - const result = await NiconicoApi.video(id) + const res = await NiconicoApi.video(id) - if (result) { - videoData.push(result) + if (res) { + videoData.push(res) } } @@ -21,10 +21,10 @@ export const getVideoData = async ( if (videoData.length === 0) { for (const id of contentIds) { - const result = await NiconicoApi.video(id, true) + const res = await NiconicoApi.video(id, true) - if (result) { - videoData.push(result) + if (res) { + videoData.push(res) } } diff --git a/src/content_script/utils/sendToPopup.ts b/src/content_script/utils/sendToPopup.ts index b45eac5..f152240 100644 --- a/src/content_script/utils/sendToPopup.ts +++ b/src/content_script/utils/sendToPopup.ts @@ -1,31 +1,8 @@ -import type { - ChromeMessage, - ChromeMessageBody, - ChromeResponse, -} from '@/types/chrome/message' - -const queue: ChromeMessage<'chrome:sendToPopup'>[] = [] -let running: Promise | null = null - -const run = (message?: ChromeMessage<'chrome:sendToPopup'>) => { - if (message) { - running = chrome.runtime - .sendMessage(message) - .finally(() => run(queue.shift())) - } else { - running = null - } -} +import type { ChromeMessage, ChromeMessageBody } from '@/types/chrome/message' export const sendToPopup = (body: ChromeMessageBody['chrome:sendToPopup']) => { - const message: ChromeMessage<'chrome:sendToPopup'> = { + chrome.runtime.sendMessage>({ type: 'chrome:sendToPopup', body: body, - } - - if (running) { - queue.push(message) - } else { - run(message) - } + }) } diff --git a/src/content_script/utils/setAction.ts b/src/content_script/utils/setAction.ts new file mode 100644 index 0000000..7469e45 --- /dev/null +++ b/src/content_script/utils/setAction.ts @@ -0,0 +1,8 @@ +import type { ChromeMessage, ChromeMessageBody } from '@/types/chrome/message' + +export const setAction = (body: ChromeMessageBody['chrome:action']) => { + chrome.runtime.sendMessage>({ + type: 'chrome:action', + body: body, + }) +} diff --git a/src/content_script/utils/setActionBadge.ts b/src/content_script/utils/setActionBadge.ts new file mode 100644 index 0000000..5753652 --- /dev/null +++ b/src/content_script/utils/setActionBadge.ts @@ -0,0 +1,10 @@ +import type { ChromeMessage, ChromeMessageBody } from '@/types/chrome/message' + +export const setActionBadge = ( + body: ChromeMessageBody['chrome:action:badge'] +) => { + chrome.runtime.sendMessage>({ + type: 'chrome:action:badge', + body: body, + }) +} diff --git a/src/content_script/utils/setActionTitle.ts b/src/content_script/utils/setActionTitle.ts new file mode 100644 index 0000000..a35635e --- /dev/null +++ b/src/content_script/utils/setActionTitle.ts @@ -0,0 +1,10 @@ +import type { ChromeMessage, ChromeMessageBody } from '@/types/chrome/message' + +export const setActionTitle = ( + body: ChromeMessageBody['chrome:action:title'] +) => { + chrome.runtime.sendMessage>({ + type: 'chrome:action:title', + body: body, + }) +} diff --git a/src/content_script/utils/setBadgeText.ts b/src/content_script/utils/setBadgeText.ts deleted file mode 100644 index 5bc5565..0000000 --- a/src/content_script/utils/setBadgeText.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { - ChromeMessage, - ChromeMessageBody, - ChromeResponse, -} from '@/types/chrome/message' - -const queue: ChromeMessage<'chrome:badge'>[] = [] -let running: Promise | null = null - -const run = (message?: ChromeMessage<'chrome:badge'>) => { - if (message) { - running = chrome.runtime - .sendMessage(message) - .finally(() => run(queue.shift())) - } else { - running = null - } -} - -export const setBadgeText = (body: ChromeMessageBody['chrome:badge']) => { - const message: ChromeMessage<'chrome:badge'> = { - type: 'chrome:badge', - body: body, - } - - if (running) { - queue.push(message) - } else { - run(message) - } -} diff --git a/src/content_script/vod/abema.ts b/src/content_script/vod/abema.ts index c557d2e..a9b7948 100644 --- a/src/content_script/vod/abema.ts +++ b/src/content_script/vod/abema.ts @@ -9,16 +9,30 @@ export default async () => { let nco: NCOverlay | null = null const getInfo = () => { - const title = document.querySelector( + const titleElem = document.querySelector( '.com-video-EpisodeTitle__series-info' ) - const episode = document.querySelector( + const episodeElem = document.querySelector( '.com-video-EpisodeTitle__episode-title' ) + let [title, season] = + titleElem?.textContent?.split('|').map((v) => v.trim()) ?? [] + + let fullTitle = title + if (title && season) { + if (season.includes(title)) { + fullTitle = season + } else { + fullTitle = `${title} ${season}` + } + } + return { - title: title?.textContent?.trim(), - episode: episode?.textContent?.trim(), + title: fullTitle, + episode: episodeElem?.textContent?.trim(), + workTitle: title, + season: season, } } @@ -45,6 +59,8 @@ export default async () => { const searchResults = await NiconicoApi.search({ title: title, duration: nco?.video.duration ?? 0, + workTitle: info.workTitle, + subtitle: info.episode, }) if (searchResults) { diff --git a/src/content_script/vod/primeVideo.ts b/src/content_script/vod/primeVideo.ts index 8b6cc12..52e46db 100644 --- a/src/content_script/vod/primeVideo.ts +++ b/src/content_script/vod/primeVideo.ts @@ -25,18 +25,18 @@ export default async () => { let nco: NCOverlay | null = null const getInfo = () => { - const title = document.querySelector( + const titleElem = document.querySelector( '.atvwebplayersdk-title-text' ) - const subtitle = document.querySelector( + const subtitleElem = document.querySelector( '.atvwebplayersdk-subtitle-text' ) const se_raw = - subtitle?.firstChild?.textContent?.trim().replace(/\s+/g, '') ?? '' + subtitleElem?.firstChild?.textContent?.trim().replace(/\s+/g, '') ?? '' return { - title: title?.textContent?.trim(), - subtitle: subtitle?.lastChild?.textContent?.trim(), + title: titleElem?.textContent?.trim(), + subtitle: subtitleElem?.lastChild?.textContent?.trim(), season: Number(se_raw.match(/(?<=(シーズン|season))\d+/i)?.at(0)), episode: Number(se_raw.match(/(?<=(エピソード|ep\.))\d+/i)?.at(0)), } diff --git a/src/manifest.json b/src/manifest.json index bed4af2..ea87569 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -2,7 +2,14 @@ "manifest_version": 3, "name": "NCOverlay", "description": "動画配信サービスの再生画面にニコニコのコメントを表示する拡張機能", - "version": "0.0.5", + "version": "1.0.0", + + "icons": { + "16": "assets/images/icon_16.png", + "32": "assets/images/icon_32.png", + "48": "assets/images/icon_48.png", + "128": "assets/images/icon_128.png" + }, "action": { "default_title": "NCOverlay", @@ -35,7 +42,7 @@ "host_permissions": ["https://*/*"], - "permissions": ["declarativeContent", "storage", "scripting"], + "permissions": ["storage"], "minimum_chrome_version": "93" } diff --git a/src/popup/index.html b/src/popup/index.html index 7040ae5..ff71caa 100644 --- a/src/popup/index.html +++ b/src/popup/index.html @@ -16,7 +16,18 @@