Guest User

Fap Gauntlet Precise

a guest
Jul 3rd, 2013
208
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 68.00 KB | None | 0 0
  1. #!/usr/bin/env python
  2. import wx
  3. import wx.animate
  4. import os, os.path
  5. import time
  6. import urllib2
  7. import re
  8. import threading
  9. import winsound
  10. import sys
  11. from math import sin, cos, radians
  12. from random import randint, randrange
  13.  
  14. # change below to gime.gmtime to bring threads with no bump time to the front (rather than to the back)
  15. BUMPTIME_EMPTY_VAL = time.gmtime(0)
  16.  
  17. #proxy = {'http':'127.0.0.1:8118'}
  18. proxy = {}
  19.  
  20.  
  21. board_names = [
  22.     'h','gif','d','e','s','u','a','c','g','hr','m','o',
  23.     'p','r','t','v','w','wg','i','ic','cm','y','k',
  24.     'r9k','3','adv','an','cgl','ck','co','fa','fit',
  25.     'int','jp','lit','mu','n','new','po','sci','sp',
  26.     'tg','toy','trv','tv','x'
  27. ]
  28.  
  29.  
  30. spam_filters = []
  31. if os.path.exists('spamfilter.cfg'):
  32.     try:
  33.         f = None
  34.         f = open('spamfilter.cfg', 'r')
  35.         f_read = f.read()
  36.         for line in f_read.splitlines():
  37.             spam_filters.append(line)
  38.     except:
  39.         raise
  40.         pass
  41.     finally:
  42.         if f is not None:
  43.             f.close()
  44.  
  45.  
  46. def we_are_frozen():
  47.     """Returns whether we are frozen via py2exe.
  48. This will affect how we find out where we are located."""
  49.  
  50.     return hasattr(sys, "frozen")
  51.  
  52.  
  53. def module_path():
  54.     """ This will get us the program's directory,
  55. even if we are frozen using py2exe"""
  56.  
  57.     if we_are_frozen():
  58.         return os.path.dirname(unicode(sys.executable, sys.getfilesystemencoding( )))
  59.  
  60.     return os.path.dirname(unicode(__file__, sys.getfilesystemencoding( )))
  61.  
  62.  
  63. intensities = [
  64.     'extremely light',
  65.     'very light',
  66.     'light',
  67.     'medium',
  68.     'hard',
  69.     'very hard',
  70.     'extremely hard'
  71. ]
  72.  
  73. # originally included "loose" and "tight"
  74.  
  75. my_path = module_path()
  76. #print my_path
  77.  
  78. cache_dir = os.path.join(my_path, 'cache/')
  79. thumb_cache = os.path.join(cache_dir, 'thumbs/')
  80.  
  81. if not os.path.exists(cache_dir) or not os.path.isdir(cache_dir):
  82.     os.mkdir(cache_dir)
  83.  
  84.  
  85. g = []
  86.  
  87. def html_to_text(html):
  88.     html = '"'.join(html.split('"'))
  89.     html = '>'.join(html.split('>'))
  90.     html = '<'.join(html.split('&lt;'))
  91.     html = '&'.join(html.split('&amp;'))
  92.     html = ','.join(html.split('&#44;'))
  93.     html = '\n'.join(html.split('<br />'))
  94.     html = '\n'.join(html.split('<br/>'))
  95.     html = '\n'.join(html.split('<br >'))
  96.     html = '\n'.join(html.split('<br>'))
  97.     return html
  98.  
  99.  
  100. def openurl(url, timestamp=None):
  101.     # fix for urllib2 proxy bug
  102.     proxy_support = urllib2.ProxyHandler(proxy)
  103.     opener = urllib2.build_opener(proxy_support)
  104.    
  105.     # create the http request, possibly with a timestamp
  106.     request = urllib2.Request(url)
  107.     if timestamp is not None:
  108.         request.add_header('If-Modified-Since', timestamp)
  109.    
  110.     try:
  111.         url_handle = opener.open(request)
  112.         # got new data
  113.         return url_handle.read(), url_handle.info().getheader("Last-Modified")
  114.     except urllib2.HTTPError, errorInfo:
  115.         if errorInfo.code == 404:
  116.             return 404, None
  117.         if errorInfo.code == 304:
  118.             # no new data
  119.             return None, timestamp
  120.         # some other error
  121.         return errorInfo.code, None
  122.  
  123.  
  124. class OpenUrlThreaded(threading.Thread):
  125.     def __init__(self, url, timestamp=None):
  126.         threading.Thread.__init__(self)
  127.         self.url = url
  128.         self.timestamp = timestamp
  129.         self.result = None
  130.    
  131.     def run(self):
  132.         self.result = openurl(self.url, self.timestamp)
  133.  
  134.  
  135.  
  136. def random_page_thread(page_text):
  137.     # yeah this might look weird but regexes are weird
  138.     matches = [x for x in re.finditer(r'\[<a href="([^"]+)">Reply</a>\]', page_text)]
  139.     return matches[randint(0,len(g))%len(matches)].group(1)
  140.  
  141. def get_thread_image_urls(thread_text):
  142.     r = r'x(\d+)[^)]*?\)</span><br><a href="(http://images.4chan.org[^"]*)" target=_blank><img src='
  143.     matches = [x for x in re.finditer(r, thread_text)]
  144.     return [m.groups() for m in matches]
  145.  
  146.  
  147. class ThreadedResult(threading.Thread):
  148.     def __init__(self, call, *a, **k):
  149.         threading.Thread.__init__(self)
  150.         self.call = call
  151.         self.a = a
  152.         self.k = k
  153.         self.result = None
  154.    
  155.     def run(self):
  156.         self.result = self.call(*self.a, **self.k)
  157.  
  158.  
  159. class ChanBoard(object):
  160.     def __init__(self, which):
  161.         self.url = 'http://boards.4chan.org/'+which
  162.         self.pages = None
  163.         #self.page_time = [None]*self.pages
  164.         self.page_time = []
  165.         self.threads = {}
  166.    
  167.     def update_iter(self):
  168.         start = 0
  169.         if self.pages is None:
  170.             changed_t, same_t = self.update_page(0)
  171.             yield changed_t, same_t
  172.             start = 1
  173.         for i in xrange(start, self.pages):
  174.             yield self.update_page(i)
  175.    
  176.     def update(self):
  177.         for changed_t, same_t in self.update_iter():
  178.             if ((changed_t and not same_t) or not changed_t) and None not in self.page_time:
  179.                 break # everything is current
  180.    
  181.     def update_page(self, i):
  182.         # thread separator
  183.         thread_sep_regex = re.compile(r'<hr(?:[ /][^>]*?)?>')
  184.         # poster name, trip, time, id
  185.         post_header_regex = re.compile(r'>([^<]*)(?:</a>)?</span>(?: <span class="postertrip">([^<]*)</span>)?([^<]*)<span id="no[^"]*"><a href="[^"]*" class="quotejs">No.</a><a href="[^"]*" class="quotejs">([^<]*)</a>')
  186.         i = int(i)
  187.         while len(self.page_time) <= i:
  188.             self.page_time.append(None)
  189.         if i:
  190.             u = self.url+'/'+str(i)
  191.         else:
  192.             u = self.url
  193.        
  194.         html, self.page_time[i] = openurl(u, self.page_time[i])
  195.         if html is None: # page hasn't been modified
  196.             return 0, None
  197.        
  198.         if self.pages is None:
  199.             pagelen_regex = re.compile(r'(?:Previous.*<a href="[\d]*[^"]*">([\d]*)</a>)')
  200.             match = pagelen_regex.search(html)
  201.             try:
  202.                 p = int(match.group(1)) + 1
  203.             except AttributeError:
  204.                 print html
  205.             self.pages = p
  206.            
  207.             while len(self.page_time) < p:
  208.                 self.page_time.append(None)
  209.        
  210.         changed_threads = 0
  211.         same_threads = 0
  212.         thread_area = re.search(r'<form name="delform" action="[^"]*" method=POST>(.*?)</form>', html, re.S).group(1)
  213.        
  214.         threads = thread_sep_regex.split(thread_area)
  215.        
  216.         for t in threads:
  217.             t_url = re.search(r'\[<a href="([^"]+)">Reply</a>\]', t)
  218.             if t_url is None: # junk at the end of the thread area regex
  219.                 continue
  220.             t_url = t_url.group(1)
  221.             headers = post_header_regex.finditer(t)
  222.             for h in headers:
  223.                 poster_name, poster_trip, t_time, post_id = h.groups()
  224.             #print t_url
  225.             if t_url not in self.threads:
  226.                 self.threads[t_url] = ChanThread(self.url+'/'+t_url)
  227.                 self.threads[t_url].bump_time = t_time
  228.                 self.threads[t_url].need_update = True
  229.                 changed_threads += 1
  230.             elif t_time != self.threads[t_url].bump_time:
  231.                 #self.threads[t_url].update()
  232.                 self.threads[t_url].bump_time = t_time
  233.                 changed_threads += 1
  234.             else:
  235.                 same_threads += 1
  236.             try:
  237.                 added, not_added = self.threads[t_url].update_from_text(t)
  238.                 if added == 1 and not_added == 0 and len(self.threads[t_url].posts.items()) == 1: # thread only has op post
  239.                     self.threads[t_url].need_update = False
  240.                 if added and not_added == 1: # thread has more replies than are visible
  241.                     self.threads[t_url].need_update = True
  242.             except:
  243.                 import traceback
  244.                 print 'bad thread:',self.url+'/'+t_url
  245.                 traceback.print_exc()
  246.                 pass
  247.        
  248.         '''
  249. for t_match in re.finditer(r'\[<a href="([^"]+)">Reply</a>\]', html):
  250. t_url = t_match.group(1)
  251. t_time = 0
  252. if t_url not in self.threads:
  253. self.threads[t_url] = ChanThread(self.url+'/'+t_url)
  254. self.threads[t_url].bump_time = t_time
  255. changed_threads += 1
  256. elif t_time != self.threads[t_url].bump_time:
  257. #self.threads[t_url].update()
  258. self.threads[t_url].bump_time = t_time
  259. changed_threads += 1
  260. else:
  261. same_threads += 1
  262. '''
  263.        
  264.         return changed_threads, same_threads
  265.        
  266.    
  267.     def get_thread(self, t_url):
  268.         if t_url not in self.threads or self.threads[t_url] is None:
  269.             self.threads[t_url] = ChanThread(self.url+'/'+t_url)
  270.  
  271.  
  272.  
  273. class ChanThread(object):
  274.     # poster name, trip, time, id
  275.     post_header_regex = re.compile(r'>([^<]*)(?:</a>)?</span>(?: <span class="postertrip">([^<]*)</span>)?([^<]*)<span id="no[^"]*"><a href="[^"]*" class="quotejs">No.</a><a href="[^"]*" class="quotejs">([^<]*)</a>')
  276.     # image name, size, res, url, thumburl
  277.     img_regex = re.compile(r'<span class="filesize">[^<]*<a href="[^"]*" target="_blank">([^<]*)</a>-\(([^,]*), ([^),]*).*?\)</span><br><a href="([^"]*)" target=_blank><img src=(\S*) [^>]*></a>')
  278.     # post subject
  279.     subject_regex = re.compile(r'<span class="[replyfil]{4,5}title">([^<]*)</span>')
  280.     # poster email
  281.     email_regex = re.compile(r'<a href="mailto:([^"]*)" class="linkmail">')
  282.     # poster comment
  283.     comment_regex = re.compile(r'<blockquote>(.*?)</blockquote>', re.S)
  284.     # double dash (thread post splitter)
  285.     doubledash_regex = re.compile(r'<td nowrap class="doubledash">&gt;&gt;</td>')
  286.     # no week day time hack regex
  287.     noweekday_regex = re.compile(r'\([a-zA-Z]{3,3}?\)')
  288.    
  289.     def __init__(self, url):
  290.         self.url = url
  291.         self.page_time = None
  292.         self.posts = {}
  293.         self.is_source = False
  294.         self.ignore = False
  295.         self.need_update = False
  296.    
  297.     def update(self):
  298.         html, self.page_time = openurl(self.url, self.page_time)
  299.         if html is None:
  300.             return False
  301.         t_area = re.search(r'<form name="delform" action="[^"]*" method=POST>(.*?)</form>', html, re.S).group(1)
  302.         return self.update_from_text(t_area)
  303.    
  304.     def update_from_text(self, t_area):
  305.         added, not_added = 0, 0
  306.         for p in self.doubledash_regex.split(t_area):
  307.             name, trip, time, id = self.post_header_regex.search(p).groups()
  308.             if id not in self.posts: # only parse this post if we don't already have it
  309.                 comment = self.comment_regex.search(p).group(1)
  310.                 email = self.email_regex.search(p)
  311.                 if email is not None:
  312.                     email = email.group(1)
  313.                 subject = self.subject_regex.search(p).group(1)
  314.                 img = self.img_regex.search(p)
  315.                 if img is not None:
  316.                     img = img.groups()
  317.                 p = self.posts[id] = {
  318.                     'id':id,
  319.                     'subject':subject,
  320.                     'name':name,
  321.                     'trip':trip,
  322.                     'time':time,
  323.                     'comment':comment,
  324.                     'email':email,
  325.                     'has_img':False}
  326.                 if img is not None:
  327.                     #print img
  328.                     p['has_img'] = True
  329.                     p['img_name'] = img[0]
  330.                     p['img_size'] = img[1]
  331.                     p['img_w'] = int(img[2].split('x')[0])
  332.                     p['img_h'] = int(img[2].split('x')[1])
  333.                     p['img_url'] = img[3]
  334.                     p['img_thumburl'] = img[4]
  335.                 added += 1
  336.             else:
  337.                 not_added += 1
  338.         return added, not_added
  339.    
  340.     def sorted_posts(self):
  341.         result = self.posts.values()
  342.         result.sort(key=lambda p: p['id'])
  343.         return result
  344.    
  345.     def get_bump_time(self):
  346.         bump_post = self.sorted_posts()[-1]
  347.         bump_time = bump_post['time'].strip()
  348.         if bump_time == '':
  349.            # print BUMPTIME_EMPTY_VAL # for testing
  350.             return BUMPTIME_EMPTY_VAL
  351.         else:
  352.             bump_time = re.sub(self.noweekday_regex, '', bump_time) # removes the weekday so locales won't fail
  353.             #print bump_time # for testing
  354.             return time.strptime(bump_time, '%m/%d/%y%H:%M')
  355.  
  356.  
  357.  
  358. class DLManagerDialog(wx.Dialog):
  359.     def __init__(self, parent):
  360.         import wx.lib.scrolledpanel as scrolled
  361.         wx.Dialog.__init__(self, parent, title='Download Manager', size=(800,600), style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
  362.         self.parent = parent
  363.        
  364.         self.sizer_all = sizer_all = wx.BoxSizer(wx.VERTICAL)
  365.         self.sizer_main = sizer_main = wx.BoxSizer(wx.HORIZONTAL)
  366.         self.sizer_buttons = sizer_buttons = wx.BoxSizer(wx.HORIZONTAL)
  367.        
  368.         self.source_box = source_box = wx.ListBox(self, -1, size=(100,-1), choices=board_names)
  369.         #source_box.Bind(wx.EVT_CHECKLISTBOX, self.SourceCheckListBox)
  370.         source_box.Bind(wx.EVT_LISTBOX, self.SourceListBox)
  371.        
  372.         self.thread_panel = thread_panel = scrolled.ScrolledPanel(self)
  373.         self.sizer_thread = wx.BoxSizer(wx.VERTICAL)
  374.         thread_panel.SetSizer( self.sizer_thread )
  375.         thread_panel.SetAutoLayout(1)
  376.         thread_panel.SetupScrolling()
  377.        
  378.         sizer_main.Add(source_box, 0, wx.EXPAND)
  379.         sizer_main.Add(thread_panel, 1, wx.EXPAND)
  380.        
  381.         self.b_select_all = b_select_all = wx.Button(self, -1, "Select All")
  382.         self.b_select_none = b_select_none = wx.Button(self, -1, "Select None")
  383.        
  384.         sizer_buttons.Add(b_select_all, 0, wx.EXPAND)
  385.         sizer_buttons.Add(b_select_none, 0, wx.EXPAND)
  386.        
  387.         sizer_all.Add(sizer_main, 1, wx.EXPAND)
  388.         sizer_all.Add(sizer_buttons, 0, wx.EXPAND)
  389.        
  390.         b_select_all.Bind(wx.EVT_BUTTON, self.OnSelectAll)
  391.         b_select_none.Bind(wx.EVT_BUTTON, self.OnSelectNone)
  392.        
  393.         self.SetSizer(sizer_all)
  394.         self.panel_threads = []
  395.    
  396.     def OnSelectAll(self, event):
  397.         for thread_panel in self.panel_threads:
  398.             obj = thread_panel.actual_thread_obj
  399.             if not obj.ignore:
  400.                 obj.is_source = True
  401.                 thread_panel.thread_panel_check.SetValue(True)
  402.    
  403.     def OnSelectNone(self, event):
  404.         for thread_panel in self.panel_threads:
  405.             thread_panel.actual_thread_obj.is_source = False
  406.             thread_panel.thread_panel_check.SetValue(False)
  407.        
  408.     def SourceCheckListBox(self, e):
  409.         index = e.GetSelection()
  410.         label = self.source_box.GetString(index)
  411.         checked = self.source_box.IsChecked(index)
  412.         if checked:
  413.             can_select = self.UpdateBoard(label)
  414.             if not can_select:
  415.                 self.source_box.SetChecked(index, False)
  416.             else:
  417.                 self.SelectBoard(label)
  418.    
  419.     def SourceListBox(self, e):
  420.         label = e.GetString()
  421.         #self.SetAutoLayout(True)
  422.         can_select = self.UpdateBoard(label)
  423.         if can_select:
  424.             self.SelectBoard(label)
  425.    
  426.     def SelectBoard(self, label):
  427.         # make sure thumbs directory exists
  428.         if not os.path.isdir(thumb_cache):
  429.             os.mkdir(thumb_cache)
  430.        
  431.         class GoddamnButton(object):
  432.             def __init__(self, check, thread):
  433.                 self.check = check
  434.                 self.thread = thread
  435.             def __call__(self, event):
  436.                 self.thread.is_source = self.check.IsChecked()
  437.        
  438.         class GoddamnIgnoreButton(object):
  439.             def __init__(self, check, thread):
  440.                 self.check = check
  441.                 self.thread = thread
  442.             def __call__(self, event):
  443.                 self.thread.ignore = self.check.IsChecked()
  444.        
  445.         import cStringIO
  446.         if label in self.source_box.GetStrings() and label in self.parent.board_downloaders:
  447.             b = self.parent.board_downloaders[label]
  448.             for n in self.panel_threads: n.Destroy()
  449.             self.panel_threads = []
  450.             self.sizer_thread.Clear()
  451.             self.thread_panel.Show(False)
  452.             cflip = 0
  453.             sorted_threads = b.threads.items()
  454.             sorted_threads.sort(key=lambda i: i[1].get_bump_time())
  455.             sorted_threads.reverse()
  456.             sorted_threads.sort(key=lambda i: i[1].ignore) # push ignored threads to end of list
  457.             if filter(lambda a: not hasattr(a, 'thumb_bmp'), sorted_threads):
  458.                
  459.                 # figure out which to get
  460.                 to_grab = []
  461.                 for i, v in enumerate(sorted_threads):
  462.                     t_key, thread = v
  463.                     op_post = thread.sorted_posts()[0]
  464.                    
  465.                     # filter out thumbnails for spam posts
  466.                     skip_thread = False
  467.                     for spam_filter in spam_filters:
  468.                         if re.search(spam_filter, op_post['comment']):
  469.                             skip_thread = True
  470.                             break
  471.                     if skip_thread:
  472.                         continue
  473.                    
  474.                     if 'img_thumburl' not in op_post:
  475.                         pass
  476.                     elif not hasattr(thread, 'thumb_bmp'):
  477.                         op_thumb_url = op_post['img_thumburl']
  478.                         to_grab.append((thread, op_thumb_url))
  479.                
  480.                 # get the ones we need
  481.                 progress = wx.ProgressDialog(
  482.                                 "Loading Thumbnails",
  483.                                 'Loading thumbnails for /'+label+'/',
  484.                                 maximum=len(to_grab),
  485.                                 parent=self,
  486.                                 style=wx.PD_CAN_ABORT | wx.PD_ELAPSED_TIME | wx.PD_ESTIMATED_TIME | wx.PD_REMAINING_TIME
  487.                             )
  488.                 #for i, v in enumerate(to_grab):
  489.                 needed = len(to_grab)
  490.                 max_get = 8
  491.                 cur_get = []
  492.                 while to_grab or cur_get:
  493.                     progress.Update(min(needed - 1,needed - len(to_grab)))
  494.                     while len(cur_get) < max_get and to_grab:
  495.                         thread, thumb_url = to_grab.pop(0)
  496.                         thumb_path = os.path.join(thumb_cache, thumb_url.rsplit('/', 1)[-1])
  497.                         if os.path.isfile(thumb_path):
  498.                             f = open(thumb_path, 'rb')
  499.                             thumb_dat = f.read()
  500.                             f.close()
  501.                             stream = cStringIO.StringIO(thumb_dat)
  502.                             thread.thumb_bmp = wx.BitmapFromImage(wx.ImageFromStream(stream))
  503.                         else:
  504.                             getter = OpenUrlThreaded(thumb_url)
  505.                             getter.start() # begin the thread
  506.                             cur_get.append((thread, getter))
  507.                     for bleh in cur_get[:]:
  508.                         thread, getter = bleh
  509.                         if getter.result:
  510.                             thumb_dat, mod_time = getter.result
  511.                            
  512.                             # restart if the connection was lost
  513.                             if thumb_dat == 504:
  514.                                 op_post = thread.sorted_posts()[0]
  515.                                 thumb_url = op_post['img_thumburl']
  516.                                 getter = OpenUrlThreaded(thumb_url)
  517.                                 getter.start() # begin the thread
  518.                                 cur_get.append((thread, getter))
  519.                            
  520.                             # only grab the file if it still exists
  521.                             elif thumb_dat != 404:
  522.                                 # write to thumb cache
  523.                                 op_post = thread.sorted_posts()[0]
  524.                                 thumb_url = op_post['img_thumburl']
  525.                                 thumb_path = os.path.join(thumb_cache, thumb_url.rsplit('/', 1)[-1])
  526.                                 try:
  527.                                     f = None
  528.                                     f = open(thumb_path, 'wb')
  529.                                     f.write(thumb_dat)
  530.                                 finally:
  531.                                     if f is not None: f.close()
  532.                                 # turn into bitmap
  533.                                 stream = cStringIO.StringIO(thumb_dat)
  534.                                 thread.thumb_bmp = wx.BitmapFromImage(wx.ImageFromStream(stream))
  535.                            
  536.                             cur_get.remove(bleh)
  537.                     time.sleep(0.25)
  538.                 progress.Destroy()
  539.                    
  540.             for i, v in enumerate(sorted_threads):
  541.                 t_key, thread = v
  542.                 # basic setup
  543.                 op_post = thread.sorted_posts()[0]
  544.                
  545.                 # filter out all that goddamn SPAM
  546.                 skip_thread = False
  547.                 for spam_filter in spam_filters:
  548.                     if re.search(spam_filter, op_post['comment']):
  549.                         skip_thread = True
  550.                         break
  551.                 if skip_thread:
  552.                     continue
  553.                
  554.                 # create a panel for this thread
  555.                 n = wx.Panel(self.thread_panel)
  556.                 n.actual_thread_obj = thread
  557.                 n_sizer = wx.BoxSizer(wx.VERTICAL)
  558.                 # force scroll focusing
  559.                 n.force_scrolling = wx.Panel(n, -1, size=(0,0))
  560.                 n_sizer.Add(n.force_scrolling)
  561.                 class damnit(object):
  562.                     def __init__(self, bleh):
  563.                         self.bleh = bleh
  564.                     def __call__(self, e):
  565.                         if isinstance(self.bleh.FindFocus(), (wx.TextCtrl, wx.ListBox)):
  566.                             self.bleh.SetFocus()
  567.                 n.Bind(wx.EVT_ENTER_WINDOW, damnit(n.force_scrolling))
  568.                
  569.                 n_sizer.Add((5,10)) # a little bit of padding
  570.                
  571.                 # download checkbox
  572.                 check = wx.CheckBox(n, -1, "Download images from this thread")
  573.                 if thread.is_source:
  574.                     check.SetValue(True)
  575.                 check.Bind(wx.EVT_CHECKBOX, GoddamnButton(check, thread))
  576.                 n_sizer.Add(check, 0)
  577.                 n.thread_panel_check = check
  578.                
  579.                 n_sizer.Add((5,8)) # a little bit of padding
  580.                
  581.                 # content sizer
  582.                 content_sizer = wx.BoxSizer(wx.HORIZONTAL)
  583.                 content_sizer2 = wx.BoxSizer(wx.VERTICAL)
  584.                
  585.                 # thumb display
  586.                 #op_thumb_url = op_post['img_thumburl']
  587.                 if hasattr(thread, 'thumb_bmp'):
  588.                     bmp = thread.thumb_bmp
  589.                     thumb_bmp = wx.StaticBitmap(n, -1, bmp)
  590.                     content_sizer.Add(thumb_bmp, 0)
  591.                
  592.                 title_sizer = wx.BoxSizer(wx.HORIZONTAL)
  593.                 font = wx.Font(10, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD)
  594.                 if op_post['subject']:
  595.                     # subject
  596.                     t = html_to_text(op_post['subject']).decode("utf-8", "replace")
  597.                     subject = wx.StaticText(n, -1, t)
  598.                     subject.SetFont(font)
  599.                     subject.SetForegroundColour((204,17,5))
  600.                     #content_sizer2.Add(subject)
  601.                     title_sizer.Add(subject)
  602.                
  603.                 if op_post['name']:
  604.                     # poster name
  605.                     op_name_text = html_to_text(' '+op_post['name']).decode("utf-8", "replace")
  606.                     op_name = wx.StaticText(n, -1, op_name_text)
  607.                     op_name.SetFont(font)
  608.                     op_name.SetForegroundColour((17,119,67))
  609.                     title_sizer.Add(op_name)
  610.                 '''
  611. if op_post['trip'] is not None:
  612. op_name_text += ' '+html_to_text(op_post['trip']).decode("utf-8", "replace")
  613. '''
  614.                 content_sizer2.Add(title_sizer)
  615.                
  616.                
  617.                 try: # 240, 224, 214 # 255,255,238
  618.                     t = html_to_text(op_post['comment']).decode("utf-8", "replace")
  619.                     text = wx.TextCtrl(n, -1, t, style=wx.TE_READONLY|wx.TE_AUTO_URL|wx.TE_MULTILINE|wx.TE_RICH|wx.NO_BORDER)
  620.                     text.SetForegroundColour((128,0,0))
  621.                     if thread.ignore:
  622.                         text.SetBackgroundColour((111,111,128+(32<<cflip)))
  623.                     elif cflip:
  624.                         text.SetBackgroundColour((240,224,214))
  625.                     else:
  626.                         text.SetBackgroundColour((255,255,238))
  627.                     #text = wx.StaticText(n, -1, t_key+'\n'+t)
  628.                     content_sizer2.Add(text, 0, wx.EXPAND)
  629.                 except:
  630.                     print 'can\'t decode unicode text for post:\n',t
  631.                     raise
  632.                
  633.                 content_sizer.Add(content_sizer2, 1, wx.EXPAND)
  634.                 n_sizer.Add(content_sizer, 0, wx.EXPAND)
  635.                
  636.                 n_sizer.Add((5,8)) # a little bit of padding
  637.                
  638.                 # ignore checkbox
  639.                 ignore = wx.CheckBox(n, -1, "Ignore this thread (push to end of list next time this board is opened, and won't be affected by Select All)")
  640.                 if thread.ignore:
  641.                     ignore.SetValue(True)
  642.                 ignore.Bind(wx.EVT_CHECKBOX, GoddamnIgnoreButton(ignore, thread))
  643.                 n_sizer.Add(ignore, 0)
  644.                 n.thread_panel_ignore = ignore
  645.                
  646.                 n_sizer.Add((5,10)) # a little bit of padding
  647.                
  648.                 if thread.ignore:
  649.                     n.SetBackgroundColour((111,111,128+(32<<cflip)))
  650.                 elif cflip:
  651.                     n.SetBackgroundColour((240,224,214))
  652.                 else:
  653.                     n.SetBackgroundColour((255,255,238))
  654.                 cflip^=1
  655.                 n.SetSizer(n_sizer)
  656.                 self.sizer_thread.Add(n, 0, wx.EXPAND)
  657.                 self.panel_threads.append(n)
  658.             self.thread_panel.SetSizer(self.sizer_thread)
  659.             self.thread_panel.Layout()
  660.             self.thread_panel.SetupScrolling()
  661.             self.thread_panel.Show(True)
  662.             self.thread_panel.Layout()
  663.         else:
  664.             print '???'
  665.    
  666.     def UpdateBoard(self, label):
  667.         # add the board if it doesn't already exist
  668.         if label not in self.parent.board_downloaders:
  669.             self.parent.board_downloaders[label] = ChanBoard(label)
  670.         b = self.parent.board_downloaders[label]
  671.        
  672.         progress = None
  673.         brokeout = False
  674.        
  675.         #'''
  676.         if b.pages is None:
  677.             #cur_pos = 0
  678.             max_requests = 8
  679.             cur_requests = []
  680.             detected_end = None
  681.             for i in xrange(max_requests):
  682.                 r_thread = ThreadedResult(b.update_page, i)
  683.                 r_thread.start()
  684.                 cur_requests.append((i, r_thread))
  685.             while b.pages is None or None in b.page_time:
  686.                 if None in b.page_time and len(cur_requests) < max_requests:
  687.                     for i, v in enumerate(b.page_time):
  688.                         if v is None:
  689.                             # now check to make sure it isn't already being requested
  690.                             doing_it = False
  691.                             for req in cur_requests:
  692.                                 if req[0] == i:
  693.                                     doing_it = True
  694.                                     break
  695.                             if not doing_it:
  696.                                 r_thread = ThreadedResult(b.update_page, i)
  697.                                 r_thread.start()
  698.                                 cur_requests.append((i, r_thread))
  699.                 # remove completed requests
  700.                 for req in cur_requests[:]:
  701.                     if req[1].result is not None:
  702.                         cur_requests.remove(req)
  703.                 # make a progress dialog if we need one
  704.                 if progress is None and b.pages is not None:
  705.                     progress = wx.ProgressDialog(
  706.                                     "Getting Board Data",
  707.                                     'Getting list of threads from /'+label+'/',
  708.                                     maximum=b.pages,
  709.                                     parent=self,
  710.                                     style=
  711.                                         wx.PD_CAN_ABORT |
  712.                                         wx.PD_ELAPSED_TIME |
  713.                                         wx.PD_ESTIMATED_TIME |
  714.                                         wx.PD_REMAINING_TIME |
  715.                                         wx.PD_AUTO_HIDE
  716.                                 )
  717.                 # allow breakout from the update
  718.                 if progress is not None:
  719.                     keepgoing, skip = progress.Update(len(filter(lambda a: a is not None, b.page_time)))
  720.                     if not keepgoing:
  721.                         brokeout = True
  722.                         break
  723.                 time.sleep(0.1)
  724.         else:
  725.             for i, v in enumerate(b.update_iter()):
  726.                 changed_t, same_t = v
  727.                 if ((changed_t and not same_t) or not changed_t) and None not in b.page_time:
  728.                     break # everything is current
  729.                 if progress is None:
  730.                     progress = wx.ProgressDialog(
  731.                                     "Getting Board Data",
  732.                                     'Getting list of threads from /'+label+'/',
  733.                                     maximum=b.pages,
  734.                                     parent=self,
  735.                                     style=
  736.                                         wx.PD_CAN_ABORT |
  737.                                         wx.PD_ELAPSED_TIME |
  738.                                         wx.PD_ESTIMATED_TIME |
  739.                                         wx.PD_REMAINING_TIME |
  740.                                         wx.PD_AUTO_HIDE
  741.                                 )
  742.                 keepgoing, skip = progress.Update(i)
  743.                 if not keepgoing:
  744.                     brokeout = True
  745.                     break
  746.         if progress is not None:
  747.             progress.Destroy()
  748.         if brokeout:
  749.             return False
  750.         return True
  751.  
  752.  
  753.  
  754.  
  755. class ImageData(object):
  756.     def __init__(self, path):
  757.         self.path = path # does not include path
  758.         self.basename = os.path.basename(path)
  759.         self.dirname = os.path.dirname(path)
  760.         self.should_show = True
  761.         self.show_count = 0
  762.        
  763.         self.count = None
  764.         self.speed = None
  765.         self.intensity = None
  766.         self.blacklisted = None
  767.  
  768.  
  769.  
  770. class ImageManager(object):
  771.     def __init__(self):
  772.         self.files = []
  773.         self.playdata = {}
  774.         self.cur_bitmap = []
  775.         self.fs_bitmap = []
  776.         self.cur_animated = False
  777.         self.cur_ani_index = 0
  778.         self.ani_counter = 0
  779.         #self.show_count = []
  780.         self.cur_image_path = None
  781.         self.image_data = {}
  782.         self.scale_enabled = True
  783.         self.folders = ['cache/']
  784.         self.paused = False
  785.         self.auto_advance = True
  786.         self.last_pause_time = None
  787.         self.fullscreen = False
  788.        
  789.         # fap frenzy
  790.         self.additional_rate = 0.0
  791.         self.additional_rate_inc = 0.1
  792.         self.use_additional_rate = False
  793.        
  794.         # "I came" stuff
  795.         self.last_time = None
  796.         self.last_stroke_number = 0
  797.         self.intensity_image_count = {}
  798.        
  799.         # load up defaults
  800.         self.load_display_data()
  801.         self.load_folder_cfg()
  802.        
  803.         # test
  804.         for g in self.folders:
  805.             self.add_folder_images(g)
  806.         self.switch_image()
  807.        
  808.     def load_folder_cfg(self, fn=None):
  809.         if fn is None:
  810.             fn = 'folders.cfg'
  811.         try:
  812.             f = None
  813.             f = open(fn)
  814.             for dir in f.read().splitlines():
  815.                 if dir not in self.folders:
  816.                     self.folders.append(dir)
  817.         except:
  818.             pass
  819.         finally:
  820.             if f is not None:
  821.                 f.close()
  822.    
  823.     def save_folder_cfg(self, fn=None):
  824.         if fn is None:
  825.             fn = 'folders.cfg'
  826.         try:
  827.             f = None
  828.             f = open(fn, 'w')
  829.             n = dict.fromkeys(str(i) for i in self.folders).keys()
  830.             f.write('\n'.join(n))
  831.         except:
  832.             pass
  833.         finally:
  834.             if f is not None:
  835.                 f.close()
  836.    
  837.     def load_display_data(self, fn=None):
  838.         if fn is None:
  839.             fn = 'dispdata.cfg'
  840.         if os.path.exists(fn) and os.path.isfile(fn):
  841.             f_got = None
  842.             try:
  843.                 f = None
  844.                 f = open(fn)
  845.                 f_got = f.read()
  846.             except:
  847.                 pass
  848.             finally:
  849.                 if f is not None:
  850.                     f.close()
  851.             if f_got is not None:
  852.                 lines = f_got.splitlines()
  853.                 pd = None
  854.                 for line in lines:
  855.                     if ':' in line:
  856.                         head, tail = line.split(':', 1)
  857.                         if head == 'file':
  858.                             self.playdata[tail] = pd = {}
  859.                         elif pd is not None:
  860.                             if head == 'count' and tail.isdigit():
  861.                                 pd['count'] = int(tail)
  862.                             else:
  863.                                 pd[head] = tail
  864.    
  865.     def save_display_data(self, fn=None):
  866.         if fn is None:
  867.             fn = 'dispdata.cfg'
  868.         s = ''
  869.         for imgdata in self.image_data.values():
  870.             bn = imgdata.basename
  871.             if imgdata.count is not None:
  872.                 if bn not in self.playdata:
  873.                     self.playdata[bn] = {'count':imgdata.count}
  874.                 else:
  875.                     self.playdata[bn]['count'] = imgdata.count
  876.             if imgdata.speed is not None:
  877.                 if bn not in self.playdata:
  878.                     self.playdata[bn] = {'speed':imgdata.speed}
  879.                 else:
  880.                     self.playdata[bn]['speed'] = imgdata.speed
  881.             if imgdata.intensity is not None:
  882.                 if bn not in self.playdata:
  883.                     self.playdata[bn] = {'intensity':imgdata.intensity}
  884.                 else:
  885.                     self.playdata[bn]['intensity'] = imgdata.intensity
  886.             if imgdata.blacklisted is not None:
  887.                 if bn not in self.playdata:
  888.                     self.playdata[bn] = {'blacklisted':imgdata.blacklisted}
  889.                 else:
  890.                     self.playdata[bn]['blacklisted'] = imgdata.blacklisted
  891.         for key in self.playdata:
  892.             s += 'file:'+key+'\n'
  893.             for k,v in self.playdata[key].items():
  894.                 s += k+':'+str(v)+'\n'
  895.             s += '\n'
  896.         try:
  897.             f = None
  898.             f = open(fn, 'w')
  899.             f.write(s)
  900.         except:
  901.             pass
  902.         finally:
  903.             if f is not None:
  904.                 f.close()
  905.    
  906.     def switch_image(self, imagepath=None):
  907.         if imagepath is None:
  908.             a = self.image_data.keys()
  909.             if a:
  910.                 if 1: # semi-random
  911.                     l = self.image_data.items()
  912.                     l.sort(key=lambda a: a[1].show_count)
  913.                     l = filter(lambda a: a[1].show_count == l[0][1].show_count, l)
  914.                     imagepath = l[randint(0, len(l)-1)][0]
  915.                    
  916.                     # try to avoid showing the same image twice in a row
  917.                     if imagepath == self.cur_image_path:
  918.                         for i in l:
  919.                             if i is not imagepath:
  920.                                 switched = self.switch_image(i[0])
  921.                                 if switched:
  922.                                     return switched
  923.                 else: # ordered
  924.                     a.sort()
  925.                     b = 0
  926.                     if self.cur_image_path is not None:
  927.                         if self.cur_image_path in a:
  928.                             b = (a.index(self.cur_image_path) + 1) % len(a)
  929.                     imagepath = a[b]
  930.             else:
  931.                 return False
  932.        
  933.         if self.image_data[imagepath].blacklisted is not None:
  934.             self.image_data[imagepath].show_count += 50
  935.             #del self.image_data[imagepath]
  936.             return False
  937.        
  938.         # make sure the image exists
  939.         if not os.path.exists(imagepath):
  940.             #if imagepath in self.image_data:
  941.             # del self.image_data[imagepath]
  942.             self.image_data[imagepath].show_count += 50
  943.             return False
  944.        
  945.         # make sure the image data is stored
  946.         if imagepath not in self.image_data:
  947.             self.add_image_data(imagepath)
  948.        
  949.         # load the image
  950.         if imagepath[-4:] == '.gif':
  951.             try:
  952.                 ani = wx.animate.Animation(imagepath)
  953.             except:
  954.                 self.image_data[imagepath].show_count += 50
  955.                 return False
  956.             self.cur_animated = True
  957.             self.cur_ani_index = 0
  958.             self.ani_counter = 0
  959.             self.cur_bitmap = []
  960.             ani_size = ani.GetSize()
  961.             current = wx.EmptyBitmap(ani_size[0], ani_size[1])
  962.             memory_dc = wx.MemoryDC()
  963.             memory_dc.SelectObject(current)
  964.             for i in xrange(ani.GetFrameCount()):
  965.                 ani_bmp = wx.BitmapFromImage(ani.GetFrame(i))
  966.                 px, py = ani.GetFramePosition(i)
  967.                 memory_dc.DrawBitmap(ani_bmp, px, py, 1)
  968.                
  969.                 # Seems that 0.03 is the longest animation delay any browser uses:
  970.                
  971.                 # Safari: (< 0.03) -> 0.03 (not so bad)
  972.                 # Firefox: (< 0.02) -> 0.10 (make sense)
  973.                 # IE: (< 0.06) -> 0.10 (make NO sense, 0.7 renders faster than 0.6?)
  974.                 # Opera: (< 0.10) -> 0.10 (consistently bad)
  975.                 # Google Chrome is probably the same as Safari, they both use WebKit for rendering
  976.                
  977.                 # From looking at a buncha gifs,
  978.                 # it seems that a delay of 0 indicates that it expects a delay of 0.10.
  979.                 # Standard video rate is about 60hz, so we'll cap to 0.016
  980.                
  981.                 delay = ani.GetDelay(i)
  982.                 delay = delay and max(16, delay) or 100
  983.                 self.cur_bitmap.append((wx.BitmapFromImage(wx.Bitmap.ConvertToImage(current)), delay))
  984.             memory_dc.SelectObject(wx.NullBitmap)
  985.             self.last_ani_time = int(time.time() * 1000)
  986.             timelen = sum([n[1] for n in self.cur_bitmap])/1000.0
  987.             print 'Anim time:',timelen,'Recommended speed:',1/max(0.016, timelen),'* strokes'
  988.         else:
  989.             try:
  990.                 n = wx.Bitmap(imagepath)
  991.             except:
  992.                 self.image_data[imagepath].show_count += 50
  993.                 return False
  994.             self.cur_animated = False
  995.             self.cur_ani_index = 0
  996.             self.cur_bitmap = [(n, 0)]
  997.        
  998.         # clear the fullscreen bitmap
  999.         self.fs_bitmap = []
  1000.        
  1001.         imgdata = self.image_data[imagepath]
  1002.        
  1003.         self.cur_image_path = imagepath
  1004.         self.cur_image_start_time = time.time()
  1005.        
  1006.         if imgdata.intensity is not None:
  1007.             self.cur_intensity = imgdata.intensity
  1008.         else:
  1009.             intense = intensities[randint(0,len(intensities)-1)]
  1010.             self.cur_intensity = intense
  1011.        
  1012.         if imgdata.speed is None:
  1013.             if self.cur_animated:
  1014.                 anim_rate = 1 / timelen
  1015.                 if timelen < 2.5:
  1016.                     rate = anim_rate
  1017.                 else:
  1018.                     # todo: figure out a better way of auto-detecting strokes
  1019.                     rate = anim_rate * randint(3, int(timelen+1))
  1020.             else:
  1021.                 # First was 300
  1022.                 rate = randrange(1000,6000,1000)/1000.0
  1023.         else:
  1024.             try:
  1025.                 rate = float(imgdata.speed)
  1026.             except ValueError:
  1027.                 rate = 1.0 # TODO fix this
  1028.         self.cur_rate = rate
  1029.        
  1030.         if imgdata.count is None:
  1031.             if rate == 1:
  1032.                 self.cur_count = randrange(20,60,10)
  1033.             elif rate == 2:
  1034.                 self.cur_count = randrange(40,120,10)
  1035.             elif rate == 3:
  1036.                 self.cur_count = randrange(60,180,10)
  1037.             elif rate == 4:
  1038.                 self.cur_count = randrange(80,240,10)
  1039.         elif rate == 5:
  1040.         self.cur_count = randrange(100,300,10)
  1041.             else:
  1042.                 self.cur_count = randrange(1000,2000,5)
  1043.         else:
  1044.             self.cur_count = int(imgdata.count)
  1045.        
  1046.         # increment the count of how many times this image has been shown
  1047.         self.image_data[imagepath].show_count += 1
  1048.        
  1049.         # fap frenzy
  1050.         if self.use_additional_rate:
  1051.             self.additional_rate += self.additional_rate_inc
  1052.             self.cur_rate += self.additional_rate
  1053.        
  1054.         # "I came" stuff
  1055.         if self.cur_intensity not in self.intensity_image_count:
  1056.             self.intensity_image_count[self.cur_intensity] = [1, 0, 0] # imgs, stroke, time
  1057.         else:
  1058.             self.intensity_image_count[self.cur_intensity][0] += 1
  1059.         return True
  1060.    
  1061.     def update_anim(self, amount):
  1062.         if self.cur_animated:
  1063.             if self.cur_bitmap[self.cur_ani_index][1] < 1:
  1064.                 self.ani_counter = 0
  1065.                 self.cur_ani_index = (self.cur_ani_index + 1) % len(self.cur_bitmap)
  1066.             else:
  1067.                 self.ani_counter += amount
  1068.                 if self.ani_counter > self.cur_bitmap[self.cur_ani_index][1]:
  1069.                     self.ani_counter -= self.cur_bitmap[self.cur_ani_index][1]
  1070.                     self.cur_ani_index = (self.cur_ani_index + 1) % len(self.cur_bitmap)
  1071.         else:
  1072.             self.cur_ani_index = 0
  1073.    
  1074.     def show_image(self, parent):
  1075.         if self.cur_animated:
  1076.             t = int(time.time() * 1000)
  1077.             n = t - self.last_ani_time
  1078.             self.last_ani_time = t
  1079.             if not self.paused:
  1080.                 self.update_anim(n)
  1081.         else:
  1082.             self.cur_ani_index = 0
  1083.         # set up the drawing context
  1084.         dc = wx.BufferedPaintDC(parent)
  1085.         dcw, dch = parent.GetClientSize()
  1086.        
  1087.         # clear to black
  1088.         dc.SetBrush(wx.Brush(wx.BLACK))
  1089.         dc.SetPen(wx.Pen(wx.BLACK, 1))
  1090.         dc.DrawRectangle(0, 0, dcw, dch)
  1091.        
  1092.         if self.cur_bitmap:
  1093.             # get the image bitmap
  1094.             bmp = self.cur_bitmap[self.cur_ani_index][0]
  1095.             # get the image size
  1096.             iw, ih = bmp.GetSize()
  1097.            
  1098.             # draw the image/animation (and maybe scale to fit)
  1099.             if not self.scale_enabled:
  1100.                 dc.DrawBitmap(bmp, (dcw-iw)/2, (dch-ih)/2)
  1101.             else:
  1102.                 w_ratio = float(dcw) / max(1,iw)
  1103.                 h_ratio = float(dch) / max(1,ih)
  1104.                 if w_ratio < h_ratio:
  1105.                     x, y, w, h = 0, (dch-ih*w_ratio)/2, dcw, ih*w_ratio
  1106.                 else:
  1107.                     x, y, w, h = (dcw-iw*h_ratio)/2, 0, iw*h_ratio, dch
  1108.                
  1109.                 if self.fullscreen:
  1110.                     if not self.fs_bitmap:
  1111.                         self.fs_bitmap = [None] * len(self.cur_bitmap)
  1112.                     if self.fs_bitmap[self.cur_ani_index] is None:
  1113.                         current = wx.EmptyBitmap(w, h)
  1114.                         memory_dc = wx.MemoryDC()
  1115.                         memory_dc.SelectObject(current)
  1116.                         memgc = wx.GraphicsContext.Create(memory_dc)
  1117.                         memgc.DrawBitmap(bmp, 0, 0, w, h)
  1118.                         memory_dc.SelectObject(wx.NullBitmap)
  1119.                         self.fs_bitmap[self.cur_ani_index] = current
  1120.                         del memory_dc, memgc, current
  1121.                     if bmp.IsOk():
  1122.                         bmp = self.fs_bitmap[self.cur_ani_index]
  1123.                         dc.DrawBitmap(bmp, x, y)
  1124.                     else: # no idea why this would happen, but alright
  1125.                         self.fs_bitmap[self.cur_ani_index] = None
  1126.                         gc = wx.GraphicsContext.Create(dc)
  1127.                         gc.DrawBitmap(bmp, x, y, w, h)
  1128.                 else:
  1129.                     gc = wx.GraphicsContext.Create(dc)
  1130.                     gc.DrawBitmap(bmp, x, y, w, h)
  1131.            
  1132.             # timing stuff
  1133.             if self.paused:
  1134.                 if self.last_pause_time is not None:
  1135.                     self.cur_image_start_time += time.time() - self.last_pause_time
  1136.                 self.last_pause_time = time.time()
  1137.                 self.last_time = None
  1138.             else:
  1139.                 self.last_pause_time = None
  1140.        
  1141.                 # keep time count for "I came"
  1142.                 if self.last_time is None:
  1143.                     self.last_time = time.time()
  1144.                 else:
  1145.                     self.intensity_image_count[self.cur_intensity][2] += time.time() - self.last_time
  1146.                     self.last_time = time.time()
  1147.            
  1148.             t_count = time.time() - self.cur_image_start_time
  1149.             count = int(self.cur_rate * t_count)
  1150.             t_period = (1 / self.cur_rate)
  1151.             pos = (t_count % t_period) * self.cur_rate
  1152.            
  1153.             # "I came" stroke count
  1154.             if self.last_stroke_number != count:
  1155.                 self.intensity_image_count[self.cur_intensity][1] += 1
  1156.                 self.last_stroke_number = count
  1157.            
  1158.             # text stuff
  1159.             if 1:
  1160.                 c = 127-cos(radians(pos*360))*128
  1161.             else:
  1162.                 c = (pos > 0.5) * 255
  1163.                
  1164.             dc.SetFont(wx.Font(35, wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD))
  1165.            
  1166.             self.text = str(max(0, self.cur_count - count)) +', '+ self.cur_intensity
  1167.            
  1168.             tw, th = dc.GetTextExtent(self.text)
  1169.             tx, ty = (dcw-tw)/2, 0
  1170.            
  1171.             inv_c = 0#(255-c)/2
  1172.             dc.SetTextForeground((inv_c, inv_c, inv_c))
  1173.             dc.DrawText(self.text, tx+1, ty)
  1174.             dc.DrawText(self.text, tx-1, ty)
  1175.             dc.DrawText(self.text, tx, ty+1)
  1176.             dc.DrawText(self.text, tx, ty-1)
  1177.             dc.SetTextForeground((0, c, 0))
  1178.             dc.DrawText(self.text, tx, ty)#, dch-th)# 0,0)#
  1179.            
  1180.             # auto advancement
  1181.             if self.auto_advance and count == self.cur_count:
  1182.                 for i in self.image_data:
  1183.                     switched = self.switch_image()
  1184.                     if switched:
  1185.                         break
  1186.         else:
  1187.             for i in self.image_data:
  1188.                 switched = self.switch_image()
  1189.                 if switched:
  1190.                     break
  1191.    
  1192.     def add_image_data(self, path, extra_data=None):
  1193.         if path not in self.image_data:
  1194.             self.image_data[path] = ImageData(path)
  1195.         imgdat = self.image_data[path]
  1196.         if imgdat.basename in self.playdata:
  1197.             pd = self.playdata[imgdat.basename]
  1198.             if 'count' in pd:
  1199.                 imgdat.count = pd['count']
  1200.             if 'speed' in pd:
  1201.                 imgdat.speed = pd['speed']
  1202.             if 'intensity' in pd:
  1203.                 imgdat.intensity = pd['intensity']
  1204.             if 'blacklisted' in pd:
  1205.                 imgdat.blacklisted = pd['blacklisted']
  1206.         elif extra_data is not None:
  1207.             imgdat.count, imgdat.speed, imgdat.intensity = extra_data
  1208.    
  1209.     def add_folder_images(self, path):
  1210.         if os.path.isdir(path):
  1211.             for n in os.listdir(path):
  1212.                 m = os.path.join(path, n)
  1213.                 if os.path.isfile(m):
  1214.                     if os.path.splitext(m)[1] in ('.gif', '.jpg', '.bmp', '.png'):
  1215.                         self.add_image_data(m)
  1216.    
  1217.     def rem_folder_images(self, path):
  1218.         if os.path.isdir(path):
  1219.             if path[-1] in ('\\', '/'):
  1220.                 path = os.path.dirname(path)
  1221.             for i in self.image_data.keys():
  1222.                 if os.path.dirname(i) == path:
  1223.                     del self.image_data[i]
  1224.  
  1225.  
  1226.  
  1227.  
  1228.  
  1229. class DownloadManager(threading.Thread):
  1230.     def __init__(self, parent):
  1231.         threading.Thread.__init__(self)
  1232.         self.parent = parent
  1233.         #self.img_getter = RandomThreadImage()
  1234.         self.new_cached = []
  1235.         self.new_cached_lock = threading.Lock()
  1236.         self.min_refresh_delay = 45
  1237.         self.max_refresh_delay = 120
  1238.         self.last_full_refresh = time.time()
  1239.    
  1240.     def run(self):
  1241.         while 1:
  1242.             sleep = 2.5
  1243.             need_refresh = False
  1244.             try:
  1245.                 if self.parent.download_enabled and len(self.new_cached) < 50:
  1246.                     sleep = 0.0
  1247.                     threads = []
  1248.                     for b in self.parent.board_downloaders.values():
  1249.                         threads.extend(filter(lambda t: t.is_source, b.threads.values()))
  1250.                     image_posts = []
  1251.                     for t in threads:
  1252.                         if t.need_update:
  1253.                             t.update()
  1254.                             t.need_update = False
  1255.                         image_posts.extend(filter(lambda p: 'img_url' in p, t.posts.values()))
  1256.                     to_get = []
  1257.                     pd = self.parent.image_manager.playdata
  1258.                     for img_post in image_posts:
  1259.                         image_base = img_post['img_url'].rsplit('/', 1)[-1]
  1260.                         if image_base not in os.listdir(cache_dir):
  1261.                             if image_base in pd and 'blacklisted' in pd[image_base]:
  1262.                                 pass # don't grab blacklisted files
  1263.                             elif self.parent.gif_only_mode and image_base[-4:] != '.gif':
  1264.                                 pass # don't grab anything but gifs in gif mode
  1265.                             else:
  1266.                                 to_get.append(img_post)
  1267.                     if to_get:
  1268.                         which = to_get[randint(0, len(to_get) - 1)]
  1269.                         comment = html_to_text(which['comment'])
  1270.                         extra_data = None
  1271.                        
  1272.                         meh = re.search(r'(\d+)[ \t]*([,\\/])(.*)', comment)
  1273.                         if meh is not None:
  1274.                             force_doubles = 'medium', 'normal'
  1275.                             speed_map = {
  1276.                                 'extremely slow':0.25,
  1277.                                 'very slow':0.333,
  1278.                                 'slow':0.5,
  1279.                                 'medium':1.0,
  1280.                                 'normal':1.0,
  1281.                                 'fast':2.0,
  1282.                                 'very fast':3.0,
  1283.                                 'extremely fast':4.0
  1284.                             }
  1285.                             same_as_pic_texts = [
  1286.                                 'speed', 'pace', 'same as'
  1287.                             ]
  1288.                            
  1289.                             detected_count, separator, rest = meh.groups()
  1290.                             detected_speed = None
  1291.                             detected_force = None
  1292.                             speed_index = None
  1293.                             line = rest.split('\n')[0]
  1294.                             parts = line.split(separator)
  1295.                            
  1296.                             # find the speed
  1297.                             for i, v in enumerate(parts):
  1298.                                 # if it's "same as gif" or whatever, auto-detect speed
  1299.                                 for sapt in same_as_pic_texts:
  1300.                                     if sapt in v.lower().strip():
  1301.                                         speed_index = i
  1302.                                         break
  1303.                                 # if we already have a speed, we don't have to look further
  1304.                                 if speed_index is not None:
  1305.                                     break
  1306.                            
  1307.                             # find the speed (if auto-pacing didn't find it)
  1308.                             for i, v in enumerate(parts):
  1309.                                 # if we already have a speed, we don't have to look further
  1310.                                 if speed_index is not None:
  1311.                                     break
  1312.                                 # "fast", "slow", "medium", etc.
  1313.                                 if v.lower().strip() in speed_map:
  1314.                                     detect_double = False
  1315.                                     # make sure "medium, slow" doesn't get detected as speed=medium, force=slow
  1316.                                     for j, to_check in enumerate(parts[i:]):
  1317.                                         if j == i:
  1318.                                             continue
  1319.                                         if to_check in speed_map and to_check not in force_doubles:
  1320.                                             detect_double = True
  1321.                                             break
  1322.                                     if not detect_double:
  1323.                                         detected_speed = speed_map[v.lower().strip()]
  1324.                                         speed_index = i
  1325.                                         continue # got a named speed
  1326.                                 num = re.search(r'(\d+(?:.\d+)?)', v)
  1327.                                 if num is not None:
  1328.                                     detected_speed = float(num.group(1))
  1329.                                     speed_index = i
  1330.                                     continue # got a float speed
  1331.                            
  1332.                             # if we got a speed, find the force
  1333.                             if speed_index is not None:
  1334.                                 # if the speed is at the begining, use everything after it for the force
  1335.                                 if speed_index == 0:
  1336.                                     detected_force = separator.join(parts[1:])
  1337.                                 # otherwise, grab everything in the middle
  1338.                                 else:
  1339.                                     detected_force = separator.join(parts[:speed_index])
  1340.                             # if we didn't find a speed, try to find a force anyway
  1341.                             else:
  1342.                                 force_index = None
  1343.                                 for i, v in enumerate(parts):
  1344.                                     for intensity in intensities:
  1345.                                         if intensity in v.lower().strip():
  1346.                                             force_index = i
  1347.                                             detected_force = v
  1348.                                             break
  1349.                                     if force_index:
  1350.                                         break
  1351.                            
  1352.                            
  1353.                             # scan the comment text for a specific speed
  1354.                             all_lines = comment.splitlines()
  1355.                             for l in all_lines:
  1356.                                 num = re.match(r'\s*\(\s*(\d+(?:.\d+)?)\s*\)', l)
  1357.                                 if num is not None:
  1358.                                     detected_speed = float(num.group(1))
  1359.                                     break
  1360.                            
  1361.                             # even if we don't have a force or speed, we at least have a count
  1362.                             extra_data = detected_count, detected_speed, detected_force
  1363.                            
  1364.                         imgurl = which['img_url']
  1365.                         got, mod_time = openurl(imgurl)
  1366.                         image_base = imgurl.rsplit('/', 1)[-1]
  1367.                         img_cache_path = cache_dir + image_base
  1368.                         if got != 404 and got is not None:
  1369.                             f = open(img_cache_path, 'wb')
  1370.                             f.write(got)
  1371.                             f.close()
  1372.                             self.new_cached.append((img_cache_path, extra_data))
  1373.                     else:
  1374.                         need_refresh = True
  1375.                         sleep = 2.5
  1376.                 last_refresh = time.time() - self.last_full_refresh
  1377.                 if not need_refresh and last_refresh > self.max_refresh_delay:
  1378.                     sleep = 0.0
  1379.                     for b in self.parent.board_downloaders.values():
  1380.                         b.update()
  1381.                     self.last_full_refresh = time.time()
  1382.                 if need_refresh and last_refresh > self.min_refresh_delay:
  1383.                     sleep = 0.0
  1384.                     for b in self.parent.board_downloaders.values():
  1385.                         b.update()
  1386.                     self.last_full_refresh = time.time()
  1387.             except wx.PyDeadObjectError:
  1388.                 exit()
  1389.             except:
  1390.                 import traceback
  1391.                 print 'Error in Download Management Thread, attempting to recover'
  1392.                 traceback.print_exc()
  1393.                 sleep = 5.0
  1394.             if sleep:
  1395.                 time.sleep(sleep) # wait a bit
  1396.    
  1397.     def get_new_cached(self):
  1398.         self.new_cached_lock.acquire()
  1399.         result, self.new_cached = self.new_cached, []
  1400.         self.new_cached_lock.release()
  1401.         return result
  1402.  
  1403.  
  1404.  
  1405. class MainFrame(wx.Frame):
  1406.     def __init__(self, parent, id, title):
  1407.         wx.Frame.__init__(self, parent, id, title, size=(800,600))
  1408.         #self.SetDoubleBuffered(True)
  1409.         self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
  1410.         self.board_downloaders = {}
  1411.         self.download_enabled = True
  1412.         self.gif_only_mode = False
  1413.         self.refresh_timer = wx.CallLater(100, self.Refresh)
  1414.         self.mouse_timer = None
  1415.         self.key_state = False
  1416.         self.fap_frenzy = False
  1417.        
  1418.         self.image_manager = ImageManager()
  1419.         self.download_manager = DownloadManager(self)
  1420.         self.download_manager.start()
  1421.        
  1422.         # basic prep
  1423.         self.SetBackgroundColour((0,0,0))
  1424.         self.Centre()
  1425.        
  1426.         # set up the menu
  1427.         self.menu = wx.Menu()
  1428.        
  1429.         self.menu_scale_id = wx.NewId()
  1430.         self.menu_scale = wx.MenuItem(self.menu, self.menu_scale_id, '&Scale', kind=wx.ITEM_CHECK)
  1431.         self.menu.AppendItem(self.menu_scale)
  1432.         self.menu_scale.Check(True)
  1433.        
  1434.         self.menu_fullscreen_id = wx.NewId()
  1435.         self.menu_fullscreen = wx.MenuItem(self.menu, self.menu_fullscreen_id, '&FullScreen', kind=wx.ITEM_CHECK)
  1436.         self.menu.AppendItem(self.menu_fullscreen)
  1437.         self.menu_fullscreen.Check(False)
  1438.        
  1439.         self.menu_dl_id = wx.NewId()
  1440.         self.menu_dl = wx.MenuItem(self.menu, self.menu_dl_id, '&Download From Boards', kind=wx.ITEM_CHECK)
  1441.         self.menu.AppendItem(self.menu_dl)
  1442.         self.menu_dl.Check(self.download_enabled)
  1443.        
  1444.         self.menu_black_id = wx.NewId()
  1445.         self.menu_black = wx.MenuItem(self.menu, self.menu_black_id, '&Blacklist This Image')
  1446.         self.menu.AppendItem(self.menu_black)
  1447.        
  1448.         self.menu_gifonly_id = wx.NewId()
  1449.         self.menu_gifonly = wx.MenuItem(self.menu, self.menu_gifonly_id, '&Only Download Gifs', kind=wx.ITEM_CHECK)
  1450.         self.menu.AppendItem(self.menu_gifonly)
  1451.         self.menu_gifonly.Check(self.gif_only_mode)
  1452.        
  1453.         self.menu_autoadv_id = wx.NewId()
  1454.         self.menu_autoadv = wx.MenuItem(self.menu, self.menu_autoadv_id, '&Auto Advance', kind=wx.ITEM_CHECK)
  1455.         self.menu.AppendItem(self.menu_autoadv)
  1456.         self.menu_autoadv.Check(self.image_manager.auto_advance)
  1457.        
  1458.         self.menu_fapfrenzy_id = wx.NewId()
  1459.         self.menu_fapfrenzy = wx.MenuItem(self.menu, self.menu_fapfrenzy_id, '&Fap Frenzy', kind=wx.ITEM_CHECK)
  1460.         self.menu.AppendItem(self.menu_fapfrenzy)
  1461.         self.menu_fapfrenzy.Check(self.fap_frenzy)
  1462.        
  1463.         self.menu_icame_id = wx.NewId()
  1464.         self.menu_icame = wx.MenuItem(self.menu, self.menu_icame_id, '&I CAME')
  1465.         self.menu.AppendItem(self.menu_icame)
  1466.        
  1467.         self.menu.AppendSeparator()
  1468.        
  1469.         self.menu_addfolder_id = wx.NewId()
  1470.         self.menu_addfolder = wx.MenuItem(self.menu, self.menu_addfolder_id, '&Add Folder')
  1471.         self.menu.AppendItem(self.menu_addfolder)
  1472.        
  1473.         self.menu_downmng_id = wx.NewId()
  1474.         self.menu_downmng = wx.MenuItem(self.menu, self.menu_downmng_id, '&Download Manager')
  1475.         self.menu.AppendItem(self.menu_downmng)
  1476.        
  1477.         self.menu.AppendSeparator()
  1478.        
  1479.         self.menu_exit_id = wx.NewId()
  1480.         self.menu_exit = wx.MenuItem(self.menu, self.menu_exit_id, 'E&xit')
  1481.         self.menu.AppendItem(self.menu_exit)
  1482.        
  1483.         if 0: # menubar is disabled since context menu is easier
  1484.             self.menubar = wx.MenuBar()
  1485.             self.menubar.Append(self.menu, '&Menu')
  1486.             self.SetMenuBar(self.menubar)
  1487.        
  1488.         # menu bindings
  1489.         self.Bind(wx.EVT_MENU, self.OnScale, id=self.menu_scale_id)
  1490.         self.Bind(wx.EVT_MENU, self.OnFullScreen, id=self.menu_fullscreen_id)
  1491.         self.Bind(wx.EVT_MENU, self.OnDLEnable, id=self.menu_dl_id)
  1492.         self.Bind(wx.EVT_MENU, self.OnBlackList, id=self.menu_black_id)
  1493.         self.Bind(wx.EVT_MENU, self.OnGifMode, id=self.menu_gifonly_id)
  1494.         self.Bind(wx.EVT_MENU, self.OnAutoAdvMode, id=self.menu_autoadv_id)
  1495.         self.Bind(wx.EVT_MENU, self.OnFapFrenzy, id=self.menu_fapfrenzy_id)
  1496.         self.Bind(wx.EVT_MENU, self.OnICame, id=self.menu_icame_id)
  1497.         self.Bind(wx.EVT_MENU, self.OnAddFolder, id=self.menu_addfolder_id)
  1498.         self.Bind(wx.EVT_MENU, self.OnDownloadManager, id=self.menu_downmng_id)
  1499.         self.Bind(wx.EVT_MENU, self.OnExit, id=self.menu_exit_id)
  1500.        
  1501.         # mouse bindings
  1502.         self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
  1503.         self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown)
  1504.         self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouseAny)
  1505.        
  1506.         # key bindings
  1507.         self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
  1508.         self.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
  1509.        
  1510.         # visual bindings
  1511.         self.Bind(wx.EVT_PAINT, self.OnPaint)
  1512.         self.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None) # nullify the erase event (framerate)
  1513.         self.Bind(wx.EVT_CLOSE, self.OnClose)
  1514.        
  1515.         # show the frame
  1516.         self.Show(True)
  1517.    
  1518.     def OnClose(self, event):
  1519.         self.image_manager.save_display_data()
  1520.         self.image_manager.save_folder_cfg()
  1521.         event.Skip()
  1522.    
  1523.     def OnScale(self, event):
  1524.         self.image_manager.scale_enabled = not self.image_manager.scale_enabled
  1525.         self.menu_scale.Check(self.image_manager.scale_enabled)
  1526.         self.Unpause()
  1527.    
  1528.     def OnFullScreen(self, event):
  1529.         self.ShowFullScreen(not self.IsFullScreen())
  1530.         # let the image manager know the state
  1531.         self.image_manager.fullscreen = self.IsFullScreen()
  1532.         self.menu_fullscreen.Check(self.image_manager.fullscreen)
  1533.         self.Unpause()
  1534.    
  1535.     def OnDLEnable(self, event):
  1536.         self.download_enabled = not self.download_enabled
  1537.         self.menu_dl.Check(self.download_enabled)
  1538.         self.Unpause()
  1539.    
  1540.     def OnBlackList(self, event):
  1541.         self.image_manager.image_data[self.image_manager.cur_image_path].blacklisted = True
  1542.         self.image_manager.switch_image()
  1543.         self.Unpause()
  1544.    
  1545.     def OnGifMode(self, event):
  1546.         self.gif_only_mode = not self.gif_only_mode
  1547.         self.menu_gifonly.Check(self.gif_only_mode)
  1548.         self.Unpause()
  1549.    
  1550.     def OnAutoAdvMode(self, event):
  1551.         self.image_manager.auto_advance = not self.image_manager.auto_advance
  1552.         self.menu_autoadv.Check(self.image_manager.auto_advance)
  1553.         self.Unpause()
  1554.    
  1555.     def OnFapFrenzy(self, event):
  1556.         self.fap_frenzy = not self.fap_frenzy
  1557.         self.image_manager.use_additional_rate = self.fap_frenzy
  1558.         if self.fap_frenzy:
  1559.             self.image_manager.additional_rate = 0.0
  1560.         self.menu_fapfrenzy.Check(self.fap_frenzy)
  1561.         self.Unpause()
  1562.    
  1563.     def OnICame(self, event):
  1564.         import wx.lib.dialogs
  1565.         msg = ''
  1566.         iic = self.image_manager.intensity_image_count
  1567.        
  1568.         for k, v in iic.items():
  1569.             msg += k+':\n'
  1570.             msg += '\timages:\t'+str(v[0])+'\n'
  1571.             msg += '\tstrokes:\t'+str(v[1])+'\n'
  1572.             msg += '\ttime:\t'+str(v[2])+'\n'
  1573.             msg += '\n\n'
  1574.        
  1575.         msg += 'Total:\n'
  1576.         msg += '\timages:\t'+str(sum([intense[0] for intense in iic.values()]))+'\n'
  1577.         msg += '\tstrokes:\t'+str(sum([intense[1] for intense in iic.values()]))+'\n'
  1578.         msg += '\ttime:\t'+str(sum([intense[2] for intense in iic.values()]))
  1579.        
  1580.         dlg = wx.lib.dialogs.ScrolledMessageDialog(self, msg, "!!!")
  1581.         dlg.ShowModal()
  1582.         dlg.Destroy()
  1583.         self.Unpause()
  1584.    
  1585.     def OnAddFolder(self, event):
  1586.         dlg = wx.DirDialog(self, 'Choose a directory:')
  1587.         if dlg.ShowModal() == wx.ID_OK:
  1588.             self.image_manager.add_folder_images(dlg.GetPath())
  1589.         dlg.Destroy()
  1590.         self.Unpause()
  1591.    
  1592.     def OnDownloadManager(self, event):
  1593.         #import wx.lib.scrolledpanel as scrolled
  1594.         dlg = DLManagerDialog(self)
  1595.         wx.Dialog(self, title='Download Manager', size=(800,600))
  1596.         if dlg.ShowModal() == wx.ID_OK:
  1597.             pass
  1598.         dlg.Destroy()
  1599.         self.Unpause()
  1600.    
  1601.     def OnExit(self, event):
  1602.         self.Close()
  1603.         exit()
  1604.    
  1605.     def OnLeftDown(self, event):
  1606.         if self.image_manager.paused:
  1607.             self.Unpause()
  1608.         else:
  1609.             for i in self.image_manager.image_data:
  1610.                 switched = self.image_manager.switch_image()
  1611.                 if switched:
  1612.                     break
  1613.         event.Skip()
  1614.    
  1615.     def OnRightDown(self, event):
  1616.         self.Pause()
  1617.         self.PopupMenu(self.menu)
  1618.         event.Skip()
  1619.    
  1620.     def OnMouseAny(self, event):
  1621.         # make the cursor visible and stop the current timer
  1622.         if self.mouse_timer is not None:
  1623.             self.mouse_timer.Stop()
  1624.             self.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
  1625.        
  1626.         # set the cursor to vanish in 5 seconds if there's no movement
  1627.         # and we're not paused
  1628.         if not self.image_manager.paused:
  1629.             def bleh():
  1630.                 self.SetCursor(wx.StockCursor(wx.CURSOR_BLANK))
  1631.                 self.mouse_timer = None
  1632.             self.mouse_timer = wx.CallLater(5000, bleh)
  1633.        
  1634.         event.Skip()
  1635.    
  1636.     def OnKeyDown(self, event):
  1637.         if not self.key_state:
  1638.             keycode = event.GetKeyCode()
  1639.             # it's prett much just space/u/d/l/r right now, I dunno what other bindings to add
  1640.             if keycode in (wx.WXK_SPACE, wx.WXK_LEFT, wx.WXK_RIGHT, wx.WXK_UP, wx.WXK_DOWN):
  1641.                 self.key_state = True
  1642.                
  1643.                 # switch to the next image
  1644.                 for i in self.image_manager.image_data:
  1645.                     switched = self.image_manager.switch_image()
  1646.                     if switched:
  1647.                         break
  1648.        
  1649.     def OnKeyUp(self, event):
  1650.         self.key_state = False
  1651.    
  1652.     def Pause(self):
  1653.         if not self.image_manager.paused:
  1654.             self.image_manager.paused = True
  1655.         else:
  1656.             self.image_manager.paused = True
  1657.    
  1658.     def Unpause(self):
  1659.         if self.image_manager.paused:
  1660.             self.image_manager.paused = False
  1661.             self.Refresh()
  1662.    
  1663.     def OnPaint(self, event):
  1664.         if self.download_enabled and len(filter(lambda a: not a.show_count, self.image_manager.image_data.values())) < 40:
  1665.             a = self.download_manager.get_new_cached()
  1666.             for b in a:
  1667.                 self.image_manager.add_image_data(b[0], b[1])
  1668.         if self.image_manager.paused:
  1669.             self.refresh_timer.Restart(100)
  1670.         else:
  1671.             self.refresh_timer.Restart(16)
  1672.         self.image_manager.show_image(self)
  1673.        
  1674.  
  1675.  
  1676. app = wx.App(redirect=False)
  1677. mainframe = MainFrame(None, -1, '')
  1678. app.MainLoop()
Advertisement
Add Comment
Please, Sign In to add comment