diff --git a/.gitignore b/.gitignore index 30ac2fc..1605617 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ __pycache__/ *.txt !requirements.txt *.json +Temp/ diff --git a/dictdeal.py b/dictdeal.py new file mode 100644 index 0000000..33ffa9c --- /dev/null +++ b/dictdeal.py @@ -0,0 +1,26 @@ +# (C) 2021 lifegpc +# This file is part of rssbot. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +from json import dumps + + +def json2data(json: dict) -> dict: + r = {} + for key in json.keys(): + if isinstance(json[key], (dict, list, tuple)): + r[key] = dumps(json[key], ensure_ascii=False) + else: + r[key] = json[key] + return r diff --git a/fileEntry.py b/fileEntry.py new file mode 100644 index 0000000..d3e03e5 --- /dev/null +++ b/fileEntry.py @@ -0,0 +1,136 @@ +# (C) 2021 lifegpc +# This file is part of rssbot. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +from urllib.parse import urlsplit +from os.path import abspath, splitext, getsize, exists, isdir, isfile +from time import time_ns +from random import randint +from requests import get +from os import remove as removeFile, mkdir, listdir, removedirs +from typing import List + + +def remove(s: str): + try: + if not exists(s): + return + if isfile(s): + removeFile(s) + elif isdir(s): + p = s if s[-1] in ['/', '\\'] else f"{s}/" + for v in listdir(s): + remove(f"{p}{v}") + removedirs(s) + except: + remove(s) + + +class FileEntry: + def __init__(self, url: str): + if not exists('Temp'): + mkdir('Temp') + if not isdir('Temp'): + remove('Temp') + mkdir('Temp') + self._url = url + self._ext = splitext(urlsplit(url).path)[1] + if self._ext == '': + self._ext = 'temp' + self._fn = f"{time_ns()}{randint(0, 9999)}" + self._fullfn = f"{self._fn}.{self._ext}" + self._abspath = abspath(f'Temp/{self._fn}.{self._ext}') + try: + self._r = get(url, stream=True) + if self._r.ok: + with open(self._abspath, 'wb') as f: + for chunk in self._r.iter_content(1024): + if chunk: + f.write(chunk) + except: + self.ok = False + self.ok = self._r.ok + self._fileSize = getsize(self._abspath) + self._fileExist = True if exists(self._abspath) else False + self._localURI = f"file://{self._abspath}" if self._abspath[0] == '/' else f"file:///{self._abspath}" + self._f = None + + def delete(self): + if not self._fileExist: + return + if self._f is not None and not self._f.closed: + self._f.close() + try: + remove(self._abspath) + self._fileExist = False + except: + pass + + def open(self) -> bool: + if not self._fileExist: + return False + if self._f is not None and not self._f.closed: + self._f.seek(0, 0) + return True + try: + self._f = open(self._abspath, 'rb') + return True + except: + return False + + +class FileEntries: + def __init__(self): + self.__list = [] + + def add(self, url: str) -> FileEntry: + if self.has(url): + return self.get(url) + fileEntry = FileEntry(url) + if fileEntry.ok and fileEntry._fileExist: + self.__list.append(fileEntry) + return fileEntry + return None + + def clear(self): + for v in self.__list: + fileEntry: FileEntry = v + fileEntry.delete() + i = 0 + while i < len(self.__list): + fileEntry = self.__list[i] + if not fileEntry._fileExist: + self.__list.remove(fileEntry) + i = i - 1 + i = i + 1 + + def get(self, url: str) -> FileEntry: + for v in self.__list: + fileEntry: FileEntry = v + if fileEntry._url == url: + return fileEntry + return None + + def getList(self) -> List[FileEntry]: + r = [] + for v in self.__list: + r.append(v) + return r + + def has(self, url: str): + for v in self.__list: + fileEntry: FileEntry = v + if fileEntry._url == url: + return True + return False diff --git a/readset.py b/readset.py index 01a5c95..be603d8 100644 --- a/readset.py +++ b/readset.py @@ -40,6 +40,8 @@ class settings: self._maxRetryCount = int(d['maxRetryCount']) if 'maxRetryCount' in d and d['maxRetryCount'].isnumeric( ) and int(d['maxRetryCount']) >= 0 else 3 self._telegramBotApiServer = d['telegramBotApiServer'] if 'telegramBotApiServer' in d else 'https://api.telegram.org' + self._downloadMediaFile = bool(int(d['downloadMediaFile'])) if 'downloadMediaFile' in d and d['downloadMediaFile'].isnumeric() else False + self._sendFileURLScheme = bool(int(d['sendFileURLScheme'])) if 'sendFileURLScheme' in d and d['sendFileURLScheme'].isnumeric() else False class commandline: diff --git a/rssbot.py b/rssbot.py index 2bf19cc..57dfabd 100644 --- a/rssbot.py +++ b/rssbot.py @@ -24,7 +24,7 @@ from typing import List from rssparser import RSSParser from html import escape from hashl import md5WithBase64 -from enum import Enum +from enum import Enum, unique from rsstempdict import rssMetaInfo, rssMetaList from random import randrange from textc import textc, removeEmptyLine, decodeURI @@ -33,6 +33,8 @@ from rsschecker import RSSCheckerThread from rsslist import getInlineKeyBoardForRSSList, InlineKeyBoardForRSSList, getInlineKeyBoardForRSSInList, getTextContentForRSSInList, getInlineKeyBoardForRSSUnsubscribeInList, getTextContentForRSSUnsubscribeInList, getInlineKeyBoardForRSSSettingsInList from usercheck import checkUserPermissionsInChat, UserPermissionsInChatCheckResult import sys +from fileEntry import FileEntries, remove +from dictdeal import json2data def getMediaInfo(m: dict, config: RSSConfig = RSSConfig()) -> str: @@ -62,6 +64,7 @@ def getMediaInfo(m: dict, config: RSSConfig = RSSConfig()) -> str: return s +@unique class InlineKeyBoardCallBack(Enum): Subscribe = 0 SendPriview = 1 @@ -138,6 +141,9 @@ class main: def _request(self, methodName: str, HTTPMethod: str = 'get', data: dict = None, json: dict = None, files: dict = None, returnType: str = 'json', telegramBotApiServer: str = None): try: + if json is not None and files is not None: + data = json2data(json) + json = None r = self._r.request( HTTPMethod, f'{self._telegramBotApiServer if telegramBotApiServer is None else telegramBotApiServer}/bot{self._setting._token}/{methodName}', data=data, json=json, files=files) if r.status_code != 200: @@ -183,24 +189,85 @@ class main: elif getListCount(content, 'imgList') == 1 and getListCount(content, 'videoList') == 0: di['caption'] = text.tostr() di['parse_mode'] = 'HTML' - di['photo'] = content['imgList'][0] - re = self._request('sendPhoto', 'post', json=di) + if not self._setting._downloadMediaFile: + di['photo'] = content['imgList'][0] + re = self._request('sendPhoto', 'post', json=di) + else: + fileEntry = self._tempFileEntries.add(content['imgList'][0]) + if not fileEntry.ok: + return None + if self._setting._sendFileURLScheme: + di['photo'] = fileEntry._localURI + re = self._request('sendPhoto', 'post', json=di) + else: + fileEntry.open() + re = self._request('sendPhoto', 'post', json=di, files={ + 'photo': (fileEntry._fullfn, fileEntry._f)}) elif getListCount(content, 'imgList') == 0 and getListCount(content, 'videoList') == 1: di['caption'] = text.tostr() di['parse_mode'] = 'HTML' - di['video'] = content['videoList'][0]['src'] + if self._setting._downloadMediaFile and not self._setting._sendFileURLScheme: + di2 = {} + if not self._setting._downloadMediaFile: + di['video'] = content['videoList'][0]['src'] + else: + fileEntry = self._tempFileEntries.add( + content['videoList'][0]['src']) + if not fileEntry.ok: + return None + if self._setting._sendFileURLScheme: + di['video'] = fileEntry._localURI + else: + fileEntry.open() + di2['video'] = (fileEntry._fullfn, fileEntry._f) if 'poster' in content['videoList'][0] and content['videoList'][0]['poster'] is not None and content['videoList'][0]['poster'] != '': - di['thumb'] = content['videoList'][0]['poster'] + if not self._setting._downloadMediaFile: + di['thumb'] = content['videoList'][0]['poster'] + else: + fileEntry = self._tempFileEntries.add( + content['videoList'][0]['poster']) + if not fileEntry.ok: + return None + if self._setting._sendFileURLScheme: + di['thumb'] = fileEntry._localURI + else: + fileEntry.open() + di2['thumb'] = (fileEntry._fullfn, fileEntry._f) di['supports_streaming'] = True - re = self._request('sendVideo', 'post', json=di) + if not self._setting._downloadMediaFile or self._setting._sendFileURLScheme: + re = self._request('sendVideo', 'post', json=di) + else: + re = self._request('sendVideo', 'post', json=di, files=di2) else: ind = 0 + if self._setting._downloadMediaFile and not self._setting._sendFileURLScheme: + ind2 = 0 + di3 = {} di['media'] = [] for i in content['imgList']: if ind % 9 == 0 and ind != 0: - re = self._request('sendMediaGroup', 'post', json=di) - di['media'] = [] - di2 = {'type': 'photo', 'media': i} + if not self._setting._downloadMediaFile or self._setting._sendFileURLScheme: + re = self._request('sendMediaGroup', 'post', json=di) + di['media'] = [] + else: + re = self._request( + 'sendMediaGroup', 'post', json=di, files=di3) + di['media'] = [] + di3 = {} + di2 = {'type': 'photo'} + if not self._setting._downloadMediaFile: + di2['media'] = i + else: + fileEntry = self._tempFileEntries.add(i) + if not fileEntry.ok: + return None + if self._setting._sendFileURLScheme: + di2['media'] = fileEntry._localURI + else: + fileEntry.open() + di2['media'] = f'attach://file{ind2}' + di3[f'file{ind2}'] = (fileEntry._fullfn, fileEntry._f) + ind2 = ind2 + 1 if ind == 0: di2['caption'] = text.tostr() di2['parse_mode'] = 'HTML' @@ -208,18 +275,53 @@ class main: ind = ind + 1 for i in content['videoList']: if ind % 9 == 0 and ind != 0: - re = self._request('sendMediaGroup', 'post', json=di) - di['media'] = [] - di2 = {'type': 'video', - 'media': i['src'], 'supports_streaming': True} + if not self._setting._downloadMediaFile or self._setting._sendFileURLScheme: + re = self._request('sendMediaGroup', 'post', json=di) + di['media'] = [] + else: + re = self._request( + 'sendMediaGroup', 'post', json=di, files=di3) + di['media'] = [] + di3 = {} + di2 = {'type': 'video', 'supports_streaming': True} + if not self._setting._downloadMediaFile: + di2['media'] = i['src'] + else: + fileEntry = self._tempFileEntries.add(i['src']) + if not fileEntry.ok: + return None + if self._setting._sendFileURLScheme: + di2['media'] = fileEntry._localURI + else: + fileEntry.open() + di2['media'] = f'attach://file{ind2}' + di3[f'file{ind2}'] = (fileEntry._fullfn, fileEntry._f) + ind2 = ind2 + 1 if 'poster' in i and i['poster'] is not None and i['poster'] != '': - di2['thumb'] = i['poster'] + if not self._setting._downloadMediaFile: + di2['thumb'] = i['poster'] + else: + fileEntry = self._tempFileEntries.add(i['poster']) + if not fileEntry.ok: + return None + if self._setting._sendFileURLScheme: + di2['thumb'] = fileEntry._localURI + else: + fileEntry.open() + di2['thumb'] = f'attach://file{ind2}' + di3[f'file{ind2}'] = ( + fileEntry._fullfn, fileEntry._f) + ind2 = ind2 + 1 if ind == 0: di2['caption'] = text.tostr() di2['parse_mode'] = 'HTML' di['media'].append(di2) ind = ind + 1 - re = self._request('sendMediaGroup', 'post', json=di) + if not self._setting._downloadMediaFile or self._setting._sendFileURLScheme: + re = self._request('sendMediaGroup', 'post', json=di) + else: + re = self._request('sendMediaGroup', 'post', + json=di, files=di3) if re is not None and 'ok' in re and re['ok']: return True return False @@ -259,6 +361,8 @@ class main: if self._telegramBotApiServer != 'https://api.telegram.org': self._request("logOut", "post", telegramBotApiServer="https://api.telegram.org") + remove('Temp') + self._tempFileEntries = FileEntries() self._me = self._request('getMe') self._rssMetaList = rssMetaList() print(self._me) diff --git a/rsschecker.py b/rsschecker.py index 28e4a74..cd3b455 100644 --- a/rsschecker.py +++ b/rsschecker.py @@ -64,6 +64,7 @@ class RSSCheckerThread(Thread): if self._main._commandLine._rebuildHashlist and self._main._commandLine._exitAfterRebuild: _exit(0) self._main._commandLine._rebuildHashlist = False + self._main._tempFileEntries.clear() def __init__(self, m): Thread.__init__(self)