Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ CROSSREF_MAILTO=
# 키 없이도 동작(공유 풀, 혼잡 시 429↑). 키 있으면 전용 풀(1 RPS).
SEMANTICSCHOLAR_API_KEY=

# ─── Wikipedia ───────────────────────────────────────────
# (선택) 식별/연락용 User-Agent — 미설정 시 기본 식별 문자열. Wikimedia는 User-Agent를 요구한다
# (없거나 약하면 403/스로틀). 공식 권장은 연락처 포함(예: "(myapp.com, you@example.com)").
WIKIPEDIA_USER_AGENT=
# (선택) Bearer 토큰 — 설정 시 Authorization: Bearer 헤더로 전송해 레이트리밋이 완화된다.
# 토큰 없이도 전체 읽기가 동작한다(무인증).
WIKIPEDIA_API_TOKEN=

# ─── AirKorea(에어코리아) ────────────────────────────────
# data.go.kr '대기오염정보' OpenAPI(15073861) 활용신청 후 발급되는 서비스키 (필수)
# ⚠️ Encoding/Decoding 2종 중 **Decoding 키(원문)**를 넣을 것 — httpx 자동 인코딩으로 인한 이중 인코딩 방지
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,6 @@
- **telegram**: 코어 도구 확장 — getMe(헬스체크)/sendPhoto/sendDocument/editMessageText/deleteMessage 추가
- **telegram**: sendPhoto/sendDocument 로컬 파일 multipart 업로드 지원(사진≤10MB·파일≤50MB), editMessageText inline_message_id 경로 추가
- **usgs_quake**: USGS 지진 정보 읽기 서비스 추가 — FDSN Event API 검색·건수 2개 GET 도구(`usgs_search_earthquakes`/`usgs_count_earthquakes`), 무인증·`format=geojson` 고정, 시간 ISO8601·`orderby`(time/magnitude)·원형 위치(`latitude`+`longitude`+`maxradiuskm`)·`limit` 1–20000(기본 20), GeoJSON FeatureCollection 파싱(time ms→UTC, coordinates[lon,lat,depth])·`{count,maxAllowed}` 건수
- **wikipedia**: 위키백과 읽기 서비스 추가 — 검색·요약·본문·링크 4개 GET 도구(`wikipedia_search`/`wikipedia_summary`/`wikipedia_extract`/`wikipedia_links`), 무인증이나 **User-Agent 헤더 요구**(기본값 상수, `WIKIPEDIA_USER_AGENT`로 덮어씀) + (선택) `WIKIPEDIA_API_TOKEN` Bearer로 레이트리밋 완화, 세 엔드포인트 혼합(per-wiki REST 검색 `/w/rest.php/v1/search/page` · rest_v1 요약 `/api/rest_v1/page/summary/{title}`(Wikidata Q-id·좌표 노출) · Action API TextExtracts/links|categories `formatversion=2` 배열), 언어판별 호스트(`{lang}.wikipedia.org`)·**Action API HTTP 200+error 봉투** 매핑, ⚠️ deprecating `api.wikimedia.org/core/v1/*` 회피
- **zotero**: Zotero 라이브러리 읽기 서비스 추가(Web API v3 + 로컬 데스크톱 API 단일 서비스·백엔드 전환) — 검색/아이템/자식/컬렉션/컬렉션 아이템/태그/전문/헬스 8개 GET 도구, 응답 헤더 기반 페이지네이션 안내
<!-- END UNRELEASED -->
74 changes: 74 additions & 0 deletions arcsolve/services/wikipedia/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Wikipedia 서비스

위키백과(Wikipedia) **읽기** 래퍼 — 검색·요약·본문·링크. 전부 GET·읽기. **무인증**으로 전체
읽기가 동작하지만, Wikimedia는 식별용 **`User-Agent` 헤더를 요구**한다(없거나 약하면 403/스로틀).
(선택) Bearer 토큰을 주면 레이트리밋이 완화된다.

## 계약 출처 (공식 문서)
- per-wiki REST(검색) 레퍼런스: https://www.mediawiki.org/wiki/API:REST_API/Reference
- Wikimedia REST API(rest_v1 summary): https://www.mediawiki.org/wiki/Wikimedia_REST_API (per-wiki 명세: https://en.wikipedia.org/api/rest_v1/)
- TextExtracts(`prop=extracts`·`exintro`·`explaintext`·`exchars`): https://www.mediawiki.org/wiki/Extension:TextExtracts
- Action API Query(`prop=links|categories`·`formatversion=2`·`redirects`): https://www.mediawiki.org/wiki/API:Query
- 라이브 응답 확인: `/w/rest.php/v1/search/page` · `/api/rest_v1/page/summary/{title}` · `/w/api.php?action=query&prop=extracts` · `/w/api.php?action=query&prop=links|categories`

> 계약 본체는 [`contract.py`](contract.py)에 코드로 박제되어 있다(호스트/경로 빌더·언어·limit 검증·제목 인코더·HTML 스트립·부분 응답 모델).

## 인증 (없음 · User-Agent 필수 · 토큰 선택)
무인증으로 전체 읽기가 동작한다. 다만 Wikimedia는 식별용 **`User-Agent` 헤더를 요구**하므로 기본
식별 문자열(`contract.DEFAULT_USER_AGENT`)을 항상 보내며, 연락처를 넣고 싶으면
`WIKIPEDIA_USER_AGENT`로 덮어쓴다. (선택) `WIKIPEDIA_API_TOKEN`을 주면 `Authorization: Bearer`를
UA와 함께 보내 레이트리밋이 완화된다(토큰 없이도 읽기는 전부 동작).

| env | 쓰임 | 비고 |
|---|---|---|
| `WIKIPEDIA_USER_AGENT` | `User-Agent: <값>` | 선택. 미설정 시 기본 식별 문자열. 공식 권장은 연락처 포함(예: `(myapp.com, you@example.com)`) |
| `WIKIPEDIA_API_TOKEN` | `Authorization: Bearer <값>` | 선택. 있으면 레이트리밋 완화. 없어도 전체 읽기 동작 |

- 헤더는 코어 `get_json(headers=...)`로 주입한다(서비스 폴더에서 httpx 직접 생성 금지 — AGENTS 규칙).
- 언어판마다 호스트가 다르다: base `https://{lang}.wikipedia.org`.

## 엔드포인트 (전부 GET · `https://{lang}.wikipedia.org<path>`)
| 종류 | METHOD · PATH |
|------|------|
| 검색(클린 REST) | `GET /w/rest.php/v1/search/page?q=&limit=` |
| 요약(rest_v1) | `GET /api/rest_v1/page/summary/{title}` |
| 본문(TextExtracts) | `GET /w/api.php?action=query&prop=extracts&explaintext=1&formatversion=2` |
| 링크·분류 | `GET /w/api.php?action=query&prop=links\|categories&formatversion=2` |

Base: `https://{lang}.wikipedia.org` · 인증: 없음(User-Agent 필수, Bearer 선택) · 스코프: 읽기 전용

> 세 종류 엔드포인트를 섞어 쓴다: ① per-wiki REST 검색, ② rest_v1 요약, ③ Action API 본문/링크.
> ⚠️ `api.wikimedia.org/core/v1/*`(통합 REST)는 2026-07 deprecation 예정·후속 없음 → 사용하지 않는다.
> ⚠️ Action API는 잘못된 파라미터에 **HTTP 200 + `{"error":{"code","info"}}`**를 줄 수 있다(4xx가 아님) → 본문을 보고 매핑한다.
> Action API는 `formatversion=2`로 `query.pages`를 **깨끗한 배열**로 받는다(pageid-keyed 객체 아님). `redirects=1`로 리다이렉트를 추적한다.

## 셋업
1. 키 발급 단계 없음(무인증).
2. `.env`(선택): `WIKIPEDIA_USER_AGENT="(myapp.com, you@example.com)"` — 식별/연락용 User-Agent.
3. `.env`(선택): `WIKIPEDIA_API_TOKEN=...` — Bearer 토큰(레이트리밋 완화).

> 무인증·필수 User-Agent 방식 — 인터랙티브 OAuth가 아니므로 `arcsolve-mcp auth wikipedia` 단계는 없다.

## 도구
| 도구 | 설명 |
|------|------|
| `wikipedia_search(query, lang="en", limit=10)` | 클린 REST 검색. 제목·요약·스니펫(HTML 태그 제거). `limit` 1–100 |
| `wikipedia_summary(title, lang="en")` | rest_v1 lead 요약. extract·문서 URL·**Wikidata Q-id**(있으면)·좌표(지리)·동음이의 안내 |
| `wikipedia_extract(title, lang="en", intro_only=True, max_chars=None)` | TextExtracts 평문 본문. 도입부/전체 선택, `max_chars` 1–1200 |
| `wikipedia_links(title, lang="en", limit=50)` | 나가는 문서 링크(ns 0) + 분류. `limit` 1–500 |

## 범위 / 제약 (공식)
- **읽기만.** 검색·요약·본문·링크/분류(MVP).
- `lang`은 소문자 언어 코드(`[a-z]`+하이픈 변형, 예: `en`·`ko`·`de`·`zh`·`simple`·`zh-yue`) — 형식 위반은 HTTP 전에 차단(호스트 오염 방지).
- 검색 `limit` **1–100**(기본 10), 링크/분류 `limit` **1–500**(기본 50), `max_chars`(exchars) **1–1200**.
- 요약 404 → "문서를 찾을 수 없습니다". 본문/링크는 `missing:true` 또는 빈 `pages` → 동일 안내.
- 무 User-Agent → 403, 스로틀 → 429(Retry-After 권장). Action API 잘못된 파라미터 → HTTP 200 + `{error}` → `error.info` 노출.
- 제외: 편집/쓰기, 미디어 업로드, 위키데이터 직접 조회(요약의 `wikibase_item`만 브리지로 노출), `api.wikimedia.org/core/v1/*`(deprecating), CirrusSearch 고급 구문, parse/렌더 HTML, 카테고리 멤버 역방향(`list=categorymembers`).

## UNVERIFIED / provenance 노트
- 모든 엔드포인트·응답 필드는 라이브(en.wikipedia.org)에서 확인했다: REST 검색(`pages[]`, total 없음), rest_v1 요약(`type`·`extract`·`content_urls.desktop.page`·`thumbnail.source`·`wikibase_item`·`coordinates`), TextExtracts(`formatversion=2` → `query.pages[]` 배열·`missing:true`), links/categories(`links[]`·`categories[]`·`redirects[]`).
- Action API의 **HTTP 200 + `{error}`** 봉투는 라이브에서 확인(`action=nonsense` → `badvalue`, `exchars=abc` → `badinteger`). REST 검색 응답에는 **total 필드가 없다**(라이브 확인).
- `WIKIPEDIA_API_TOKEN`(Bearer) 자체는 라이브에서 토큰 없이 검증할 수 없어 **헤더 조립만 단위 테스트로 확인**했다(토큰 유효성·완화 효과는 미검증).

## 확장 포인트
- 미디어(`/api/rest_v1/page/media-list/{title}`), 관련 문서(`/api/rest_v1/page/related/{title}`), 역링크(`list=backlinks`), 카테고리 멤버(`list=categorymembers`), 좌표 기반 근접 검색(`list=geosearch`)은 동일 패턴으로 경로 상수·도구 추가. 위키데이터 엔티티 상세는 별도 서비스(요약의 `wikibase_item`이 브리지).
12 changes: 12 additions & 0 deletions arcsolve/services/wikipedia/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from arcsolve.service import Service
from arcsolve.services.wikipedia.tools import register

SERVICE = Service(
name="wikipedia",
register=register,
docs_url="https://www.mediawiki.org/wiki/API:REST_API/Reference",
summary="위키백과 읽기(검색·요약·본문·링크)",
# 무인증으로 전체 읽기 동작 — 단 식별용 User-Agent 헤더 요구(기본값 contract.DEFAULT_USER_AGENT,
# WIKIPEDIA_USER_AGENT로 덮어씀). (선택) WIKIPEDIA_API_TOKEN으로 레이트리밋 완화.
# 인터랙티브 OAuth 아님 → make_auth_client 없음.
)
Loading
Loading