Example #1
0
    def __init__(self):
        super().__init__()
        self.bbsmenu: Optional[Bbsmenu] = None
        self.board: Optional[Board] = None
        self.thread: Optional[Thread] = None
        self.image: Optional[Union[str, HTTPError, URLError]] = None

        self.ng: NG = NG()

        if NG_PATH.is_file():
            s = NG_PATH.read_text()

            # Ensure it's not empty
            if len(s.replace(" ", "")) != 0:
                self.ng.deserialize(s)

        self.history = History(MAX_HISTORY)

        if HISTORY_PATH.is_file():
            s = HISTORY_PATH.read_text()
            self.history.deserialize(s)

        self.favorites = Favorites()

        if FAVORITES_PATH.is_file():
            s = FAVORITES_PATH.read_text()
            self.favorites.deserialize(s)

        self.on_property_changed = PropertyChangedEventHandler()
Example #2
0
    def __init__(self, server: str, board: str):
        super().__init__()

        self.server = server
        self.board = board
        self.threads: List[ThreadHeader] = []
        self.on_property_changed = PropertyChangedEventHandler()
Example #3
0
    def __init__(self, app_context: AppContext):
        super().__init__()

        self._app_context = app_context
        self._board = app_context.board
        self._threads = None

        if "number" in DEFAULT_SORT:
            self._sort_by = "number"
        elif "title" in DEFAULT_SORT:
            self._sort_by = "title"
        elif "count" in DEFAULT_SORT:
            self._sort_by = "count"
        elif "speed" in DEFAULT_SORT:
            self._sort_by = "speed"

        self._reverse_sort = DEFAULT_SORT.startswith("!")

        self._active_sort = False
        self._search_word = None

        self._app_context.on_property_changed.add(self._app_context_changed)
        self._app_context.ng.on_collection_changed.add(self._ng_changed)
        self._app_context.history.on_property_changed.add(
            self._history_changed)
        self._app_context.favorites.on_property_changed.add(
            self.favorite_changed)
        self.on_property_changed = PropertyChangedEventHandler()
Example #4
0
class NGVM:
    def __init__(self, app_context: AppContext):
        super().__init__()
        self._app_context = app_context
        self._app_context.ng.on_collection_changed.add(self._ng_changed)
        self.on_property_changed = PropertyChangedEventHandler()

    @property
    def names(self) -> List[NGName]:
        return self._app_context.ng.names

    @property
    def ids(self) -> List[NGId]:
        return self._app_context.ng.ids

    @property
    def words(self) -> List[NGWord]:
        return self._app_context.ng.words

    @property
    def titles(self) -> List[NGTitle]:
        return self._app_context.ng.titles

    def update_ng(self, id, values):
        self._app_context.ng.update_ng(id, values)

    def delete_ng(self, id):
        self._app_context.ng.delete_ng(id)

    def _ng_changed(self, e: CollectionChangedEventArgs):
        self.on_property_changed.invoke(
            PropertyChangedEventArgs(self, e.property_name))
Example #5
0
    def __init__(self, app_context: AppContext):
        super().__init__()
        self._app_context = app_context
        self._bbsmenu = app_context.bbsmenu
        self.selected_category: Optional[Category] = None
        self.on_property_changed = PropertyChangedEventHandler()

        self._app_context.on_property_changed.add(self._app_context_changed)
Example #6
0
class Favorites:
    def __init__(self):
        super().__init__()
        self.list = []
        self.on_property_changed = PropertyChangedEventHandler()
        self.on_order_changed = OrderChangedEventHandler()

    def add(self, item: Union[FavoriteThread, FavoriteBoard]):
        self.list.append(item)
        self.on_property_changed.invoke(PropertyChangedEventArgs(self, "list"))

    def remove(self, item: Union[FavoriteThread, FavoriteBoard]):
        self.list.remove(item)
        self.on_property_changed.invoke(PropertyChangedEventArgs(self, "list"))

    def raise_order(self, item: Union[FavoriteThread, FavoriteBoard]):
        for i, x in enumerate(self.list):
            if x == item:
                if i == 0:
                    return
                else:
                    tmp = self.list[i-1]
                    self.list[i-1] = x
                    self.list[i] = tmp
                    self.on_order_changed.invoke(OrderChangedEventArg(self, "list", x, i-1))
                    return

    def lower_order(self, item: Union[FavoriteThread, FavoriteBoard]):
        for i, x in enumerate(self.list):
            if x == item:
                if i == len(self.list) - 1:
                    return
                else:
                    tmp = self.list[i+1]
                    self.list[i+1] = x
                    self.list[i] = tmp
                    self.on_order_changed.invoke(OrderChangedEventArg(self, "list", x, i+1))
                    return

    def serialzie(self) -> str:
        d = {"items": []}

        for item in self.list:
            if isinstance(item, FavoriteThread):
                d["items"].append({"title": item.title, "server": item.server, "board": item.board, "key": item.key})
            else:
                d["items"].append({"name": item.name, "server": item.server, "board": item.board})

        return json.dumps(d)

    def deserialize(self, s: str):
        d = json.loads(s)

        for item in d["items"]:
            if "title" in item:
                self.list.append(FavoriteThread(item["title"], item["server"], item["board"], item["key"]))
            else:
                self.list.append(FavoriteBoard(item["name"], item["server"], item["board"]))
Example #7
0
    def __init__(self, app_context: AppContext):
        super().__init__()
        self._app_context = app_context
        self._app_context.favorites.on_property_changed.add(
            self._context_changed)
        self._app_context.favorites.on_order_changed.add(self._order_changed)

        self.on_property_changed = PropertyChangedEventHandler()
        self.on_order_changed = OrderChangedEventHandler()
Example #8
0
    def __init__(self, server: str, board: str, key: str):
        super().__init__()

        self.server = server
        self.board = board
        self.key = key
        self.title = None
        self.responses: List[Response] = []
        self._is_pastlog: bool = False
        self.on_property_changed = PropertyChangedEventHandler()
        self.on_collection_changed = CollectionChangedEventHandler()
Example #9
0
class ImageVM:
    def __init__(self, context: AppContext):
        super().__init__()
        self._app_context = context
        self._app_context.on_property_changed.add(self._context_changed)
        self.on_property_changed = PropertyChangedEventHandler()

    @property
    def image(self) -> Optional[Union[str, HTTPError, URLError]]:
        return self._app_context.image

    def _context_changed(self, e: PropertyChangedEventArgs):
        if e.property_name == "image":
            self.on_property_changed.invoke(PropertyChangedEventArgs(self, "image"))
Example #10
0
    def __init__(self, app_context: AppContext):
        super().__init__()

        self._app_context = app_context
        self._thread = app_context.thread
        self._responses = None
        self._links = None
        self._replies = None
        self._ids = None
        self.on_property_changed = PropertyChangedEventHandler()
        self.on_collection_changed = CollectionChangedEventHandler()

        app_context.on_property_changed.add(self._app_context_changed)
        app_context.ng.on_collection_changed.add(self._ng_changed)
        app_context.history.on_property_changed.add(self._history_changed)
        app_context.favorites.on_property_changed.add(self._favorites_changed)
Example #11
0
class Bbsmenu:
    def __init__(self):
        super().__init__()

        self.categories = None
        self.dns = {}
        self.on_property_changed = PropertyChangedEventHandler()

    def update(self):
        html = get_bbsmenu()
        parser = BbsmenuParser(html)
        self.categories = []

        for c in parser.categories():
            boards = []
            for b in c["boards"]:
                boards.append(BoardHeader(b["server"], b["board"], b["name"]))
                self.dns[b["board"]] = b["name"]

            self.categories.append(Category(c["name"], boards))

        self.on_property_changed.invoke(PropertyChangedEventArgs(self, "categories"))
Example #12
0
class FavoritesVM:
    def __init__(self, app_context: AppContext):
        super().__init__()
        self._app_context = app_context
        self._app_context.favorites.on_property_changed.add(
            self._context_changed)
        self._app_context.favorites.on_order_changed.add(self._order_changed)

        self.on_property_changed = PropertyChangedEventHandler()
        self.on_order_changed = OrderChangedEventHandler()

    @property
    def list(self):
        return self._app_context.favorites.list

    def add(self, item: Union[FavoriteThread, FavoriteBoard]):
        self._app_context.favorites.add(item)

    def remove(self, item: Union[FavoriteThread, FavoriteBoard]):
        self._app_context.favorites.remove(item)

    def raise_order(self, item: Union[FavoriteThread, FavoriteBoard]):
        self._app_context.favorites.raise_order(item)

    def lower_order(self, item: Union[FavoriteThread, FavoriteBoard]):
        self._app_context.favorites.lower_order(item)

    def open_thread(self, item: FavoriteThread):
        self._app_context.set_thread(item.server, item.board, item.key)

    def open_board(self, item: FavoriteBoard):
        self._app_context.set_board(item.server, item.board)

    def _context_changed(self, e: PropertyChangedEventArgs):
        self.on_property_changed.invoke(PropertyChangedEventArgs(self, "list"))

    def _order_changed(self, e: OrderChangedEventArg):
        self.on_order_changed.invoke(e)
Example #13
0
class History:
    def __init__(self, max_history: int):
        super().__init__()
        self._dict = {}
        self._max_history = max_history
        self.on_property_changed = PropertyChangedEventHandler()

    def get(self, board: str, key: str) -> Optional[ThreadHistory]:
        return self._dict.get(board + key)

    def save(self, board: str, key: str, bookmark: int, retrieved_reses: int):
        while self._max_history <= len(self._dict):
            self._dict.popitem()

        self._dict[board + key] = ThreadHistory(bookmark, retrieved_reses)

        self.on_property_changed.invoke(PropertyChangedEventArgs(self, "self"))

    def serialize(self) -> str:
        d = {}

        for k, v in self._dict.items():
            d[k] = {
                "bookmark": v.bookmark,
                "retrieved_reses": v.retrieved_reses
            }

        return json.dumps(d)

    def deserialize(self, s: str):
        obj = json.loads(s)

        for k, v in obj.items():
            if len(self._dict) >= self._max_history:
                break

            self._dict[k] = ThreadHistory(v["bookmark"], v["retrieved_reses"])
Example #14
0
class BbsmenuVM:
    def __init__(self, app_context: AppContext):
        super().__init__()
        self._app_context = app_context
        self._bbsmenu = app_context.bbsmenu
        self.selected_category: Optional[Category] = None
        self.on_property_changed = PropertyChangedEventHandler()

        self._app_context.on_property_changed.add(self._app_context_changed)

    @property
    def categories(self) -> Optional[List[Category]]:
        return self._bbsmenu.categories if self._bbsmenu is not None else None

    def select_category(self, idx: int):
        if self._bbsmenu is not None and idx < len(self._bbsmenu.categories)\
                and idx >= 0:
            self.selected_category = self._bbsmenu.categories[idx]
            self.on_property_changed.invoke(
                PropertyChangedEventArgs(self, "selected_category"))

    def select_board(self, idx: int):
        if self.selected_category is not None and idx < len(
                self.selected_category.boards) and idx >= 0:
            self._app_context.set_board(
                self.selected_category.boards[idx].server,
                self.selected_category.boards[idx].board)

    def update(self):
        if self._bbsmenu is not None:
            self._bbsmenu.update()

    def _app_context_changed(self, e: PropertyChangedEventArgs):
        if e.property_name == "bbsmenu":
            if self._bbsmenu is not None:
                self._bbsmenu.on_property_changed.remove(self._bbsmenu_changed)

            self._bbsmenu = self._app_context.bbsmenu
            self._bbsmenu.on_property_changed.add(self._bbsmenu_changed)

            self.on_property_changed.invoke(
                PropertyChangedEventArgs(self, "categories"))

    def _bbsmenu_changed(self, e: PropertyChangedEventArgs):
        if e.property_name == "categories":
            self.on_property_changed.invoke(
                PropertyChangedEventArgs(self, "categories"))
Example #15
0
class AppContext:
    def __init__(self):
        super().__init__()
        self.bbsmenu: Optional[Bbsmenu] = None
        self.board: Optional[Board] = None
        self.thread: Optional[Thread] = None
        self.image: Optional[Union[str, HTTPError, URLError]] = None

        self.ng: NG = NG()

        if NG_PATH.is_file():
            s = NG_PATH.read_text()

            # Ensure it's not empty
            if len(s.replace(" ", "")) != 0:
                self.ng.deserialize(s)

        self.history = History(MAX_HISTORY)

        if HISTORY_PATH.is_file():
            s = HISTORY_PATH.read_text()
            self.history.deserialize(s)

        self.favorites = Favorites()

        if FAVORITES_PATH.is_file():
            s = FAVORITES_PATH.read_text()
            self.favorites.deserialize(s)

        self.on_property_changed = PropertyChangedEventHandler()

    def set_bbsmenu(self):
        self.bbsmenu = Bbsmenu()
        self.bbsmenu.update()
        self.on_property_changed.invoke(
            PropertyChangedEventArgs(self, "bbsmenu"))

    def set_board(self, server: str, board: str):
        if CACHE_BOARD:
            self.save_board()

            if board_cache.contains(board):
                s = board_cache.get(board)
                self.board = Board.deserialize(s)
                self.board.update()
                self.on_property_changed.invoke(
                    PropertyChangedEventArgs(self, "board"))
                return

        self.board = Board(server, board)
        self.board.update()
        self.on_property_changed.invoke(PropertyChangedEventArgs(
            self, "board"))

    def set_thread(self, server: str, board: str, key: str):
        if CACHE_THREAD:
            self.save_thread()

            if thread_cache.contains(board + key):
                s = thread_cache.get(board + key)
                self.thread = Thread.deserialize(s)
                self.thread.update()
                self.on_property_changed.invoke(
                    PropertyChangedEventArgs(self, "thread"))
                return

        self.thread = Thread(server, board, key)
        self.thread.init()
        self.on_property_changed.invoke(
            PropertyChangedEventArgs(self, "thread"))

    def save_board(self):
        if self.board is not None:
            s = self.board.serialize()
            board_cache.store(self.board.board, s.encode())

    def save_thread(self):
        if self.thread is not None:
            s = self.thread.serialize()
            thread_cache.store(self.thread.board + self.thread.key, s.encode())

    def save_context(self):
        if CACHE_BOARD:
            self.save_board()

        if CACHE_THREAD:
            self.save_thread()

        s = self.ng.serialize()
        NG_PATH.write_text(s)

        s = self.history.serialize()
        HISTORY_PATH.write_text(s)

        s = self.favorites.serialzie()
        FAVORITES_PATH.write_text(s)

    def set_image(self, url: str):
        if CACHE_IMAGE:
            file_name = re.sub(r'https?://|/', "", url)

            if image_cache.contains(file_name):
                self.image = image_cache.path + "/" + file_name
            else:
                result = download_image(url)

                if isinstance(result, HTTPError) or isinstance(
                        result, URLError):
                    self.image = result
                else:
                    image_cache.store(file_name, result)
                    self.image = image_cache.path + "/" + file_name
        else:
            result = download_image(url)

            if isinstance(result, HTTPError) or isinstance(result, URLError):
                self.image = result
            else:
                f = tempfile.NamedTemporaryFile()
                f.write(result)
                self.image = f.name
                f.close()

        self.on_property_changed.invoke(PropertyChangedEventArgs(
            self, "image"))
Example #16
0
    def __init__(self):
        super().__init__()

        self.categories = None
        self.dns = {}
        self.on_property_changed = PropertyChangedEventHandler()
Example #17
0
class BoardVM:
    def __init__(self, app_context: AppContext):
        super().__init__()

        self._app_context = app_context
        self._board = app_context.board
        self._threads = None

        if "number" in DEFAULT_SORT:
            self._sort_by = "number"
        elif "title" in DEFAULT_SORT:
            self._sort_by = "title"
        elif "count" in DEFAULT_SORT:
            self._sort_by = "count"
        elif "speed" in DEFAULT_SORT:
            self._sort_by = "speed"

        self._reverse_sort = DEFAULT_SORT.startswith("!")

        self._active_sort = False
        self._search_word = None

        self._app_context.on_property_changed.add(self._app_context_changed)
        self._app_context.ng.on_collection_changed.add(self._ng_changed)
        self._app_context.history.on_property_changed.add(
            self._history_changed)
        self._app_context.favorites.on_property_changed.add(
            self.favorite_changed)
        self.on_property_changed = PropertyChangedEventHandler()

    @property
    def board(self) -> Optional[str]:
        return self._board.board if self._board is not None else None

    @property
    def threads(self) -> Optional[List[ThreadHeader]]:
        return self._threads

    @property
    def name(self) -> Optional[str]:
        return self._app_context.bbsmenu.dns[self._board.board] \
            if self._board is not None else None

    @property
    def is_favorite(self) -> Optional[bool]:
        if self._board is not None:
            if self._board.board in [
                    x.board for x in self._app_context.favorites.list
                    if isinstance(x, FavoriteBoard)
            ]:
                return True
            else:
                return False

    def sort_threads(self, sort_by: str, reverse_sort: bool = False):
        self._sort_by = sort_by
        self._reverse_sort = reverse_sort
        self._search_word = None
        self._update_threads()

    def sort_threads_by_word(self, word: str):
        self._search_word = word
        self._update_threads()

    def switch_active_sort(self):
        self._active_sort = not self._active_sort
        self._update_threads()

    def set_thread(self, header: ThreadHeader):
        self._app_context.set_thread(self._board.server, self._board.board,
                                     header.key)

    def update(self):
        if self._board is not None:
            self._board.update()

    def add_ng_title(self, value, use_reg, board):
        self._app_context.ng.add_ng_title(value, use_reg, board)

    def favorite(self):
        if self._app_context.board is not None:
            target = self._app_context.board

            # Check if the board has already been registered
            for f in self._app_context.favorites.list:
                if isinstance(f, FavoriteBoard) and f.board == target.board:
                    self._app_context.favorites.remove(f)
                    return

            board_name = self._app_context.bbsmenu.dns[target.board]

            self._app_context.favorites.add(
                FavoriteBoard(board_name, target.server, target.board))

    def _update_threads(self):
        if self._board is None:
            return

        self._threads = []

        for t in self._app_context.ng.filter_threads(self._board.threads,
                                                     self._board.board):
            history = self._app_context.history.get(self._board.board, t.key)

            if history is not None:
                unread = max(t.count,
                             history.retrieved_reses) - history.bookmark
                self._threads.append(
                    ThreadHeaderVM(t.key, t.number, t.title,
                                   max(t.count, history.retrieved_reses),
                                   t.is_new, t.speed, unread))
            else:
                self._threads.append(
                    ThreadHeaderVM(t.key, t.number, t.title, t.count, t.is_new,
                                   t.speed, None))

        if self._sort_by == "number":
            self._threads.sort(key=lambda x: x.number,
                               reverse=self._reverse_sort)
        elif self._sort_by == "title":
            self._threads.sort(key=lambda x: x.title,
                               reverse=self._reverse_sort)
        elif self._sort_by == "count":
            self._threads.sort(key=lambda x: x.count,
                               reverse=self._reverse_sort)
        elif self._sort_by == "speed":
            self._threads.sort(key=lambda x: x.speed,
                               reverse=self._reverse_sort)

        if self._active_sort:
            self._threads.sort(key=self._active_sort_key, reverse=True)

        if self._search_word is not None:
            self._threads.sort(
                key=lambda x: (self._search_word not in x.title))

        self.on_property_changed.invoke(
            PropertyChangedEventArgs(self, "threads"))

    def _app_context_changed(self, e: PropertyChangedEventArgs):
        if e.property_name == "board":
            if self._board is not None:
                self._board.on_property_changed.remove(self._board_changed)

            self._board = self._app_context.board
            self._board.on_property_changed.add(self._board_changed)
            self._update_threads()

    def _board_changed(self, e: PropertyChangedEventArgs):
        if e.property_name == "threads":
            self._search_word = None
            self._update_threads()

    def _ng_changed(self, e: CollectionChangedEventArgs):
        self._search_word = None
        self._update_threads()

    def _history_changed(self, e: PropertyChangedEventArgs):
        self._update_threads()

    def favorite_changed(self, e: PropertyChangedEventArgs):
        self.on_property_changed.invoke(
            PropertyChangedEventArgs(self, "is_favorite"))

    def _active_sort_key(self, item: ThreadHeaderVM):
        if item.unread is None:
            if item.is_new:
                return 1
            else:
                return 0
        else:
            if item.unread == 0:
                return 2
            else:
                return 3
Example #18
0
 def __init__(self, context: AppContext):
     super().__init__()
     self._app_context = context
     self._app_context.on_property_changed.add(self._context_changed)
     self.on_property_changed = PropertyChangedEventHandler()
Example #19
0
class Board:
    def __init__(self, server: str, board: str):
        super().__init__()

        self.server = server
        self.board = board
        self.threads: List[ThreadHeader] = []
        self.on_property_changed = PropertyChangedEventHandler()

    def update(self):
        s = get_board(self.server, self.board)
        parser = BoardParser(s)

        new_threads = []

        for i, t in enumerate(parser.threads(), 1):
            # If thread is new arrival
            if t["key"] not in [x.key for x in self.threads]:
                now = int(time.time())
                key = int(t["key"])

                # If the time since thread is created is less than NEW_THREAD_INTERVAL minutes from now
                if now - (NEW_THREAD_INTERVAL * 60) < key:
                    new_threads.append(
                        ThreadHeader(t["key"], i, t["title"], t["count"],
                                     True))
                    continue

            new_threads.append(
                ThreadHeader(t["key"], i, t["title"], t["count"], False))

        self.threads = new_threads

        self.on_property_changed.invoke(
            PropertyChangedEventArgs(self, "threads"))

    def sort_threads(self, key: str, reverse=False):
        if key == "number":
            self.threads.sort(key=lambda x: x.number, reverse=reverse)
        elif key == "title":
            self.threads.sort(key=lambda x: x.title, reverse=reverse)
        elif key == "count":
            self.threads.sort(key=lambda x: x.count, reverse=reverse)
        elif key == "unread":
            self.threads.sort(key=lambda x: x.count - x.bookmark
                              if x.bookmark != 0 else -1,
                              reverse=True)
        elif key == "speed":
            self.threads.sort(key=lambda x: x.speed, reverse=reverse)

        self.on_property_changed.invoke(
            PropertyChangedEventArgs(self, "threads"))

    def sort_threads_by_word(self, word: str):
        self.threads.sort(key=lambda x: (word not in x.title))
        self.on_property_changed.invoke(
            PropertyChangedEventArgs(self, "threads"))

    def serialize(self) -> str:
        d = {}
        d["server"] = self.server
        d["board"] = self.board
        d["threads"] = []

        for t in self.threads:
            d["threads"].append({
                "number": t.number,
                "title": t.title,
                "count": t.count,
                "speed": t.speed,
                "key": t.key,
                "is_new": t.is_new
            })

        return json.dumps(d)

    @staticmethod
    def deserialize(s: str):
        obj = json.loads(s)

        b = Board(obj["server"], obj["board"])

        for t in obj["threads"]:
            b.threads.append(
                ThreadHeader(t["key"], t["number"], t["title"], t["count"],
                             t["is_new"]))

        return b

    def _thread_property_changed(self, e: PropertyChangedEventArgs):
        if e.property_name == "bookmark":
            self.on_property_changed.invoke(
                PropertyChangedEventArgs(self, "threads"))
Example #20
0
 def __init__(self):
     super().__init__()
     self.list = []
     self.on_property_changed = PropertyChangedEventHandler()
     self.on_order_changed = OrderChangedEventHandler()
Example #21
0
 def __init__(self, max_history: int):
     super().__init__()
     self._dict = {}
     self._max_history = max_history
     self.on_property_changed = PropertyChangedEventHandler()
Example #22
0
class ThreadVM:
    def __init__(self, app_context: AppContext):
        super().__init__()

        self._app_context = app_context
        self._thread = app_context.thread
        self._responses = None
        self._links = None
        self._replies = None
        self._ids = None
        self.on_property_changed = PropertyChangedEventHandler()
        self.on_collection_changed = CollectionChangedEventHandler()

        app_context.on_property_changed.add(self._app_context_changed)
        app_context.ng.on_collection_changed.add(self._ng_changed)
        app_context.history.on_property_changed.add(self._history_changed)
        app_context.favorites.on_property_changed.add(self._favorites_changed)

    @property
    def server(self) -> Optional[str]:
        return self._thread.server if self._thread is not None else None

    @property
    def board(self) -> Optional[str]:
        return self._thread.board if self._thread is not None else None

    @property
    def key(self) -> Optional[str]:
        return self._thread.key if self._thread is not None else None

    @property
    def title(self) -> Optional[str]:
        return self._thread.title if self._thread is not None else None

    @property
    def is_pastlog(self) -> Optional[bool]:
        return self._thread.is_pastlog if self._thread is not None else None

    @property
    def responses(self) -> Optional[List[Union[Response, Aborn, Hide]]]:
        return self._responses

    @property
    def links(self) -> Optional[List[str]]:
        return self._links

    @property
    def replies(self) -> Optional[List[str]]:
        return self._replies

    @property
    def ids(self) -> Optional[Dict[str, List[Response]]]:
        return self._ids

    @property
    def bookmark(self) -> Optional[int]:
        if self._thread is not None:
            history = self._app_context.history.get(self._thread.board,
                                                    self._thread.key)
            return history.bookmark if history is not None else None

    @property
    def is_favorite(self) -> Optional[bool]:
        if self._thread is not None:
            if self._thread.key in [
                    x.key for x in self._app_context.favorites.list
                    if isinstance(x, FavoriteThread)
            ]:
                return True
            else:
                return False

    @property
    def ng(self) -> NG:
        return self._app_context.ng

    def update(self):
        if self._app_context.thread is not None:
            self._app_context.thread.update()

    def set_thread(self, server: str, board: str, key: str):
        self._app_context.set_thread(server, board, key)

    def set_image(self, url: str):
        self._app_context.set_image(url)

    def post(self, name: str, mail: str, msg: str) -> Optional[str]:
        if self._thread is not None:
            return self._thread.post(name, mail, msg)

        return None

    def add_ng_name(self, value, use_reg, hide, auto_ng_id, board, key):
        self._app_context.ng.add_ng_name(value, use_reg, hide, auto_ng_id,
                                         board, key)

    def add_ng_id(self, value, use_reg, hide, board, key):
        self._app_context.ng.add_ng_id(value, use_reg, hide, board, key)

    def add_ng_word(self, value, use_reg, hide, auto_ng_id, board, key):
        self._app_context.ng.add_ng_word(value, use_reg, hide, auto_ng_id,
                                         board, key)

    def save_history(self, bookmark: int):
        self._app_context.history.save(self._thread.board, self._thread.key,
                                       bookmark, len(self._thread.responses))

    def favorite(self):
        if self._app_context.thread is not None:
            target = self._app_context.thread

            # Check if the thread has already been registered
            for f in self._app_context.favorites.list:
                if isinstance(
                        f, FavoriteThread
                ) and f.key == target.key and f.board == target.board:
                    self._app_context.favorites.remove(f)
                    return

            self._app_context.favorites.add(
                FavoriteThread(self._app_context.thread.title,
                               self._app_context.thread.server,
                               self._app_context.thread.board,
                               self._app_context.thread.key))

    def _app_context_changed(self, e: PropertyChangedEventArgs):
        if e.property_name == "thread":
            if self._thread is not None:
                self._thread.on_property_changed.remove(
                    self._thread_property_changed)
                self._thread.on_collection_changed.remove(
                    self._thread_collection_changed)

            self._thread = self._app_context.thread
            self._thread.on_property_changed.add(self._thread_property_changed)
            self._thread.on_collection_changed.add(
                self._thread_collection_changed)

            self._responses = []
            self._links = []
            self._replies = {}
            self._ids = {}

            for r in self._thread.responses:
                self._filter_response(r)

            self.on_property_changed.invoke(
                PropertyChangedEventArgs(self, "server"))
            self.on_property_changed.invoke(
                PropertyChangedEventArgs(self, "board"))
            self.on_property_changed.invoke(
                PropertyChangedEventArgs(self, "key"))
            self.on_property_changed.invoke(
                PropertyChangedEventArgs(self, "title"))
            self.on_property_changed.invoke(
                PropertyChangedEventArgs(self, "is_pastlog"))
            self.on_property_changed.invoke(
                PropertyChangedEventArgs(self, "responses"))
            self.on_property_changed.invoke(
                PropertyChangedEventArgs(self, "links"))

    def _thread_property_changed(self, e: PropertyChangedEventArgs):
        if e.property_name == "is_pastlog":
            self.on_property_changed.invoke(
                PropertyChangedEventArgs(self, "is_pastlog"))

    def _thread_collection_changed(self, e: CollectionChangedEventArgs):
        if e.property_name == "responses":
            last_count = len(self._responses)

            for r in e.item:
                self._filter_response(r)

            self.on_collection_changed.invoke(
                CollectionChangedEventArgs(self, "responses",
                                           CollectionChangedEventKind.EXTEND,
                                           self._responses[last_count:]))

    def _ng_changed(self, e: CollectionChangedEventArgs):
        if self._thread is not None:
            self._responses = []
            self._links = []
            self._replies = {}
            self._ids = {}

            for r in self._thread.responses:
                self._filter_response(r)

            self.on_property_changed.invoke(
                PropertyChangedEventArgs(self, "responses"))

        self.on_property_changed.invoke(PropertyChangedEventArgs(self, "ng"))

    def _history_changed(self, e: PropertyChangedEventArgs):
        self.on_property_changed.invoke(
            PropertyChangedEventArgs(self, "history"))

    def _favorites_changed(self, e: PropertyChangedEventArgs):
        self.on_property_changed.invoke(
            PropertyChangedEventArgs(self, "is_favorite"))

    def _filter_response(self, r: Response):
        """
        Before calling this method, make sure self._thread is not None &
        self._responses, self._links, self._replies and self._ids have been initialized
        """

        kind = self._app_context.ng.is_ng_response(r, self._thread.board,
                                                   self._thread.key)

        if kind == NGKind.NOT_NG:
            self._responses.append(r)

            for link in re.finditer(r'(https?://.*?)(?=$|\n| )', r.message):
                self._links.append(link.group(1))

            for reply in re.finditer(r'>>(\d{1,4})', r.message):
                key = int(reply.group(1))

                if key not in self._replies:
                    self._replies[key] = []

                if r not in self._replies[key]:
                    self._replies[key].append(r)

            if r.id in self._ids:
                self._ids[r.id].append(r)
            else:
                self._ids[r.id] = [r]
        elif kind == NGKind.HIDE:
            self._responses.append(Hide(r))
        else:
            self._responses.append(Aborn(r))
Example #23
0
 def __init__(self, app_context: AppContext):
     super().__init__()
     self._app_context = app_context
     self._app_context.ng.on_collection_changed.add(self._ng_changed)
     self.on_property_changed = PropertyChangedEventHandler()
Example #24
0
class Thread:
    def __init__(self, server: str, board: str, key: str):
        super().__init__()

        self.server = server
        self.board = board
        self.key = key
        self.title = None
        self.responses: List[Response] = []
        self._is_pastlog: bool = False
        self.on_property_changed = PropertyChangedEventHandler()
        self.on_collection_changed = CollectionChangedEventHandler()

    @property
    def is_pastlog(self) -> bool:
        return self._is_pastlog

    @is_pastlog.setter
    def is_pastlog(self, value: bool):
        self._is_pastlog = value
        self.on_property_changed.invoke(
            PropertyChangedEventArgs(self, "is_pastlog"))

    def serialize(self) -> str:
        d = {}
        d["server"] = self.server
        d["board"] = self.board
        d["key"] = self.key
        d["title"] = self.title
        d["is_pastlog"] = self.is_pastlog
        d["responses"] = []

        for r in self.responses:
            d2 = {}
            d2["number"] = r.number
            d2["name"] = r.name
            d2["mail"] = r.mail
            d2["date"] = r.date
            d2["id"] = r.id
            d2["message"] = r.message

            d["responses"].append(d2)

        return json.dumps(d, ensure_ascii=False)

    @staticmethod
    def deserialize(s: str) -> "Thread":
        d = json.loads(s)

        t = Thread(d["server"], d["board"], d["key"])
        t.title = d["title"]
        t.is_pastlog = d["is_pastlog"]

        for r in d["responses"]:
            t.responses.append(
                Response(r["number"], r["name"], r["mail"], r["date"], r["id"],
                         r["message"]))

        return t

    def init(self):
        html = get_responses_after(self.server, self.board, self.key,
                                   len(self.responses))
        parser = ThreadParserH(html)

        self.is_pastlog = parser.is_pastlog()

        if self.title is None:
            self.title = parser.title()

        for r in parser.responses():
            self.responses.append(
                Response(r["number"], r["name"], r["mail"], r["date"], r["id"],
                         r["message"]))

        self.on_property_changed.invoke(
            PropertyChangedEventArgs(self, "is_pastlog"))
        self.on_property_changed.invoke(PropertyChangedEventArgs(
            self, "title"))

        self.on_collection_changed.invoke(
            CollectionChangedEventArgs(self, "responses",
                                       CollectionChangedEventKind.EXTEND,
                                       self.responses[0:]))

    def update(self):
        html = get_responses_after(self.server, self.board, self.key,
                                   len(self.responses))
        parser = ThreadParserH(html)

        self.is_pastlog = parser.is_pastlog()
        self.on_property_changed.invoke(
            PropertyChangedEventArgs(self, "is_pastlog"))

        new_responses = []
        for r in parser.responses():
            new_responses.append(
                Response(r["number"], r["name"], r["mail"], r["date"], r["id"],
                         r["message"]))

        last_count = len(self.responses)

        # There are no new posts
        if len(new_responses) == 1:
            return

        self.responses.extend(new_responses[1:])

        self.on_collection_changed.invoke(
            CollectionChangedEventArgs(self, "responses",
                                       CollectionChangedEventKind.EXTEND,
                                       self.responses[last_count:]))

    def post(self, name: str, mail: str, message: str) -> str:
        return post_response(self.server, self.board, self.key, name, mail,
                             message)