API 없이 프로그래밍 방식으로 DokuWiki에 게시

내가 작업 중인 프로젝트에서 우리는 문서화를 위해 DokuWiki을 사용합니다. 손으로 쓴 텍스트 외에도 매일 업데이트되는 자동 생성 콘텐츠를 통합하고 싶었습니다.

Plugin that provides a REST API 이 있지만 기본적으로 XML-RPC 만 있습니다. 그다지 화려하지는 않지만 단순히 간단한 페이지 콘텐츠를 푸시하는 데는 충분합니다. 그래서 sample jQuery clientfetch 버전을 시도했습니다.

ℹ Note that the following examples use the that required Node 18+.



const WIKI_URL = 'https://example.com/dokuwiki'

const xml=`
  <?xml version="1.0"?>
  <methodCall>
    <methodName>wiki.getRPCVersionSupported</methodName>
    <params></params>
  </methodCall>`

fetch(WIKI_URL + '/lib/exe/xmlrpc.php', {
  method: 'POST',
  body: xml,
  headers: { 'Content-Type': 'text/xml' }
})
  .then(response => response.text())
  .then(text => console.log(text))


다음 응답을 인쇄했습니다.

<methodResponse>
  <fault>
    <value>
      <struct>
        <member>
          <name>faultCode</name>
          <value><int>-32605</int></value>
        </member>
        <member>
          <name>faultString</name>
          <value><string>XML-RPC server not enabled.</string></value>
        </member>
      </struct>
    </value>
  </fault>
</methodResponse>


어, 안타까운 😐
이제 DokuWiki 인스턴스는 여러 팀에서 공유됩니다. 플러그인을 설치하거나 XML-RPC API를 활성화하려면 최소한 관료주의가 필요합니다.

그래서 API 없이 해결하려고 노력했습니다. 브라우저에서 할 수 있다면 스크립트가 할 수 있는 방법이 있어야 합니다. 맞습니까?

그래서 가장 먼저 한 일은 브라우저에서 페이지를 편집할 때 DokuWiki가 내부적으로 수행하는 작업을 살펴보는 것이었습니다. 원칙적으로는 간단한 HTML 형식입니다.



양식 데이터는 해당 HTTP 요청을 만드는 것이 그렇게 간단하지 않다는 것을 보여줍니다. 양식에서 일부 값을 읽거나 값을 생성하는 방법을 리버스 엔지니어링해야 할 수도 있습니다. 특히 sectokchangecheck가 수상해 보입니다.

시간을 절약하기 위해 첫 번째 충동은 Selenium과 같은 UI 자동화 도구를 사용하는 것이었지만 운 좋게도 더 간단한 솔루션을 찾았습니다.

"서버용으로 특별히 설계된 핵심 jQuery 구현"인 cheerio을 기반으로 합니다. 이를 통해 요청을 작성하는 것은 jQuery를 사용하여 양식을 제출하는 것만큼 쉬워집니다(거의).

하지만 먼저 로그인해야 합니다. 이를 위해 fetch 지원 쿠키를 만들어야 합니다(참고로 XML-RPC에서도 this would have been the case 🙄). 패키지fetch-cookie를 활용해서 했습니다. 그러다가 DokuWiki의 로그인 양식이 어떻게 작동하는지 살펴보았고 다음과 같은 요청을 했습니다.

const makeFetchCookie = require('fetch-cookie')
const fetchCookie = makeFetchCookie(fetch)

const WIKI_URL = 'https://example.com/dokuwiki'
const USERNAME = 'example'
const PASSWORD = 'example'

await fetchCookie(WIKI_URL + '/doku.php?do=login', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({ u: USERNAME, p: PASSWORD })
})


그런 다음 업데이트하려는 페이지의 편집 양식을 요청할 수 있습니다.

const PAGE_PATH = 'path/to/page'

const html = await fetchCookie(`${WIKI_URL}/doku.php/${PAGE_PATH}?do=edit`)
      .then(response => response.text())


HTML을 cheerio에 로드하고 ID가 wiki__text인 텍스트 영역을 찾습니다. 그런 다음 텍스트 영역의 내용을 업데이트할 수 있습니다.

const cheerio = require('cheerio')

const $ = cheerio.load(html)

const textarea =  $('textarea#wiki__text')

textarea.text('This is the new, //generated//, page content.')


기존 텍스트의 일부를 교체하려는 경우 인수 없이 textarea.text()를 호출하여 현재 내용을 가져올 수 있습니다.

이제 다음과 같은 방법으로 POST 요청에 대한 콘텐츠 본문을 가져올 수 있습니다.

const formData = $('form#dw__editform').serialize() + '&do[save]='

do[save]=의 추가는 제출 버튼을 클릭할 때 추가되기 때문에 필요합니다. 이것이 없으면 실제로 데이터가 저장되지 않습니다.

마지막으로 요청을 작성하고 보낼 수 있습니다.

await fetchCookie(`${WIKI_URL}/doku.php/${PAGE_PATH}?do=edit`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: formData
})


우리는 API 없이 프로그래밍 방식으로 DokuWiki에 성공적으로 게시했습니다 🎉

전체 예제 코드:

const cheerio = require('cheerio')
const makeFetchCookie = require('fetch-cookie')
const fetchCookie = makeFetchCookie(fetch)

const WIKI_URL = 'https://example.com/dokuwiki'
const PAGE_PATH = 'path/to/page'
const USERNAME = 'example'
const PASSWORD = 'example'

async function main() {

  await fetchCookie(WIKI_URL + '/doku.php?do=login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({ u: USERNAME, p: PASSWORD })
  })

  const html = await fetchCookie(`${WIKI_URL}/doku.php/${PAGE_PATH}?do=edit`)
      .then(response => response.text())

  const $ = cheerio.load(html)

  const textarea =  $('textarea#wiki__text')

  textarea.text('This is the new, //generated//, page content.')

  const formData = $('form#dw__editform').serialize() + '&do[save]='

  await fetchCookie(`${WIKI_URL}/doku.php/${PAGE_PATH}?do=edit`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: formData
  })
}

main()

좋은 웹페이지 즐겨찾기