텔레그램 메신저로 토렌트 검색하고 다운받기 (Search torrents by telegram bot)

지난번엔 텔레그램API를 활용해
토렌트 다운로드가 완료되면 텔레그램으로 알람을 받는 기능을 사용해 보았는데요.
이번엔 아예 텔레그램으로 토렌트 검색 및 다운로드 받는 기능을 구현해봤습니다.

자~ 곰탱이 봇! 일명 곰봇입니다~!

IMG_4550재밌겠죠?

아래는 GomBot.py 소스입니다.

파이썬으로 작성되어 있으며,

최신본은 https://github.com/zeroidle/GomBot 에서 확인할 수 있습니다

 

#!/usr/bin/env python3.5
#-*- coding: utf-8 -*-

import telepot
import time
import logging
import json
import pprint
import feedparser
import urllib
import sys
from _ctypes import Array
#reload(sys)
#sys.setdefaultencoding('utf-8')

class GomBot(telepot.Bot):
    token=''
    admin_id=[]
    public_room=[]
    menu = {}

    def __init__(self):
        self.stdin_path = '/dev/null'
        self.stdout_path = '/dev/tty'
        self.stderr_path = '/dev/tty'
        self.pidfile_path =  '/var/run/gombot.pid'
        self.pidfile_timeout = 5
        super(GomBot, self).__init__(self.token)
        self._answerer = telepot.helper.Answerer(self)

    def run(self):
        try:
            self.message_loop(self.handle)
            log.debug('Listening ...')
            while 1:
                tc = Transmission()
                arr = Transmission.garbage_collection(tc)
                
                if len(arr)>0: #노티할애가 있으면
                    pms = Plexmediaserver()
                    if not pms.refresh(2):
                        log.debug("refresh 실패로 완료된 시드유지함")
                        return
                    str = "\n".join(arr)
                    for room in self.public_room:   
                        self.sendMessage(room, "%s\n 준비되었습니다."%str)
    
                time.sleep(10)
        except Exception as e:
            log.exception("Main loop error")

    def handle(self, msg):
        flavor = 'normal'
        # normal message
        if flavor == 'normal':
            content_type, chat_type, chat_id = telepot.glance(msg)
            log.info('Normal Message:%s %s %s', content_type, chat_type, chat_id)
            log.debug(json.dumps(msg, ensure_ascii=False))
            command = msg['text']
            from_id = msg['from']['id']
            chat_id = msg['chat']['id'] 
            log.debug(command)
            
            if not command.startswith('/'): # 명령커맨드일 경우
                return
            
            keyword = command[1:].split(' ')
            
            if keyword[0] == "셧다운":
                log.debug("셧다운 권한확인")
                if from_id in self.admin_id:
                    self.sendMessage(chat_id,"모든 서버를 셧다운 합니다.")
                else:
                    log.debug(" 권한 없는 사용자(%d)가 셧다운 시도" % from_id)
                    self.sendMessage(chat_id,"권한이 없습니다.")
            elif keyword[0] == "하이":
                self.sendMessage(chat_id,"반갑구만 반가워요")
                    
            elif keyword[0] == "검색": # 토렌트 검색해야지
                if chat_id in self.public_room: # 채팅방이 공방이면
                    self.sendMessage(chat_id, "공개방입니다.\n 봇을 따로 소환해 검색하세요")
                    return

                result = self.get_search_list(' '.join(keyword[1:]))
                self.set_menu(chat_id, from_id,result)
                                
            elif keyword[0] == '받기': # 토렌트 검색해야지
                idx = int(keyword[1].split('.')[0]) - 1
                menu = self.menu[chat_id][idx]
                log.debug("다운로드주소 : %s" % menu['link'])
                
                tc = Transmission()
                dn_path = tc.get_dnpath(menu['title'])
                log.debug ("title: %s "% menu['title'])
                log.debug ("link: %s "% menu['link'])
                log.debug ("다운로드경로는 %s" % dn_path)
                to = tc.add_torrent(menu['link'],download_dir=dn_path)
                
                if (to):
                    log.debug("downloading : %s %s" % (to.id, to.name))
                    self.sendMessage(chat_id,'%s 다운로딩' % menu['title'])
                else:
                    self.sendMessage(chat_id,'다운로드 실패')
            
            elif keyword[0] == '확인': # 토렌트 진행상황 확인
                tc = Transmission()
                torrents = tc.get_torrents()
            
                if len(torrents) == 0:
                    self.sendMessage(chat_id, "다운로드중인 파일은 없습니다.")
                    return
                
                str = ''
                for torrent in torrents:
                    str = str + "%s - %s peers %.2f %%\n" % (torrent.name[:20],torrent.peersConnected, torrent.percentDone*100)
                self.sendMessage(chat_id, str)
            elif keyword[0] == "갱신":
                pms = Plexmediaserver()
                arr = [1,2]
                for num in arr:
                    pms.refresh(num)
            else:
                str = {"/검색 [검색어] - 검색어를 토렌트사이트에서 검색합니다.",
                       "/확인 - 토렌트 다운로드 진행상황을 확인합니다.",
                       "/갱신 - Plex Media Server 라이브러리를 갱신합니다."}
                str = "\n".join(str)
                self.sendMessage(chat_id,str)
                       
        # inline query - need `/setinline`
        elif flavor == 'inline_query':
            query_id, from_id, query_string = telepot.glance(msg, flavor=flavor)
            print('Inline Query:', query_id, from_id, query_string)

            def compute_answer():
                # Compose your own answers
                articles = [{'type': 'article',
                                'id': 'abc', 'title': query_string, 'message_text': query_string}]

                return articles

            self._answerer.answer(msg, compute_answer)

        # chosen inline result - need `/setinlinefeedback`
        elif flavor == 'chosen_inline_result':
            result_id, from_id, query_string = telepot.glance(msg, flavor=flavor)
            print('Chosen Inline Result:', result_id, from_id, query_string)

            # Remember the chosen answer to do better next time

        #else:
        #   raise telepot.BadFlavor(msg)
    
    def get_search_list(self, keyword):
        from bs4 import BeautifulSoup
        import urllib.request
        log.debug(keyword)
        url = Transmission.url % urllib.request.quote(keyword)
        headers = { 'User-Agent' : 'Mozilla/5.0' }
        req = urllib.request.Request(url, None, headers)
        handle = urllib.request.urlopen(req)

        data = handle.read()
        soup = BeautifulSoup(data,"lxml")

        count = 0
        
        
        arrData = []
        for item in soup.findAll("item"):
            count = count + 1
            if (count>10):
                break
            
            arr = {}
            arr['title'] = item.title.next_element
            arr['link'] = item.link.next_element
            log.debug("%d 제목 : %s" % (count,arr['title']))
            arrData.append(arr)
                
        return arrData
    
    def set_menu(self,chat_id, from_id, searchresult):
        self.menu[chat_id] = []
        
        for (i,entry) in enumerate(searchresult):
            arr = {}
            arr['title'] = entry['title']
            arr['link'] = entry['link']
            
            self.menu[chat_id].append(arr)
        
        self.set_keyboard(chat_id, searchresult)
        
    def set_keyboard(self,chat_id, searchresult):
        output_list =[]
        
        for (i,entry) in enumerate(searchresult):
            title = "/받기 " + str(i+1) + ". " + entry['title']
            temp_list = []
            temp_list.append(title[:50])
            log.debug(title)
            output_list.append(temp_list)
            
        show_keyboard = {'keyboard': output_list}
        self.sendMessage(chat_id,'선택해주세요',reply_markup=show_keyboard)


        
        
class Plexmediaserver():
    #LD_LIBRARY_PATH=/usr/lib/plexmediaserver
    #curl http://192.168.0.20:32400/library/sections/2/refresh
    host = ''
    port = ''
    
    def __init__(selfself):
        log.debug("plex init")
        
    def refresh(self, section_id):
        try:
            refresh_url = "http://%s:%s/library/sections/%s/refresh" % (self.host, self.port, section_id)
            log.debug("Plex Media Server - %d refresh", section_id)
            log.debug(refresh_url)
            f = urllib.request.urlopen(refresh_url)
            return True
        except:
            return False
        
import transmissionrpc
class Transmission(transmissionrpc.Client):
    shost = ''
    sport = ''
    uname = ''
    upass = ''
    url = ''
    mediapath = ''
    tvdir = ''
    moviedir=''
    tc = ''

    def __init__(self):
        super(Transmission, self).__init__(self.shost, self.sport, self.uname,self.upass)
    
    def garbage_collection(self):
        torrents = self.get_torrents()
        
        arr = []
        for torrent in torrents:
            if torrent.percentDone == 1: # Downloaded
                self.remove_torrent(torrent.id)
                log.debug("Downloaded - %d %s"%(torrent.id, torrent.name))
                ext_str = torrent.downloadDir.replace(self.mediapath,'')
                arr.append("%s (%s)"%(torrent.name,ext_str))
        return arr
    
    def get_dir(self):
        import os
        list = os.listdir(self.mediapath+self.tvdir)
        log.debug(json.dumps(list, ensure_ascii=False))
        return list

    def get_dnpath(self, filename):
        title = filename.split('.')[0]
        log.debug(title +" 제목을  기준으로 확인중")
        list = self.get_dir()

        found = False
        for n in list:
            if n.replace(' ','') in title.replace(' ',''):
                #문자열이 존재하면
                dn_dir = self.mediapath+self.tvdir+"/"+n+"/"
                log.info("기존 디렉토리 찾음 " + n)
                return dn_dir

        if (self.is_tv(title)):
            # TV구만
            dn_dir =  self.mediapath+self.tvdir+"/"+title+"/"
        else:
            # 영화라 치자
            dn_dir =  self.mediapath+self.moviedir+"/"+title+"/"

        log.info(dn_dir+"에 다운로드 합니다.")
        return dn_dir

    def is_tv(self,title):
        found = False
        list = self.get_dir()


        if  (not found):
            log.debug("_"+title + "_ TVDB를 참조합니다.")
            # TV프로그램인지 확인해보자
            import urllib
            
            import xml.etree.ElementTree as ET
            
            TVDB_TITLE='http://thetvdb.com/api/GetSeries.php?seriesname=%s&language=ko'

            url = TVDB_TITLE % (urllib.parse.quote(title))
            rss = ET.parse(urllib.request.urlopen(url)).getroot()

            seriesid = -1
            for element in rss.findall("Series"):
                seriesid = element.findtext("seriesid")
                dn_dir = self.mediapath+self.tvdir+"/"+title+"/"
                log.debug(dn_dir)
            if (seriesid != -1):
                found = True
            else:
                log.debug("찾지 못했습니다." + url)
        return  found

def loadConf():
    fp = open(conf_file,'r')
    conf = json.loads(fp.read())
    fp.close()

    GomBot.token = conf['telegram']['token']
    GomBot.admin_id = conf['telegram']['admin_id']
    GomBot.public_room = conf['telegram']['public_room']

    Transmission.shost = conf['transmission']['host']
    Transmission.sport = conf['transmission']['port']
    Transmission.uname = conf['transmission']['user']
    Transmission.upass = conf['transmission']['pass']
    Transmission.url = conf['transmission']['url']
    Transmission.mediapath = conf['transmission']['mediapath']
    Transmission.tvdir = conf['transmission']['tvdir']
    Transmission.moviedir = conf['transmission']['moviedir']
    Plexmediaserver.host = conf['plexmediaserver']['host']
    Plexmediaserver.port = conf['plexmediaserver']['port']
    
import time
from daemon import runner # python-daemon2
import logging.handlers
#load configuration
conf_file = 'gombot-settings.json'
loadConf()

bot = GomBot()

log = logging.getLogger("GomBot")
log.setLevel(logging.DEBUG)
formatter = logging.Formatter("%(asctime)s %(name)s:%(levelname)s %(message)s (%(filename)s:%(lineno)s)",
                            datefmt='%Y-%m-%d %H:%M:%S')
handler = logging.handlers.RotatingFileHandler("GomBot.log", maxBytes=10240, backupCount=1)
handler.setFormatter(formatter)
handler2 = logging.StreamHandler()
handler2.setFormatter(formatter)
log.addHandler(handler)
log.addHandler(handler2)

if (len(sys.argv)>1 and sys.argv[1] == "foreground"):
    log.info("Foreground mode start")
    log.setLevel(logging.DEBUG)
    log.debug("Debug mode setted.")
    bot.run()
    exit()

daemon_runner = runner.DaemonRunner(bot)
daemon_runner.daemon_context.files_preserve=[handler.stream]
daemon_runner.do_action()