Created
August 20, 2012 06:09
-
-
Save FeepingCreature/3401510 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module mspatest; | |
import std.string, sqlite3, std.boehm, std.zlib, std.http, std.util, std.file; | |
static import c.stdlib; | |
import std.fun, std.cgi; | |
import mspa; | |
alias FOURSTRING = (string, string, string, string); // small shortcut | |
alias FIVESTRING = (string, string, string, string, string); | |
string filterTag(string tag) { | |
char[auto~] filtered-tagname; | |
for auto ch <- tag { | |
if ch >= "a" && ch <= "z" || ch >= "A" && ch <= "Z" || ch >= "0" && ch <= "9" | |
|| "_- .,".find(""~ch) != -1 | |
filtered-tagname ~= ch; | |
} | |
return filtered-tagname[]; | |
} | |
string getThreadName(string url) { | |
url = url.between("?", ""); | |
if (auto first = url.between("", "/")) url = first; | |
return url.between("-", ""); | |
} | |
int counter; | |
string reformatPost(string content, bool spoilers, string idText, bool linkToAnchors, noNyud = false) { | |
/* strip out existing anchor links */ | |
do { | |
auto startpos = content.find("#post"); | |
} while (startpos) { | |
content = content[0 .. startpos] ~ "\"" ~ content[startpos .. $].between("\"", ""); | |
} | |
if (linkToAnchors) { | |
content = content.replace("href=\"showthread.php?p=", "href=\"#post"); | |
} else { | |
content = content.replace("href=\"showthread.php?p=", "href=\"$idText"); | |
} | |
string startMarker = "<!-- BEGIN TEMPLATE: bbcode_quote"; | |
string endMarker = "<!-- END TEMPLATE: bbcode_quote"; | |
string handleQuotes(string str) { | |
do { | |
auto quotePos = str.find(startMarker); | |
auto endPos = str.find(endMarker); | |
} while (quotePos && endPos && quotePos < endPos) { | |
auto startPos = quotePos + startMarker.length; | |
while (str[startPos .. endPos].find startMarker) != -1 { | |
str = str[0 .. startPos] ~ handleQuotes str[startPos .. $]; // recurse | |
endPos = str.find(endMarker); | |
if (endPos == -1) { | |
raise new Error "end marker no longer found after recursion step! unbalanced? $content"; | |
} | |
} | |
auto end = int:endPos; | |
end += str[end .. $].find(">") + 1; | |
auto quote = str[quotePos .. end]; | |
auto name = quote.between("<strong>", "</strong>"); | |
auto ref = quote.between("href=\"", "\""); | |
auto msg = quote.between("class=\"message\">", "\t\t</div"); | |
auto newtext = "<table><tr><td valign=\"top\">></td><td><div style=\"border: 1px solid; \"><div><span style=\"border-bottom: 1px solid; \">Originally posted by <a href=\"$ref\">$name</a></span></div>"; | |
newtext = "$newtext$msg</td></tr></table>"; | |
str = str[0..quotePos] ~ newtext ~ str[end .. $]; | |
} | |
return str; | |
} | |
string nyudImages(string src, string baseUrl) { | |
int offset; | |
do { | |
int srcpos = src[offset .. $].find("src=\""); | |
} while (srcpos != -1) { | |
srcpos += offset; | |
srcpos += 5; | |
int endpos = src[srcpos .. $].find("\""); | |
if (endpos == -1) raise new Error "Unterminated img src string wtf!! "; | |
endpos += srcpos; | |
auto tag = src[srcpos .. endpos]; | |
tag = baseUrl.followLink tag; | |
tag = tag.startsWith "http://"; | |
tag = "http://" ~ slice(&tag, "/") ~ ".nyud.net/" ~ tag; // lol side effect abuse | |
src = src[0 .. srcpos] ~ tag ~ src[endpos .. $]; | |
offset = srcpos + tag.length; | |
} | |
return src; | |
} | |
content = handleQuotes content; | |
if (!noNyud) content = nyudImages (content, "http://www.mspaforums.com/"); | |
do { | |
auto spoilerpos = content.find spoilertext; | |
} while spoilerpos != -1 { | |
string replacement; | |
if (spoilers) { | |
replacement = `<div style="border: 1px solid gray; padding: 1px; margin: 2px; "><span style="border: 1px dotted; ">Spoiler</span><div>`; | |
} else { | |
int id = counter ++; | |
replacement = `<div style="border: 1px solid gray; padding: 1px; margin: 2px; "><span style="border: 1px dotted; "> | |
<span class="box" id="??C_plus" onclick="javascript: css_show('??C'); css_show('??C_minus'); css_hide('??C_plus'); ">Show Spoiler</span> | |
<span class="box" id="??C_minus" onclick="javascript: css_hide('??C'); css_show('??C_plus'); css_hide('??C_minus'); " style="display: none; ">Hide spoiler</span> | |
</span><div id="??C" style="display: none; ">`.replace("??C", "spoilerdiv$id"); | |
} | |
content = content[0..spoilerpos] ~ replacement ~ content[spoilerpos + spoilertext.length .. $]; | |
} | |
content = content.replace("src=\"images/smilies", "src=\"http://www.mspaforums.com/images/smilies"); | |
content = content.replace("<b>", "<div class=\"inline bold\">").replace("</b>", "</div>"); | |
return content; | |
} | |
// prevent google from following links that potentially go to very large pages | |
alias sharedCSS = ` | |
<meta name="robots" content="index, nofollow" /> | |
<meta http-equiv="Content-type" content="text/html;charset=UTF-8" /> | |
<link rel="shortcut icon" href="favicon.ico" /> | |
<style type="text/css"> | |
table.boxed { | |
border: 1px solid black; | |
border-collapse: collapse; | |
} | |
th { | |
border-bottom: 2px solid black; | |
} | |
th.first, th.mid, td.first, td.mid { | |
border-right: 1px solid gray; | |
} | |
tr.even { | |
background-color: #def; | |
} | |
div.plustag:hover *.hidden { | |
visibility: visible; | |
} | |
div.minustag:hover *.hidden { | |
visibility: visible; | |
} | |
div.inline { | |
display: inline; | |
} | |
*.bold { | |
font-weight: bold; | |
} | |
*.box { | |
border: 1px solid; | |
background-color: #fff; | |
padding: 0px 2px 0px 2px; | |
margin: 1px; | |
} | |
*.box_outer { | |
border: 1px solid; | |
background-color: #fff; | |
padding: 0px 2px 0px 2px; | |
margin: 5px; | |
} | |
*.box_inner { | |
border: 1px solid; | |
padding: 0px 2px 0px 2px; | |
margin: 1px; | |
background-color: #efe; | |
} | |
*.hidden { | |
visibility: hidden; | |
position: absolute; | |
background-color: #ddd; | |
} | |
div.mewdiv { | |
position: absolute; | |
display: inline; | |
background-color: #eef; | |
visibility: hidden; | |
width: 60%; height: 90%; | |
} | |
table.mewtable { | |
width: 100%; height: 100%; | |
border-collapse: collapse; | |
} | |
iframe.mewframe { | |
width: 100%; height: 100%; | |
background-color: #ffe; | |
} | |
</style>`; | |
alias sharedJS = ` | |
<script type="text/javascript" src="http://code.jquery.com/jquery-latest.js"></script> | |
<script type="text/javascript"> | |
function submitForm(name) { | |
document.getElementById(name).submit(); | |
} | |
function loadFrame(name, divname, url) { | |
$('.mewdiv').css('visibility', 'hidden'); | |
var frame = document.getElementById(name), div = document.getElementById(divname); | |
frame.src = url; | |
div.style.visibility = "visible"; | |
scrollToThingy (div); | |
} | |
function scrollToThingy (thing) { | |
var targetY = 0; | |
while (thing) { | |
targetY += thing.offsetTop; | |
thing = thing.offsetParent; | |
} | |
window.scroll(0, targetY); | |
} | |
function css_show (name) { | |
document.getElementById(name).style.display = "inline"; | |
} | |
function css_hide (name) { | |
document.getElementById(name).style.display = "none"; | |
} | |
</script>`; | |
import c.stdio, std.time; | |
extern(C) { int fprintf(void*, char*, ...); void* stderr; } | |
void main(string[] args) { | |
sqlite3.profile = false; | |
bool wroteHeader; | |
void writeHTMLHeader(bool doctype = true) { | |
if (wroteHeader) return; | |
writeln "Content-type: text/html; charset=utf-8"; | |
writeln ""; | |
if (doctype) { | |
writeln `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">`; | |
writeln `<html xmlns="http://www.w3.org/1999/xhtml">`; | |
} else { | |
writeln `<html>`; | |
} | |
wroteHeader = true; | |
} | |
set-handler (Error err) { | |
writeHTMLHeader(); | |
writeln "<head><title>Error</title></head><body>"; | |
writeln "<h2>An error has occurred! </h2>"; | |
writeln "$err"; | |
writeln "</body></html>"; | |
invoke-exit "return"; | |
} | |
set-handler (SQLiteBusy) { | |
fprintf(FILE*:stderr, "Database busy. \n"); | |
sleep 1; | |
fprintf(FILE*:stderr, "Retrying. \n"); | |
invoke-exit "retry"; | |
} | |
define-exit "return" return; | |
initBoehm(); | |
auto db = new Database "threads.sqlite3"; | |
onExit db.close; | |
db.mkfast; | |
onSuccess if sqlite3.profile { | |
void errprint(string s) fprintf(FILE*:stderr, toStringz "$s\n"); | |
errprint "profile: "; | |
for auto tup <- profiledata { | |
errprint " $(tup[2]): $(tup[1]): $(tup[0])"; | |
} | |
} | |
string getEnvVar(string name) { | |
return CToString c.stdlib.getenv toStringz name; | |
} | |
auto query_string = getEnvVar "QUERY_STRING"; | |
auto http_cookie = getEnvVar "HTTP_COOKIE"; | |
// lol wtf | |
while (query_string.find("&") != -1) { | |
query_string = query_string.replace("&", "&"); | |
} | |
(string, string)[auto~] params; | |
for auto part <- query_string.split "&" { | |
if (part.find("=") != -1) | |
params ~= slice(part, "="); | |
else | |
params ~= (part, ""); | |
} | |
void processCookie() { | |
if (http_cookie) { | |
(string, string)[auto~] cookieparts; | |
for auto part <- http_cookie.split(";") { | |
auto name = slice(&part, "="); | |
cookieparts ~= (strip name, strip part); | |
} | |
for (string name, string value) <- cookieparts if name == "idmask" { | |
bool hit; | |
for int i <- 0..params.length if !hit { | |
if (params[i][0] == "idmask") { | |
params[i][1] = "$value"; | |
hit = true; | |
} | |
} | |
if !hit params ~= ("idmask", "$value"); | |
} | |
} | |
} | |
string getParam(string name, deflt = null) { | |
for auto par <- params | |
if (par[0] == name) return par[1].urldecode(); | |
return deflt; | |
} | |
void removeParam(string name) { | |
(string, string)[auto~] newparams; | |
for auto par <- params | |
if (par[0] != name) newparams ~= par; | |
params = newparams; | |
} | |
bool noNyud = getParam("nyud") == "0"; | |
string urlWithParams((string, string)[] array) { | |
string[auto~] res; | |
auto handledEntries = new bool[] array.length; | |
for auto par <- params { | |
bool foundReplacement; | |
for (int i, (string, string) tup) <- zip(0..-1, array) if !foundReplacement { | |
if (par[0] == tup[0]) { | |
if (tup[1]) res ~= "$(tup[0])=$(tup[1])"; | |
handledEntries[i] = true; | |
foundReplacement = true; | |
} | |
} | |
if !foundReplacement res ~= "$(par[0])=$(par[1])"; | |
} | |
for (int i, bool handled) <- zip(0..-1, handledEntries) | |
if !handled res ~= "$(array[i][0])=$(array[i][1])"; | |
return "?" ~ join!(string[auto~], string)(res, "&"); | |
} | |
string urlWithParam(string name, value) { | |
return urlWithParams [(name, value)]; | |
} | |
string authormask, authormask-sql = "and not length(?)"; | |
if auto author = getParam "author" { | |
auto alts = author.split("|"); | |
int offs; | |
authormask = author; | |
authormask-sql = "select ? AUTHORMASK where 0"; | |
for auto alt <- alts { | |
authormask-sql ~= " or posts.name = substr(AUTHORMASK, $(offs+1), $(alt.length))"; | |
offs += alt.length + 1; // for "|" | |
} | |
authormask-sql = "and exists ($authormask-sql)"; | |
} | |
string threadmask, threadmask-sql = "and not length(?)"; | |
if auto thread = getParam "thread" { | |
threadmask = thread; | |
threadmask-sql = "and thread_ids.thread_id = ?"; | |
} | |
string tagmask, tagmask-sql = "and not length(?)"; | |
if auto tag = getParam "tag" { | |
tagmask = tag; | |
tagmask-sql = "and tags.tag = ?"; | |
} | |
bool textmask; | |
string textmask-sql = "where 1"; | |
if "yes" == getParam ("noTextPosts", "no") { | |
textmask = true; | |
textmask-sql = "join image_post on thread_ids.msgid = image_post.msgid where 1"; | |
} | |
string idmask, idmask-sql = "and not length(?)"; | |
if auto id = getParam "idmask" { | |
idmask-sql = "select ? IDMASK where 0"; | |
while (id.length) { | |
if (auto rest = id.startsWith "from_") { | |
int from = atoi slice(&rest, "_"); | |
id = rest; | |
auto start = idmask.length; | |
idmask ~= "$from"; | |
auto len = idmask.length - start; | |
idmask-sql ~= " or abs(posts.msgid) >= abs(substr(IDMASK, $(start+1), $(len)))"; | |
} else { | |
raise new Error "Invalid id: $id (cookie $http_cookie)"; | |
} | |
} | |
idmask-sql = "and exists ($idmask-sql)"; | |
} | |
int count = -1; | |
if auto c = getParam "count" | |
count = atoi c; | |
bool showSpoilers; | |
if (getParam("spoilers") == "visible") showSpoilers = true; | |
void writeSiteHeader(bool handleSpoilers, linkToOverview, linkToList, linkToFullView, showRSS = true, floatIt = true, showFilters = true, string additionalText = null) { | |
if (floatIt) writeln "<div style=\"background-color: #eef; position: fixed; top: 0px; border: 1px solid; \">"; | |
else writeln "<div style=\"background-color: #eef; border: 1px solid; \">"; | |
bool firstLine = true; | |
int linecount; | |
void myWriteLine(string msg, bool sameLine = false) { | |
if (firstLine) { firstLine = false; linecount = 1; } | |
else if (!sameLine) { | |
writeln "<br />"; | |
linecount ++; | |
} | |
writeln msg; | |
} | |
if (linkToOverview) myWriteLine "<a href=\"$(urlWithParam(\"site\", \"main\"))\">Back to overview</a> | "; | |
if (handleSpoilers) { | |
string spoiler-change; | |
if showSpoilers { | |
spoiler-change = "<a href=\"$(urlWithParam(\"spoilers\", null))\">Hide Spoilers</a> "; | |
} else { | |
spoiler-change = "<a href=\"$(urlWithParam(\"spoilers\", \"visible\"))\">Always Show Spoilers</a> |"; | |
} | |
myWriteLine ("$spoiler-change", true); | |
} | |
if (linkToList) myWriteLine ("<a href=\"$(urlWithParam(\"site\", \"overview\"))\">Go to post list</a> |", true); | |
if (linkToFullView) myWriteLine ("<a href=\"$(urlWithParams [(\"site\", \"full-html\"), (\"count\", \"50\")])\"> | |
Go to full HTML view</a> |", true); | |
if (showRSS) myWriteLine ("<a href=\"$(urlWithParams [(\"site\", \"rss\"), (\"count\", string:null)])\">RSS</a> |", true); | |
myWriteLine ("<a href=\"$(urlWithParam(\"noTextPosts\", \"yes\"))\">No Text Posts</a>", true); | |
myWriteLine (additionalText, true); | |
if (showFilters) { | |
if (authormask) { | |
myWriteLine "Filter by author: $authormask (<a href=\"$(urlWithParam(\"author\", null))\">remove</a>)"; | |
} | |
if (threadmask) { | |
myWriteLine "Filter by thread: $threadmask (<a href=\"$(urlWithParam(\"thread\", null))\">remove</a>)"; | |
} | |
if (tagmask) { | |
myWriteLine "Filter by tag: $tagmask (<a href=\"$(urlWithParam(\"tag\", null))\">remove</a>)"; | |
} | |
if (idmask) { | |
myWriteLine "Only show posts after $idmask (<a href=\"$(urlWithParam(\"id\", null))\">remove</a>)"; | |
} | |
if (count != -1) { | |
myWriteLine "Only show $count posts (<a href=\"$(urlWithParam(\"count\", null))\">remove</a>)"; | |
} | |
if (textmask) { | |
myWriteLine "Filter: show only image posts (<a href=\"$(urlWithParam(\"noTextPosts\", null))\">remove</a>)"; | |
} | |
} | |
writeln "</div>"; | |
if floatIt | |
for 0..linecount | |
writeln "<br style=\"clear: both; \" />"; | |
} | |
void writeFooter() { | |
writeln "<div style=\"color: #aaa; font-size: 70%; \">disclaimer: all copyrights remain with the respective owners. this site acts merely as a caching layer for mspaforums.com and tgchan.org; | |
the creator disclaims all liability for the site itself or the content displayed within. </div>"; | |
} | |
auto site = getParam ("site"); | |
if (!site || site == "main") { | |
removeParam("id"); // cleanup | |
writeHTMLHeader(); | |
writeln "<head><title>Thread Overview</title>"; | |
writeln sharedCSS; | |
writeln sharedJS; | |
writeln "</head><body>"; | |
onSuccess { | |
p \{ | |
a.href "http://validator.w3.org/check?uri=referer" | |
$img #.src "http://www.w3.org/Icons/valid-xhtml10" #.alt "Valid XHTML 1.0 Transitional" #.height 31 #.width 88; | |
a.href "http://jigsaw.w3.org/css-validator/check/referer" | |
$img #.style "border:0;width:88px;height:31px" #.src "http://jigsaw.w3.org/css-validator/images/vcss" #.alt "Valid CSS!"; | |
$img #.src "valid-rss-rogers.png" #.height 31 #.alt "RSS feeds validated"; | |
} | |
writeFooter(); | |
writeln "</body></html>"; | |
} | |
writeln "<div>"; | |
writeSiteHeader(handleSpoilers => false, linkToOverview => false, linkToList => true, linkToFullView => true, showRSS => eval authormask || threadmask || tagmask); | |
writeln "</div>"; | |
writeln "<p>"; | |
(string, string)[] postParams; | |
string getPostParam(string name, deflt = null) { | |
for auto par <- postParams | |
if (par[0] == name) return par[1]; | |
return deflt; | |
} | |
if (getEnvVar "REQUEST_METHOD") == "POST" { | |
auto postText = string: join readfile 0; | |
// wtf some more | |
while (postText.find("&") != -1) { | |
postText = postText.replace("&", "&"); | |
} | |
// writeln "Post mode <br />"; | |
// writeln "$postText"; | |
for auto part <- postText.split "&" { | |
(string name, string value) = slice(part, "="); | |
value = urldecode value; | |
postParams ~= (name, value); | |
} | |
auto action = getPostParam("action"); | |
if (action == "add_tag") { | |
string tagname = filterTag getPostParam "tagname"; | |
if (!tagname.length) | |
writeln "Tag invalid! "; | |
else { | |
int threadId = getPostParam "threadId" #.atoi(); | |
db.exec("insert or replace into tags(thread_id, tag) values(?, ?)", threadId, tagname); | |
} | |
} | |
if (action == "rm_tag") { | |
string tagname = filterTag getPostParam "tagname"; | |
if (!tagname.length) | |
writeln "Tag invalid! "; | |
else { | |
int threadId = getPostParam "threadId" #.atoi(); | |
db.exec("delete from tags where thread_id=? and tag=?", threadId, tagname); | |
} | |
} | |
if (action == "add_thread") { | |
string url = getPostParam "url"; | |
void addThread(string url) { | |
db.exec("insert or replace into page_urls (msgid, page_url) values(null, ?)", url); | |
} | |
if (url.find("tgchan.org/wiki/")) { | |
auto links = string: url.downloadCached() #.betweens("href=\"", "\"") #.select \(string s) -> s.find "tgchan.org/kusaba/quest/" #.eval; | |
for auto link <- links addThread link; | |
writeln "$(links.length) threads added! Please wait for them to be scanned. This will take ten minutes at most. "; | |
} else if (!url.startsWith("http://www.mspaforums.com/showthread.php?") && !url.startsWith "http://tgchan.org/kusaba/") { | |
writeln "Can only submit MSPA or tgchan threads/wiki pages! "; | |
} else { | |
if (auto base = url.between("", "/page")) url = base; | |
addThread url; | |
writeln "Thread added! Please wait for it to be scanned. This will take ten minutes at most. "; | |
} | |
} | |
} | |
writeln "</p>"; | |
form #.action "?site=main" #.method "post" p \{ | |
text "URL"; | |
input #.type "text" #.name "url" #.size 40; | |
input #.type "submit" #.value "Add Thread"; | |
input #.type "hidden" #.name "action" #.value "add_thread"; | |
} | |
p \{ | |
writeln "Hint: You can add Tags to threads! To do this, use the + symbol next to the threads. Then, by clicking on the tag name, you can select only threads that have the same tag."; br; | |
writeln "This is useful if you want to follow a quest that spans multiple threads."; br; | |
} | |
writeln "<table class=\"boxed\">"; | |
onSuccess writeln "</table>"; | |
tr \{ | |
th #.class "first" "Thread"; | |
th #.class "mid" "Starter"; | |
th #.class "mid" "Posts"; | |
th #.class "mid" "First post date"; | |
th #.class "mid" "Last post date"; | |
th #.class "last" "Tags"; | |
} | |
auto evenodd_iter = loop ["even", "odd"]; | |
int formid; | |
if (getParam("hax") == "yes") { | |
for (int msgid, ubyte[] data) <- db.exec "select msgid, content from content" { | |
auto pureText = removeSpoilers string: inflate data; | |
bool hasVisiblePics = eval pureText.find("img src=\"http") != -1; | |
if (hasVisiblePics) { | |
db.exec("insert or replace into image_post (msgid) values(?)", msgid); | |
} else { | |
db.exec("insert or replace into text_post (msgid) values(?)", msgid); | |
} | |
} | |
return; | |
} | |
for (string thread, int thread_id, string threadname) <- db.exec (" | |
select page_url, A.thread_id, titles.title from ( | |
select thread_ids.thread_id, thread_ids.msgid from thread_ids | |
left outer join tags on thread_ids.thread_id = tags.thread_id | |
$textmask-sql $tagmask-sql $threadmask-sql | |
group by thread_ids.thread_id | |
) A | |
join page_urls B on A.msgid=B.msgid | |
join posts on A.msgid=posts.msgid | |
left outer join titles on A.thread_id=titles.thread_id | |
order by A.thread_id desc", tagmask, threadmask) | |
{ | |
(thread, string bogus) = slice(thread, "/page"); | |
if (!threadname) | |
threadname = getThreadName thread; | |
db.openStatementList(); | |
eval (string threadStarter, int threadStarterId) <- db.exec(" | |
select name, A.msgid from thread_ids A | |
join postnums B on A.msgid = B.msgid | |
join posts C on A.msgid = C.msgid | |
where A.thread_id=? and B.postnr=1", thread_id); | |
if (!authormask || authormask == threadStarter) { | |
eval int posts <- db.exec("select count(*) from thread_ids join posts on thread_ids.msgid = posts.msgid $textmask-sql and thread_id=? $authormask-sql", thread_id, authormask); | |
eval int maxPostNum <- db.exec(" | |
select max(postnr) from thread_ids A | |
join posts on A.msgid = posts.msgid | |
join postnums B on A.msgid = B.msgid | |
where thread_id=? $authormask-sql", thread_id, authormask); | |
eval string firstDate <- db.exec ("select date from posts where msgid=? $authormask-sql", threadStarterId, authormask); | |
eval (string lastDate, int lastDateId) <- db.exec(" | |
select posts.date, posts.msgid from thread_ids A | |
join postnums B on A.msgid = B.msgid | |
join posts on A.msgid = posts.msgid | |
where A.thread_id=? and B.postnr=? $authormask-sql", thread_id, maxPostNum, authormask); | |
string[auto~] tags; | |
for string tag <- db.exec("select tag from tags where thread_id = ?", thread_id) | |
tags ~= tag.dup; | |
string genTag(string tag) { | |
string res = "<div class=\"inline box\"><a href=\"$(urlWithParam(\"tag\", \"$tag\"))\">$tag</a></div>"; | |
/*string minus; | |
using scoped outputfn = \(string s) minus ~= s; { | |
b "-"; | |
div #.class "hidden box inline" \{ | |
form #.id "form$formid" #.method "post" #.action "#" \{ | |
input #.type "hidden" #.name "threadId" #.value "$thread_id"); | |
input #.type "hidden" #.name "tagname" #.value "$tag"; | |
input #.type "hidden" #.name "action" #.value "rm_tag"; | |
a #.href "javascript:submitForm('form$formid');" "Remove Tag"; | |
} | |
} | |
} | |
res = "$res<div class=\"minustag inline box\" style=\"vertical-align: super; \">$minus</div>";*/ | |
formid ++; | |
return res; | |
} | |
string tagstr = [for str <- tags extra &genTag: extra(str)].join ", "; | |
string firstDateText = "$firstDate"; | |
string lastDateText = "$lastDate"; | |
if (thread_id >= 0) { | |
firstDateText = "<a href=\"http://www.mspaforums.com/showthread.php?$thread_id\">$firstDate</a>"; | |
lastDateText = "<a href=\"http://www.mspaforums.com/showthread.php?$thread_id&p=$lastDateId&viewfull=1#post$lastDateId\">$lastDate</a>"; | |
} | |
eval string evenodd <- evenodd_iter; | |
// writeln "TEST <<$evenodd>>"; | |
tr .class evenodd \{ | |
td .class "first" a .href urlWithParam("thread", "$thread_id") threadname; | |
td .class "mid" a .href urlWithParam("author", threadStarter) threadStarter; | |
td .class "mid" "$posts"; | |
td .class "mid" firstDateText; | |
td .class "mid" lastDateText; | |
td .class "last" \{ | |
text tagstr; | |
div .class "plustag inline box" \{ | |
text "+"; | |
div #.class "hidden box inline " #.style "position: absolute; " \{ | |
form #.id "form$formid" #.method "post" #.action "#" \{ | |
input #.type "text" #.name "tagname"; | |
input #.type "hidden" #.name "threadId" #.value "$thread_id"; | |
input #.type "hidden" #.name "action" #.value "add_tag"; | |
a #.href "javascript:submitForm('form$formid');" "Add Tag"; | |
} | |
} | |
} | |
} | |
formid ++; | |
} | |
} | |
db.finStatementList(); | |
} | |
return; | |
} | |
if (site == "overview") { | |
writeHTMLHeader(doctype => false); | |
std.cgi.head \{ | |
title "Overview"; | |
text sharedCSS; | |
text sharedJS; | |
} | |
writeln "<body>"; | |
onSuccess { | |
writeFooter(); | |
writeln "</body></html>"; | |
} | |
writeSiteHeader(handleSpoilers => true, linkToOverview => true, linkToList => false, linkToFullView => true, floatIt => false); | |
{ | |
writeln "<table class=\"boxed\">"; | |
tr \{ | |
th.class "first" "Thread"; | |
th.class "mid" "Name"; | |
th.class "mid" "Date"; | |
th.class "last" "Post ID"; | |
} | |
auto evenodd_iter = loop ["even", "odd"]; | |
int iframe_id; | |
void writeRowFor(string page_url, int next_msg_id, msg_id, prev_msg_id, string name, string date, bool showSpoilers) { | |
eval string evenodd <- evenodd_iter; | |
string threadname = getThreadName page_url; | |
string spoilerpar; | |
if (showSpoilers) spoilerpar = "&spoilers"; | |
string next_post_link = "?site=rss&id=$next_msg_id$spoilerpar"; | |
string post_link = "?site=rss&id=$msg_id$spoilerpar"; | |
string prev_post_link = "?site=rss&id=$prev_msg_id$spoilerpar"; | |
tr.class evenodd \{ | |
td.class "first" threadname; | |
td.class "mid" a.href urlWithParam("author", name) name; | |
td.class "mid" date; | |
td.class "last" \{ | |
a.href "javascript: loadFrame('mewframe$iframe_id', 'mewdiv$iframe_id', '$post_link'); " "$msg_id"; | |
div #.class "mewdiv" #.id "mewdiv$iframe_id" \{ | |
table.class "mewtable boxed" \{ | |
tr.style "height: 20px; " \{ | |
td.onclick "javascript:loadFrame('mewframe$(iframe_id-1)', 'mewdiv$(iframe_id-1)', '$prev_post_link');" "< Back"; | |
td.onclick "javascript:loadFrame('mewframe$(iframe_id+1)', 'mewdiv$(iframe_id+1)', '$next_post_link');" "Forth >"; | |
} | |
tr td.colspan 2 $ iframe #.class "mewframe" #.id "mewframe$iframe_id" #.src "" #.scrolling "auto" #.frameborder 0 "mew!"; | |
} | |
} | |
} | |
} | |
iframe_id ++; | |
} | |
{ | |
auto first = cat([true], loop [false]); | |
string page_url; | |
int msg_id = -1, prev_msg_id = -1; | |
string name, date; | |
bool aborted; | |
alias limit = 256; | |
string sqltext = " | |
select page_urls.page_url, posts.msgid, name, date from posts | |
join thread_ids on posts.msgid = thread_ids.msgid | |
join page_urls on posts.msgid = page_urls.msgid | |
join ( | |
select thread_ids.thread_id, thread_ids.msgid from thread_ids | |
left outer join tags on thread_ids.thread_id = tags.thread_id | |
where 1 $tagmask-sql $threadmask-sql | |
group by thread_ids.thread_id | |
) B on thread_ids.thread_id = B.thread_id | |
join date_codes on posts.msgid = date_codes.msgid | |
$textmask-sql $authormask-sql | |
order by date_codes.datecode asc"; | |
for (int i, (string next_page_url, int next_msg_id, string next_name, string next_date)) <- zip(0..limit, db.exec!FOURSTRING!(string, int, string, string)(sqltext, tagmask, threadmask, authormask)) | |
{ | |
eval bool b <- first; | |
if !b { | |
writeRowFor (page_url, next_msg_id, msg_id, prev_msg_id, name, date, showSpoilers); | |
prev_msg_id = msg_id; | |
} | |
(page_url, msg_id, name, date) = (next_page_url.dup, next_msg_id, next_name.dup, next_date.dup); | |
if (i == limit - 1) aborted = true; | |
} | |
if (aborted) { | |
writeln "</table>"; | |
writeln "<br />"; | |
writeln "Rest omitted due to sanity checking. "; | |
} else { | |
writeRowFor (page_url, -1, msg_id, prev_msg_id, name, date, showSpoilers); | |
writeln "</table>"; | |
} | |
// writeln ">> $sqltext"; | |
} | |
} | |
return; | |
} | |
alias base_url = "http://demented.no-ip.org/~feep/threadfeed.cgi"; | |
if (site == "rss") { | |
processCookie(); | |
if auto id = getParam "id" { | |
bool spoilers = eval getParam ("spoilers"); | |
writeHTMLHeader(); | |
writeln "<head><title>Post view</title>"; | |
writeln sharedCSS; | |
writeln sharedJS; | |
writeln "</head><body>"; | |
onSuccess { | |
writeFooter(); | |
writeln "</body></html>"; | |
} | |
auto msgid = id.atoi; | |
eval int thread_id <- db.exec("select thread_id from thread_ids where msgid = ?", msgid); | |
string backlink = "| <a href=\"http://www.mspaforums.com/showthread.php?$thread_id&p=$msgid&viewfull=1#post$msgid\">MSPA forum link</a>"; | |
writeSiteHeader(handleSpoilers => true, linkToOverview => true, linkToList => false, linkToFullView => false, | |
showRSS => false, floatIt => false, showFilters => false, additionalText => backlink); | |
for byte[] data <- db.exec("select content from content where msgid = ?", msgid) | |
{ | |
string idText = urlWithParam("id", ""); | |
string content = reformatPost (string: inflate data, spoilers, idText, linkToAnchors => false, noNyud => noNyud); | |
writeln "$content"; | |
} | |
return; | |
} | |
string sqlcode = " | |
select posts.msgid, postnr, A.thread_id, name, date, content from | |
( | |
select thread_ids.thread_id, msgid from thread_ids | |
left outer join tags on thread_ids.thread_id = tags.thread_id | |
where 1 $tagmask-sql $threadmask-sql | |
group by thread_ids.thread_id | |
) A | |
join thread_ids on A.thread_id = thread_ids.thread_id | |
join posts on posts.msgid = thread_ids.msgid | |
join postnums C on C.msgid = thread_ids.msgid | |
join content D on D.msgid = thread_ids.msgid | |
join date_codes on date_codes.msgid = thread_ids.msgid | |
$textmask-sql $authormask-sql $idmask-sql | |
order by date_codes.datecode asc"; | |
alias Tup = (int, int, int, string, string, string); | |
auto qit = db.exec!FIVESTRING!Tup(sqlcode, tagmask, threadmask, authormask, idmask); | |
int lastMsgId; | |
for (int msgId, int postId, int threadId, string name, string date, string content) <- qit { | |
lastMsgId = msgId; | |
} | |
writeln "Content-type: application/rss+xml"; | |
if (lastMsgId != 0) | |
writeln "Set-Cookie: idmask=from_$lastMsgId"; | |
writeln ""; | |
writeln "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>"; | |
writeln "<rss version=\"2.0\" xmlns:atom=\"http://www.w3.org/2005/Atom\">"; | |
onSuccess writeln "</rss>"; | |
writeln "<channel>"; | |
onSuccess writeln "</channel>"; | |
writeln "<title>Thread Feed</title>"; | |
writeln "<link>$base_url</link>"; | |
writeln "<description>RSS feed for MSPA and tgchan forums</description>"; | |
// writeln "<atom:link href=\"$base_url?$query_string\" rel=\"self\" type=\"application/rss+xml\" />"; | |
qit = db.exec!FIVESTRING!Tup(sqlcode, tagmask, threadmask, authormask, idmask); | |
for (int msgId, int postId, int threadId, string name, string date, string content) <- qit | |
{ | |
// parse the date | |
date.cleanupDate(); | |
writeln "<item>"; | |
onSuccess writeln "</item>"; | |
writeln "<title>$name</title>"; | |
writeln "<pubDate>$date</pubDate>"; | |
writeln "<guid isPermaLink=\"false\">mspa_forum_post_id_on_feeps_rss_$msgId</guid>"; | |
writeln "<link>$base_url$(urlWithParam(\"id\", \"\\$msgId\"))</link>"; // .replace("&", "&"); /* not necessary for rss */ | |
// writeln "<link>$base_url?id=$msgId</link>"; | |
} | |
return; | |
} | |
if (site == "full-html") { | |
string sqlcode = " | |
select content, name, date, posts.msgid, thread_ids.thread_id from | |
( | |
select thread_ids.thread_id, msgid from thread_ids | |
left outer join tags on thread_ids.thread_id = tags.thread_id | |
where 1 $tagmask-sql $threadmask-sql | |
group by thread_ids.thread_id | |
) A | |
join thread_ids on A.thread_id = thread_ids.thread_id | |
join posts on thread_ids.msgid = posts.msgid | |
join content B on posts.msgid = B.msgid | |
join date_codes on posts.msgid = date_codes.msgid | |
$textmask-sql $authormask-sql $idmask-sql | |
order by date_codes.datecode asc"; | |
auto qit = db.exec!FIVESTRING!(byte[], string, string, int, int)(sqlcode, tagmask, threadmask, authormask, idmask); | |
if (getParam("funtimemode") == "yes") { | |
writeln "Content-type: text/html"; | |
writeln ""; | |
for (byte[] data, string name, string date, int postId, int threadId) <- qit { | |
auto text = string: inflate data #.dup; | |
// writeln "[$name]"; | |
/*char[auto~] newtext; | |
while (text.length) { | |
void flush(int to) { newtext ~= text[0 .. to]; } | |
auto bpos = text.find("<b>"); | |
auto bcpos = text.find("</b>"); | |
if (bpos != -1 && bpos < bcpos) { | |
text = text[bpos + 3 .. $]; | |
} | |
else if (bcpos != -1 && bcpos < bpos) { | |
flush bcpos; | |
text = text[bcpos + 4 .. $]; | |
} else text = new char[] 0; | |
} | |
text = newtext[];*/ | |
writeln string:text; | |
} | |
return; | |
} | |
writeHTMLHeader(); | |
writeln "<head><title>Full HTML View</title>"; | |
writeln sharedCSS; | |
writeln sharedJS; | |
writeln "</head><body>"; | |
// writeln ">> $sqlcode"; | |
onSuccess { | |
writeFooter(); | |
writeln "</body></html>"; | |
} | |
writeSiteHeader(handleSpoilers => true, linkToOverview => true, linkToList => true, linkToFullView => false, floatIt => false); | |
if (!authormask && !tagmask && !threadmask) { | |
writeln "Uh-oh! You loaded the Full-HTML view but no author, thread or tag mask is set! <br /> | |
This means that I should by all rights dump my entire post database at your feet now. <br /> | |
Since I'm a nice person and don't want to crash your system, I'm not going to do that. <br /> | |
You should go back. There is nothing for you here. "; | |
return; | |
} | |
void loopBody(byte[] data, string name, date, int postId, threadId) { | |
string idText = urlWithParam("id", ""); | |
string content = reformatPost (string: inflate data #.dup, showSpoilers, idText, linkToAnchors => true, noNyud => noNyud); | |
a.name "post$postId" ""; | |
div.class "box_outer" \{ | |
string backlink = "http://www.mspaforums.com/showthread.php?$threadId&p=$postId&viewfull=1#post$postId"; | |
div.class "box_inner" \{ | |
b "Name: "; | |
text name; | |
text " "; | |
b "Date: "; | |
text date; | |
text " "; | |
b \{ if (postId >= 0) a.href backlink "MSPA link"; } // otherwise non-mspa | |
a #.style "text-decoration: none; color: #cdc; " #.href urlWithParam("idmask", "from_$postId") "▼"; | |
} | |
text content; | |
} | |
} | |
if (count == -1) { | |
for (byte[] data, string name, string date, int postId, int threadId) <- qit { | |
loopBody(data, name, date, postId, threadId); | |
} | |
} else { | |
int lastPostId = 0; | |
for (int k, (byte[] data, string name, string date, int postId, int threadId)) <- zip(0..count + 1, qit) { | |
if (k != count) loopBody(data, name, date, postId, threadId); | |
else lastPostId = postId; | |
} | |
if (lastPostId != 0) { | |
int left = 1; | |
int[auto~] restIds; | |
for (byte[] data, string name, string date, int postId, int threadId) <- qit { restIds ~= postId; } | |
left += restIds.length; | |
string extralink; | |
int lastpage; | |
if (count < restIds.length) lastpage = restIds[$-count+1]; | |
else lastpage = restIds[0]; | |
a.href urlWithParam("idmask", "from_$lastPostId") "Next"; | |
text "($left posts left)"; | |
a.href urlWithParam("idmask", "from_$lastpage") "Last Page"; | |
br; | |
} | |
} | |
return; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment