Qita에서 Zenn으로 이동

23003 단어 ZennQiitaidea
최근 몇 년간 Qita는 "업데이트됐다"며 이용 편의에 변함이 없었고, 로그인할 때마다 관심 없는 홍보 활동 알림을 보내는 등 체험이 좋지 않아 젠으로 옮기려 했다.

1. Qita 기사를 Zenn으로 옮기기


Qita의 글을 Zenn으로 옮겨 다음 기사에 소개된 것ikawaha/zenn-importer만 수행한다.
Qita/Zenn으로도 손잡이 글 관리
Qita API는 사용자를 지정하면 공개된 글의 일람표와 그 내용을 다운로드할 수 있기 때문에 Qita의 방문 영패를 발행하지 않아도 일괄적으로 글을 다운로드할 수 있다.

2. Qita의 글 내용을 Zenn에 다시 쓰기


꼭 필요한 건 아니지만 Qita와 Zenn에 같은 글을 남기면 예의가 아닌 것 같아 Qita의 글 내용을 Zenn의 글로 다시 썼다.
목적지 Zenn의 글의 URL은 1.의 절차에 사용된 ikawaha/zenn-importerREADME에 적힌 규칙을 따르기 때문에 목적지로 방향을 바꾸는 URL을 기계적으로 생성할 수 있다.
통일적으로 다시 쓴 각본으로 다음과 같은 내용을 썼다.(글 내용을 다시 쓰기 위해서는 과연 Qita의 방문 영패가 필요하다)
너무 오래된 기사는 옮길 필요가 없다고 생각해 처음 쓴 매터모스트 관련 기사 이후 기사를 옮길 대상으로 삼았다QiitaPerPage = 74라는 어설픈 숫자가 됐다.
QitaQiitaAccessTokenQiitaUser를 방문하기 위해 어떤 보도를 이전 대상의 상수QiitaPerPage, QiitaMaxPage로 할지 결정하는 것을 개작하고 집행하기만 하면 모든 보도의 내용은 Zenn에 보도하는 방향으로 바뀐다.(본인이 쓴 스크립트라 동작이 보장되지 않음)
main.go
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"strconv"
	"time"
)

const (
	QiitaAccessToken = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
	QiitaUser        = "kaakaa_hoe"
	QiitaPerPage     = 74
	QiitaMaxPage = 1
)

type QiitaItem struct {
	CreatedAt string `json:"created_at"`
	Id        string `json:"id"`
	Title     string `json:"title"`
	Private   bool   `json:"private"`
}

type QiitaPatch struct {
	Body  string `json:"body"`
	Title string `json:"title"`
}

func main() {
	client := &http.Client{}

	var all []QiitaItem
	page := 1
	for {
		log.Printf("Get items. page %d", page)
		items, err := getItems(client, page)
		if err != nil {
			log.Fatal(err)
		}
		all = append(all, items...)
		if len(items) < QiitaPerPage {
			break
		}
		log.Println(len(items))
		page += 1
		if QiitaMaxPage < page {
			break
		}
	}

	nums := len(all)
	for i, v := range all {
		if v.Private {
			// skip private article
			continue
		}
		log.Printf("Patching article(%d/%d): %s", i+1, nums, v.Title)
		if err := patchItem(client, v); err != nil {
			log.Printf(" [ERROR] %s", err.Error())
		}
	}
}

func getItems(client *http.Client, page int) ([]QiitaItem, error) {
	req, err := http.NewRequest(http.MethodGet, "https://qiita.com/api/v2/authenticated_user/items", nil)
	if err != nil {
		return nil, err
	}

	params := req.URL.Query()
	params.Add("page", strconv.Itoa(page))
	params.Add("per_page", fmt.Sprintf("%d", QiitaPerPage))
	params.Add("query", fmt.Sprintf("user:%s", QiitaUser))
	req.URL.RawQuery = params.Encode()

	req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", QiitaAccessToken))

	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("failed to get items %s", resp.Status)
	}
	defer resp.Body.Close()
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	var items []QiitaItem
	if err := json.Unmarshal(b, &items); err != nil {
		return nil, err
	}
	return items, nil
}

func patchItem(client *http.Client, item QiitaItem) error {
	t, err := time.Parse(time.RFC3339, item.CreatedAt)
	if err != nil {
		log.Fatal(err)
	}
	url := fmt.Sprintf("https://zenn.dev/kaakaa/articles/qiita-%s-%s", t.Format("20060102"), item.Id)

	b, err := json.Marshal(QiitaPatch{
		Body:  fmt.Sprintf("この記事は Zenn に移行しました。\n%s", url),
		Title: item.Title,
	})
	if err != nil {
		return err
	}
	req, err := http.NewRequest(http.MethodPatch, fmt.Sprintf("https://qiita.com/api/v2/items/%s", item.Id), bytes.NewBuffer(b))
	if err != nil {
		return err
	}
	req.Header.Add("Content-Type", "application/json")
	req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", QiitaAccessToken))

	resp, err := client.Do(req)
	if err != nil {
		return nil
	}
	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("failed to request patch: %s", resp.Status)
	}
	return nil
}
위 스크립트를 실행하면 Qita의 문장을 통일적으로 개작할 수 있다.

과도문에 대한 투고 시간


전환할 때의 병목.
젠은 프론트마터에 공개 시점 등을 지정할 수 없었고, 큐타에서 오래된 기사는 젠에서도 최신 기사로 처리됐기 때문이다.
이로 인해 자신의 계정 글 일람이 오래된 물건에 묻히고, 해시태그(Zenn에서 Topics)가 오래된 기사에 오염된 영향이 컸다.
전자에 관해서는 먼저 기사를 전부 투고한 다음에 최신 하고 싶은 기사로 갱신하면 기사의 순서를 조작할 수 있을 것 같지만, 그게 아니라 투고 날짜에 따라 결정되는 것 같다.여긴 포기할 수밖에 없잖아.
후자에 관해서는 자신 이외의 사용자에게 영향을 미칠 수 있는 부분이지만, 자신이 쓴 많은 Mattermost의 글이 있으니, Mattermot는 다행이다.아직 젠에 기사가 많지 않아서 눈을 감았다.열람자가 많아 보이는 토픽스를 착용했다면 토픽스를 떼어내라.
Frontmatter에 설정date할 수 있다는 요구를 하고 싶었지만 지금은 날짜를 설정해도 무시하는 동작입니다. 때문에 date 상당한 정보를 유효화하는 변경이 기존 기사에 미치는 영향이 커 보이기 때문에 좋은 방법이 아니라고 생각해서 요구를 포기했다.

좋은 웹페이지 즐겨찾기