Skip to content

Commit

Permalink
Initial Beta Release
Browse files Browse the repository at this point in the history
  • Loading branch information
wtheisen committed Jun 11, 2017
1 parent 8f47b9f commit f4bcf57
Show file tree
Hide file tree
Showing 3 changed files with 275 additions and 0 deletions.
2 changes: 2 additions & 0 deletions README.md
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
169 changes: 169 additions & 0 deletions commandChan.py
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()
104 changes: 104 additions & 0 deletions parsers.py
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('&#039;', "'")
item = item.replace('&quot;', '"')
item = item.replace('&amp;', '&')
item = item.replace('&gt;', '>')

contents.append(urwid.AttrWrap(urwid.Text(item), 'quote'))
quote = False
elif 'span class="quote' in item:
comment = True
continue
elif comment:
item = item.replace('&#039;', "'")
item = item.replace('&quot;', '"')
item = item.replace('&amp;', '&')
item = item.replace('&gt;', '>')

contents.append(urwid.AttrWrap(urwid.Text(item), 'greenText'))
comment = False
elif 'pre class="prettyprint"' in item:
codeBlock = True
else:
item = item.replace('&#039;', "'")
item = item.replace('&quot;', '"')
item = item.replace('&amp;', '&')

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)

0 comments on commit f4bcf57

Please sign in to comment.