示例#1
0
文件: yottu.py 项目: yottu/yottu
def main(argv):
	def __call__(self):
		pass

	dlog = DebugLog("debug.log")
	dlog.msg("Logging started\n")
	
	stdscr = curses.initscr()
	curses.noecho()
	curses.cbreak()
	stdscr.keypad(1)
	curses.start_color()
	
	try:
		wl = WindowLogic(stdscr)
		wl.start()

		ci = CommandInterpreter(stdscr, wl)
		ci.start()
	except Exception as e:
		dlog.excpt(e)
		raise

	ci.join()
	dlog.msg("Command Interpreter joined.\n")
	wl.stop()
	wl.join()
	dlog.msg("Thread Fetcher joined.\n")

	curses.nocbreak()
	stdscr.keypad(0)
	curses.echo()
	curses.endwin()
	curses.resetty()
	dlog.msg("Terminal restored.\n")
示例#2
0
class WindowLogic(threading.Thread):
	'''
	classdocs
	'''

	def __init__(self, stdscr):
		
		self.stdscr = stdscr
		self.dlog = DebugLog()
		try:
			self.windowList = []
			
			self.compad = CommandPad(stdscr)
			
			self.windowList.append(self.compad)
			self.set_active_window(0)
			self.compad.draw()
			
#			board = "int"
#			threadno = "50294416"
			self.nickname = "asdfasd"
			
			
#			self.bp = BoardPad(stdscr)
#			self.bp.join(board, threadno, self.nickname)
#			self.windowList.append(self.bp)
#			self.set_active_window(1)
			
			Thread.__init__(self)
			self._stop = threading.Event()
		except:
			raise
		
	def join_thread(self, board, thread):
		try:
			self.dlog.msg("Creating new boardpad for " + thread + " on /" + board + "/\n")
			boardpad = BoardPad(self.stdscr)
			boardpad.join(board, thread, self.nickname)
			self.windowList.append(boardpad)
			self.set_active_window(1)
		except Exception, err:
			self.dlog.excpt(err)
示例#3
0
	def run(self):
		dlog = DebugLog()
		dlog.msg("ThreadFetcher: Running on /" + self.board + "/" + self.threadno + "\n", 3)
		
		try:
			dictOutput = DictOutput(self.bp)
			getThread = Autism(self.threadno, self.board)
		except Exception as e:
			dlog.excpt(e)
			self.stdscr.addstr(0, 0, str(e), curses.A_REVERSE)
			self.stdscr.refresh()
				
		self.tb.draw()
		
		while True:
			
			dlog.msg("ThreadFetcher: Fetching for /" + self.board + "/" + self.threadno + "\n", 3)
			if self._stop.is_set():
				dlog.msg("ThreadFetcher: Stop signal for /" + self.board + "/" + self.threadno + "\n", 3)
				break
	
			try:
				getThread.setstdscr(self.stdscr)
				getThread.get()
				thread = getattr(getThread, "threadjson")
				dictOutput.refresh(thread)
				self.tb.set_title(dictOutput.getTitle())
			except Exception as e:
				self.sb.setStatus(str(e))
				dlog.excpt(e)
				pass
				
			for update_n in range (9, -1, -1):
				if self._stop.is_set():
					break
				
				try:
					if self._active:
						self.sb.draw(update_n)
				except Exception as e:
					dlog.excpt(e)
					pass
				

				time.sleep(1)
示例#4
0
文件: DictOutput.py 项目: yottu/yottu
	def refresh(self, json):
		self.thread = json
		
		try:
			debug = DebugLog("debug.log")
		except:
			raise
		
		
		for posts in self.thread['posts']:
			try:
			# skip if record found in dictionary
				no = posts['no']
				
				# Post is OP
				try:
					if posts['resto'] is 0:
						self.tdict['OP'] = {'no': posts['no'], 'sub': posts['sub'].encode('utf-8'), 'semantic_url': posts['semantic_url'].encode('utf-8')}					
				
				except Exception as e:
					debug.msg("Exception:" + e.msg() + "\n")
					raise
					
				if no in self.tdict:
					continue
				
				name = posts['name']
				time = datetime.datetime.fromtimestamp(posts['time']).strftime('%H:%M')
			except:
				continue
					
			curses.use_default_colors()
			for i in range(0, curses.COLORS):  # @UndefinedVariable
				curses.init_pair(i + 1, i, -1)
						
			# assign color to post number
			color = randint(2, 255)
	
	
	
			try: country = posts['country']
			except: country = ""
			try:
				com = posts['com']
				com = re.sub('<br>', ' ', com)
				refposts = ""
				refposts = re.findall('&gt;&gt;(\d+)', com)
				com = re.sub('&gt;&gt;(\d+)', '\g<1> ', com)
				com = re.sub('&#039;', '\'', com)
				com = re.sub('&gt;', '>', com)
				com = re.sub('&lt;', '<', com)
				com = re.sub('&quot;', '"', com)
				com = re.sub('<[^<]+?>', '', com)
			except: com = "[File only]"
			try:
				trip = posts['trip']
			except:
				trip = ""
	
	
	
			self.tdict[no] = {'country':country, 'name':name, 'time':time, 'com':com, 'trip':trip, 'color':color}
	
#			try:
#				line = u' '.join((time, ">>" + str(no), country, "<" + name + ">", com)).encode('utf-8')
#			except:
#				raise
						
			try:
				self.bp.addstr("", curses.color_pair(color))
				self.bp.addstr(time)
				self.bp.addstr(" >>" + str(no), curses.color_pair(color))
				self.bp.addstr(" " + country)
	
				if re.match(self.nickname, name) is not None:
					self.bp.addstr(" <" + self.nickname + "> ", curses.A_BOLD)
	
				else:
					self.bp.addstr(" <" + name.encode('utf8') + "> ")
				#self.bp.addstr(com.encode('utf8'))
				
				comlist = com.split()
				try:
					for word in comlist:
						if word not in refposts:
							self.bp.addstr(u''.join((word + " ")).encode('utf8'))
						else:
							# Comment and reference color encoding
							try:
								refcolor = self.tdict[int(word)]['color']
								self.bp.addstr(">>" + word + " ", curses.color_pair(refcolor))
								# if reference points to nickname, higligt the name
								if re.match(self.tdict[int(word)]['name'], self.nickname):
									self.bp.addstr("(You) ", curses.A_BOLD | curses.color_pair(221))
									Notifier.send(com)
#								if re.match(word, threadno):
#									self.bp.addstr("(OP) ", curses.A_BOLD | curses.color_pair(197))
							except:
								self.bp.addstr(word)
				except:
					self.bp.addstr("[File only]")
			except:
				raise
	
			self.bp.addstr("\n")
			
		try:
			self.title = self.tdict['OP']['sub']
		except Exception as e:
			debug.msg("Couldn't set title" + str(e) + "\n")
			pass
class PostReply(object):
    '''
    classdocs
    '''
    def __init__(self, board, threadno):
        self.board = board
        self.threadno = threadno

        self.sitekey = "6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc"

        self.captcha2_url = "https://www.google.com/recaptcha/api/fallback?k=" + self.sitekey
        self.captcha2_payload_url = "https://www.google.com/recaptcha/api2/payload"
        self.captcha2_image_base_url = ""
        self.site_ref = "https://boards.4chan.org/"
        self.user_agent = 'Mozilla/5.0 (X11; Linux x86_64; rv:58.0) Gecko/20100101 Firefox/58.0'

        self.captcha2_challenge_text = None  # "Select all images with ducks."
        self.captcha2_challenge_id = None

        self.captcha2_image = ""  # Binary image
        self.captcha2_image_filename = "yottu-captcha.jpg"  # TODO don't save the image

        self.captcha2_solution = None  # Array of integers associated to the captcha checkboxes, usually 0-8
        self.captcha2_response = None  # Response the Reply post form wants (the actual solution)

        self.lock = thread.allocate_lock()
        self.dictOutput = None
        self.bp = None

        self.dlog = DebugLog()

    class PostError(Exception):
        def __init__(self, *args, **kwargs):
            Exception.__init__(self, *args, **kwargs)

#    def get_captcha_solution(self):
#        return self.__captcha_solution

    def set_captcha2_solution(self, value):
        # Append checkbox integer values to response array
        try:
            self.captcha2_solution = []
            for i in str(value):
                self.captcha2_solution.append(str(int(i) - 1))
        except ValueError as err:
            self.dlog.excpt(err,
                            msg=">>>in PostReply.set_captcha2_solution()",
                            cn=self.__class__.__name__)
            raise
        except Exception as err:
            self.dlog.excpt(err,
                            msg=">>>in PostReply.set_captcha2_solution()",
                            cn=self.__class__.__name__)

    def _query(self):
        pass

    def get_captcha_challenge(self):
        """
            1. Query captcha url with site key
            2. From the result get
                a) the challenge text
                b) the challenge id
            3. Query payload url with site key and cid and get
                c) the captcha image
        """
        try:
            headers = {'Referer': self.site_ref, 'User-Agent': self.user_agent}
            r = requests.get(self.captcha2_url, headers=headers)

            r.raise_for_status()

            html_content = r.content
            soup = BeautifulSoup(html_content, 'html.parser')

            try:
                self.captcha2_challenge_text = soup.find(
                    "div", {
                        'class': 'rc-imageselect-desc-no-canonical'
                    }).text
            except:
                self.captcha2_challenge_text = soup.find(
                    "div", {
                        'class': 'rc-imageselect-desc'
                    }).text

            self.captcha2_challenge_id = soup.find(
                "div", {
                    'class': 'fbc-imageselect-challenge'
                }).find('input')['value']

            # Get captcha image
            headers = {
                'Referer': self.captcha2_url,
                'User-Agent': self.user_agent
            }
            r = requests.get(self.captcha2_payload_url + '?c=' +
                             self.captcha2_challenge_id + '&k=' + self.sitekey,
                             headers=headers)
            self.captcha2_image = r.content
            #self.save_image(self.captcha2_image_filename)

        except Exception as err:
            self.dlog.excpt(err,
                            msg=">>>in PostReply.get_captcha_challenge()",
                            cn=self.__class__.__name__)
            raise

    def save_image(self, filename):
        """save image to file system"""
        try:

            with open(filename, "w") as f:
                f.write(self.captcha2_image)

        except Exception as err:
            self.dlog.excpt(err,
                            msg=">>>in PostReply.save_image()",
                            cn=self.__class__.__name__)

    def display_captcha(self):

        # Reformat picture to be displayed horizontally
        try:
            TermImage.image_split_h(StringIO(self.captcha2_image),
                                    self.captcha2_image_filename)
        except Exception as err:
            self.dlog.warn(err,
                           msg=">>>in PostReply.display_captcha()",
                           cn=self.__class__.__name__)

        # Overlay the captcha in the terminal
        try:
            TermImage.display(self.captcha2_image_filename)
            return True
        except:
            pass
        # On failure fall back to using the external image viewer
        try:
            TermImage.display_img(self.captcha2_image_filename)
            return False
        except:
            raise

    # FIXME captchav2 update
    def defer(self, time_wait, **kwargs):
        ''' wait for timer to run out before posting '''

        if not self.bp.cfg.get('user.pass.enabled'):
            self.get_response()
            captcha2_response = self.captcha2_response
            self.dlog.msg("Waiting C: " + captcha2_response[:12] + str(kwargs),
                          4)
        self.bp.sb.setStatus("Deferring comment: " + str(time_wait) + "s")

        self.lock.acquire()
        self.dlog.msg("Lock acquired for deferred post " + str(kwargs))

        try:
            while time_wait > 0:
                time.sleep(time_wait)
                # get new lastpost value and see if post needs to be deferred further
                time_wait = self.bp.time_last_posted_thread + 60 - int(
                    time.time())

            if not self.bp.cfg.get('user.pass.enabled'):
                kwargs.update(dict(captcha2_response=captcha2_response))
                self.dlog.msg(
                    "Now posting: C: " + captcha2_response[:12] + str(kwargs),
                    4)
            else:
                self.dlog.msg("Now posting deferred comment", 4)

            rc = self.post(**kwargs)
            if rc != 200:
                self.bp.sb.setStatus("Deferred comment was not posted: " +
                                     str(rc))
        except Exception as err:
            self.bp.sb.setStatus("Deferred: " + str(err))
        finally:
            self.lock.release()

    def get_response(self):

        try:

            headers = {
                'Referer': self.captcha2_url,
                'User-Agent': self.user_agent
            }
            data = {
                'c': self.captcha2_challenge_id,
                'response': self.captcha2_solution
            }
            r = requests.post(self.captcha2_url, headers=headers, data=data)
            html_post = r.content
            soup = BeautifulSoup(html_post, 'html.parser')

        except Exception as err:
            self.dlog.excpt(err,
                            msg=">>>in PostReply.get_response()",
                            cn=self.__class__.__name__)
            raise

        try:
            self.captcha2_response = soup.find(
                "div", {
                    'class': 'fbc-verification-token'
                }).text
        except AttributeError as err:
            self.dlog.warn(
                err,
                msg=
                "Could not get verification token, captcha input error (input: "
                + str(self.captcha2_solution) + ")?",
                cn=self.__class__.__name__)
            raise
        except Exception as err:
            self.dlog.excpt(err,
                            msg=">>>in PostReply.get_response()",
                            cn=self.__class__.__name__)

    def auth(self):
        # Set user.pass.cookie by posting the auth form containing user.pass.token and user.pass.pin

        try:
            token = self.bp.cfg.get('user.pass.token')
            pin = self.bp.cfg.get('user.pass.pin')
            if not token:
                token = self.bp.query_userinput(label="Token: ",
                                                input_type='text')

            if not pin:
                pin = self.bp.query_userinput(label="PIN: ",
                                              input_type='number')

            self.dlog.msg(
                "Authenticating Pass with PIN/Token length: " + str(len(pin)) +
                "/" + str(len(token)), 3)

            data = dict(act='do_login', id=token, pin=pin)

            res = requests.post('https://sys.4chan.org/auth', data=data)

            self.bp.cfg.set('user.pass.cookie', res.cookies['pass_id'])

            if self.bp.cfg.get('config.autosave'):
                self.dlog.msg("Autosaving user.pass.cookie ..")
                self.bp.cfg.writeConfig()

        except KeyError as err:
            self.dlog.excpt(err,
                            msg=">>>in PostReply.auth()",
                            cn=self.__class__.__name__)
            raise PostReply.PostError("Could not authenticate pass token/pin.")
        except Exception as err:
            self.dlog.excpt(err,
                            msg=">>>in PostReply.auth()",
                            cn=self.__class__.__name__)

    def post(self,
             nickname="",
             comment="",
             subject="",
             file_attach="",
             ranger=False,
             captcha2_response=""):
        '''
        subject: not implemented
        file_attach: (/path/to/file.ext) will be uploaded as "file" + extension
        ranger: extract path from ranger's --choosefile file
        '''
        cookies = None
        try:
            if self.bp.cfg.get('user.pass.enabled'):

                if not self.bp.cfg.get('user.pass.cookie'):
                    # get and set new cookie from pass and pin
                    self.auth()
                cookies = dict(pass_id=self.bp.cfg.get('user.pass.cookie'),
                               pass_enabled="1")

            elif not captcha2_response:
                self.get_response()
                captcha2_response = self.captcha2_response

            if nickname == None:
                nickname = ""
            else:
                nickname = u''.join(nickname)

            # Read file / get mime type
            try:
                if file_attach:

                    # extract file path from ranger file and re-assign it
                    if ranger:
                        with open(file_attach, "r") as f:
                            file_attach = f.read()

                    _, file_ext = os.path.splitext(file_attach)
                    filename = "file" + file_ext
                    content_type, _ = mimetypes.guess_type(filename)
                    with open(file_attach, "rb") as f:
                        filedata = f.read()

                    if content_type is None:
                        raise TypeError("Could not detect mime type of file " +
                                        str(filename))
                else:
                    filename = filedata = content_type = ""
            except Exception as err:
                self.dlog.excpt(err,
                                msg=">>>in PostReply.post() -> file_attach",
                                cn=self.__class__.__name__)
                raise

            url = "https://sys.4chan.org/" + self.board + "/post"
            #url = 'http://httpbin.org/status/404'
            #url = "http://localhost/" + self.board + "/post"
            #url = 'http://httpbin.org/post'
            #url = 'http://requestbin.fullcontact.com/1i28ed51'

            values = {
                'MAX_FILE_SIZE': (None, '4194304'),
                'mode': (None, 'regist'),
                # 'pwd' : ('', 'tefF92alij2j'),
                'name': (None, nickname),
                # 'sub' : ('', ''),
                'resto': (None, str(self.threadno)),
                # 'email' : ('', ''),
                'com': (None, comment),
                'g-recaptcha-response': (None, captcha2_response),
                'upfile': (filename, filedata, content_type)
            }

            headers = {'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64)'}

            response = requests.post(url,
                                     headers=headers,
                                     files=values,
                                     cookies=cookies)

            # raise exception on error code
            response.raise_for_status()
            if re.search("is_error = \"true\"", response.text):
                perror = "Unknown Error."

                if self.bp.cfg.get('user.pass.cookie'):
                    perror += " user.pass.cookie might be invalid."
                try:
                    perror = re.search(r"Error: ([A-Za-z.,]\w*\s*)+",
                                       response.text).group(0)
                except:
                    if re.search("blocked due to abuse", response.text):
                        perror = "You are range banned ;_;"
                finally:
                    raise PostReply.PostError(perror)

            if response.status_code == 200 and self.dictOutput:
                self.dictOutput.mark(comment)
                self.bp.post_success(int(time.time()))
            else:
                self.dlog.msg("response.status_code: " +
                              str(response.status_code))
                self.dlog.msg("self.dictOutput: " + str(self.dictOutput))

            return response.status_code

        except Exception as err:
            self.dlog.excpt(err,
                            msg=">>>in PostReply.post()",
                            cn=self.__class__.__name__)
            raise
示例#6
0
class CommandInterpreter(threading.Thread):
	def __init__(self, stdscr, wl):
		self.stdscr = stdscr
		self.wl = wl
		self.screensize_x, self.screensize_y = stdscr.getmaxyx();
		Thread.__init__(self)
		
		self.cfg = Config.Config(".config/yottu/", "config")
		self.cmode = False # command mode
		self.tmode = False # talking mode (no need to prefix /say)
		self.clinepos = 4
		self.command = ""
		self.context = "int" # context in which command is executed
		
		self.stdscr.addstr(self.screensize_x-1, 0, "[^] ")
		curses.curs_set(False)
		
		self.terminate = 0
		
		self.dlog = DebugLog()
		
	def cout(self, c):
		self.stdscr.addstr(self.screensize_x-1, self.clinepos, c)
		self.command += c
		self.clinepos += 1
		
	def clean(self):
		self.stdscr.addstr(self.screensize_x-1, 0, str(" "*(self.screensize_y-1)))
		self.command = ""
		self.clinepos = 4
		
	def parse_param(self, string):
		''' return list from whitespace separated string '''
		''' TODO: Implement validity matching logic '''
		return string.split()
		
	def exec_com(self):
		
		self.dlog.msg("Trying to execute command: " + self.command + "\n")
		#cmd_arg = re.compile('\w+\W+(\w+)')
		cmd_args = self.command.split()
		if re.match("join", self.command):
			
			try:
				self.wl.join_thread(self.context, cmd_args[1])
				self.wl.compadout("Joining /" + self.context + "/" + cmd_args[1])
			except IndexError:
				self.wl.compadout("Usage: /join <thread number>")
			except:
				raise
			
		elif re.match("board", self.command):
			
			try:
				self.context = cmd_args[1]
			except IndexError:
				self.wl.compadout("Usage /board <board>")
			except:
				raise
			
		elif re.match("part", self.command):
			self.wl.destroy_active_window()
			
		elif re.match("load", self.command):
			self.cfg.readConfig()
			
		elif re.match("save", self.command):
			self.cfg.writeConfig()
			
		elif re.match("set", self.command):
			
			# Show settings if no args are given
			if len(cmd_args) is 1: 
				configItems = self.cfg.getSettings()
				self.wl.compadout("[Main]")
				for pair in configItems:
					self.wl.compadout(pair[0] + ": " + pair[1])
					
			# Else assign new value to setting
			else:
				key = cmd_args[1]
				val = ' '.join(cmd_args[2:])
				try:
					self.cfg.set(key, val)
				except Exception as e:
					self.dlog.excpt(e)
					
		elif re.match("quit", self.command):
			self.terminate = 1
			
		else:
			self.dlog.msg("Invalid command: " + self.command + "\n")
		
	def run(self):
		curses.mousemask(-1)
		while True:
			
			if self.terminate is 1:
				break
			
			self.stdscr.move(self.screensize_x-1, self.clinepos)
			if self.cmode:
				curses.curs_set(True)
				
			c = self.stdscr.getkey()
			
			if self.cmode:
				curses.curs_set(True)
	
			self.dlog.msg("getkey(): "+ c + "\n", 5)
			#c = self.stdscr.getch()
			
			if self.cmode:
				
				if c == "KEY_BACKSPACE":
					
					if self.clinepos > 4:
						self.command = self.command[:-1]
						self.clinepos = self.clinepos - 1
						self.stdscr.addstr(self.screensize_x-1, self.clinepos, " ")
						self.stdscr.move(self.screensize_x-1, self.clinepos)
						
					continue
				
				try:
					if c == u'\n' or ord(c) == 27:
						if c == u'\n':
							self.exec_com()
						self.cmode = False
						self.clean()
						self.stdscr.addstr(self.screensize_x-1, 0, "[^] ")
						curses.curs_set(False)
						continue
				except Exception as e:
					self.dlog.excpt(e)
					pass
					
				try:
					self.cout(c)
					continue
				except:
					pass
				
			if c == u'q':
				break
			elif c == u'/' or c == u'i':
				self.stdscr.addstr(self.screensize_x-1, 0, "[/] ")
				self.cmode = True
				
			elif c == u't':
				self.stdscr.addstr(self.screensize_x-1, 0, "[>] ")
				self.tmode = True
			elif c == u'w':
				self.wl.moveup()
			elif c == u's':
				self.wl.movedown()
			elif c == u'1':
				try:
					self.wl.raise_window(0)
				except:
					raise
			elif c == u'2':
				try:
					self.wl.raise_window(1)
				except:
					raise
			elif c == u'3':
				try:
					self.wl.raise_window(2)
				except:
					raise				
			elif c == u'n':
				try:
					self.wl.next() #TODO: implement in wl
				except:
					raise
			elif c == "KEY_MOUSE":
				mouse_state = curses.getmouse()[4]
				self.dlog.msg("getmouse(): "+ str(mouse_state) + "\n", 5)
				#self.stdscr.addstr(str(mouse_state))
				if int(mouse_state) == 134217728:
					self.wl.movedown(5)
				elif int(mouse_state) == 524288:
					self.wl.moveup(5)
示例#7
0
class WindowLogic(object):
    '''
	classdocs
	'''
    def __init__(self, stdscr):

        self.curses = curses
        self.stdscr = stdscr
        curses.use_default_colors()  # @UndefinedVariable
        # assign color to post number, pairs 1-10 are reserved
        for i in range(0, curses.COLORS):  # @UndefinedVariable
            curses.init_pair(i + 10, i, -1)  # @UndefinedVariable
        # reserved color pairs
        curses.init_pair(1, curses.COLOR_BLACK,
                         curses.COLOR_GREEN)  # @UndefinedVariable
        curses.init_pair(2, curses.COLOR_YELLOW,
                         curses.COLOR_GREEN)  # @UndefinedVariable
        curses.init_pair(3, curses.COLOR_RED,
                         curses.COLOR_GREEN)  # @UndefinedVariable
        curses.init_pair(4, curses.COLOR_RED, -1)  # @UndefinedVariable

        self.dlog = DebugLog(self)
        try:
            self.cfg = Config()
            self.cfg.register(self)

            self.sb = None
            self.tw = None
            self.db = Database(self)

            self.tw = ThreadWatcher(self)

            self.windowList = []  # Array of all window objects (i.e. Pads)
            self.windowListProperties = {
            }  # Associating a window object with its properties

            self.ci = None  # Set by CommandInterpreter.__init__()

            self.compad = CommandPad(stdscr, self)
            self.msgpad = MessagePad(stdscr, self)

            self.append_pad(self.compad)
            self.append_pad(self.msgpad)
            self.set_active_window(0)

            self.nickname = ""

    #		Thread.__init__(self)
    #		self._stop = threading.Event()
        except Exception as err:
            self.dlog.excpt(err,
                            msg=">>>in WindowLogic.__init__()",
                            cn=self.__class__.__name__)
            raise

    def on_config_change(self, *args, **kwargs):
        self.cfg = Config()
        self.db.on_config_change(*args, **kwargs)
        self.dlog.msg("Config change detected")
        if self.cfg.get('threadwatcher.enable') and not self.tw:
            self.dlog.msg("Starting ThreadWatcher")
            self.tw = ThreadWatcher(self)

    def set_nickname(self, value):
        self.__nickname = value
        if self.__nickname:
            self.__nickname = value
        for window in self.windowList:
            window.set_nickname(self.get_nickname())

    def get_nickname(self):
        return self.__nickname

    def get_window_list(self):
        return self.__windowList

    def get_property(self, window, prop):
        return self.windowListProperties[window][prop]

    def set_property(self, window, prop, value):
        self.windowListProperties[window][prop] = value

    def set_window_list(self, value):
        self.__windowList = value

    def append_pad(self, window):
        try:
            self.windowList.append(window)
            # Properties of a window instance, note: use deepcopy from copy if not assigning it directly
            self.windowListProperties[window] = {
                'sb_unread': False,
                'sb_lines': 0,
                'sb_mentioned': False
            }

            # Let statusbar of window know what window number it has
            # TODO: This needs to be reset when a window gets destroyed or moved
            window.sb.set_sb_windowno(len(self.windowList))
        except Exception as err:
            self.dlog.excpt(err,
                            msg=">>>in WindowLogic.append_pad()",
                            cn=self.__class__.__name__)

    def join_thread(self, board, thread):
        try:
            boardpad = BoardPad(self.stdscr, self)
            boardpad.join(board, thread, self.nickname)
            self.append_pad(boardpad)
            self.raise_window(len(self.windowList) - 1)
        except Exception, err:
            self.dlog.excpt(err,
                            msg=">>>in WindowLogic.join_thread()",
                            cn=self.__class__.__name__)