diff --git a/app/src/main/assets/extensions/my_extension/Novel_Scraper/content.js b/app/src/main/assets/extensions/my_extension/Novel_Scraper/content.js
new file mode 100644
index 00000000..5031877a
--- /dev/null
+++ b/app/src/main/assets/extensions/my_extension/Novel_Scraper/content.js
@@ -0,0 +1,152 @@
+// 페이지 로딩 완료 후 실행
+window.addEventListener('load', async () => {
+ // 1. URL 패턴 확인
+ const match = window.location.pathname.match(/\/novel\/(\d+)\/(\d+)/);
+ if (!match) return;
+
+ const bookId = match[1];
+ const chapterId = match[2];
+ console.log(`웹소설 스크래퍼: 책 ID [${bookId}] 감지됨.`);
+
+ // ⏳ 2. 1~2초 동안 '페이지 다운(80%)' 스크롤 실행
+ const initialScrollTime = Math.floor(Math.random() * (10000 - 1000 + 1)) + 8000;
+ console.log(`${(initialScrollTime / 1000).toFixed(1)}초 동안 큼직하게 훑어보며 대기합니다... 👀`);
+ await pageSkimScroll(initialScrollTime);
+
+ // 3. 데이터 스크래핑
+ const scrapedData = scrapePage();
+ if (!scrapedData) return;
+
+ // 4. 책 ID별 Storage 저장
+ chrome.storage.local.get(['scrapedBooks'], async (result) => {
+ let scrapedBooks = result.scrapedBooks || {};
+
+ if (!scrapedBooks[bookId]) {
+ scrapedBooks[bookId] = {
+ id: 'book_' + bookId,
+ fileName: scrapedData.novelTitle,
+ chapters: []
+ };
+ }
+
+ let bookData = scrapedBooks[bookId];
+
+ if (scrapedData.novelTitle !== '제목없음') {
+ bookData.fileName = scrapedData.novelTitle;
+ }
+
+ // 중복 챕터 검사
+ const isDuplicate = bookData.chapters.some(chap => chap.chapterId === chapterId);
+ if (isDuplicate) {
+ console.log(`⚠️ 이미 저장된 챕터입니다 (ID: ${chapterId}). 자동 진행을 중지합니다.`);
+ return;
+ }
+
+ // 챕터 추가
+ bookData.chapters.push({
+ chapterId: chapterId,
+ title: scrapedData.chapterTitle,
+ content: scrapedData.content
+ });
+
+ // Storage에 저장
+ scrapedBooks[bookId] = bookData;
+ chrome.storage.local.set({
+ scrapedBooks: scrapedBooks,
+ currentBookId: bookId
+ }, async () => {
+ console.log(`✅ [${scrapedData.novelTitle} - ${scrapedData.chapterTitle}] 저장 완료!`);
+
+ // ⏳ 5. 저장 완료 후 2~3초 랜덤 대기
+ const waitDelay = Math.floor(Math.random() * (5000 - 2000 + 1)) + 4000;
+ console.log(`${(waitDelay / 1000).toFixed(1)}초 대기 후 다음 챕터로 이동합니다... ⏳`);
+ await new Promise(resolve => setTimeout(resolve, waitDelay));
+
+ // '다음화' 버튼 클릭
+ const buttons = Array.from(document.querySelectorAll('a.btn.btn-black.btn-sm'));
+ const nextButton = buttons.find(btn => btn.innerText.trim().includes('다음화'));
+
+ if (nextButton) {
+ console.log("👉 다음 화 버튼 클릭 진행");
+ nextButton.click();
+ } else {
+ console.log("⚠️ '다음화' 버튼을 찾을 수 없거나 마지막 화입니다.");
+ }
+ });
+ });
+});
+
+// ==========================================
+// 🚀 화면 높이의 약 80%씩 큼직하게 스크롤하는 함수
+// ==========================================
+function pageSkimScroll(duration) {
+ return new Promise((resolve) => {
+ let elapsed = 0;
+
+ const scrollLoop = () => {
+ if (elapsed >= duration) {
+ resolve();
+ return;
+ }
+
+ // 400ms ~ 700ms 사이의 랜덤한 대기 시간 (큼직하게 내리기 때문에 간격을 조금 더 줌)
+ const stepTime = Math.floor(Math.random() * (1700 - 400 + 1)) + 1400;
+
+ // 현재 브라우저 창 높이(Viewport)를 구함
+ const screenHeight = window.innerHeight;
+
+ // 75% ~ 85% 사이의 랜덤한 비율 설정 (평균 80%)
+ const scrollRatio = 0.75 + (Math.random() * 0.1);
+ const stepAmount = screenHeight * scrollRatio;
+
+ // 부드럽게 스크롤
+ window.scrollBy({ top: stepAmount, behavior: 'smooth' });
+
+ elapsed += stepTime;
+ setTimeout(scrollLoop, stepTime);
+ };
+
+ // 루프 시작
+ scrollLoop();
+ });
+}
+
+// ==========================================
+// 스크래핑 헬퍼 함수
+// ==========================================
+function scrapePage() {
+ const titleEl = document.querySelector('.page-title h2');
+ let novelTitle = titleEl ? titleEl.innerText.trim() : '제목없음';
+
+ let chapterTitle = document.querySelector('.theme-novel-title');
+ chapterTitle = chapterTitle ? chapterTitle.innerText.trim() : '1화';
+
+ let novelContent = '';
+ const contentHost = document.querySelector('.theme-novel-content');
+
+ if (contentHost) {
+ if (contentHost.shadowRoot) {
+ const pTags = contentHost.shadowRoot.querySelectorAll('p');
+ const textArray = Array.from(pTags).map(p => p.innerText.trim());
+ novelContent = textArray.join('\n\n');
+ } else {
+ const template = contentHost.querySelector('template[shadowrootmode="open"]');
+ if (template && template.content) {
+ const pTags = template.content.querySelectorAll('p');
+ const textArray = Array.from(pTags).map(p => p.textContent.trim());
+ novelContent = textArray.join('\n\n');
+ }
+ }
+ }
+
+ if (!novelContent) {
+ console.warn('본문을 추출할 수 없습니다.');
+ return null;
+ }
+
+ return {
+ novelTitle: novelTitle,
+ chapterTitle: chapterTitle,
+ content: novelContent
+ };
+}
\ No newline at end of file
diff --git a/app/src/main/assets/extensions/my_extension/Novel_Scraper/manifest.json b/app/src/main/assets/extensions/my_extension/Novel_Scraper/manifest.json
new file mode 100644
index 00000000..d4dc5134
--- /dev/null
+++ b/app/src/main/assets/extensions/my_extension/Novel_Scraper/manifest.json
@@ -0,0 +1,29 @@
+{
+ "manifest_version": 3,
+ "name": "웹소설 스크래퍼 (나만의 서재용)",
+ "version": "1.2",
+ "description": "웹소설 챕터를 수집하여 서재 리더기용 JSON으로 다운로드합니다.",
+ "action": {
+ "default_popup": "popup.html"
+ },
+ "permissions": [
+ "activeTab",
+ "scripting",
+ "storage",
+ "downloads",
+ "unlimitedStorage"
+ ],
+ "host_permissions": [
+ "https://api.telegram.org/*"
+ ],
+ "content_scripts": [
+ {
+ "matches": [
+ "*://newtoki1.org/novel/*",
+ "*://*.newtoki1.org/novel/*"
+ ],
+ "js": ["content.js"],
+ "run_at": "document_end"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/app/src/main/assets/extensions/my_extension/Novel_Scraper/popup.html b/app/src/main/assets/extensions/my_extension/Novel_Scraper/popup.html
new file mode 100644
index 00000000..e7df4a13
--- /dev/null
+++ b/app/src/main/assets/extensions/my_extension/Novel_Scraper/popup.html
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+📖 소설 스크래퍼
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+대기 중...
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/assets/extensions/my_extension/Novel_Scraper/popup.js b/app/src/main/assets/extensions/my_extension/Novel_Scraper/popup.js
new file mode 100644
index 00000000..14d02ad7
--- /dev/null
+++ b/app/src/main/assets/extensions/my_extension/Novel_Scraper/popup.js
@@ -0,0 +1,267 @@
+document.addEventListener('DOMContentLoaded', () => {
+ updateStatus();
+ loadTelegramConfig();
+
+ // 1. [설정 저장] 버튼
+ document.getElementById('btn-save-config').addEventListener('click', saveTelegramConfig);
+
+ // 2. [저장하기] 버튼
+ // 2. [저장하기] 버튼
+ document.getElementById('btn-save').addEventListener('click', async () => {
+ let [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
+
+ chrome.scripting.executeScript({
+ target: { tabId: tab.id },
+ function: scrapePage
+ }, (results) => {
+ if (results && results[0] && results[0].result) {
+ const data = results[0].result;
+ const match = tab.url ? tab.url.match(/\/novel\/(\d+)\/(\d+)/) : null;
+ const bookId = match ? match[1] : 'manual_' + Date.now();
+ const chapterId = match ? match[2] : 'manual_chap_' + Date.now(); // 챕터 ID 추가
+
+ // chapterId를 같이 넘겨줍니다.
+ saveToExtensionStorage(data, bookId, chapterId);
+ }
+ });
+ });
+
+ // 3. [다운로드] 버튼
+ document.getElementById('btn-download').addEventListener('click', downloadJson);
+
+ // 4. [텔레그램 전송] 버튼
+ document.getElementById('btn-telegram').addEventListener('click', sendToTelegram);
+
+ // 5. [초기화] 버튼
+ document.getElementById('btn-clear').addEventListener('click', clearStorage);
+});
+
+// 텔레그램 설정 불러오기
+function loadTelegramConfig() {
+ chrome.storage.local.get(['tgToken', 'tgChatId'], (result) => {
+ if (result.tgToken) document.getElementById('tg-token').value = result.tgToken;
+ if (result.tgChatId) document.getElementById('tg-chatid').value = result.tgChatId;
+ });
+}
+
+// 텔레그램 설정 저장
+function saveTelegramConfig() {
+ const token = document.getElementById('tg-token').value.trim();
+ const chatId = document.getElementById('tg-chatid').value.trim();
+
+ chrome.storage.local.set({ tgToken: token, tgChatId: chatId }, () => {
+ alert("✅ 텔레그램 설정이 저장 되었습니다!");
+ });
+}
+
+// 🚀 텔레그램 API로 JSON 파일 전송 함수
+function sendToTelegram() {
+ chrome.storage.local.get(['scrapedBooks', 'currentBookId', 'tgToken', 'tgChatId'], async (result) => {
+ const token = result.tgToken;
+ const chatId = result.tgChatId;
+
+ if (!token || !chatId) {
+ alert("⚠️ 먼저 텔레그램 Bot Token과 Chat ID를 설정하고 저장해주세요!");
+ return;
+ }
+
+ const books = result.scrapedBooks || {};
+ const currentBookId = result.currentBookId;
+ const bookData = currentBookId ? books[currentBookId] : null;
+
+ if (!bookData || bookData.chapters.length === 0) {
+ alert("전송할 데이터가 없습니다.");
+ return;
+ }
+
+ // 리더기 포맷 페이로드 생성
+ const payload = {
+ type: 'single_book',
+ book: bookData,
+ progress: { chapter: 0, page: 0, tags: [], memo: "스크래퍼 확장프로그램으로 수집됨", isFavorite: false, isCompleted: false }
+ };
+
+ const jsonString = JSON.stringify(payload, null, 2);
+ const safeFileName = bookData.fileName.replace(/[\\/:*?"<>|]/g, '_');
+
+ // JSON 문자열을 Blob 파일 객체로 변환
+ const blob = new Blob([jsonString], { type: 'application/json' });
+
+ // Multipart FormData 생성 (텔레그램 API 전송용)
+ const formData = new FormData();
+ formData.append('chat_id', chatId);
+ formData.append('document', blob, `book_${safeFileName}.json`);
+ formData.append('caption', `📖 [${bookData.fileName}]\n총 ${bookData.chapters.length}화 수집 완료 JSON 파일입니다.`);
+
+ // 버튼 상태 변경
+ const btnTg = document.getElementById('btn-telegram');
+ btnTg.innerText = "⏳ 전송 중...";
+ btnTg.disabled = true;
+
+ try {
+ // Telegram sendDocument API 호출
+ const response = await fetch(`https://api.telegram.org/bot${token}/sendDocument`, {
+ method: 'POST',
+ body: formData
+ });
+
+ const resData = await response.json();
+
+ if (resData.ok) {
+ alert(`🚀 [${bookData.fileName}] 텔레그램으로 전송 성공!`);
+ } else {
+ alert(`❌ 전송 실패: ${resData.description}`);
+ }
+ } catch (error) {
+ console.error(error);
+ alert("❌ 네트워크 오류가 발생했습니다.");
+ } finally {
+ btnTg.innerText = "🚀 텔레그램으로 JSON 전송";
+ btnTg.disabled = false;
+ }
+ });
+}
+
+// 스크래핑 함수 (수동 저장용)
+function scrapePage() {
+ const titleEl = document.querySelector('.page-title h2');
+ let novelTitle = titleEl ? titleEl.innerText.trim() : '제목없음';
+
+ let chapterTitle = document.querySelector('.theme-novel-title');
+ chapterTitle = chapterTitle ? chapterTitle.innerText.trim() : '1화';
+
+ let novelContent = '';
+ const contentHost = document.querySelector('.theme-novel-content');
+
+ if (contentHost) {
+ if (contentHost.shadowRoot) {
+ const pTags = contentHost.shadowRoot.querySelectorAll('p');
+ const textArray = Array.from(pTags).map(p => p.innerText.trim());
+ novelContent = textArray.join('\n\n');
+ } else {
+ const template = contentHost.querySelector('template[shadowrootmode="open"]');
+ if (template && template.content) {
+ const pTags = template.content.querySelectorAll('p');
+ const textArray = Array.from(pTags).map(p => p.textContent.trim());
+ novelContent = textArray.join('\n\n');
+ }
+ }
+ }
+
+ if (!novelContent) {
+ alert('본문을 추출할 수 없습니다.');
+ return null;
+ }
+
+ return {
+ novelTitle: novelTitle,
+ chapterTitle: chapterTitle,
+ content: novelContent
+ };
+}
+
+// 수동 저장 함수
+function saveToExtensionStorage(scrapedData, bookId) {
+ chrome.storage.local.get(['scrapedBooks'], (result) => {
+ let scrapedBooks = result.scrapedBooks || {};
+
+ if (!scrapedBooks[bookId]) {
+ scrapedBooks[bookId] = {
+ id: 'book_' + bookId,
+ fileName: scrapedData.novelTitle,
+ chapters: []
+ };
+ }
+
+ let bookData = scrapedBooks[bookId];
+
+ // 챕터 ID 기준으로 중복 검사
+ const isDuplicate = bookData.chapters.some(chap => chap.chapterId === chapterId);
+ if (isDuplicate) {
+ alert('⚠️ 이미 저장된 챕터입니다!');
+ return;
+ }
+
+ bookData.chapters.push({
+ chapterId: chapterId,
+ title: scrapedData.chapterTitle,
+ content: scrapedData.content
+ });
+ scrapedBooks[bookId] = bookData;
+
+ chrome.storage.local.set({ scrapedBooks: scrapedBooks, currentBookId: bookId }, () => {
+ updateStatus();
+ alert(`✅ [${scrapedData.chapterTitle}] 저장 완료!`);
+ });
+ });
+}
+
+// 다운로드 함수
+function downloadJson() {
+ chrome.storage.local.get(['scrapedBooks', 'currentBookId'], (result) => {
+ const books = result.scrapedBooks || {};
+ const currentBookId = result.currentBookId;
+ const bookData = currentBookId ? books[currentBookId] : null;
+
+ if (!bookData || bookData.chapters.length === 0) {
+ alert("다운로드할 데이터가 없습니다.");
+ return;
+ }
+
+ const payload = {
+ type: 'single_book',
+ book: bookData,
+ progress: { chapter: 0, page: 0, tags: [], memo: "스크래퍼 확장프로그램으로 수집됨", isFavorite: false, isCompleted: false }
+ };
+
+ const jsonString = JSON.stringify(payload, null, 2);
+ const blob = new Blob([jsonString], { type: 'application/json' });
+ const url = URL.createObjectURL(blob);
+
+ const safeFileName = bookData.fileName.replace(/[\\/:*?"<>|]/g, '_');
+
+ chrome.downloads.download({
+ url: url,
+ filename: `book_${safeFileName}.json`,
+ saveAs: true
+ });
+ });
+}
+
+// 초기화 함수
+function clearStorage() {
+ chrome.storage.local.get(['scrapedBooks', 'currentBookId'], (result) => {
+ const books = result.scrapedBooks || {};
+ const currentBookId = result.currentBookId;
+
+ if (!currentBookId || !books[currentBookId]) {
+ alert("삭제할 수집 데이터가 없습니다.");
+ return;
+ }
+
+ const bookTitle = books[currentBookId].fileName;
+ if(confirm(`[${bookTitle}] 수집 데이터를 삭제하시겠습니까?`)) {
+ delete books[currentBookId];
+ chrome.storage.local.set({ scrapedBooks: books, currentBookId: null }, () => {
+ updateStatus();
+ });
+ }
+ });
+}
+
+// 상태창 업데이트
+function updateStatus() {
+ chrome.storage.local.get(['scrapedBooks', 'currentBookId'], (result) => {
+ const books = result.scrapedBooks || {};
+ const currentBookId = result.currentBookId;
+ const book = currentBookId ? books[currentBookId] : null;
+
+ const count = book ? book.chapters.length : 0;
+ const title = book ? book.fileName : '없음';
+
+ document.getElementById('status').innerHTML = `
+ 현재 선택 소설: ${title}
+ 모인 챕터: ${count}화
+ `;
+ });
+}
\ No newline at end of file
diff --git a/app/src/main/assets/extensions/my_extension/bookget.js b/app/src/main/assets/extensions/my_extension/bookget.js
new file mode 100644
index 00000000..79f128cf
--- /dev/null
+++ b/app/src/main/assets/extensions/my_extension/bookget.js
@@ -0,0 +1,85 @@
+// ==========================================
+// 1. 챕터를 로컬 스토리지에 누적 저장하는 함수
+// ==========================================
+function saveChapterToLocal(bookTitle, chapterTitle, chapterContent) {
+ const STORAGE_KEY = 'webReader_scraper_temp';
+ let bookData = JSON.parse(localStorage.getItem(STORAGE_KEY));
+
+ // 기존 데이터가 없거나, 다른 책을 스크랩하기 시작한 경우 초기화
+ if (!bookData || bookData.fileName !== bookTitle) {
+ bookData = {
+ id: 'book_scraped_' + Date.now(), // 고유 ID 생성
+ fileName: bookTitle,
+ chapters: []
+ };
+ console.log(`새로운 책 [${bookTitle}] 스크랩을 시작합니다.`);
+ }
+
+ // 중복 저장 방지 로직 (동일한 챕터 제목이 있는지 검사)
+ const isDuplicate = bookData.chapters.some(chap => chap.title === chapterTitle);
+ if (isDuplicate) {
+ console.warn(`⚠️ 이미 저장된 챕터입니다: ${chapterTitle}`);
+ return false;
+ }
+
+ // 챕터 데이터 추가
+ bookData.chapters.push({
+ title: chapterTitle,
+ content: chapterContent
+ });
+
+ // 변경된 데이터를 다시 스토리지에 저장
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(bookData));
+ console.log(`✅ [${bookTitle}] ${chapterTitle} 저장 완료! (현재 총 ${bookData.chapters.length}화)`);
+ return true;
+}
+
+// ==========================================
+// 2. 누적된 데이터를 리더기 호환 JSON으로 다운로드하는 함수
+// ==========================================
+function downloadBookAsJson() {
+ const STORAGE_KEY = 'webReader_scraper_temp';
+ const bookData = JSON.parse(localStorage.getItem(STORAGE_KEY));
+
+ if (!bookData || !bookData.chapters || bookData.chapters.length === 0) {
+ alert("다운로드할 챕터 데이터가 없습니다. 먼저 챕터를 저장해주세요.");
+ return;
+ }
+
+ // ★ 리더기의 '개별 책 복원' 형식과 완벽히 일치하도록 페이로드 구성
+ const payload = {
+ type: 'single_book',
+ book: bookData,
+ progress: {
+ chapter: 0,
+ page: 0,
+ tags: [], // 리더기에서 불러올 때 재분석 기능으로 태그를 달 수 있습니다.
+ memo: "스크랩을 통해 추가된 소설입니다.",
+ isFavorite: false,
+ isCompleted: false
+ }
+ };
+
+ // JSON 파일로 변환 및 다운로드 처리
+ const jsonString = JSON.stringify(payload, null, 2);
+ const blob = new Blob([jsonString], { type: 'application/json' });
+ const url = URL.createObjectURL(blob);
+
+ const a = document.createElement('a');
+ a.href = url;
+
+ // 파일명에 사용할 수 없는 특수문자 제거
+ const safeFileName = bookData.fileName.replace(/[\\/:*?"<>|]/g, '_');
+ a.download = `book_${safeFileName}.json`;
+
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ URL.revokeObjectURL(url);
+
+ // 다운로드 후 임시 데이터 정리
+ if (confirm("파일 다운로드가 완료되었습니다.\n다음 스크랩을 위해 임시 저장된 데이터를 초기화할까요?")) {
+ localStorage.removeItem(STORAGE_KEY);
+ console.log("임시 데이터가 초기화되었습니다.");
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/assets/extensions/my_extension/reader.html b/app/src/main/assets/extensions/my_extension/reader.html
index 621205fb..3d3ef1a0 100644
--- a/app/src/main/assets/extensions/my_extension/reader.html
+++ b/app/src/main/assets/extensions/my_extension/reader.html
@@ -166,7 +166,6 @@
input[type="file"] { margin-top: 15px; cursor: pointer; color: #ccc; }
/* 서재 리스트 UI */
- /* 서재 팝업창 크기 및 여백 조정 */
.library-box {
width: 90%;
max-width: 480px;
@@ -176,37 +175,36 @@
padding: 16px;
}
- /* 1열 리스트에서 -> 2열 카드 그리드 레이아웃으로 변경 */
.library-list {
list-style: none;
padding: 10px 2px;
margin: 0;
overflow-y: auto;
+ overflow-x: hidden; /* 가로 스크롤 방지 */
flex: 1;
text-align: left;
border-top: 1px solid var(--border-color);
border-bottom: 1px solid var(--border-color);
-
- /* 그리드 핵심 설정 */
- display: grid;
- grid-template-columns: repeat(1, 1fr); /* 모바일 2열 배치 (화면에 약 6개 노출) */
- gap: 10px; /* 카드 사이 간격 */
+ /* 💡 Grid 대신 Flex Column으로 변경하여 세로 스크롤에 최적화 */
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
}
- /* 카드 아이템 스타일 설정 */
.library-item {
background: var(--modal-content-bg);
border: 1px solid var(--border-color);
border-radius: 10px;
- padding: 12px;
+ padding: 14px;
cursor: pointer;
color: var(--text-color);
display: flex;
flex-direction: column;
justify-content: space-between;
box-sizing: border-box;
- min-height: 160px; /* 140px -> 160px로 여유 제공 */
- height: auto; /* 내용이 많아지면 자동으로 카드 높이 늘어남 */
+ width: 100%;
+ flex-shrink: 0; /* 💡 핵심: 화면이 좁아도 세로로 절대 찌그러지지 않게 강제 방어 */
+ overflow: hidden;
transition: transform 0.15s, background 0.15s;
}
@@ -215,12 +213,10 @@
}
.library-item:active {
- transform: scale(0.97); /* 터치 시 눌리는 효과 */
+ transform: scale(0.97);
}
.btn-go-new { margin-top: 15px; padding: 12px; background: var(--accent-color); color: #fff; border: none; border-radius: 6px; cursor: pointer; font-size: 15px; font-weight: bold; }
-
-
/* 로딩 스피너 */
#loading-overlay { display: none; }
.spinner { margin: 20px auto; width: 40px; height: 40px; border: 4px solid #444; border-top: 4px solid var(--accent-color); border-radius: 50%; animation: spin 1s linear infinite; }
@@ -256,14 +252,28 @@
-
📚 내 서재
+
+
📚 내 서재
+
+
+
+
+
+
-
-
+
+
@@ -316,6 +326,12 @@
×
+
+
+
+
+
+
태그명과 감지할 키워드(쉼표로 구분)를 설정하세요.
@@ -334,13 +350,34 @@
-
-