1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
|
package main
import (
"bytes"
"database/sql"
"rss-kakapo/logic"
"strings"
"unicode/utf8"
"github.com/mmcdole/gofeed"
)
// metaSchema is filled by analyzeMeta: for every analysed page, what it and
// the feed rss-kakapo follows for it say the site is called, and what it is
// about. Which of the two to name a feed account after is the question.
const metaSchema = `
CREATE TABLE IF NOT EXISTS meta (
handle TEXT PRIMARY KEY REFERENCES page(handle),
page_title TEXT NOT NULL, -- logic.ReadSiteMeta's, as rss-kakapo names the account now
page_desc TEXT NOT NULL,
feed_url TEXT, -- NULL: no feed fetched for the page
feed_error TEXT, -- the fetch failed, or the feed did not parse
feed_type TEXT, -- rss, atom or json
feed_title TEXT, -- white space collapsed, like page_title
feed_desc TEXT,
items INTEGER,
-- page_title is the title of one of the feed's items, or holds it: the
-- page is a post, or names itself after the newest one.
page_is_item INTEGER,
-- the title of the item page_is_item matched, for checking by eye
item_title TEXT
);
`
// minItemTitle is the shortest item title page_is_item matches on, in
// characters: shorter ones ("Home", "News", "第1話") are as likely part of
// the site's own name. "No Alarm" and "Comet Ice" are long enough.
const minItemTitle = 6
// analyzeMeta fills meta for the pages of page, within analyze's tx, which
// emptied it.
func analyzeMeta(tx *sql.Tx) error {
rows, err := tx.Query(`SELECT f.handle, f.body, ff.url, ff.status, ff.error, ff.body
FROM page p JOIN fetch f ON f.handle=p.handle
LEFT JOIN feed_fetch ff ON ff.handle=p.handle`)
if err != nil {
return err
}
type row struct {
handle string
page []byte
feedUrl, errText *string
status *int
feed []byte
}
var all []row
for rows.Next() {
var r row
if err = rows.Scan(&r.handle, &r.page, &r.feedUrl, &r.status, &r.errText, &r.feed); err != nil {
rows.Close()
return err
}
all = append(all, r)
}
rows.Close()
if err = rows.Err(); err != nil {
return err
}
for _, r := range all {
sm := logic.ReadSiteMeta(bytes.NewReader(r.page))
m := metaRow{handle: r.handle, pageTitle: sm.Title, pageDesc: sm.Description, feedUrl: r.feedUrl}
if r.feedUrl != nil {
m.readFeed(r.status, r.errText, r.feed)
}
if err = m.store(tx); err != nil {
return err
}
}
return nil
}
type metaRow struct {
handle, pageTitle, pageDesc string
feedUrl *string
feedError *string
feedType, feedTitle *string
feedDesc *string
items *int
pageIsItem *bool
itemTitle *string
}
func (m *metaRow) readFeed(status *int, errText *string, body []byte) {
fail := func(s string) { m.feedError = &s }
switch {
case errText != nil:
fail(*errText)
return
case status == nil || *status != 200:
fail("status not 200")
return
}
feed, err := gofeed.NewParser().Parse(bytes.NewReader(body))
if err != nil {
fail("parse: " + err.Error())
return
}
typ, title, desc := feed.FeedType, collapse(feed.Title), collapse(feed.Description)
n := len(feed.Items)
m.feedType, m.feedTitle, m.feedDesc, m.items = &typ, &title, &desc, &n
isItem := false
pt := strings.ToLower(m.pageTitle)
for _, itm := range feed.Items {
it := collapse(itm.Title)
if utf8.RuneCountInString(it) < minItemTitle || !strings.Contains(pt, strings.ToLower(it)) {
continue
}
// A site whose posts are named after it is not a page named after a
// post.
if title != "" && strings.EqualFold(it, title) {
continue
}
isItem = true
m.itemTitle = &it
break
}
m.pageIsItem = &isItem
}
func (m *metaRow) store(tx *sql.Tx) error {
_, err := tx.Exec(`INSERT INTO meta (handle, page_title, page_desc, feed_url, feed_error,
feed_type, feed_title, feed_desc, items, page_is_item, item_title)
VALUES (?,?,?,?,?,?,?,?,?,?,?)`,
m.handle, m.pageTitle, m.pageDesc, m.feedUrl, m.feedError,
m.feedType, m.feedTitle, m.feedDesc, m.items, m.pageIsItem, m.itemTitle)
return err
}
// collapse trims s and collapses its runs of white space, as
// logic.ReadSiteMeta does with a page's title.
func collapse(s string) string {
return strings.Join(strings.Fields(s), " ")
}
|