Advertisement
Guest User

Untitled

a guest
Sep 21st, 2014
223
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 22.56 KB | None | 0 0
  1. #!/usr/bin/python2
  2. # coding: UTF-8
  3.  
  4. import urllib2
  5. import urllib
  6. import re
  7. import cookielib
  8. import sys
  9. import time
  10. import json
  11. import MySQLdb as db
  12. import datetime
  13. import logging
  14. import getopt
  15. import traceback
  16. from HTMLParser import HTMLParser
  17.  
  18. # максимальная продолжительность аудио в секундах
  19. MAX_AUDIO_DURATION = 600
  20. AUDIO_REGEX = re.compile(r"[\w0-9\. ,\-\(\)]+$", re.U)
  21. DEBUG = True
  22. LOG_TO_CONSOLE = True
  23.  
  24. def consoleOut(text):
  25.     if not LOG_TO_CONSOLE:
  26.         return
  27.     if type(text) == unicode:
  28.         print text.encode('ascii', 'ignore')
  29.     else:
  30.         print text
  31.        
  32. def getNumOfWeekInMonth(dtime):
  33.     timeDelta = datetime.timedelta(dtime.day - 1)
  34.     firstDayInMonth = dtime - timeDelta
  35.     firstDayWeekNumber = int(firstDayInMonth.strftime('%w'))
  36.     weekNumberOfDayInMonth = (firstDayWeekNumber + dtime.day) / 7
  37.     return weekNumberOfDayInMonth
  38.    
  39.  
  40. class Vk:
  41.     pageEncoding = 'utf-8'
  42.     connectionTimeout = 15
  43.     def __init__(self, login, password):
  44.         self.login = login
  45.         self.password = password
  46.         self.cj = cookielib.CookieJar()
  47.         self.opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.cj))
  48.         self.opener.addheaders = [('User-agent', 'Mozilla/5.0 (Windows NT 5.1; rv:29.0) Gecko/20100101 Firefox/29.0'),
  49.                                             ('Referer', 'http://www.lesstroy.net/')]
  50.                                            
  51.     # функция авторизации. Возвращает True в случае успешной авторизации, иначе False
  52.     def auth(self):
  53.         actionUrlReg = re.compile(r"action=\"(https://login\.vk\.com/\?act=login.+?)\"")
  54.         html = self.opener.open("http://m.vk.com/login", timeout=Vk.connectionTimeout).read()  # TODO сделать обработчик исключений
  55.         actionUrlMatch = actionUrlReg.search(html)
  56.         if actionUrlMatch is None:
  57.             consoleOut("html doesnt contain url matching actionUrlReg in auth()")
  58.             return False
  59.         actionUrl = actionUrlMatch.group(1)
  60.         postDic = {}
  61.  
  62.         postDic["email"] = self.login
  63.         postDic["pass"] = self.password
  64.  
  65.         postStr = urllib.urlencode(postDic)
  66.         html = self.opener.open(actionUrl, postStr, timeout=Vk.connectionTimeout).read()  # TODO сделать обработчик исключений
  67.         if self.needToAcceptOwner(html):
  68.             logger = logging.getLogger('Vk.auth')
  69.             logger.critical('The site needs accepting owner. Exit...')
  70.             sys.exit(-1)
  71.         if (html.find(self.toSiteEncode("Не удается войти.")) != -1):
  72.             consoleOut("invalid login/password")
  73.             return False
  74.         else:
  75.             return True
  76.  
  77.     def getOpener(self):
  78.         return self.opener
  79.    
  80.     def logout(self):
  81.         self.opener.open("https://login.vk.com/?act=logout_mobile&hash=f4ef82a16b90ea5a78&_origin=http%3A%2F%2Fm.vk.com",
  82.                               timeout=Vk.connectionTimeout).read()
  83.        
  84.     """постит сообщение в группу с адресом groupUrl, сообщением message, идентификатором картинки imageId,
  85.       с музыкой, идентификаторы которой указаны в списке auidioIds, с таймером postTime (время в unix формате)
  86.   """
  87.     def postInGroup(self, groupName, message, imageId, audioIds, postTime):
  88.         groupUrl = "http://m.vk.com/" + groupName
  89.         html = self.opener.open(groupUrl).read()
  90.         if self.needToAcceptOwner(html):
  91.             logger = logging.getLogger('Vk.postInGroup')
  92.             logger.critical('The site needs accepting owner. Exit...')
  93.             sys.exit(-1)
  94.         groupPageParser = GroupPageParser()
  95.         groupPageParser.feed(self.toUnicode(html))
  96.         formActionUrl = groupPageParser.getFormActionUrl();  # TODO добавить проверку на None
  97.        
  98.         postDic = {}
  99.         postDic['message'] = message
  100.         postDic['postpone'] = postTime
  101.         postDic['attach1'] = imageId
  102.         postDic['attach1_type'] = 'photo'
  103.         c = 1
  104.         # DUMMY UNCOMMENT
  105.         postData = urllib.urlencode(postDic)
  106.         response = self.opener.open(formActionUrl, postData, timeout=Vk.connectionTimeout)  # TODO добавить обработчик исключений
  107.         html = response.read()
  108.         if self.needToAcceptOwner(html):
  109.             logger = logging.getLogger('Vk.postInGroup')
  110.             logger.critical('The site needs accepting owner. Exit...')
  111.             sys.exit(-1)
  112.    
  113.     def findAudios(self, name):
  114.         utf8Name = name.encode('utf-8')
  115.         findAudioUrl = "http://m.vk.com/audio?act=search&q=" + urllib.quote_plus(utf8Name)
  116.         html = self.opener.open(findAudioUrl, timeout=Vk.connectionTimeout).read();  # TODO добавить обработчик исключений
  117.         if self.needToAcceptOwner(html):
  118.             logger = logging.getLogger('Vk.findAudios')
  119.             logger.critical('The site needs accepting owner. Exit...')
  120.             sys.exit(-1)
  121.         audioPageParser = AudioPageParser()
  122.         audioPageParser.feed(self.toUnicode(html))
  123.         return audioPageParser.getAudios()
  124.    
  125.     def toUnicode(self, text):
  126.     # переводит текст, который в кодировки контакта, в юникод
  127.         return text.decode(Vk.pageEncoding)    
  128.    
  129.     def toSiteEncode(self, text):
  130.     # переводит текст, который в кодировки скрипта(utf-8), в кодировку контакта
  131.         return text.decode('UTF-8').encode(Vk.pageEncoding)
  132.        
  133.     def needToAcceptOwner(self, html):
  134.         """проверяет html документ на наличие кода, требующего подтверждения владельца страницы"""
  135.         securityCheckText = "act=security_check"
  136.         if self.toSiteEncode(securityCheckText) in html:
  137.             return True
  138.         else:
  139.             return False
  140. # -----------------------------------------------
  141. class AudioInfo:
  142.     def __init__(self):
  143.         self.author = None
  144.         self.songName = None
  145.         self.duration = None
  146.         self.identifier = None
  147.    
  148.     def __str__(self):
  149.         result = u""
  150.         if self.author is not None:
  151.             result += self.author
  152.         else:
  153.             result += u"NONE"
  154.         result += u" - "
  155.         if self.songName is not None:
  156.             result += self.songName
  157.         else:
  158.             result += u"NONE"
  159.         result += u"("
  160.         if self.duration is not None:
  161.             result += str(self.duration)
  162.         else:
  163.             result += u'NONE'
  164.         result += u' s), id : '
  165.         if self.identifier is not None:
  166.             result += self.identifier
  167.         else:
  168.             result += u'NONE'
  169.         return result
  170.    
  171.     def __repr__(self):
  172.         return self.__str__()
  173. # -----------------------------------------------
  174. class AttributeUtils:
  175.     @staticmethod
  176.     def getAttrsDic(attrsList):
  177.         attrsDic = {}
  178.         for i in attrsList:
  179.             attrsDic[i[0]] = i[1]
  180.         return attrsDic
  181.    
  182.     @staticmethod
  183.     def hasAttributeEqualingValue(attrsDic, attribute, value):
  184.         if attrsDic.has_key(attribute) and attrsDic[attribute] == value:
  185.             return True
  186.         else:
  187.             return False
  188. # -----------------------------------------------
  189. # Парсер вап-страницы группы, рассчитанный на версию сайта для оперы мини
  190. class GroupPageParser(HTMLParser):
  191.     def __init__(self):
  192.         HTMLParser.__init__(self)
  193.         self.formActionUrl = None
  194.    
  195.     def getFormActionUrl(self):
  196.         if self.formActionUrl is not None:
  197.             return "http://m.vk.com" + self.formActionUrl
  198.         else:
  199.             return None
  200.    
  201.     def handle_starttag(self, tag, attrs):
  202.         attrsDic = AttributeUtils.getAttrsDic(attrs)
  203.         if tag == 'form':
  204.             self.formActionUrl = attrsDic['action']
  205. # -----------------------------------------------
  206. class AudioPageParser(HTMLParser):
  207.     def __init__(self):
  208.         HTMLParser.__init__(self)
  209.         self.audios = []
  210.         self.isAudioSection = False
  211.         self.isAuthorSection = False
  212.         self.isSongNameSection = False
  213.         self.buffer = ''
  214.         self.curAudioDuration = None
  215.         self.curAudioSongName = None
  216.         self.curAudioAuthor = None
  217.         self.curAudioId = None
  218.        
  219.     def getAudios(self):
  220.         return self.audios
  221.    
  222.     def handle_starttag(self, tag, attrs):
  223.         attrsDic = AttributeUtils.getAttrsDic(attrs)
  224.         if tag == 'div' and AttributeUtils.hasAttributeEqualingValue(attrsDic, 'class', 'audio_item ai_has_btn'):
  225.             self.isAudioSection = True
  226.             self.curAudioId = attrsDic['data-id']        
  227.         elif self.isAudioSection:
  228.             if tag == 'div' and AttributeUtils.hasAttributeEqualingValue(attrsDic, 'class', 'ai_dur'):
  229.                 self.curAudioDuration = int(attrsDic['data-dur'])
  230.             elif tag == 'span' and AttributeUtils.hasAttributeEqualingValue(attrsDic, 'class', 'ai_artist'):
  231.                 self.isAuthorSection = True
  232.             elif tag == 'span' and AttributeUtils.hasAttributeEqualingValue(attrsDic, 'class', 'ai_title'):
  233.                 self.isSongNameSection = True
  234.    
  235.     def handle_endtag(self, tag):
  236.         if self.isAudioSection:
  237.             if tag == 'table':
  238.                 self.isAudioSection = False
  239.                 audio = AudioInfo()
  240.                 audio.duration = self.curAudioDuration
  241.                 audio.songName = self.curAudioSongName
  242.                 audio.author = self.curAudioAuthor
  243.                 audio.identifier = self.parseId(self.curAudioId)
  244.                 self.audios.append(audio)
  245.                 self.curAudioDuration = None
  246.                 self.curAudioSongName = None
  247.                 self.curAudioAuthor = None
  248.                 self.curAudioId = None
  249.                 self.isAudioSection = False
  250.             if tag == 'span' and self.isAuthorSection:
  251.                 self.curAudioAuthor = self.buffer
  252.                 self.buffer = ''
  253.                 self.isAuthorSection = False
  254.             elif tag == 'span' and self.isSongNameSection:
  255.                 self.curAudioSongName = self.buffer
  256.                 self.buffer = ''
  257.                 self.isSongNameSection = False
  258.            
  259.     def handle_data(self, data):
  260.         if self.isAudioSection:
  261.             if self.isAuthorSection or self.isSongNameSection:
  262.                 self.buffer += data
  263.                
  264.     def parseId(self, musicId):
  265.         return re.search(r"(\-?\d+_\d+)", musicId, re.U).group(1)
  266. # -----------------------------------------------
  267. class DataBaseInfo:
  268.     def __init__(self, host, login, password, dbName):
  269.         self.host = host
  270.         self.login = login
  271.         self.password = password
  272.         self.dbName = dbName
  273. # -----------------------------------------------
  274. class VkGroupInfo:
  275.     def __init__(self, groupName, message, imageSchedule):
  276.         self.groupName = groupName
  277.         self.message = message
  278.         self.imageSchedule = imageSchedule
  279. # -----------------------------------------------
  280. class ApplicationSettings:
  281.     def __init__(self, fileName):
  282.         f = open(fileName, 'r')
  283.         self.settings = json.load(f)
  284.         f.close()
  285.        
  286.     def getGroupsInfo(self):
  287.         return [VkGroupInfo(i['groupName'], i['message'], self.settings['imageSchedules'][i[u'imageSchedule']]) for i in self.settings['VkGroups']]
  288.    
  289.     def getVkLogin(self):
  290.         return self.settings['VkCreditianals']['login']
  291.    
  292.     def getVkPassword(self):
  293.         return self.settings['VkCreditianals']['password']
  294.    
  295.     def getDataBaseInfo(self):
  296.         host = self.settings['DBCreditianals']['host']
  297.         login = self.settings['DBCreditianals']['login']
  298.         password = self.settings['DBCreditianals']['password']
  299.         dbName = self.settings['DBCreditianals']['DBName']
  300.         return DataBaseInfo(host, login, password, dbName)
  301.    
  302.     def getAudiosNumToPost(self):
  303.         return self.settings['audiosNumber']
  304.    
  305.     def getDateFrom(self):
  306.         return datetime.datetime.strptime(self.settings['dateFrom'], '%d.%m.%Y')
  307.    
  308.     def getDateTo(self):
  309.         return datetime.datetime.strptime(self.settings['dateTo'], '%d.%m.%Y')
  310.    
  311.     def getPostTimes(self):
  312.         return [strToTime(i) for i in self.settings['postTimes']]
  313.    
  314.     def getMinTracksPerPost(self):
  315.         return self.settings['minTracksPerPost']
  316.    
  317.     def getTimeoutMaxAttemps(self):
  318.         return self.settings['timeoutMaxAttempts']
  319.    
  320.     def getTimeoutRepeatAt(self):
  321.         return self.settings['timeoutRepeatAt']
  322.    
  323.     def getSearchMusicDelay(self):
  324.         return self.settings['searchMusicDelay']
  325.    
  326.     def getPostingDelay(self):
  327.         return self.settings['postingDelay']
  328.    
  329.     def getJumpToNextGroupDelay(self):
  330.         return self.settings['jumpToNextGroupDelay']
  331.    
  332.     def getLogFile(self):
  333.         return self.settings['logFile']
  334. # -----------------------------------------------
  335. class MusicRepository:
  336.     weekdays = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
  337.     def __init__(self, cursor, tableName):
  338.         self.tableName = tableName
  339.         self.cursor = cursor
  340.    
  341.     # в качестве дня недели передаем номер дня от 0 до 6 включительно
  342.     def getAudiosForWeekday(self, weekdayNum):
  343.         return ["music" for i in xrange(50)] # DUMMY
  344.         self.cursor.execute("SELECT track FROM " + db.escape_string(self.tableName) + " WHERE day=%s;",
  345.                                   (MusicRepository.weekdays[weekdayNum],))
  346.         return [i[0] for i in self.cursor.fetchall()]
  347. # -----------------------------------------------
  348. def strToTime(timeStr):
  349.     t = timeStr.split(':')
  350.     return datetime.time(int(t[0]), int(t[1]))
  351.  
  352. def datetimeToUnixtime(dtime):
  353.     return time.mktime(dtime.timetuple())
  354.  
  355. def db_connect(dbInfo):
  356.     return None # DUMMY
  357.     con = db.connect(host=dbInfo.host, user=dbInfo.login,
  358.                           passwd=dbInfo.password, db=dbInfo.dbName, charset='utf8')
  359.     return con
  360.  
  361. def filterAudio(audioList):
  362.     skipStrings = ['>>>', '<<<']
  363.     for audio in audioList:
  364.         skipSong = False
  365.         for skipString in skipStrings:
  366.             if skipString in audio.songName:
  367.                 skipSong = True
  368.                 break
  369.         if skipSong:
  370.             continue
  371.         if audio.duration <= MAX_AUDIO_DURATION and AUDIO_REGEX.match(audio.author) and AUDIO_REGEX.match(audio.songName):
  372.             return audio
  373.     logging.debug(u"can't find audio in follow audio list: %s", audioList);
  374.     return None
  375.  
  376. def usage():
  377.     consoleOut("usage: python vk.py [--config jsonConfigPath]\n"
  378.                "Starts autoposting to groups with settings placed in jsonConfigPath"
  379.                " (default config.json)")
  380.  
  381. def main():
  382.     cfg = 'config.json'
  383.     opts, args = getopt.getopt(sys.argv[1:], "h", ['config=']);
  384.     for opt, arg in opts:
  385.         print opt, arg
  386.         if opt == '--config':
  387.             cfg = arg
  388.         elif opt == '-h':
  389.             usage()
  390.             sys.exit(0)
  391.     applicationSettings = ApplicationSettings(cfg)
  392.     if DEBUG:
  393.         logLevel = logging.DEBUG
  394.     else:
  395.         logLevel = logging.WARNING
  396.     logging.basicConfig(filename=applicationSettings.getLogFile(), level=logLevel)
  397.     logger = logging.getLogger('main')
  398.    
  399.     logger.info('start working')
  400.     vk_login = applicationSettings.getVkLogin()
  401.     vk_password = applicationSettings.getVkPassword()
  402.     dbInfo = applicationSettings.getDataBaseInfo()
  403.    
  404.     logger.debug('connection to DB')
  405.     try :
  406.         dbCon = db_connect(dbInfo)
  407.     except db.Error as e:
  408.         consoleOut(e.message())
  409.         logger.critical('Excepton. %s', e)
  410.         logger.critical('can\'t connect to DB. Exit...')
  411.         sys.exit(-1)
  412.  
  413.     dateFrom = applicationSettings.getDateFrom()
  414.     dateTo = applicationSettings.getDateTo()
  415.     postTimes = applicationSettings.getPostTimes()
  416.     vkGroupsList = applicationSettings.getGroupsInfo()
  417.     audiosNumber = applicationSettings.getAudiosNumToPost()
  418.     minTracksPerPost = applicationSettings.getMinTracksPerPost()
  419.     timeoutMaxAttempts = applicationSettings.getTimeoutMaxAttemps()
  420.     timeoutRepeatAt = applicationSettings.getTimeoutRepeatAt()
  421.     jumpToNextGroupDelay = applicationSettings.getJumpToNextGroupDelay()
  422.     searchMusicDelay = applicationSettings.getSearchMusicDelay()
  423.     vk = Vk(vk_login, vk_password)
  424.     connectionAttemptsCounter = 0
  425.     logger.info('authorization')
  426.     while connectionAttemptsCounter < timeoutMaxAttempts:
  427.         try:
  428.             if not vk.auth():
  429.                 logger.critical("can't auth. Exit...")
  430.                 sys.exit(-1)
  431.             break
  432.         except Exception as e:
  433.             consoleOut(str(e))
  434.             logger.warning("Excepton. %s", e)
  435.             connectionAttemptsCounter += 1
  436.             time.sleep(timeoutRepeatAt)
  437.     if connectionAttemptsCounter >= timeoutMaxAttempts:
  438.         logger.critical("max connection timeouts number is reached while auth. Exit...")
  439.         sys.exit(-1)
  440.     logger.debug('start groupList iteration(Num groups: %d)', len(vkGroupsList))
  441.     for vkGroup in vkGroupsList:
  442.         logger.debug('start processing of group %s', vkGroup.groupName)
  443.         musicRepository = MusicRepository(dbCon, vkGroup.groupName) # DUMMY replace dbCon with dbCon.cursor()
  444.         dateDelta = datetime.timedelta(1)  # необходим для прибавления одного дня к dateIt
  445.         dateIt = dateFrom  # для обхода даты с dateFrom до dateTo
  446.         while dateIt <= dateTo:
  447.             logger.debug('start processing date %s', dateIt.isoformat())
  448.             try:
  449.                 dayMusicList = musicRepository.getAudiosForWeekday(dateIt.weekday())
  450.             except db.Error as e:
  451.                 logger.error("can't get music for day %s. Go to next day...", dateIt.isoformat())
  452.                 consoleOut(str(e))
  453.                 consoleOut("can't get music for day %s. Go to next day..." % (dateIt.isoformat(),))
  454.                 dateIt += dateDelta
  455.                 continue
  456.             consoleOut("post today: " + dayMusicList.__str__())
  457.             musicCounter = 0
  458.             for t in postTimes:
  459.                 consoleOut("timebegin"), t
  460.                 logger.debug('start processing time %s', t.isoformat())
  461.                 dayAudiosToPost = []
  462.                 dtime = datetime.datetime.combine(dateIt, t)
  463.                 numOfWeekInMonth = getNumOfWeekInMonth(dtime)
  464.                 imageId = vkGroup.imageSchedule[dtime.strftime('%w')][numOfWeekInMonth] # айдишник картинки, которую постим
  465.                 for m in xrange(audiosNumber):
  466.                     searchSoundProcess = True
  467.                     while searchSoundProcess and musicCounter < len(dayMusicList):
  468.                         consoleOut(u'search for ' + dayMusicList[musicCounter])
  469.                         logger.debug(u'search for %s', dayMusicList[musicCounter])
  470.                         connectionAttemptsCounter = 0
  471.                         while connectionAttemptsCounter < timeoutMaxAttempts:
  472.                             try:
  473.                                 audios = vk.findAudios(dayMusicList[musicCounter])
  474.                                 break
  475.                             except Exception as e :
  476.                                 consoleOut(e)
  477.                                 logger.warning("Excepton. %s", e)
  478.                                 connectionAttemptsCounter += 1
  479.                                 time.sleep(timeoutRepeatAt)
  480.                         if connectionAttemptsCounter >= timeoutMaxAttempts:
  481.                             logger.critical("max connection timeouts number is reached while search audio. Exit...")
  482.                             logger.critical(u"Rest audios: %s\nDate: %s\nTime: %s", dayMusicList[musicCounter:],
  483.                                                                                                 dateIt.isoformat(), t.isoformat())
  484.                             sys.exit(-1)
  485.                         time.sleep(searchMusicDelay)
  486.                         audio = filterAudio(audios)
  487.                         musicCounter += 1
  488.                         if audio is not None:
  489.                             consoleOut(u'audio found: ' + audio.__str__() + u'm =' + str(m))
  490.                             dayAudiosToPost.append(audio.identifier)
  491.                             searchSoundProcess = False
  492.                         else:
  493.                             consoleOut('audio not found! ' + str(len(audios)))
  494.                 if len(dayAudiosToPost) < minTracksPerPost:
  495.                     logger.debug('not enough audios to post')
  496.                     break
  497.                 else:
  498.                     logger.debug("POSTING!")
  499.                     consoleOut("POSTING!")
  500.                     connectionAttemptsCounter = 0
  501.                     while connectionAttemptsCounter < timeoutMaxAttempts:
  502.                         try:
  503.                             vk.postInGroup(vkGroup.groupName, vkGroup.message, imageId, dayAudiosToPost, datetimeToUnixtime(dtime))
  504.                             break
  505.                         except Exception as e:
  506.                             consoleOut(str(e))
  507.                             logger.warning("Excepton. %s", e)
  508.                             timeoutMaxAttempts += 1
  509.                             time.sleep(timeoutRepeatAt)
  510.                     if connectionAttemptsCounter >= timeoutMaxAttempts:
  511.                         logger.critical('max connection timeouts number is reached while posting message. Exit...')
  512.                         logger.critical(u"Rest audios: %s\nDate: %s\nTime: %s",
  513.                                         dayMusicList[musicCounter:],
  514.                                         dateIt.isoformat(),
  515.                                         t.isoformat())
  516.                         sys.exit(-1)
  517.             dateIt += dateDelta
  518.         time.sleep(jumpToNextGroupDelay)
  519.     logger.info("programm is exited normally")
  520.  
  521. if __name__ == '__main__':
  522.     consoleOut("vkposter starting")
  523.     try:
  524.         main()
  525.     except getopt.GetoptError as e:
  526.         consoleOut("invalid arguments")
  527.         usage()
  528.     except Exception as e:
  529.         consoleOut("Unhandled exception")
  530.         consoleOut(str(e))
  531.         traceback.print_exc()
  532.     consoleOut("end")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement