PCでKindleの書籍リストを取得するGo言語のコード

Kindle for PCが持つ情報を使って、Kindleの書籍リストを取得するGo言語のコードです。Gistにはっつけてたものです([extkindlecache.go](https://gist.github.com/hoshimin108/795b5010733136b7521e569331debda6 "extkindlecache.go"))。昔はQiitaにGist連携なんてものがあったようですが、いまは無くなったみたいですね。

[Kindle蔵書一覧を出力するツール作成時の工夫](https://qiita.com/tsukasa_labz/items/08d6217723941fd139b3 "Kindle蔵書一覧を出力するツール作成時の工夫")で紹介されているアプリケーションを使うのが便利かと思いますが、Go言語を使っている人で自分でやってみたいかたには役に立つんでは無いかな？と思い記事を書きました。

macOSを使っている人は[Kindle for Macの情報を使って蔵書一覧を作成する](https://namaraii.com/post/2022/20220107/ "Kindle for Macの情報を使って蔵書一覧を作成する")にKindleSyncMetadataCache.xmlの場所が書かれています。RubyでXMLをパースするコードがあります。Kindle for MacのKindleSyncMetadataCache.xmlの構造がKindle for PCと同じならば僕が書いたコードも動作するでしょう。

あと、「Kindle Unlimitedで読んでいる本はXMLに出力されない」そうです。Kindle for Macの話ですが、Kindle for PCも同じでしょう。自分はKindle Unlimitedを利用していないのでそのあたりは知りません。

```go:ReadKindleCache.go
package main

//
// Kindle for PCが作る "KindleSyncMetadataCache.xml" を読み込んで、書籍リストを作る。
// 保存場所：
// "C:\Users\{アカウント名}\AppData\Local\Amazon\Kindle\Cache\KindleSyncMetadataCache.xml"
//
//　"output.txt"という名前のファイルに、情報をタブ区切り形式で出力する。
//

import (
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"os"
	"strings"
	"time"
)

type kindleCache struct {
	XMLName      xml.Name   `xml:"response"`
	MetaDataList []metadata `xml:"add_update_list>meta_data"`
}

type metadata struct {
	XMLName         xml.Name `xml:"meta_data"`
	Asin            string   `xml:"ASIN"`
	Title           string   `xml:"title"`
	Authors         []string `xml:"authors>author"`
	PublicationDate xtime    `xml:"publication_date"`
	PurchaseDate    xtime    `xml:"purchase_date"`
	TextbookType    string   `xml:"textbook_type"`
	CdeContenttype  string   `xml:"cde_contenttype"`
	ContentType     string   `xml:"content_type"`
}

type xtime struct {
	time.Time
}

func (t *xtime) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
	var v string
	if err := d.DecodeElement(&v, &start); err != nil {
		return err
	}
	v = strings.Trim(v, " \t") // 要らないとは思うが……。
	if v == "" {
		v = "0001-01-01T00:00:00+0000"
	}
	tm, err := time.Parse("2006-01-02T15:04:05-0700", v)
	if err != nil {
		return err
	}
	*t = xtime{tm}
	return nil
}

// わざわざタイムゾーンを設定しないでも良いかもしれないが……。
const location = "Asia/Tokyo"

func init() {
	loc, err := time.LoadLocation(location)
	if err != nil {
		loc = time.FixedZone(location, 9*60*60)
	}
	time.Local = loc
}

func main() {
	file, err := os.Open("KindleSyncMetadataCache.xml") // For read access.
	if err != nil {
		fmt.Printf("error: %v", err)
		return
	}
	defer file.Close()
	data, err := ioutil.ReadAll(file)
	if err != nil {
		fmt.Printf("error: %v", err)
		return
	}
	v := kindleCache{}
	err = xml.Unmarshal(data, &v)
	if err != nil {
		fmt.Printf("error: %v", err)
		return
	}

	ofp, err := os.Create("output.txt")
	if err != nil {
		fmt.Printf("error: %v", err)
		return
	}
	defer ofp.Close()

	for _, md := range v.MetaDataList {
		// 著者名は"|"でつなげて出力するので、"|"があれば" "に置換しておく。
		// 外国人名で"James, E L"などあるので連結する文字に","は使わず、まず著者名に使われないと思われる"|"としておく。
		// 下の置換もいらないと思うが念のため。
		sz := len(md.Authors)
		authors := make([]string, sz, sz)
		for idx, author := range md.Authors {
			authors[idx] = strings.ReplaceAll(author, "|", " ")
		}
		ofp.WriteString(fmt.Sprintf("%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", md.Asin, md.Title, strings.Join(authors, "|"), md.PublicationDate.Local().Format("2006/01/02 15:04:05"), md.PurchaseDate.Local().Format("2006/01/02 15:04:05"), md.TextbookType, md.CdeContenttype, md.ContentType))
	}
}
```