Archived
1
0

Merge branch 'master' of ssh://github.com/majestrate/nntpchan

This commit is contained in:
Jeff Becker 2018-11-27 10:25:16 -05:00
commit 053708a9cb
No known key found for this signature in database
GPG Key ID: F357B3B42F6F9B05
9 changed files with 92 additions and 40 deletions

View File

@ -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()
@ -81,7 +82,7 @@ type Database interface {
GetAllArticlesInGroup(group string, send chan ArticleEntry) GetAllArticlesInGroup(group string, send chan ArticleEntry)
CountAllArticlesInGroup(group string) (int64, error) CountAllArticlesInGroup(group string) (int64, error)
GetAllArticles() []ArticleEntry GetAllArticles() []ArticleEntry
SetConnectionLifetime(seconds int) SetConnectionLifetime(seconds int)
SetMaxOpenConns(n int) SetMaxOpenConns(n int)
SetMaxIdleConns(n int) SetMaxIdleConns(n int)
@ -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

View File

@ -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

View File

@ -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()

View File

@ -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:], "/")

View File

@ -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 )"

View File

@ -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)
} }

View File

@ -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)

View File

@ -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>

View File

@ -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>