diff --git a/app.py b/app.py index aa45d84..ddb879a 100644 --- a/app.py +++ b/app.py @@ -12,18 +12,26 @@ def give_html(): @app.route('/api/articles//info', methods=['GET']) def article_info(article_id): - return cra.get_article(article_id, is_include_content=False) + board = request.args.get('board') + return cra.get_article(article_id, board, is_include_content=False) @app.route('/api/articles/', methods=['GET']) def article_content(article_id): - return cra.get_article(article_id, is_include_content=True) + board = request.args.get('board') + return cra.get_article(article_id, board, is_include_content=True) @app.route('/api/articles', methods=['GET']) def article_ids(): page = request.args.get('page') - return cra.get_article_ids(page) + board = request.args.get('board') + return cra.get_article_ids(board, page) + + +@app.route('/api/boards', methods=['GET']) +def get_boards(): + return cra.get_popular_boards() if __name__ == '__main__': diff --git a/crawler.py b/crawler.py index ae72d0b..8690d4b 100644 --- a/crawler.py +++ b/crawler.py @@ -3,7 +3,7 @@ # 取出頁數或是 id -def get_last_session_of_url(url): +def get_last_session_of_url(url) -> str: url_split = url.split('/') page_or_id = url_split[3] page_or_id = page_or_id.replace('index', '') @@ -11,8 +11,11 @@ def get_last_session_of_url(url): return page_or_id -def get_article(id_, is_include_content=False): - url = 'https://www.ptt.cc/bbs/Gossiping/' + id_ + '.html' +def get_article(id_, board="Gossiping", is_include_content=False) -> dict: + base_url = "https://www.ptt.cc/bbs/" + if board is None: + board = "Gossiping" + url = base_url + board + "/" + id_ + '.html' # 以 GET 傳請求給目標伺服器,伺服器回傳 response 物件 # response 接收回傳值 @@ -51,13 +54,16 @@ def get_article(id_, is_include_content=False): return articles -def get_article_ids(page=""): - +def get_article_ids(board="Gossiping", page="") -> dict: + base_url = "https://www.ptt.cc/bbs/" if page is None: page = "" + if board is None: + board = "Gossiping" + # format 中的內容會替換 {} 所在的位置 - url = 'https://www.ptt.cc/bbs/Gossiping/index{}.html'.format(page) + url = base_url + board + '/index{}.html'.format(page) cookies = dict(over18="1") response = requests.get(url, cookies=cookies) @@ -96,3 +102,13 @@ def get_article_ids(page=""): error = soup.find('title') error_message = dict(error=error.get_text()) return error_message + + +def get_popular_boards() -> dict: + url = "https://www.ptt.cc/bbs/index.html" + response = requests.get(url) + if not response or response.status_code != 200: + return dict(boards=["Gossiping"]) + soup = BeautifulSoup(response.text, 'html.parser') + board_names = [div.get_text() for div in soup.find_all("div", class_="board-name")] + return dict(boards=board_names) diff --git a/website-new/css/main.css b/website-new/css/main.css index 2b309a6..8fde7c2 100755 --- a/website-new/css/main.css +++ b/website-new/css/main.css @@ -26,7 +26,7 @@ html, body { #main-container .main-text { font-size: xx-large; font-weight: bolder; - padding: 20px 0 10px; + padding: 20px 0 0; } .colorful-line { @@ -110,9 +110,11 @@ html, body { #main-title { display: flex; - justify-content: space-between; - flex-wrap: wrap; + justify-content: start; + flex-wrap: nowrap; align-items: end; + -webkit-align-items: flex-end; + white-space: nowrap; } #page-title { @@ -190,12 +192,13 @@ html, body { } #article-container .title-text { - font-size: xx-large; + font-size: x-large; font-weight: bold; margin: 0 10px 10px; padding: 10px 0 0; white-space: nowrap; - overflow: scroll; + overflow: hidden; + text-overflow: ellipsis; } diff --git a/website-new/index.html b/website-new/index.html index 4600500..cf5ff36 100644 --- a/website-new/index.html +++ b/website-new/index.html @@ -14,7 +14,11 @@
- ✾ Hey! PTT Gossiping + ✾ Hey! PTT + + + +
diff --git a/website-new/js/index.js b/website-new/js/index.js index dd24e2d..1d5fdb4 100644 --- a/website-new/js/index.js +++ b/website-new/js/index.js @@ -1,4 +1,5 @@ -let prevPage; +let currentBoard = getCookie("board") || "Gossiping"; +let prevPage, boards; let loadMoreButton = document.getElementById("load-more-button"); let loadingIcon = document.getElementById("loading-icon"); let loadingIconChildren = loadingIcon.children; @@ -22,11 +23,15 @@ let loadingIconChildren = loadingIcon.children; } }; + let boardsSelectBox = document.getElementById("boards"); + boardsSelectBox.addEventListener("change", changeBoard); + let colorBars = document.querySelectorAll(".colorful-line"); const randomColor = () => Math.floor(Math.random() * 16777215).toString(16); colorBars.forEach((coloBar) => { coloBar.style.background = "linear-gradient(to top right," + "#" + randomColor() + "," + "#" + randomColor() + ")"; }); + await showBoardsSelectorOptions(); await showList(); }()); @@ -54,6 +59,17 @@ function request(url, method, parameters, ...header) { }); } +function getCookie(name) { + let cookieArr = document.cookie.split(";"); + for (let i = 0; i < cookieArr.length; i++) { + let cookiePair = cookieArr[i].split("="); + if (name === cookiePair[0].trim()) { + return decodeURIComponent(cookiePair[1]); + } + } + return null; +} + function errorHandler(e) { loadingIconChildren[0].style.display = "inline"; loadingIconChildren[0].textContent = e.slice(0, 3) + " ERROR"; @@ -66,43 +82,79 @@ function leaveError() { loadingIconChildren[1].style.display = "inline"; } -async function getArticleWithContent(articleId) { - return await request("/api/articles/" + articleId, "GET") - .catch((e) => errorHandler(e)); +async function getArticleWithContent(articleId, board) { + return await request("/api/articles/" + articleId, "GET", `board=${board || currentBoard}`) + .catch((e) => errorHandler(e)) || ""; +} + +async function getArticleTitle(articleId, board) { + return await request("/api/articles/" + articleId + "/info", "GET", `board=${board || currentBoard}`) + .catch((e) => errorHandler(e)) || ""; } -async function getArticleTitle(articleId) { - return await request("/api/articles/" + articleId + "/info", "GET") +async function getList(page, board) { + let list = await request("/api/articles", "GET", `page=${page || ""}&board=${board || currentBoard}`) .catch((e) => errorHandler(e)); + return [list["articles"] || [], list["prev"] || []]; } -async function getList(page) { - let list = await request("/api/articles", "GET", `page=${page || ""}`) +async function getBoards() { + let boards = await request("/api/boards", "GET") .catch((e) => errorHandler(e)); - return [list["articles"], list["prev"]]; + return boards["boards"] || {boards: "Gossiping"}; +} + +async function showBoardsSelectorOptions() { + leaveError(); + loadingIcon.style.display = "flex"; + boards = await getBoards(); + let boardsSelectBox = document.getElementById("boards"); + boards.forEach(board => { + let option = document.createElement("option"); + option.text = board; + boardsSelectBox.add(option); + }); + let inferredBoardIndex = getCookie("board-index") || 0; + if (boardsSelectBox[inferredBoardIndex] !== currentBoard) { + boardsSelectBox.selectedIndex = boards.findIndex(board => board === currentBoard); + } else { + boardsSelectBox.selectedIndex = inferredBoardIndex; + } + loadingIcon.style.display = "none"; +} + +async function changeBoard() { + let listContainer = document.getElementById("list"); + listContainer.innerHTML = ""; + currentBoard = boards[this.selectedIndex]; + await showList("", currentBoard); + document.cookie = `board=${currentBoard}`; + document.cookie = `board-index=${this.selectedIndex}`; } -async function showList(page) { +async function showList(page, board) { leaveError(); loadingIcon.style.display = "flex"; loadMoreButton.classList.add("disabled"); loadMoreButton.disabled = true; loadMoreButton.textContent = "載入中..."; let list; - [list, prevPage] = await getList(page); + [list, prevPage] = await getList(page, board); let listContainer = document.getElementById("list"); let progressBar = document.getElementsByClassName("progress-bar")[0]; let partOfProgress = 100 / list.length; + let progressBarStatus = 0; progressBar.parentNode.style.display = "flex"; progressBar.style.width = "0"; - (await Promise.all(list.map(async (articleId, index) => { - let cardData = await getArticleTitle(articleId).catch(e => ({ + (await Promise.all(list.map(async (articleId) => { + let cardData = await getArticleTitle(articleId, board).catch(e => ({ title: "無法載入文章", time: e, author: "", disabled: true })); - progressBar.style.width = (index + 2) * partOfProgress + "%"; + progressBarStatus += partOfProgress; + progressBar.style.width = progressBarStatus + "%"; await new Promise(resolve => setTimeout(() => resolve(), 800)); return { title: cardData["title"], @@ -148,10 +200,10 @@ async function showList(page) { loadingIcon.style.display = "none"; } -async function showArticle(articleId) { +async function showArticle(articleId, board) { leaveError(); loadingIcon.style.display = "flex"; - let article = await getArticleWithContent(articleId); + let article = await getArticleWithContent(articleId, board); if (article === undefined) { errorHandler("404"); return;