Merge branch 'master' of ssh://github.com/majestrate/nntpchan
This commit is contained in:
commit
053708a9cb
@ -47,13 +47,12 @@ func (self PostEntry) Count() int64 {
|
|||||||
return self[1]
|
return self[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PostEntryList []PostEntry
|
||||||
|
|
||||||
|
|
||||||
// stats about newsgroup postings
|
// stats about newsgroup postings
|
||||||
type NewsgroupStats struct {
|
type NewsgroupStats struct {
|
||||||
Posted []PostEntry
|
PPD int64
|
||||||
Delted []PostEntry
|
|
||||||
Hits []PostEntry
|
|
||||||
Start time.Time
|
|
||||||
End time.Time
|
|
||||||
Name string
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,6 +69,8 @@ type NewsgroupListEntry [3]string
|
|||||||
|
|
||||||
type NewsgroupList []NewsgroupListEntry
|
type NewsgroupList []NewsgroupListEntry
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
type Database interface {
|
type Database interface {
|
||||||
Close()
|
Close()
|
||||||
CreateTables()
|
CreateTables()
|
||||||
@ -124,6 +125,9 @@ type Database interface {
|
|||||||
// if N <= 0 then count all we have now
|
// if N <= 0 then count all we have now
|
||||||
CountPostsInGroup(group string, time_frame int64) int64
|
CountPostsInGroup(group string, time_frame int64) int64
|
||||||
|
|
||||||
|
// get the stats for the overview page
|
||||||
|
GetNewsgroupStats() ([]NewsgroupStats, error)
|
||||||
|
|
||||||
// get all replies to a thread
|
// get all replies to a thread
|
||||||
// if last > 0 then get that many of the last replies
|
// if last > 0 then get that many of the last replies
|
||||||
// start at reply number start
|
// start at reply number start
|
||||||
|
@ -224,6 +224,8 @@ type boardPageRow struct {
|
|||||||
Hour int64
|
Hour int64
|
||||||
Day int64
|
Day int64
|
||||||
All int64
|
All int64
|
||||||
|
Hi int64
|
||||||
|
Lo int64
|
||||||
}
|
}
|
||||||
|
|
||||||
type boardPageRows []boardPageRow
|
type boardPageRows []boardPageRow
|
||||||
@ -242,6 +244,7 @@ func (self boardPageRows) Swap(i, j int) {
|
|||||||
self[i], self[j] = self[j], self[i]
|
self[i], self[j] = self[j], self[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
type postsGraphRow struct {
|
type postsGraphRow struct {
|
||||||
day time.Time
|
day time.Time
|
||||||
Num int64
|
Num int64
|
||||||
|
@ -776,10 +776,13 @@ func (self *nntpConnection) handleLine(daemon *NNTPDaemon, code int, line string
|
|||||||
// wtf?!
|
// wtf?!
|
||||||
conn.PrintfLine("503 idkwtf happened: %s", err.Error())
|
conn.PrintfLine("503 idkwtf happened: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// we dont got it (by msgid)
|
||||||
|
conn.PrintfLine("430 %s", msgid)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// we dont got it
|
// we dont got it (by num)
|
||||||
conn.PrintfLine("430 %s", msgid)
|
conn.PrintfLine("423 %s", msgid)
|
||||||
}
|
}
|
||||||
} else if cmd == "IHAVE" {
|
} else if cmd == "IHAVE" {
|
||||||
if !self.authenticated {
|
if !self.authenticated {
|
||||||
@ -1175,7 +1178,7 @@ func (self *nntpConnection) handleLine(daemon *NNTPDaemon, code int, line string
|
|||||||
dw := conn.DotWriter()
|
dw := conn.DotWriter()
|
||||||
for _, entry := range list {
|
for _, entry := range list {
|
||||||
if ValidNewsgroup(entry[0]) {
|
if ValidNewsgroup(entry[0]) {
|
||||||
io.WriteString(dw, fmt.Sprintf("%s %s %s y\r\n", entry[0], entry[1], entry[2]))
|
io.WriteString(dw, fmt.Sprintf("%s %s %s y\r\n", entry[0], entry[2], entry[1]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dw.Close()
|
dw.Close()
|
||||||
|
@ -97,6 +97,12 @@ func (self *nullHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// board list page
|
||||||
|
if strings.ToLower(path) == "/b/" {
|
||||||
|
template.genBoardList(self.prefix, self.name, w, self.database, i18n)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(path, "/b/") {
|
if strings.HasPrefix(path, "/b/") {
|
||||||
// board handler
|
// board handler
|
||||||
parts := strings.Split(path[3:], "/")
|
parts := strings.Split(path[3:], "/")
|
||||||
|
@ -152,9 +152,11 @@ const GetCitesByPostHashLike = "GetCitesByPostHashLike"
|
|||||||
const GetYearlyPostHistory = "GetYearlyPostHistory"
|
const GetYearlyPostHistory = "GetYearlyPostHistory"
|
||||||
const GetNewsgroupList = "GetNewsgroupList"
|
const GetNewsgroupList = "GetNewsgroupList"
|
||||||
const CountUkko = "CountUkko"
|
const CountUkko = "CountUkko"
|
||||||
|
const GetNewsgroupStats = "GetNewsgroupStats"
|
||||||
|
|
||||||
func (self *PostgresDatabase) prepareStatements() {
|
func (self *PostgresDatabase) prepareStatements() {
|
||||||
self.stmt = map[string]string{
|
self.stmt = map[string]string{
|
||||||
|
GetNewsgroupStats: "SELECT COUNT(message_id), newsgroup FROM articleposts WHERE time_posted > (EXTRACT(epoch FROM NOW()) - (24*3600)) GROUP BY newsgroup",
|
||||||
NewsgroupBanned: "SELECT 1 FROM BannedGroups WHERE newsgroup = $1",
|
NewsgroupBanned: "SELECT 1 FROM BannedGroups WHERE newsgroup = $1",
|
||||||
ArticleBanned: "SELECT 1 FROM BannedArticles WHERE message_id = $1",
|
ArticleBanned: "SELECT 1 FROM BannedArticles WHERE message_id = $1",
|
||||||
GetAllNewsgroups: "SELECT name FROM Newsgroups WHERE name NOT IN ( SELECT newsgroup FROM BannedGroups )",
|
GetAllNewsgroups: "SELECT name FROM Newsgroups WHERE name NOT IN ( SELECT newsgroup FROM BannedGroups )",
|
||||||
@ -2051,6 +2053,21 @@ func (self *PostgresDatabase) GetUkkoPageCount(perpage int) (count int64, err er
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *PostgresDatabase) GetNewsgroupStats() (stats []NewsgroupStats, err error) {
|
||||||
|
var rows *sql.Rows
|
||||||
|
rows, err = self.conn.Query(self.stmt[GetNewsgroupStats])
|
||||||
|
if err == nil {
|
||||||
|
for rows.Next() {
|
||||||
|
var s NewsgroupStats
|
||||||
|
rows.Scan(&s.PPD, &s.Name)
|
||||||
|
stats = append(stats, s)
|
||||||
|
}
|
||||||
|
rows.Close()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
func (self *PostgresDatabase) FindHeaders(group, headername string, lo, hi int64) (hdr ArticleHeaders, err error) {
|
func (self *PostgresDatabase) FindHeaders(group, headername string, lo, hi int64) (hdr ArticleHeaders, err error) {
|
||||||
hdr = make(ArticleHeaders)
|
hdr = make(ArticleHeaders)
|
||||||
q := "SELECT header_value FROM nntpheaders WHERE header_name = $1 AND header_article_message_id IN ( SELECT message_id FROM articleposts WHERE newsgroup = $2 )"
|
q := "SELECT header_value FROM nntpheaders WHERE header_name = $1 AND header_article_message_id IN ( SELECT message_id FROM articleposts WHERE newsgroup = $2 )"
|
||||||
|
@ -475,36 +475,29 @@ func (self *templateEngine) genGraphs(prefix string, wr io.Writer, db Database,
|
|||||||
|
|
||||||
func (self *templateEngine) genBoardList(prefix, name string, wr io.Writer, db Database, i18n *I18N) {
|
func (self *templateEngine) genBoardList(prefix, name string, wr io.Writer, db Database, i18n *I18N) {
|
||||||
// the graph for the front page
|
// the graph for the front page
|
||||||
var frontpage_graph boardPageRows
|
var graph boardPageRows
|
||||||
|
|
||||||
// for each group
|
stats, err := db.GetNewsgroupStats()
|
||||||
groups := db.GetAllNewsgroups()
|
if err != nil {
|
||||||
for _, group := range groups {
|
log.Println("error getting board list", err)
|
||||||
// exclude banned
|
io.WriteString(wr, err.Error())
|
||||||
banned, _ := db.NewsgroupBanned(group)
|
return
|
||||||
if banned {
|
}
|
||||||
continue
|
|
||||||
}
|
for idx := range stats {
|
||||||
// posts this hour
|
graph = append(graph, boardPageRow{
|
||||||
hour := db.CountPostsInGroup(group, 3600)
|
Board: stats[idx].Name,
|
||||||
// posts today
|
Day: stats[idx].PPD,
|
||||||
day := db.CountPostsInGroup(group, 86400)
|
|
||||||
// posts total
|
|
||||||
all := db.CountPostsInGroup(group, 0)
|
|
||||||
frontpage_graph = append(frontpage_graph, boardPageRow{
|
|
||||||
All: all,
|
|
||||||
Day: day,
|
|
||||||
Hour: hour,
|
|
||||||
Board: group,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
param := map[string]interface{}{
|
param := map[string]interface{}{
|
||||||
"prefix": prefix,
|
"prefix": prefix,
|
||||||
"frontend": name,
|
"frontend": name,
|
||||||
}
|
}
|
||||||
sort.Sort(frontpage_graph)
|
sort.Sort(graph)
|
||||||
param["graph"] = frontpage_graph
|
param["graph"] = graph
|
||||||
_, err := io.WriteString(wr, self.renderTemplate("boardlist", param, i18n))
|
_, err = io.WriteString(wr, self.renderTemplate("boardlist", param, i18n))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("error writing board list page", err)
|
log.Println("error writing board list page", err)
|
||||||
}
|
}
|
||||||
|
@ -60,10 +60,34 @@ func EnsureDir(dirname string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var exp_valid_message_id = regexp.MustCompilePOSIX(`^<[a-zA-Z0-9$.]{2,128}@[a-zA-Z0-9\-.]{2,63}>$`)
|
// printableASCII tells whether string is made of US-ASCII printable characters
|
||||||
|
// except of specified one.
|
||||||
|
func printableASCII(s string, e byte) bool {
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
c := s[i]
|
||||||
|
// NOTE: doesn't include space, which is neither printable nor control
|
||||||
|
if c <= 32 || c >= 127 || c == e {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func ValidMessageID(id string) bool {
|
func ValidMessageID(id string) bool {
|
||||||
return exp_valid_message_id.MatchString(id)
|
/*
|
||||||
|
{RFC 3977}
|
||||||
|
o A message-id MUST begin with "<", end with ">", and MUST NOT
|
||||||
|
contain the latter except at the end.
|
||||||
|
o A message-id MUST be between 3 and 250 octets in length.
|
||||||
|
o A message-id MUST NOT contain octets other than printable US-ASCII
|
||||||
|
characters.
|
||||||
|
|
||||||
|
additionally, we check path characters, they may be dangerous
|
||||||
|
*/
|
||||||
|
return len(id) >= 3 && len(id) <= 250 &&
|
||||||
|
id[0] == '<' && id[len(id)-1] == '>' &&
|
||||||
|
printableASCII(id[1:len(id)-1], '>') &&
|
||||||
|
strings.IndexAny(id[1:len(id)-1], "/\\") < 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// message id hash
|
// message id hash
|
||||||
@ -482,7 +506,7 @@ func IPNet2MinMax(inet *net.IPNet) (min, max net.IP) {
|
|||||||
maskb := []byte(inet.Mask)
|
maskb := []byte(inet.Mask)
|
||||||
maxb := make([]byte, len(netb))
|
maxb := make([]byte, len(netb))
|
||||||
|
|
||||||
for i, _ := range maxb {
|
for i := range maxb {
|
||||||
maxb[i] = netb[i] | (^maskb[i])
|
maxb[i] = netb[i] | (^maskb[i])
|
||||||
}
|
}
|
||||||
min = net.IP(netb)
|
min = net.IP(netb)
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<center>
|
<center>
|
||||||
<table class="activity" cellspacing="0" cellpadding="0" border="0" width="100%">
|
<table class="activity" cellspacing="0" cellpadding="0" border="0">
|
||||||
<tr>
|
<tr>
|
||||||
<!--
|
<!--
|
||||||
<td class="usage_td">
|
<td class="usage_td">
|
||||||
@ -32,9 +32,9 @@
|
|||||||
-->
|
-->
|
||||||
<td class="board_td">
|
<td class="board_td">
|
||||||
<table cellspacing="0" class="datatable" style="border-collapse: inherit;">
|
<table cellspacing="0" class="datatable" style="border-collapse: inherit;">
|
||||||
<tr><th>posts today</th><th>post rate</th><th>board</th></tr>
|
<tr><th>posts today</th><th>board</th></tr>
|
||||||
{{#graph}}
|
{{#graph}}
|
||||||
<tr><td class="right">{{Day}}</td><td class="right">{{Hour}} PPH</td><td><a href="{{prefix}}b/{{Board}}/">{{Board}}</a></td></tr>
|
<tr><td class="right">{{Day}}</td><td><a href="{{prefix}}b/{{Board}}/">{{Board}}</a></td></tr>
|
||||||
{{/graph}}
|
{{/graph}}
|
||||||
</table>
|
</table>
|
||||||
</td>
|
</td>
|
||||||
|
@ -26,9 +26,11 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<center>
|
<center>
|
||||||
<a href="{{prefix}}o/?lang={{i18n.Name}}"><img id="logo" src="{{prefix}}static/changolia-logo.png" /><h1>Enter</h1></a>
|
<img id="logo" src="{{prefix}}static/changolia-logo.png" />
|
||||||
<a href="{{prefix}}boards.html"><h2>Board List</h2></a>
|
<a href="{{prefix}}b/"><h2>Board List</h2></a>
|
||||||
|
<a href="{{prefix}}o/?lang={{i18n.Name}}">Firehose</a>
|
||||||
<a href="{{prefix}}static/faq.html">FAQ</a>
|
<a href="{{prefix}}static/faq.html">FAQ</a>
|
||||||
|
|
||||||
</center>
|
</center>
|
||||||
<hr/>
|
<hr/>
|
||||||
<footer>
|
<footer>
|
||||||
|
Reference in New Issue
Block a user