도커 컨텐츠 뷰어 Dock마루 0.0.3-beta 업데이트

스크린샷 2026-01-19 18.04.36.png

스크린샷 2026-01-19 18.04.45.png

0.0.3-beta 업데이트가 됐습니다

 

개발자 모드가 추가 됐습니다

플러그인 시스템이 개발자 모드에 추가 됐습니다

기본 태그에 연재중, 완결, 휴재 상태 태그와 웹소설 플랫폼 태그가 추가 됐습니다

그 외에 여러 버그나 레이아웃 수정이 있었습니다

 

스크린샷 2026-01-19 18.21.04.png

개발자 모드가 활성화 되면 플러그인 시스템을 사용 가능합니다

 

스크린샷 2026-01-19 12.43.49.png

스크린샷 2026-01-19 12.44.12.png

역할 권한 관리와 사용자 권한 관리에서 플러그인 시스템 사용 권한을 할당 가능합니다

 

스크린샷 2026-01-19 13.06.31.png

권한이 있으면 계정 설정에 플러그인 탭이 표시 됩니다

슈퍼 관리자의 경우 개발자 모드가 활성화 되면 플러그인 시스템 켜고 끄기와 상관없이 사용이 가능 합니다

 

스크린샷 2026-01-19 12.42.54.png

플러그인을 생성 하거나 파일 업로드를 통해 플러그인을 추가하면

 

스크린샷 2026-01-19 13.09.09.png

스크린샷 2026-01-19 13.10.14.png

책 수정 화면에서 플러그인을 통해 검색이 가능합니다

 

image.png

검색 결과가 있는 경우 적용을 누르면 책의 표지와 제목, 저자, 줄거리 등을 가져와서 채웁니다만

플러그인을 규격에 맞게 만들어야겠죠?

 

직접 여러 사이트 플러그인을 만들어 봤습니다만

사이트 구조상 되는 곳이 있고 안되는 곳도 있고 뭐 그렇습니다

그리고 다시 한 번 이야기 하지만

크롤링이나 스크래핑은 ip밴이나 계정 밴등의 위험이 있을 수 있으니 주의 하시길 바랍니다

 

스크린샷 2026-01-19 18.47.08.png

기본 추가 된 태그는 이런 식입니다

필요 없는 태그는 삭제하시면 되겠습니다

필요한 태그는 추가 하시면 되고요 

 

개발자 모드는 비활성화시 기존과 다름 없이 사용 가능합니다

필요하신 분만 개방하시면 될 거 같고요

 

개발자 모드 개방 방법은 공개를 할지 필요하신 분에게만 전달을 할지 고민을 해봤는데

이걸 굳이 몰래 전달하는 것도 웃긴 거 같고요

 

처음에는 온라인 라이센스 방식으로 만들어볼까? 하다가 

결제를 통해 등급을 나눠서 기능을 개방 하는 것도 아니고

라이센스 서버 따로 돌리는 것도 귀찮고해서

 

개발자 모드 개방 방식으로 변경을 했습니다

그냥 가스 밸브 같은 안전 장치라고 보시면 되겠습니다

안드로이드 개발자 모드 같은 느낌이죠

 

따로 제작한 플러그인 공유는 힘들 거 같으니

직접 만들어서 사용하실 분들만 사용하시면 될 거 같습니다

 

스크린샷 2026-01-19 18.53.05.png

개방 방법은 전체 사용자 카드 2번 – 전체 책 카드 3번 – 공개게시판 책 카드 1번 – 사용 중인 저장공간 카드 2번 클릭 하시면 됩니다

 

코나미 커맨드를 넣어볼까 하다가 간단하게 구현 했습니다

실수로 개방할 일은 없겠죠

 

커맨드 입력하시면 활성화 약관 뜨는데 동의 하시고 활성화 하시면 개방 됩니다

 

사용 해보실 분들은 사용 해보세요 

 

플러그인 개발 가이드 입니다
# Dockmaru 플러그인 개발 가이드

## 개요

Dockmaru 플러그인은 JavaScript로 작성되며, 사용자 브라우저에서 실행됩니다.
서버의 Proxy API를 통해 외부 사이트에 요청을 보낼 수 있습니다.

---

## 플러그인 구조

```javascript
const plugin = {
  // 필수 속성
  name: 'Plugin Name',           // 플러그인 이름
  version: '1.0.0',              // 버전
  description: '설명',           // 설명
  
  // 필수 메서드 (메타데이터 검색 플러그인)
  async search(query) {
    // 검색어로 책 목록 검색
    // 반환: Array<{title, author, cover, description, url, extra}>
  },
  
  // 선택 메서드
  async getMetadata(url) {
    // URL에서 상세 메타데이터 추출
    // 반환: {title, author, cover, description}
  }
};
```

---

## Proxy API 사용법

### proxyFetch(url, options)

외부 사이트에 HTTP 요청을 보냅니다. CORS 제한을 우회합니다.

```javascript
// GET 요청
const response = await proxyFetch('https://example.com/api/search?q=test', {
  headers: {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
    'Accept': 'text/html,application/xhtml+xml'
  }
});

// POST 요청
const response = await proxyFetch('https://example.com/api', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ query: 'test' })
});
```

### 반환값

```javascript
{
  status: 200,           // HTTP 상태 코드
  headers: {...},        // 응답 헤더
  body: '...',           // 원본 응답 (문자열)
  json: () => {...},     // JSON 파싱 함수
  text: () => '...'      // 텍스트 반환 함수
}
```

---

## 검색 결과 반환 형식

```javascript
async search(query) {
  const response = await proxyFetch(
    `https://api.example.com/search?q=${encodeURIComponent(query)}`
  );
  const data = response.json();
  
  return data.results.map(item => ({
    title: item.title,           // 필수: 제목
    author: item.author,         // 선택: 저자
    cover: item.thumbnailUrl,    // 선택: 표지 이미지 URL
    description: item.synopsis,  // 선택: 줄거리
    url: item.detailUrl,         // 선택: 상세 페이지 URL (getMetadata에서 사용)
    extra: {                     // 선택: 추가 정보
      platform: 'Example',
      genre: item.genre
    }
  }));
}
```

---

## HTML 파싱 기법

proxyFetch는 HTML을 문자열로 반환합니다. 정규식으로 파싱할 수 있습니다.

### 기본 패턴

```javascript
async getMetadata(url) {
  const response = await proxyFetch(url);
  const html = response.body || '';
  
  // OG 태그 파싱 (가장 일반적)
  const titleMatch = html.match(/<meta[^>]+property="og:title"[^>]+content="([^"]+)"/i);
  const title = titleMatch ? titleMatch[1] : '';
  
  const imageMatch = html.match(/<meta[^>]+property="og:image"[^>]+content="([^"]+)"/i);
  const cover = imageMatch ? imageMatch[1] : '';
  
  const descMatch = html.match(/<meta[^>]+property="og:description"[^>]+content="([^"]+)"/i);
  const description = descMatch ? descMatch[1] : '';
  
  return { title, cover, description };
}
```

### 정규식 작성 팁

1. **속성 순서 고려**: HTML 속성 순서가 다를 수 있음
   ```javascript
   // 나쁜 예: 특정 순서 가정
   /<img src="([^"]+)" alt="([^"]+)">/
   
   // 좋은 예: [^>]*로 유연하게
   /<img[^>]+alt="([^"]+)"[^>]*>/
   ```

2. **공백 처리**: HTML 포맷팅에 따라 공백이 다름
   ```javascript
   // 나쁜 예: 정확한 공백 위치 가정
   /<a href="\/path\/"><img
   
   // 좋은 예: \s* 또는 [^>]* 사용
   /<a[^>]+href="\/path\/"[^>]*><img/
   ```

3. **HTML 엔티티 디코딩**: `&amp;`, `&lt;` 등 처리
   ```javascript
   cover = cover.replace(/&amp;/g, '&');
   description = description
     .replace(/&nbsp;/g, ' ')
     .replace(/&lt;/g, '<')
     .replace(/&gt;/g, '>')
     .replace(/&quot;/g, '"')
     .replace(/&#39;/g, "'");
   ```

4. **JSON-LD 데이터 활용**: 일부 사이트는 구조화된 데이터 제공
   ```javascript
   const jsonLdMatch = html.match(/<script[^>]+type="application\/ld\+json"[^>]*>([\s\S]*?)<\/script>/i);
   if (jsonLdMatch) {
     try {
       const data = JSON.parse(jsonLdMatch[1]);
       title = data.name || '';
       cover = data.image || '';
       description = data.description || '';
     } catch (e) {
       console.log('JSON-LD 파싱 실패');
     }
   }
   ```

---

## 디버깅 팁

### 콘솔 로그 활용

```javascript
async search(query) {
  console.log('[플러그인] 검색:', query);
  
  const response = await proxyFetch(url);
  console.log('[플러그인] 응답 상태:', response.status);
  console.log('[플러그인] HTML 길이:', response.body.length);
  
  // 문제 발생 시 HTML 일부 출력
  const idx = html.indexOf('target-element');
  if (idx > 0) {
    console.log('[플러그인] 타겟 주변:', html.substring(idx, idx + 200));
  }
  
  // 결과 개수 확인
  console.log('[플러그인] 결과:', results.length);
  return results;
}
```

### 일반적인 문제와 해결

| 문제 | 원인 | 해결 |
|------|------|------|
| 검색 결과 0 | 정규식 불일치 | HTML 구조 재분석, 패턴 수정 |
| 504 Gateway Timeout | 서버 차단 또는 과부하 | 요청 간격 조절, User-Agent 변경 |
| 403 Forbidden | 봇 차단 | User-Agent 헤더 추가 |
| HTML 비어있음 | JavaScript 렌더링 필요 | SSR 사이트만 지원 가능 |

---

## 플러그인 테스트

1. Dockmaru 앱에서 **계정 설정 > 플러그인** 탭 이동
2. **플러그인 추가** 클릭
3. 코드 입력 후 **검증** 버튼으로 구문 확인
4. 저장 후 **책 수정** 화면에서 **메타데이터 검색** 버튼 사용
5. 브라우저 개발자 도구(F12) 콘솔에서 로그 확인

---

## 주의사항

1. **법적 책임**: 플러그인 사용으로 인한 법적 문제는 사용자 책임입니다.
2. **사이트 정책**: 대상 사이트의 이용약관 및 robots.txt를 준수하세요.
3. **Rate Limiting**: 과도한 요청은 IP 차단될 수 있습니다.
4. **HTTPS 전용**: Proxy API는 HTTPS URL만 지원합니다.
5. **SSR 사이트 전용**: JavaScript로 렌더링되는 SPA 사이트는 지원하지 않습니다.

---

## 사이트 유형별 접근법

| 사이트 유형 | 특징 | 접근법 |
|-------------|------|--------|
| API 제공 | JSON 응답 | response.json() 사용 |
| SSR (서버 렌더링) | HTML 완성 | 정규식 파싱 |
| SPA (클라이언트 렌더링) | JavaScript 필요 | ❌ 지원 불가 |
| 로그인 필요 | 세션/쿠키 필요 | ❌ 지원 불가 |

 

댓글 남기기

이메일 주소는 공개되지 않습니다. 필수 항목은 *(으)로 표시합니다