|
| 1 | +#!/usr/bin/python |
| 2 | +# pcgiwebnews.py - PCGI version of WebNews |
| 3 | +# 1997.09.27 Jim.Tittsler@tpc.ml.org |
| 4 | +# 1998.07.24 Jim.Tittsler@tpc.ml.org converted to PCGI |
| 5 | +"""Tokyo PC Users Group WebNews reader by Jim Tittsler""" |
| 6 | + |
| 7 | +import sys, os, string, time, cgi, re |
| 8 | +from nntplib import NNTP |
| 9 | +from urllib import quote |
| 10 | +from rfc822 import parseaddr, parsedate |
| 11 | + |
| 12 | +print "Content-type: text/html" |
| 13 | +print |
| 14 | + |
| 15 | +NEWS_SERVER = 'news.tpc.ml.org' |
| 16 | +#NEWS_SERVER = 'pengu.mww.dyn.ml.org' |
| 17 | +default_show_articles = 30 # number of article headers to show on first entry |
| 18 | +NewsError = "Error communicating with server" |
| 19 | + |
| 20 | +logo_html = """<HTML><HEAD></HEAD><BODY BGCOLOR="#FFFFFF"> |
| 21 | +<A HREF="http://www.tpc.ml.org/" TARGET="_top"><IMG SRC="/tpc/images/tpcsml.gif" BORDER="0" ALT="TPC" ALIGN=LEFT></A> |
| 22 | +<B><FONT SIZE=+1>Tokyo PC</FONT></B> |
| 23 | +<B><FONT SIZE=+1>Users Group</FONT></B> |
| 24 | +<BR CLEAR=LEFT> |
| 25 | +<B><FONT SIZE="+1">%s</FONT></B><BR> |
| 26 | +<EM>%s</EM><P> |
| 27 | +<A HREF="mailto:%s@tpc.ml.org">Post new article</A><BR> |
| 28 | +<A HREF="http://www.tpc.ml.org/" TARGET="_top">[Home]</A> |
| 29 | +<A HREF="http://www.tpc.ml.org/tpc/newsgroups.html" TARGET="_top">[Newsgroups]</A> |
| 30 | +</BODY></HTML>""" |
| 31 | + |
| 32 | +ttalk_logo_html = """<HTML><HEAD></HEAD><BODY BGCOLOR="#FFFFFF"> |
| 33 | +<A HREF="http://www.ttalk.com/" TARGET="_top"><IMG SRC="http://ttalk.soholutions.com/images/tt.gif" BORDER="0" ALT="TTalk" ALIGN=LEFT WIDTH=59 HEIGHT=42></A> |
| 34 | +<B><FONT SIZE=+1>TTalk</FONT></B> |
| 35 | +<B>Broadcasting</B> |
| 36 | +<BR CLEAR=LEFT> |
| 37 | +<B><FONT SIZE="+1">%s</FONT></B><BR> |
| 38 | +<EM>%s</EM><P> |
| 39 | +<A HREF="mailto:%s@ttalk.soholutions.com">Post new article</A><BR> |
| 40 | +<A HREF="http://www.ttalk.com/" TARGET="_top">[Home]</A> |
| 41 | +<A HREF="http://ttalk.soholutions.com/%s" TARGET="_top">[Newsgroups]</A> |
| 42 | +</BODY></HTML>""" |
| 43 | + |
| 44 | +interesting_headers = re.compile(r""" |
| 45 | + ((?P<from>From:) |
| 46 | + |(?P<newsgroups>Newsgroups:\ tpc\.(?P<group>[-0-9a-zA-z\.]+)(?P<othergroups>.*)) |
| 47 | + |(?P<subject>Subject:\ (?P<re>re:\s?)?(?P<real_subject>.*)) |
| 48 | + |(?P<keep>Date:))""", re.X | re.I) |
| 49 | + |
| 50 | +def logo(G="", D=""): |
| 51 | + """The logo pane for the TPC WebNews reader""" |
| 52 | + # turn the newsgroup name into a mailing list name by changing first . to - |
| 53 | + if string.find(G, "ttalk") >= 0: |
| 54 | + if string.find(G, "staff") >= 0: |
| 55 | + return ttalk_logo_html % (G, D, string.replace(G, '.', '-'), "staff") |
| 56 | + else: |
| 57 | + return ttalk_logo_html % (G, D, string.replace(G, '.', '-'), "") |
| 58 | + else: |
| 59 | + return logo_html % (G, D, string.replace(G, '.', '-')) |
| 60 | + |
| 61 | +def show_article_and_kids(index, depth, lines, childof, |
| 62 | + acc_string, pass_string, RESPONSE): |
| 63 | + art_nr, subject, poster, date, id, references, size, line_cnt = lines[index] |
| 64 | + name, email = parseaddr(poster) |
| 65 | + RESPONSE.write('<LI><A HREF="http:/cgi-bin/webnews/readnews?I=%s%s%s" TARGET="d">%s</A> (%s) %s, %s' % (quote(id), acc_string, pass_string, cgi.escape(subject), line_cnt, name, time.strftime('%b %d, %H:%M', parsedate(date)))) |
| 66 | + if index in childof: |
| 67 | + RESPONSE.write('<UL>') |
| 68 | + for i in range(len(lines)): |
| 69 | + if childof[i] == index: |
| 70 | + show_article_and_kids(i, depth+1, lines, childof, |
| 71 | + acc_string, pass_string, RESPONSE) |
| 72 | + RESPONSE.write('</UL>') |
| 73 | + |
| 74 | +def newsgroup(G='', F='', C='', A=None, P=None, RESPONSE=None): |
| 75 | + """The article list for group G in framestyle F""" |
| 76 | + if G=='': return "Missing newsgroup name." |
| 77 | + group = G |
| 78 | + showframes = 1 |
| 79 | + show_articles = default_show_articles |
| 80 | + if os.environ.has_key('HTTP_USER_AGENT'): |
| 81 | + browser = os.environ['HTTP_USER_AGENT'] |
| 82 | + else: |
| 83 | + browser = "unknown" |
| 84 | + if string.find(browser, "Mozilla/") == 0: |
| 85 | + browser_version = string.atof(browser[8:string.index(browser, ' ')]) |
| 86 | + if browser_version >= 2.00: |
| 87 | + showframes = 3 |
| 88 | + if F != '': |
| 89 | + try: |
| 90 | + showframes = string.atoi(F) |
| 91 | + except AttributeError: |
| 92 | + showframes = 0 |
| 93 | + if C != '': |
| 94 | + try: |
| 95 | + show_articles = string.atoi(C) |
| 96 | + except AttributeError: |
| 97 | + show_articles = default_show_articles |
| 98 | + |
| 99 | + user = A |
| 100 | + acc_string = '' |
| 101 | + if A: acc_string = '&A=' + A |
| 102 | + password = P |
| 103 | + pass_string = '' |
| 104 | + if P: pass_string = '&P=' + P |
| 105 | + |
| 106 | + lines = [] |
| 107 | + RESPONSE.headers['expires'] = time.asctime(time.gmtime(time.time() + 60)) |
| 108 | + RESPONSE.write( """<HTML><HEAD><TITLE>Tokyo PC Users Group: %s</TITLE></HEAD>""" % group) |
| 109 | + try: |
| 110 | + try: |
| 111 | + news = NNTP(NEWS_SERVER) |
| 112 | + except: |
| 113 | + RESPONSE.write( "<BODY><B>Can not connect to server:</B> ", NEWS_SERVER) |
| 114 | + raise NewsError |
| 115 | + |
| 116 | + try: |
| 117 | + resp = news.shortcmd('MODE READER') |
| 118 | + except: |
| 119 | + RESPONSE.write( "<BODY><B>Can not communicate with server:</B> ", NEWS_SERVER) |
| 120 | + raise NewsError |
| 121 | + |
| 122 | + if user: |
| 123 | + resp = news.shortcmd('authinfo user '+user) |
| 124 | + if resp[:3] == '381': |
| 125 | + if not password: |
| 126 | + RESPONSE.write( "<BODY><B>Can not fetch newsgroup</B>") |
| 127 | + raise NewsError |
| 128 | + else: |
| 129 | + resp = news.shortcmd('authinfo pass '+password) |
| 130 | + if resp[:3] != '281': |
| 131 | + RESPONSE.write( "<BODY><B>Can not fetch newsgroup</B>") |
| 132 | + raise NewsError |
| 133 | + |
| 134 | + try: |
| 135 | + resp, count, first, last, name = news.group(group) |
| 136 | + except: |
| 137 | + RESPONSE.write( "<BODY><B>No such newsgroup:</B> " + group ) |
| 138 | + raise NewsError |
| 139 | + |
| 140 | + description = "" |
| 141 | + try: |
| 142 | + resp, lines = news.xgtitle(group) |
| 143 | + except: |
| 144 | + pass |
| 145 | + else: |
| 146 | + for line in lines: |
| 147 | + name, description = line |
| 148 | + |
| 149 | + if showframes == 0: |
| 150 | + RESPONSE.write( '<BODY BGCOLOR="#FFFFFF"><H1>%s</H1>' % group) |
| 151 | + RESPONSE.write( "<EM>%s</EM><P>" % cgi.escape(description)) |
| 152 | + elif showframes == 1 or showframes == 3: |
| 153 | + if description: description = "&D="+quote(description) |
| 154 | + RESPONSE.write( '<FRAMESET ROWS="33%,*">') |
| 155 | + RESPONSE.write( ' <FRAMESET COLS="220,*">') |
| 156 | + RESPONSE.write( ' <FRAME SRC="/cgi-bin/webnews/logo?G=%s%s" scrolling="auto">' % (group, description)) |
| 157 | + RESPONSE.write( ' <FRAME SRC="/cgi-bin/webnews/newsgroup?G=%s&F=2%s%s#last" scrolling="yes"> ' % (group, acc_string, pass_string)) |
| 158 | + RESPONSE.write( ' </FRAMESET>') |
| 159 | + if string.find(G, "ttalk") >= 0: |
| 160 | + RESPONSE.write( ' <FRAME SRC="http://ttalk.soholutions.com/welcome.html" scrolling="auto" name="d">') |
| 161 | + else: |
| 162 | + RESPONSE.write( ' <FRAME SRC="/webnews/welcome.html" scrolling="auto" name="d">') |
| 163 | + RESPONSE.write( '</FRAMESET><BODY BGCOLOR="#FFFFFF">') |
| 164 | + else: |
| 165 | + RESPONSE.write( '<BODY BGCOLOR="#FFFFFF">') |
| 166 | + |
| 167 | + if showframes == 3: |
| 168 | + raise NewsError |
| 169 | + |
| 170 | + if (show_articles > 0): |
| 171 | + ilast = string.atoi(last) |
| 172 | + ifirst = string.atoi(first) |
| 173 | + if ((ilast - ifirst + 1) > show_articles): |
| 174 | + first = "%d" % (ilast - show_articles + 1) |
| 175 | + RESPONSE.write( '<A HREF="/cgi-bin/webnews/newsgroup?G=%s&F=%d&C=0%s%s"><I>Retrieve earlier article headers</I></A> ' % (group, showframes, acc_string, pass_string)) |
| 176 | + |
| 177 | + try: |
| 178 | + resp, lines = news.xover(first, last) |
| 179 | + except: |
| 180 | + RESPONSE.write( "<B>Unable to get article list for:</B> " + group) |
| 181 | + raise NewsError |
| 182 | + |
| 183 | + RESPONSE.write( '<UL TYPE="none">') |
| 184 | + |
| 185 | + # pass 1: build a dictionary of message IDs |
| 186 | + ids = {} |
| 187 | + index = 0 |
| 188 | + for line in lines: |
| 189 | + art_nr, subject, poster, date, id, references, size, line_cnt = line |
| 190 | + ids[id] = index |
| 191 | + index = index + 1 |
| 192 | + |
| 193 | + # pass 2: discover child articles |
| 194 | + childof = [] |
| 195 | + subs = {} |
| 196 | +# subject_re_less = regex.symcomp("\([Rr]e:\)? *\(<real_subject>.*\)") |
| 197 | + subject_re_less = re.compile(r"(re:)?\s*(?P<real_subject>.*)") |
| 198 | + index = 0 |
| 199 | + for line in lines: |
| 200 | + art_nr, subject, poster, date, id, references, size, line_cnt = line |
| 201 | + childof.append(-1) |
| 202 | +# if subject_re_less.match(subject) > 0: |
| 203 | +# subject = subject_re_less.group('real_subject') |
| 204 | + srl = subject_re_less.match(subject) |
| 205 | + if srl: subject = srl.group('real_subject') |
| 206 | + # if there are references, use them (most recent first) |
| 207 | + if len(references) > 0: |
| 208 | + references.reverse() |
| 209 | + for ref in references: |
| 210 | + if ids.has_key(ref): |
| 211 | + childof[index] = ids[ref] |
| 212 | + break |
| 213 | + # if no references (or referee not found), use subject |
| 214 | + if childof[index] == -1: |
| 215 | + if subs.has_key(subject) : |
| 216 | + childof[index] = subs[subject] |
| 217 | + else: |
| 218 | + subs[subject] = index |
| 219 | + index = index + 1 |
| 220 | + |
| 221 | +# index = 0 |
| 222 | +# for line in lines: |
| 223 | +# art_nr, subject, poster, date, id, size, line_cnt, references = line |
| 224 | +# print index,childof[index],subject |
| 225 | +# index = index + 1 |
| 226 | + |
| 227 | + index = 0 |
| 228 | + for seq in childof: |
| 229 | + if seq == -1: |
| 230 | + show_article_and_kids(index, 0, lines, childof, |
| 231 | + acc_string, pass_string, RESPONSE) |
| 232 | + index = index + 1 |
| 233 | + |
| 234 | +# art_nr, subject, poster, date, id, size, line_cnt, references = line |
| 235 | +# name, email = parseaddr(poster) |
| 236 | +# print '<LI><A HREF="http:/cgi-bin/readnews.cgi?%s" TARGET="d">%s</A> (%s) %s, %s' % (quote(id), subject, line_cnt, name, time.strftime('%b %d, %H:%M', parsedate(date))) |
| 237 | + |
| 238 | + RESPONSE.write('<A NAME="last"> </A></UL>') |
| 239 | + |
| 240 | + finally: |
| 241 | + if showframes != 2: |
| 242 | + if string.find(G, "ttalk") >= 0: |
| 243 | + RESPONSE.write( """<P><HR><P>A service of the |
| 244 | + <A HREF="http://www.soholutions.com/">SoHolutions</A>.""") |
| 245 | + else: |
| 246 | + RESPONSE.write( """<P><HR><P>A service of the |
| 247 | + <A HREF="http://www.tpc.ml.org/">Tokyo PC Users Group</A>.""") |
| 248 | + # print "<P><ADDRESS>",os.environ['HTTP_USER_AGENT'],"</ADDRESS>" |
| 249 | + RESPONSE.write( """</BODY></HTML>""") |
| 250 | + |
| 251 | +def liven_url(matchobj): |
| 252 | + # if it was a URL-type match, but there is a zero length address part, |
| 253 | + # don't make it live (return the original string) |
| 254 | + if matchobj.group('uri') != None and matchobj.group('uri') == "": |
| 255 | + return matchobj.group(0) |
| 256 | + url = matchobj.group('url') |
| 257 | + text = url |
| 258 | + if matchobj.group('mailadr'): # if an assumed Email address, |
| 259 | + if url[0] in string.digits: # and it starts with a digit |
| 260 | + return matchobj.group(0) # don't make it live (msg id?) |
| 261 | + url = "mailto:" + url # otherwise add mailto: tag |
| 262 | + if matchobj.group('web'): # if a web link, |
| 263 | + url = url + '" target="fromtpc' # put it into a new window |
| 264 | + return matchobj.group('opening') + \ |
| 265 | + '<A HREF="'+url+'">'+text+'</A>' + \ |
| 266 | + matchobj.group('closing') |
| 267 | + |
| 268 | +def readnews(I="", A=None, P=None, RESPONSE=None): |
| 269 | + """Display article in HTML""" |
| 270 | + article = I |
| 271 | + user = A |
| 272 | + password = P |
| 273 | + |
| 274 | + RESPONSE.write("""<HTML><HEAD><TITLE>Tokyo PC Users Group</TITLE></HEAD> |
| 275 | + <BODY BGCOLOR="#FFFFFF">""") |
| 276 | + |
| 277 | + try: |
| 278 | + news = NNTP(NEWS_SERVER) |
| 279 | + except: |
| 280 | + RESPONSE.write("Can not connect to server: " + NEWS_SERVER) |
| 281 | + resp = news.shortcmd('MODE READER') |
| 282 | + |
| 283 | + if user: |
| 284 | + resp = news.shortcmd('authinfo user '+user) |
| 285 | + if resp[:3] == '381': |
| 286 | + if not password: |
| 287 | + RESPONSE.write("<B>Can not fetch article</B><P>") |
| 288 | + else: |
| 289 | + resp = news.shortcmd('authinfo pass '+password) |
| 290 | + if resp[:3] != '281': |
| 291 | + RESPONSE.write("<B>Can not fetch article</B><P>") |
| 292 | + |
| 293 | + try: |
| 294 | + resp, nr, id, subs = news.head(article) |
| 295 | + except: |
| 296 | + RESPONSE.write("Article %s not available" % quote(article)) |
| 297 | + |
| 298 | + RESPONSE.write('<TABLE WIDTH="100%" BGCOLOR="#CFCFCF"><TR><TD>') |
| 299 | + |
| 300 | + # build up the header (so we know Subject: by Newsgroups: output time) |
| 301 | + from_line = "" |
| 302 | + newsgroup_line = "" |
| 303 | + subject_line = "" |
| 304 | + keep_lines = "" |
| 305 | + mail_subject = "" |
| 306 | + for line in subs: |
| 307 | + ihdr = interesting_headers.match(line) |
| 308 | + if ihdr: |
| 309 | + if ihdr.group('from'): |
| 310 | + name, email = parseaddr(line[6:]) |
| 311 | + if name: |
| 312 | + from_line = 'From: <A HREF="mailto:%s%s">%s</A> <%s><BR>' % ( |
| 313 | + email, "%s", name, email) |
| 314 | + else: |
| 315 | + from_line = 'From: <A HREF="mailto:%s%s">%s</A><BR>' % ( |
| 316 | + email, "%s", email) |
| 317 | + elif ihdr.group('newsgroups'): |
| 318 | + newsgroup_line = 'Newsgroups: <A HREF="mailto:tpc-%s@tpc.ml.org%s">tpc.%s</A>%s<BR>' % ( |
| 319 | + ihdr.group('group'), "%s", |
| 320 | + ihdr.group('group'), ihdr.group('othergroups')) |
| 321 | + elif ihdr.group('subject'): |
| 322 | + subject_line = 'Subject: <B>%s</B><BR>' % line[9:] |
| 323 | + if ihdr.group('re'): |
| 324 | + mail_subject = "?subject="+line[9:] |
| 325 | + else: |
| 326 | + mail_subject = "?subject=Re: "+line[9:] |
| 327 | + elif ihdr.group('keep'): |
| 328 | + keep_lines = keep_lines+line+"<BR>" |
| 329 | + |
| 330 | + if from_line: |
| 331 | + RESPONSE.write(from_line % mail_subject) |
| 332 | + if newsgroup_line: |
| 333 | + RESPONSE.write(newsgroup_line % mail_subject) |
| 334 | + RESPONSE.write(subject_line + keep_lines) |
| 335 | + |
| 336 | + RESPONSE.write('</TD></TR></TABLE><P>') |
| 337 | + |
| 338 | + try: |
| 339 | + resp, nr, id, subs = news.body(article) |
| 340 | + except: |
| 341 | + RESPONSE.write("Article %s body not available" % article) |
| 342 | + |
| 343 | + RESPONSE.write("<CODE>") |
| 344 | + for line in subs: |
| 345 | + RESPONSE.write(re.sub(r'''(?i)(?x) |
| 346 | + (?P<opening>[<(";]?) |
| 347 | + (?P<url>(((?P<web>http:)|(news:)|(mailto:)|(telnet:))(?P<uri>\S*?)) |
| 348 | + # a mail address is some non-ws characters followed by @ |
| 349 | + # followed by a domain name that has at least one . in it |
| 350 | + |(?P<mailadr>\S+@(\S+\.)+\S+?)) |
| 351 | + # either a URL or a mail address will not contain [)">\s] |
| 352 | + # and will not end with punctuation just before the whitespace |
| 353 | + (?P<closing>([)"'>\s]|$|([&.?,:;]\s)+))''', |
| 354 | + liven_url, line) + "<BR>") |
| 355 | + RESPONSE.write("</CODE>") |
| 356 | + |
| 357 | + RESPONSE.write("</BODY></HTML>") |
| 358 | + resp = news.quit() |
| 359 | + |
| 360 | +if __name__ == '__main__': |
| 361 | + newsgroup('tpc.unix', '2') |
| 362 | +# readnews('<slrn6np315.g6.Jim.Tittsler@mon.dskk.co.jp>') |
0 commit comments