-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
275 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,4 @@ | ||
# commandChan | ||
|
||
CLI curses 4chan browser | ||
Created out of a desire to browse 4chan at work |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
import parsers | ||
import sys, urwid | ||
import urwid.raw_display | ||
import urwid.web_display | ||
|
||
boards = ['/g/', '/v/', '/tv/', '/sp/', '/fa/', '/pol/', '/vg/', | ||
'/a/', '/b/', '/c/', '/d/', '/e/', | ||
'/f/', '/gif/', '/h/', '/hr/', '/k/', | ||
'/m/', '/o/', '/p/', '/r/', '/s/', | ||
'/t/', '/u/', '/vr/', | ||
'/w/', '/wg/', '/i/', '/ic/', '/r9k/', | ||
'/s4s/', '/vip/', '/cm/', '/hm/', '/lgbt/', | ||
'/y/', '/3/', '/aco/', '/adv/', '/an/', | ||
'/asp/', '/bant/', '/biz/', '/cgl/', '/ck/', | ||
'/co/', '/diy/', '/fit/', '/gd/', '/hc/', | ||
'/his/', '/int/', '/jp/', '/lit/', '/mlp/', | ||
'/mu/', '/n/', '/news/', '/out/', '/po/', | ||
'/qst/', '/sci/', '/soc/', '/tg/', 'toy', | ||
'/trv/', '/vp/', '/wsg/', '/wsr/', '/x/'] | ||
|
||
boardListWidget = None | ||
currentBoard = '' | ||
currentBoardWidget = None | ||
currentThread = '' | ||
level = 0 | ||
|
||
def main(): | ||
|
||
header = urwid.AttrWrap(urwid.Text('CommandChan'), 'header') | ||
|
||
def getBoard(board): | ||
'''returns the board widget''' | ||
titles = parsers.getJSONCatalog('https://a.4cdn.org' + board + 'catalog.json') | ||
|
||
test = [] | ||
|
||
for title, number in titles.items(): | ||
title = title.replace('-', ' ') | ||
threadButton = urwid.Button(str(number), displayThread) | ||
test.append(urwid.LineBox(urwid.Pile([threadButton, urwid.Divider('-'), urwid.Divider(), urwid.Text(title), urwid.Divider()]))) | ||
|
||
MEOW = urwid.GridFlow(test, 30, 2, 2, 'center') | ||
listbox_content = [MEOW] | ||
|
||
listbox = urwid.ListBox(urwid.SimpleListWalker(listbox_content)) | ||
|
||
return listbox | ||
|
||
def getThread(board, threadNum): | ||
comments = parsers.getJSONThread('https://a.4cdn.org' + board + 'thread/', '4chan', threadNum) | ||
|
||
test = [] | ||
|
||
for num, comment in comments.items(): | ||
test.append(urwid.LineBox(parsers.commentTagParser(num, comment))) | ||
|
||
listbox_content = test | ||
|
||
listbox = urwid.ListBox(urwid.SimpleListWalker(listbox_content)) | ||
|
||
return listbox | ||
|
||
def displayBoard(button): | ||
global currentBoard | ||
currentBoard = button.get_label() | ||
global level | ||
level = 1 | ||
|
||
temp = getBoard(button.get_label()) | ||
|
||
global currentBoardWidget | ||
currentBoardWidget = temp | ||
|
||
catalogue = urwid.Overlay(temp, test, 'center', ('relative', 90), 'middle', ('relative', 95)) | ||
frame = urwid.Frame(urwid.AttrWrap(catalogue, 'body'), header=header) | ||
frame.footer = urwid.AttrWrap(urwid.Text('Board: ' + button.get_label()), 'header') | ||
|
||
urwid.MainLoop(frame, palette, screen, unhandled_input=unhandled).run() | ||
|
||
def displayThread(button): | ||
global currentThread | ||
currentThread = button.get_label() | ||
global level | ||
level = 2 | ||
global currentBoardWidget | ||
global currentBoard | ||
|
||
listbox = getThread(currentBoard, currentThread) | ||
thread = urwid.Overlay(listbox, currentBoardWidget, 'center', ('relative', 60), 'middle', ('relative', 95)) | ||
frame = urwid.Frame(urwid.AttrWrap(thread, 'body'), header=header) | ||
frame.footer = urwid.AttrWrap(urwid.Text('Board: ' + currentBoard + ', Thread: ' + button.get_label()), 'header') | ||
|
||
urwid.MainLoop(frame, palette, screen, unhandled_input=unhandled).run() | ||
|
||
boardButtons = [] | ||
for board in boards: | ||
boardButtons.append(urwid.LineBox(urwid.AttrWrap(urwid.Button(board, displayBoard), 'center'))) | ||
|
||
buttonGrid = urwid.GridFlow(boardButtons, 12, 2, 2, 'center') | ||
listbox_content = [buttonGrid] | ||
|
||
test = urwid.ListBox(urwid.SimpleListWalker(listbox_content)) | ||
|
||
frame = urwid.Frame(urwid.AttrWrap(test, 'body'), header=header) | ||
|
||
global boardListWidget | ||
boardListWidget = frame | ||
|
||
def unhandled(key): | ||
if key == 'q' and level == 0: | ||
sys.exit() | ||
elif key =='q' and level == 1: | ||
global level | ||
level = 0 | ||
global currentBoard | ||
currentBoard = '' | ||
global currentBoardWidget | ||
currentBoardWidget = None | ||
|
||
global boardListWidget | ||
urwid.MainLoop(boardListWidget, palette, screen, unhandled_input=unhandled).run() | ||
elif key == 'q' and level == 2: | ||
global level | ||
level = 1 | ||
global currentBoard | ||
global currentBoardWidget | ||
|
||
catalogue = urwid.Overlay(currentBoardWidget, test, 'center', ('relative', 90), 'middle', ('relative', 95)) | ||
frame = urwid.Frame(urwid.AttrWrap(catalogue, 'body'), header=header) | ||
frame.footer = urwid.AttrWrap(urwid.Text('Board: ' + currentBoard), 'header') | ||
|
||
urwid.MainLoop(frame, palette, screen, unhandled_input=unhandled).run() | ||
|
||
|
||
palette = [ | ||
('body', 'light gray', 'black', 'standout'), | ||
('quote', 'light cyan', 'black'), | ||
('greenText', 'dark green', 'black'), | ||
('reverse', 'light gray', 'black'), | ||
('header', 'white', 'dark red', 'bold'), | ||
('important', 'dark blue', 'light gray', ('standout', 'underline')), | ||
('editfc', 'white', 'dark blue', 'bold'), | ||
('editbx', 'light gray', 'dark blue'), | ||
('editcp', 'black', 'light gray', 'standout'), | ||
('bright', 'dark gray', 'light gray', ('bold', 'standout')), | ||
('buttn', 'black', 'dark cyan'), | ||
('buttnf', 'white', 'dark blue', 'bold'), | ||
] | ||
|
||
|
||
# use appropriate Screen class | ||
if urwid.web_display.is_web_request(): | ||
screen = urwid.web_display.Screen() | ||
else: | ||
screen = urwid.raw_display.Screen() | ||
|
||
|
||
urwid.MainLoop(frame, palette, screen, unhandled_input=unhandled).run() | ||
|
||
def setup(): | ||
urwid.web_display.set_preferences("Urwid Tour") | ||
# try to handle short web requests quickly | ||
if urwid.web_display.handle_short_request(): | ||
return | ||
|
||
main() | ||
|
||
if '__main__'==__name__ or urwid.web_display.is_web_request(): | ||
setup() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import requests, json, collections, urwid, re | ||
from bs4 import BeautifulSoup | ||
|
||
######################### CATALOG PARSERS ###################################### | ||
|
||
def getJSONCatalog(url): | ||
response = requests.get(url) | ||
data = response.json() | ||
|
||
if "4cdn" in url: | ||
return parseFourCatalog(data) | ||
|
||
def parseFourCatalog(data): | ||
titles = {} | ||
for i in range(0, 10): | ||
page = data[i] | ||
threadsList = page["threads"] | ||
for j in range(0, len(threadsList)): | ||
titles[threadsList[j]["semantic_url"]] = str(threadsList[j]["no"]) | ||
return titles | ||
|
||
########################### THREAD PARSERS ##################################### | ||
|
||
def getJSONThread(url, chan, threadNumber): | ||
if "4chan" in chan: | ||
response = requests.get(url + str(threadNumber) + '.json') | ||
data = response.json() | ||
return parseFourThread(data) | ||
|
||
def parseFourThread(data): | ||
comments = collections.OrderedDict() | ||
posts = data["posts"] | ||
for post in posts: | ||
try: | ||
comments[str(post["no"]) + ' ' + post["now"]] = post["com"] | ||
except: | ||
comments[str(post["no"]) + ' ' + post["now"]] = '' | ||
return comments | ||
|
||
def commentTagParser(postNum, comment): | ||
soup = BeautifulSoup(comment, "html.parser") | ||
tags = [str(tag) for tag in soup.find_all()] | ||
contents = [] | ||
|
||
test = re.split('<|>', comment) | ||
|
||
contents.append(urwid.Text(str(postNum).strip())) | ||
contents.append(urwid.Divider('-')) | ||
|
||
quote = False | ||
comment = False | ||
codeBlock = False | ||
inlineCode = [] | ||
for item in test: | ||
item = item.encode('utf-8') | ||
if len(item) < 1: | ||
continue | ||
if '/pre' in item: | ||
codeBlock = False | ||
contents.append(urwid.LineBox(urwid.Pile(inlineCode))) | ||
inlineCode = [] | ||
elif item[0] == '/' and not codeBlock: | ||
continue | ||
elif item == 'br': | ||
continue | ||
elif 'a href=' in item: | ||
quote = True | ||
continue | ||
elif quote: | ||
item = item.replace(''', "'") | ||
item = item.replace('"', '"') | ||
item = item.replace('&', '&') | ||
item = item.replace('>', '>') | ||
|
||
contents.append(urwid.AttrWrap(urwid.Text(item), 'quote')) | ||
quote = False | ||
elif 'span class="quote' in item: | ||
comment = True | ||
continue | ||
elif comment: | ||
item = item.replace(''', "'") | ||
item = item.replace('"', '"') | ||
item = item.replace('&', '&') | ||
item = item.replace('>', '>') | ||
|
||
contents.append(urwid.AttrWrap(urwid.Text(item), 'greenText')) | ||
comment = False | ||
elif 'pre class="prettyprint"' in item: | ||
codeBlock = True | ||
else: | ||
item = item.replace(''', "'") | ||
item = item.replace('"', '"') | ||
item = item.replace('&', '&') | ||
|
||
if not codeBlock: | ||
contents.append(urwid.Text(item)) | ||
else: | ||
inlineCode.append(urwid.Text(item)) | ||
|
||
contents.append(urwid.Divider()) | ||
contents.append(urwid.Divider('-')) | ||
contents.append(urwid.Text('img: ')) | ||
|
||
return urwid.Pile(contents) |