/
pyffle_boards.py
executable file
·510 lines (439 loc) · 18.8 KB
/
pyffle_boards.py
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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
###
### This file is part of Pyffle BBS.
###
### Pyffle BBS is free software: you can redistribute it and/or modify
### it under the terms of the GNU General Public License as published by
### the Free Software Foundation, either version 3 of the License, or
### (at your option) any later version.
###
### Pyffle BBS is distributed in the hope that it will be useful,
### but WITHOUT ANY WARRANTY; without even the implied warranty of
### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
### GNU General Public License for more details.
###
### You should have received a copy of the GNU General Public License
### along with Foobar. If not, see <http://www.gnu.org/licenses/>.
###
###
## Models for SqlAlchemy version 6
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relation, backref
from sqlalchemy import *
from sqlalchemy.dialects.postgresql import *
from sqlalchemy.orm import sessionmaker
from pyffle_tables import *
from pyffle_data import *
from pyffle_exception import *
from pyffle_editor import Editor
from datetime import datetime
import sys
import getpass
import os
import tempfile
def getIdentity():
return "pyffle_boards v0.01"
## Returns True if the version of pyffle is compatible this version of module
def confirmVersion(version):
return True
class PyffleModule:
currentUser = None
data = None
def eventDispatched(self, event):
pass
def resetScan(self):
self.data.stateChange("board_resetstart")
self.currentUser.datelastnewscan = None
self.data.storeUser(self.currentUser)
self.currentUser.datelastnewscan = datetime.fromtimestamp(0)
self.data.storeUser(self.currentUser)
self.data.util.println("Scan has been reset to epoch")
self.data.stateChange("board_resetend")
## UI for displaying a board list
def listBoards(self, boards):
## Loop through board objects, display one numbered line / board
self.data.stateChange("board_listboardsstart")
self.data.util.println("\nBoards available:")
outs = ""
i = 1
for board in boards:
if not board.name.startswith('__'):
if self.data.srmcheck(board.aclid,self.currentUser.username,"READ",minlevel=board.minreadlevel):
outs = outs + " [^" + str(i) + "^] " + str(board.name) + " - (" + board.description + ") \n"
i = i + 1
outs = self.data.util.expandText(outs)
self.data.util.printPagedRaw(outs)
self.data.stateChange("board_listboardsend")
## UI for selecting a message board
def select(self):
self.data.stateChange("board_selectstart")
## Load board objects, pass them to listBoards() to display
boards = self.data.getBoards()
self.listBoards(boards)
## Prompt user for board
self.data.stateChange("board_selectpromptstart")
choice = self.data.util.prompt("\nSelect board: ")
self.data.stateChange("board_selectpromptend")
self.data.setCurrentBoard(boards[int(choice)-1].name)
## UI for displaying a message list
def listPosts(self,messageIds):
## Loop through the message ids, load message object, display one numbered line / message
self.data.util.println(" ")
i = 1
try:
outs = ""
for msgId in messageIds:
msg = self.data.getMessage(msgId)
outs = outs + "{0:5}".format(str(i)) + " {0:15} ".format(str(msg.fromname)[0:14]) + "{0:30}".format(str(msg.subject)[0:29]) + self.data.util.formatTimeString(msg.sentdate) + " " + self.data.util.formatDateString(msg.sentdate) + "\n"
i = i + 1
outs = self.data.util.expandText(outs)
self.data.util.printPagedRaw(outs)
except PyffleException as ex:
pass ## Catch a user break
## UI for displaying a message in boardname, current = current message being red, high = highest in board
def displayMessage(self,msgid,current,high):
self.data.stateChange("board_displaystart")
## Load the message object from the DB
instance = self.data.getMessage(msgid)
## Clear the user's screen
self.data.util.cls()
self.data.stateChange("board_displayhdrstart")
board = self.data.getBoard(instance.boardid)
boardname = "<none>"
if not board == None:
boardname = str(board.name)
## Top line: Msgin and boardname, followed by current/high, in reverse video
self.data.util.printn("^{0:<39}".format("#%s in %s" % (str(instance.id),boardname)))
self.data.util.println("{0:>39}^".format("(%s/%s)" % (str(current+1), str(high)))) ## we're now back to (back to normal video)
## Header..
self.data.util.printraw("From: " + instance.fromname.strip())
if not instance.toname == None:
self.data.util.printraw("To: " + instance.toname.strip())
self.data.util.printraw("Subj: " + instance.subject)
## Bottom line: Date and time in reverse video
self.data.util.println("^{0:>78}^".format("%s %s" % (self.data.util.formatDateString(instance.sentdate),self.data.util.formatTimeString(instance.sentdate))))
self.data.stateChange("board_displayhdrend")
self.data.stateChange("board_displaybodystart")
## Finally the message itself
try:
for msgtext in self.data.getMessagetexts(msgid):
for line in msgtext.split("\n"):
self.data.util.printraw(line)
except PyffleException as ex:
pass ## Catch a user break
self.data.stateChange("board_displaybodyend")
self.data.stateChange("board_displayend")
def search(self,allBoards=False,searchSpecial=False):
boardname = None
text = self.data.util.prompt("Search for: ")
self.data.util.println("\nSearching (please be patient)")
if not allBoards:
boardname = self.data.currentBoard
## search the message texts
textmsgids = self.data.searchMessagetexts(text,boardname,searchSpecial=searchSpecial)
## search the subjects
subjmsgids = self.data.searchMessageSubjects(text,boardname,searchSpecial=searchSpecial)
msgids = textmsgids
## merge the two
for msgid in subjmsgids:
if not msgid in msgids:
msgids.append(msgid)
msgids.sort()
self.read(msgids)
def read(self,messageIds=None,inScan=False):
self.data.stateChange("board_readstart")
## If we get passed some ids, we are being called from a new scan
if messageIds == None:
self.data.stateChange("board_readnormal")
messageIds = self.data.getMessagesByBoardname(self.data.getCurrentBoard())
else:
self.data.stateChange("board_readinscan")
## We will return this to tell any scan if the user wanted to abort or not
userAbort = False
## If there are messages, loop through them, starting with a list
if not messageIds == []:
self.data.stateChange("board_liststart")
self.listPosts(messageIds)
self.data.stateChange("board_listend")
currentMessage = 0
userQuits = False
self.data.stateChange("board_readpostloop")
while not (userQuits):
choice = ""
## Check if we're at the end of the messages
if currentMessage > (len(messageIds) - 1):
## We are at the end of the messages, display relevant prompt
if inScan:
self.data.stateChange("board_readinscanendpromptstart")
choice = self.data.util.prompt("^P^ost, ^L^ist, [1-" +str(len(messageIds)) + "] ^Q^uit Board, ^!^ Abort Scan: ")
self.data.stateChange("board_readinscanendpromptend")
else:
self.data.stateChange("board_readnormalendpromptstart")
choice = self.data.util.prompt("^P^ost, ^L^ist, [1-" +str(len(messageIds)) + "] ^Q^uit: ")
self.data.stateChange("board_readnormalendpromptend")
else:
## We are not at the end of the messages, display relevant prompt
if inScan:
self.data.stateChange("board_readinscanpromptstart")
choice = self.data.util.prompt("\n[^RET^] 1-" +str(len(messageIds)) + ", ^P^ost, ^L^ist, ^Q^uit Board, ^!^ Abort Scan: ")
self.data.stateChange("board_readinscanpromptend")
else:
self.data.stateChange("board_readnormalpromptstart")
choice = self.data.util.prompt("\n[^RET^] 1-" +str(len(messageIds)) + ", ^P^ost, ^L^ist, ^Q^uit: ")
self.data.stateChange("board_readnormalpromptend")
## Clean the choice string, see what the user wants to do
choice = choice.strip()
if choice == "":
## User pressed enter, try to show the next message
if not currentMessage > (len(messageIds) - 1):
self.displayMessage(messageIds[currentMessage],currentMessage,len(messageIds))
currentMessage = currentMessage + 1
self.data.stateChange("board_readnextmessage")
else:
## At the end of the messages, tell the user
self.data.stateChange("board_readendofmsgsstart")
self.data.util.println("- End of messages -")
self.data.stateChange("board_readendofmsgsend")
if inScan:
userQuits = True
if choice == "f":
self.data.stateChange("board_readfollowupstart")
msgid = messageIds[currentMessage-1]
msg = self.data.getMessage(msgid)
subject = msg.subject
subjectPrefix = "Re: "
if not subject.startswith(subjectPrefix):
subject = subjectPrefix + subject
msgtext = "\n\n%s wrote on %s: \n" % (msg.fromname, self.data.util.formatDateTimeString(msg.sentdate))
msgtexts = self.data.getMessagetexts(msgid)
msgtextlines = []
for mt in msgtexts:
for l in mt.split("\n"):
msgtextlines.append(l)
for s in msgtextlines:
msgtext = msgtext + "> " + s + "\n"
self.data.stateChange("board_followupcreatestart")
self.post(subject=subject,theText=msgtext)
self.data.stateChange("board_followupcreateend")
self.data.stateChange("board_readfollowupend")
if choice == "!" and inScan:
## If the user is in a new scan and hits !, we stop and let the scan know
self.data.stateChange("board_readuserabort")
userAbort = True
break
if choice == "p":
## User wants to post a message, let's do it
self.data.stateChange("board_readpoststart")
self.post()
self.data.stateChange("board_readpostend")
if choice == "l":
self.data.stateChange("board_readliststart")
## User wants to see the message list again
self.listPosts(messageIds)
## Not sure whether to reset the list?
## currentMessage = 0
self.data.stateChange("board_readlistend")
if choice == "q":
self.data.stateChange("board_readuserquit")
## User wants to stop
userQuits = True
break
if choice.isdigit():
## The user is trying to jump to a numbered message
## Messages are numbered from 1 to len on screen, but stored 0 to len-1
self.data.stateChange("board_readjumpstart")
chosenMessage = int(choice)
if chosenMessage > 0 and chosenMessage <= len(messageIds):
## It's a valid choice, set the pointer, show the message
currentMessage = chosenMessage - 1
self.displayMessage(messageIds[currentMessage],currentMessage,len(messageIds))
self.data.stateChange("board_readjumpok")
## Move to the next message
currentMessage = currentMessage + 1
else:
self.data.stateChange("board_readjumpbad")
## The user tried to choose an invalid message
self.data.util.println("\nMessage out of range.\n")
self.data.stateChange("board_readjumpend")
self.data.stateChange("board_readpostloopend")
## If the user hit !, we'll return True to a scan
return userAbort
else:
## No messages in this area.
self.data.stateChange("board_reademptystart")
self.data.util.println("No posts.")
self.data.stateChange("board_reademptyend")
def new(self):
## FIXME - args
## FIXME - join
## Remember the currently selected board
oldCurrent = self.data.getCurrentBoard()
## Get a list of all boards FIXME: Should only get the subscribed ones
boards = self.data.getBoards()
self.data.stateChange("board_newstart")
if self.currentUser.datelastnewscan == None:
self.data.stateChange("board_newnodatestart")
self.data.util.println("No last scan, setting to epoch..")
self.resetScan()
self.data.stateChange("board_newnodateend")
## Loop through the boards
self.data.util.println("\nScanning for new messages..\n")
self.data.stateChange("board_newloop")
scannedBoards = 0
for board in boards:
if not board.name.startswith('__') and (board.id in self.data.getJoinedBoardids()): ## ignore any internal boards
## Check that we're allowed to read this board
if self.data.srmcheck(board.aclid,self.currentUser.username,"READ",minlevel=board.minreadlevel):
## Get the messages
scannedBoards = scannedBoards + 1
msgids = self.data.getMessagesSince(board,self.currentUser.datelastnewscan)
if not msgids == []:
self.data.stateChange("board_newfoundstart")
self.data.util.println(" {0:30}".format(board.name) + " " + str(len(msgids)) + " new message(s)")
self.data.stateChange("board_newfoundend")
else:
self.data.stateChange("board_newemptystart")
self.data.util.println(" {0:30}".format(board.name) + " No new messages")
self.data.stateChange("board_newemptyend")
self.data.stateChange("board_newloopend")
if scannedBoards == 0:
self.data.stateChange("board_newjoinwarnstart")
self.data.util.println("No boards actually scanned, check your JOIN settings..")
self.data.stateChange("board_newjoinwarnend")
else:
self.data.stateChange("board_newsummarystart")
self.data.util.println("\nScanned %s boards\n" % (str(scannedBoards)))
self.data.stateChange("board_newsummaryend")
self.data.stateChange("board_newend")
return
def scan(self):
## FIXME - scan dates
## FIXME - args
## FIXME - join
## Remember the currently selected board
oldCurrent = self.data.getCurrentBoard()
## Get a list of all boards FIXME: Should only get the subscribed ones
boards = self.data.getBoards()
## Tell the user what's happening
self.data.stateChange("board_scanstartmsgstart")
self.data.util.prompt("Starting NEW SCAN (you can abort the scan by entering ! at a prompt)\nPress ENTER to start..")
self.data.stateChange("board_scanstartmsgend")
if self.currentUser.datelastnewscan == None:
self.data.stateChange("board_scannodatestart")
self.data.util.println("No last scan, setting to epoch..")
self.resetScan()
self.data.stateChange("board_scannodateend")
## Loop through the boards
self.data.stateChange("board_scanloop")
scannedBoards = 0
for board in boards:
if not board.name.startswith('__') and (board.id in self.data.getJoinedBoardids()): ## ignore any internal boards
## Check that we're allowed to read this board
if self.data.srmcheck(board.aclid,self.currentUser.username,"READ",minlevel=board.minreadlevel):
scannedBoards = scannedBoards + 1
## Get the messages
self.data.util.println("\nScanning board " + board.name + "...")
msgids = self.data.getMessagesSince(board,self.currentUser.datelastnewscan)
if not msgids == []:
self.data.stateChange("board_scanfoundnewstart")
## If there are any messages, temporarily set the current board to the board we're looking at
self.data.setCurrentBoard(board.name)
## Clear the screen
self.data.util.cls()
## Scan header
self.data.util.println("^" + board.name + "^ " + str(len(msgids)) + " message(s)")
## Read the messages we found
userAbort = self.read(messageIds = msgids,inScan = True)
## If the user hit ! during the read, we bail
self.data.stateChange("board_scanfoundnewend")
if userAbort:
self.data.stateChange("board_scanuserabort")
break
else:
self.data.stateChange("board_scanemptyboard")
self.data.util.println("Board %s has no new messages" % (board.name))
self.data.stateChange("board_scanloopend")
if scannedBoards == 0:
self.data.stateChange("board_scanjoinwarnstart")
self.data.util.println("No boards actually scanned, check your JOIN settings..")
self.data.stateChange("board_scanjoinwarnsend")
else:
self.data.stateChange("board_scansummarystart")
self.data.util.println("\nScanned %s boards" % (str(scannedBoards)))
self.data.stateChange("board_scansummaryend")
## Restore the original selected board to the session
self.data.setCurrentBoard(oldCurrent)
## Set the scan pointer to now
self.currentUser.datelastnewscan = None
self.data.storeUser(self.currentUser)
self.currentUser.datelastnewscan = datetime.now()
self.data.storeUser(self.currentUser)
return
def post(self,subject=None,theText=None):
## Get the board object for the currently selected board
board = self.data.getBoardByName(self.data.getCurrentBoard())
## Check that we're allowed to post on it
if self.data.srmcheck(board.aclid,self.currentUser.username,"POST",minlevel=board.minpostlevel):
## Get the subject of the post from the user
if subject == None:
subject = self.data.util.prompt("\nSubject: ")
## Launch an editor for the user and get the message text
if theText == None:
theText = ""
editor = Editor()
theText = editor.getText(text=theText)
## Confirm the user wants to go through with the posting
confirm = self.data.util.yesnoprompt("Proceed? ")
if confirm:
## They do, create the message
sentId = self.data.createMessage(self.currentUser.username,None,subject.strip(),theText,board=self.data.getCurrentBoard())
## Let the user know it was succesful
self.data.util.println("Message posted.")
## Update the user's posting stats
self.data.updateUserPosts()
## Display a cookie (it's what the old Waffle used to)
cookieModule = __import__("pyffle_cookie")
cookieInstance = cookieModule.PyffleModule()
cookieInstance.data = self.data
cookieInstance.currentUser = self.currentUser
cookieInstance.go("justacookie",["justacookie"])
else:
## Secure failure
self.data.util.println("Sorry, you are not allowed post on this board.")
def go(self, command, args):
command = command.strip()
if command == "search":
self.data.stateChange("board_cmdresetstart")
self.search()
self.data.stateChange("board_cmdresetend")
if command == "searchall":
self.data.stateChange("board_cmdresetstart")
self.search(allBoards=True)
self.data.stateChange("board_cmdresetend")
if command == "adminsearch":
self.data.stateChange("board_cmdresetstart")
self.search(allBoards=True,searchSpecial=True)
self.data.stateChange("board_cmdresetend")
if command == "reset":
self.data.stateChange("board_cmdresetstart")
self.resetScan()
self.data.stateChange("board_cmdresetend")
if command == "select":
self.data.stateChange("board_cmdselectstart")
self.select()
self.data.stateChange("board_cmdselectend")
if command == "post":
self.data.stateChange("board_cmdpoststart")
self.post()
self.data.stateChange("board_cmdpostend")
if command == "read" or command == "list":
self.data.stateChange("board_cmdreadstart")
self.read()
self.data.stateChange("board_cmdreadend")
if command == "new":
self.data.stateChange("board_cmdnewstart")
self.new()
self.data.stateChange("board_cmdnewend")
if command == "scan":
self.data.stateChange("board_cmdscanstart")
self.scan()
self.data.stateChange("board_cmdscanend")