peertube-plugin-admin-message
Version:
動画・配信ページの説明欄下にインスタンス管理者からのメッセージを表示するプラグインです。
326 lines • 15.3 kB
JavaScript
function register(options) {
const { registerHook, peertubeHelpers } = options;
console.log('Admin Message Plugin: Registering hooks for PeerTube 7.2.1');
// 動画視聴ページが読み込まれた時のフック
registerHook({
target: 'action:video-watch.init',
handler: () => {
console.log('Admin Message Plugin: Video watch page initialized');
// DOM の準備を待つためにより長い遅延
setTimeout(() => displayAdminMessage(), 1500);
}
});
// 動画データが読み込まれた時のフック
registerHook({
target: 'action:video-watch.video.loaded',
handler: () => {
console.log('Admin Message Plugin: Video loaded');
setTimeout(() => displayAdminMessage(), 2000);
}
});
// ページナビゲーション後のフック
registerHook({
target: 'action:router.navigation-end',
handler: () => {
if (window.location.pathname.includes('/watch/') || window.location.pathname.includes('/w/')) {
console.log('Admin Message Plugin: Navigation ended on video page');
setTimeout(() => displayAdminMessage(), 2500);
}
}
});
async function displayAdminMessage() {
try {
console.log('Admin Message Plugin: Starting displayAdminMessage()');
// 既存のメッセージを削除
const existingMessage = document.getElementById('admin-message-container');
if (existingMessage) {
existingMessage.remove();
console.log('Admin Message Plugin: Removed existing message');
}
// プラグイン設定を取得
let settings = {};
try {
// PeerTube 7.2.1 では getSettings() の挙動が安定している
settings = await peertubeHelpers.getSettings();
console.log('Admin Message Plugin: Raw settings object:', settings);
console.log('Admin Message Plugin: Settings keys:', Object.keys(settings));
}
catch (error) {
console.error('Admin Message Plugin: Error getting settings', error);
return;
}
// PeerTube 7.2.1 では設定キーにプラグイン名のプレフィックスが付く場合がある
const getSettingValue = (key) => {
return settings[key] ??
settings[`admin-message-${key}`] ??
settings[`peertube-plugin-admin-message-${key}`] ??
null;
};
const isEnabled = getSettingValue('enable-admin-message');
console.log('Admin Message Plugin: Enable setting value:', isEnabled, typeof isEnabled);
// boolean チェックを厳密に
if (isEnabled === false || isEnabled === 'false') {
console.log('Admin Message Plugin: Disabled in settings');
return;
}
const messageContent = getSettingValue('admin-message-content');
const messageStyle = getSettingValue('message-style') || 'info';
const fontSize = getSettingValue('font-size') || 'normal';
const showOnVideo = getSettingValue('show-on-video-pages') ?? true;
const showOnLive = getSettingValue('show-on-live-pages') ?? true;
const insertPosition = getSettingValue('insert-position') || 'after-description';
console.log('Admin Message Plugin: Processed settings:', {
isEnabled,
messageContent: messageContent?.substring(0, 50) + '...',
messageStyle,
fontSize,
showOnVideo,
showOnLive,
insertPosition
});
if (!messageContent || messageContent.trim() === '') {
console.log('Admin Message Plugin: No message content found');
return;
}
// 現在のページタイプを判定
const isLivePage = window.location.pathname.includes('/live/');
const isVideoPage = window.location.pathname.includes('/watch/') || window.location.pathname.includes('/w/');
console.log('Admin Message Plugin: Page type check', {
isLivePage,
isVideoPage,
pathname: window.location.pathname,
showOnVideo,
showOnLive
});
if (isLivePage && showOnLive === false) {
console.log('Admin Message Plugin: Live page but live display disabled');
return;
}
if (isVideoPage && showOnVideo === false) {
console.log('Admin Message Plugin: Video page but video display disabled');
return;
}
// PeerTube 7.2.1 の DOM 構造を確認
await waitForDOMReady();
// メッセージコンテナを作成
const messageContainer = document.createElement('div');
messageContainer.id = 'admin-message-container';
// スタイルクラスを設定
let className = `admin-message admin-message-${messageStyle}`;
// 文字サイズクラスを追加
if (fontSize === 'large') {
className += ' admin-message-large';
}
else if (fontSize === 'extra-large') {
className += ' admin-message-extra-large';
}
messageContainer.className = className;
messageContainer.innerHTML = sanitizeBasicHtml(messageContent);
console.log('Admin Message Plugin: Created message container');
// メッセージを挿入
const inserted = await insertMessageAtPosition(messageContainer, insertPosition);
if (inserted) {
console.log('Admin Message Plugin: Message successfully inserted');
}
else {
console.error('Admin Message Plugin: Failed to insert message');
}
}
catch (error) {
console.error('Admin Message Plugin: Error in displayAdminMessage', error);
}
}
// DOM の準備を待つ関数
async function waitForDOMReady() {
const maxAttempts = 10;
let attempts = 0;
return new Promise((resolve) => {
const checkDOM = () => {
attempts++;
// PeerTube 7.2.1 の主要な要素をチェック
const videoInfo = document.querySelector('my-video-watch-video-info');
const videoDetails = document.querySelector('.video-info');
const description = document.querySelector('.video-info-description');
console.log(`Admin Message Plugin: DOM check attempt ${attempts}:`, {
videoInfo: !!videoInfo,
videoDetails: !!videoDetails,
description: !!description
});
if (videoInfo || videoDetails || description || attempts >= maxAttempts) {
resolve();
}
else {
setTimeout(checkDOM, 500);
}
};
checkDOM();
});
}
// 基本的なHTMLサニタイゼーション関数
function sanitizeBasicHtml(html) {
let sanitized = html
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>') // **bold**
.replace(/\*(.*?)\*/g, '<em>$1</em>') // *italic*
.replace(/\n/g, '<br>') // 改行をbrタグに
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener">$1</a>'); // [text](url)
// 自動リンク化: より安全なアプローチで実装
// 既存のHTMLタグとMarkdownリンクを一時的に保護
const protectedSections = [];
let placeholderIndex = 0;
// 既存のHTMLタグを保護
sanitized = sanitized.replace(/<[^>]+>/g, (match) => {
const placeholder = `__PROTECTED_TAG_${placeholderIndex}__`;
protectedSections.push({ placeholder, content: match });
placeholderIndex++;
return placeholder;
});
// HTTP/HTTPS URLの自動リンク化
sanitized = sanitized.replace(/(https?:\/\/[^\s<>"']+)/gi, '<a href="$1" target="_blank" rel="noopener">$1</a>');
// www.で始まるURLの自動リンク化
sanitized = sanitized.replace(/\b(www\.[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(?:\/[^\s<>"']*)?)/gi, '<a href="http://$1" target="_blank" rel="noopener">$1</a>');
// メールアドレスの自動リンク化
sanitized = sanitized.replace(/\b([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,})\b/gi, '<a href="mailto:$1">$1</a>');
// 保護されたセクションを復元
protectedSections.forEach(({ placeholder, content }) => {
sanitized = sanitized.replace(placeholder, content);
});
// 重複したリンクタグを修正(ネストしたaタグを解決)
sanitized = sanitized.replace(/<a[^>]*><a[^>]*>(.*?)<\/a><\/a>/gi, '<a href="$1" target="_blank" rel="noopener">$1</a>');
// href内にaタグが含まれている場合の修正
sanitized = sanitized.replace(/<a href="<a[^>]*>(.*?)<\/a>"[^>]*>/gi, '<a href="$1"');
// 危険なイベントハンドラーや javascript: スキームを除去
sanitized = sanitized
.replace(/on\w+\s*=\s*["|'][^"']*["|']/gi, '')
.replace(/javascript:/gi, '')
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
.replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi, '');
return sanitized;
}
async function insertMessageAtPosition(messageElement, position) {
console.log('Admin Message Plugin: Attempting to insert at position:', position);
// PeerTube 7.2.1 の DOM 構造をログ出力
logDOMStructure();
switch (position) {
case 'before-description':
return insertBeforeDescription(messageElement);
case 'after-description':
return insertAfterDescription(messageElement);
case 'after-comments':
return insertAfterComments(messageElement);
default:
return insertAfterDescription(messageElement);
}
}
function logDOMStructure() {
console.log('Admin Message Plugin: Current DOM structure:');
// PeerTube 7.2.1 の主要な要素を確認
const selectors = [
'my-video-watch-video-info',
'.video-info',
'.video-info-description',
'.video-info-name',
'.video-info-first-row',
'my-video-description',
'my-video-comments',
'.comments'
];
selectors.forEach(selector => {
const element = document.querySelector(selector);
if (element) {
console.log(`✓ Found: ${selector}`, element);
}
else {
console.log(`✗ Not found: ${selector}`);
}
});
}
function insertBeforeDescription(messageElement) {
// PeerTube 7.2.1 用のセレクタ
const selectors = [
'.video-info-name',
'.video-info-first-row',
'my-video-watch-video-info .video-info-name'
];
for (const selector of selectors) {
const targetElement = document.querySelector(selector);
if (targetElement && targetElement.parentNode) {
targetElement.parentNode.insertBefore(messageElement, targetElement.nextSibling);
console.log(`Admin Message Plugin: Inserted before description using ${selector}`);
return true;
}
}
// フォールバック
const videoInfo = document.querySelector('my-video-watch-video-info, .video-info');
if (videoInfo) {
videoInfo.appendChild(messageElement);
console.log('Admin Message Plugin: Appended to video info container');
return true;
}
return false;
}
function insertAfterDescription(messageElement) {
// PeerTube 7.2.1 用のセレクタ
const selectors = [
'.video-info-description',
'my-video-description',
'.video-info-description-more'
];
for (const selector of selectors) {
const descriptionElement = document.querySelector(selector);
if (descriptionElement && descriptionElement.parentNode) {
descriptionElement.parentNode.insertBefore(messageElement, descriptionElement.nextSibling);
console.log(`Admin Message Plugin: Inserted after description using ${selector}`);
return true;
}
}
// フォールバック: video-info コンテナの最後に追加
const videoInfo = document.querySelector('my-video-watch-video-info, .video-info');
if (videoInfo) {
videoInfo.appendChild(messageElement);
console.log('Admin Message Plugin: Appended to video info container as fallback');
return true;
}
// 最終フォールバック
const mainCol = document.querySelector('.main-col, main');
if (mainCol) {
// コメント欄の前に挿入を試みる
const comments = document.querySelector('my-video-comments, .comments');
if (comments) {
mainCol.insertBefore(messageElement, comments);
console.log('Admin Message Plugin: Inserted before comments as final fallback');
}
else {
mainCol.appendChild(messageElement);
console.log('Admin Message Plugin: Appended to main column as ultimate fallback');
}
return true;
}
console.error('Admin Message Plugin: Could not find suitable insertion point');
return false;
}
function insertAfterComments(messageElement) {
const selectors = [
'my-video-comments',
'.comments',
'.video-comments'
];
for (const selector of selectors) {
const commentsElement = document.querySelector(selector);
if (commentsElement && commentsElement.parentNode) {
commentsElement.parentNode.insertBefore(messageElement, commentsElement.nextSibling);
console.log(`Admin Message Plugin: Inserted after comments using ${selector}`);
return true;
}
}
// フォールバック
const mainCol = document.querySelector('.main-col, main');
if (mainCol) {
mainCol.appendChild(messageElement);
console.log('Admin Message Plugin: Appended to main column');
return true;
}
return false;
}
}
export { register };
//# sourceMappingURL=video-watch-client-plugin.js.map