├── Dockerfile ├── LICENSE ├── RDRSS.py ├── README.md └── requirements.txt /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10-slim-bullseye 2 | WORKDIR /app 3 | COPY ./requirements.txt . 4 | RUN pip3 install -r requirements.txt 5 | COPY ./RDRSS.py . 6 | VOLUME /app/RDRSSconfig 7 | CMD ["/app/RDRSS.py"] 8 | ENTRYPOINT ["python3"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Michal Dobes 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /RDRSS.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Python script for feeding magnet links from RSS feed into Real-Debrid 4 | 5 | import json 6 | import requests 7 | import feedparser 8 | import argparse 9 | import datetime 10 | import os 11 | 12 | # SECTION: VARIABLES 13 | __location__ = os.path.realpath(os.path.join( 14 | os.getcwd(), os.path.dirname(__file__))) 15 | 16 | # Save file information 17 | save_file_name = "RDRSSconfig/rdrss.json" 18 | save_file_path = os.path.join(__location__, save_file_name) 19 | 20 | BASE_DATE_STRING = "2000-01-01 00:00:00" 21 | 22 | # Variables loaded from file 23 | _auth_token = "" 24 | _data = {} 25 | _headers = {"Authorization": "Bearer " + _auth_token} 26 | # !SECTION 27 | 28 | 29 | # SECTION: METHODS 30 | 31 | 32 | def load_data(initialize_if_not: bool) -> bool: 33 | """Load data from config file into data variable 34 | 35 | @param initialize_if_not Create empty boilerplate data if file didnt exist 36 | 37 | @return bool File does exist 38 | """ 39 | global _data 40 | try: 41 | json_file = open(save_file_path, "r+", encoding="utf-8") 42 | _data = json.load(json_file) 43 | json_file.close() 44 | return True 45 | except Exception: 46 | if initialize_if_not: 47 | _data["rssUrls"] = [] 48 | _data["updated"] = BASE_DATE_STRING 49 | _data["authToken"] = "" 50 | return False 51 | 52 | 53 | def store_data() -> bool: 54 | """Store data to config file from data variable 55 | 56 | @return bool Storing was successful 57 | """ 58 | 59 | try: 60 | os.makedirs(os.path.dirname(save_file_path), exist_ok=True) 61 | json_file = open(save_file_path, "w", encoding="utf-8") 62 | json.dump(_data, json_file, indent=4) 63 | json_file.close() 64 | return True 65 | except Exception: 66 | return False 67 | 68 | 69 | def ready_and_parse(): 70 | """Try to parse RSS urls to Real-Debrid """ 71 | global _data 72 | 73 | # Check for token 74 | if not (token_check()): 75 | return 76 | 77 | # Load stored last updated time 78 | if not load_data(True): 79 | return 80 | try: 81 | last_updated_date = datetime.datetime.strptime( 82 | str(_data["updated"]), '%Y-%m-%d %H:%M:%S').timetuple() 83 | except Exception: 84 | last_updated_date = datetime.datetime.strptime( 85 | str(BASE_DATE_STRING), '%Y-%m-%d %H:%M:%S').timetuple() 86 | 87 | # Load stored urls 88 | urls = get_rss() 89 | if len(urls) < 1: 90 | print("Missing RSS url. To add RSS url, use --add ") 91 | return 92 | 93 | # For each url print info and fetch to Real-Debrid 94 | x = 0 95 | for rss in urls: 96 | x += 1 97 | print("(" + str(x) + "/" + str(len(urls)) + ") " + rss) 98 | parse_feed(rss, last_updated_date) 99 | 100 | # Store now as last update time 101 | _data["updated"] = datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S') 102 | store_data() 103 | 104 | # Select files in Real-Debrid 105 | select_files() 106 | 107 | 108 | def parse_feed(rss_url, last_load_date): 109 | """Parse RSS feed into Real-Debrid 110 | 111 | @param rss_url RSS feed url 112 | @param last_load_date Last date this feed was updated (when to fetch new entries from) 113 | """ 114 | 115 | feed = feedparser.parse(rss_url) 116 | 117 | # If feed is empty return 118 | if len(feed.entries) == 0: 119 | print("-> Fetch from RSS failed. (RSS had no entries)") 120 | return 121 | 122 | # Try to add magnet from each entry that has not yet been added to Real-Debrid 123 | # based on update time 124 | for entry in feed.entries: 125 | if (entry.updated_parsed > last_load_date): 126 | add_magnet(entry.link) 127 | 128 | print("-> Successfully fetched RSS to RD.") 129 | 130 | 131 | def process_api_response(result, indent = 1) -> bool: 132 | """Process response codes from Real-Debrid api 133 | 134 | @param result Recieved result 135 | @param indent Requested indentation size 136 | 137 | @returns bool Response is ok 138 | """ 139 | 140 | if not result.ok: 141 | 142 | # Process error message indentation 143 | indent_string = "" 144 | for x in range(indent): 145 | indent_string += "-" 146 | if indent > 0: 147 | indent_string += "> " 148 | 149 | if result.status_code == 401: 150 | print(indent_string + 151 | "Failed reaching RD: Invalid token, to enter authentication token, use --token .") 152 | elif result.status_code == 402: 153 | print(indent_string + "Failed reaching RD: User not premium.") 154 | elif result.status_code == 503: 155 | print(indent_string + "Failed reaching RD: Service not available.") 156 | else: 157 | print(indent_string + "Failed reaching RD.") 158 | return False 159 | return True 160 | 161 | 162 | def add_magnet(magnet) -> bool: 163 | """Add magnet url into Real-Debrid using API 164 | 165 | @param magnet Url to magnet 166 | 167 | @returns bool Magnet added successfully 168 | """ 169 | 170 | print("--> Adding magnet: " + magnet) 171 | 172 | # Add magnet to Real-Debrid and process response 173 | request_data = {"magnet": magnet, "host": "real-debrid.com"} 174 | result = requests.post( 175 | "https://api.real-debrid.com/rest/1.0/torrents/addMagnet", headers=_headers, data=request_data) 176 | if not process_api_response(result, 3): 177 | return False 178 | 179 | return True 180 | 181 | 182 | def select_files() -> bool: 183 | """Select files added into Real-Debrid using API 184 | 185 | @returns bool Files selected successfully 186 | """ 187 | 188 | # Get files from Real-Debrid 189 | result = requests.get( 190 | "https://api.real-debrid.com/rest/1.0/torrents?limit=100", headers=_headers) 191 | if not process_api_response(result): 192 | print("-> Selecting files on RD failed.") 193 | return False 194 | 195 | # Select correct files 196 | files = result.json() 197 | for file in files: 198 | if file["status"] == "waiting_files_selection": 199 | result = requests.post("https://api.real-debrid.com/rest/1.0/torrents/selectFiles/" + 200 | file["id"], data={"files": "all"}, headers=_headers) 201 | if not process_api_response(result): 202 | print("--> File could not be selected.") 203 | return False 204 | 205 | print("-> Successfully selected files on RD.") 206 | 207 | return True 208 | 209 | 210 | def get_rss(): 211 | """ Retrieve stored RSS urls 212 | 213 | @return array of urls 214 | """ 215 | 216 | if load_data(True): 217 | if ("rssUrls" in _data) and (len(_data["rssUrls"]) != 0): 218 | return _data["rssUrls"] 219 | return [] 220 | 221 | 222 | def set_token(token): 223 | """Store Real-Debrid token 224 | 225 | @param token Real-Debrid user token 226 | """ 227 | global _data 228 | 229 | # Load data and store token 230 | load_data(True) 231 | _data["authToken"] = token 232 | 233 | # Store data 234 | if not store_data(): 235 | print("Couldn't store token.") 236 | return 237 | print("Token succesfully added.") 238 | 239 | 240 | def token_check() -> bool: 241 | """Check if Real-Debrid token is stored 242 | 243 | @returns bool If true, token is stored 244 | """ 245 | 246 | global _auth_token 247 | global _headers 248 | 249 | # Check if token is in loaded data 250 | if load_data(True): 251 | if len(_data["authToken"]) != 0: 252 | _auth_token = _data["authToken"] 253 | _headers = {"Authorization": "Bearer " + _auth_token} 254 | return True 255 | 256 | print( 257 | "Missing Real-Debrid authentication token. To enter auth token, use --token ") 258 | return False 259 | 260 | 261 | def add_rss(rss): 262 | """Store RSS url 263 | 264 | param rss Url to RSS feed 265 | """ 266 | 267 | global _data 268 | 269 | # Load data and add new rss 270 | load_data(True) 271 | _data["rssUrls"].append(rss) 272 | 273 | # Store data 274 | if not store_data(): 275 | print("Couldn't store RSS url.") 276 | return 277 | print("RSS url succesfully added.") 278 | 279 | 280 | def list_rss(): 281 | """List stored RSS urls""" 282 | 283 | if load_data(True): 284 | if ("rssUrls" in _data) and (len(_data["rssUrls"]) != 0): 285 | print("RSS urls stored:") 286 | 287 | # Loop through urls and print them numbered 288 | x = 0 289 | for rss in _data["rssUrls"]: 290 | x += 1 291 | print(" [" + str(x) + "] " + rss) 292 | if (x > 0): 293 | return 294 | 295 | print("No RSS url added. To add RSS url, use --add ") 296 | 297 | 298 | def remove_rss(n): 299 | """Remove stored RSS url number n 300 | 301 | @param n Index of url to remove 302 | """ 303 | 304 | global _data 305 | 306 | if not load_data(True): 307 | print("Configuration file is empty.") 308 | 309 | # Check if url at index exists 310 | if ("rssUrls" not in _data) or (len(_data["rssUrls"]) < n): 311 | print("No url at index " + str(n) + " found.") 312 | return 313 | 314 | # Remove url from data 315 | _data["rssUrls"].pop(n-1) 316 | 317 | # Store data back into file 318 | if not store_data(): 319 | print("Couldn't remove RSS url.") 320 | return 321 | print("RSS url succesfully removed.") 322 | 323 | # !SECTION 324 | 325 | 326 | # SECTION: ARGUMENT PROCESSING 327 | parser = argparse.ArgumentParser(description='RSS feed to Real-Debrid.') 328 | parser.add_argument('-t', '--token', type=str, 329 | help='set Real-Debrid token (acquire token at https://real-debrid.com/apitoken)') 330 | parser.add_argument('-l', '--list', 331 | help='list RSS urls', action='store_true') 332 | parser.add_argument('-a', '--add', type=str, help='add RSS url') 333 | parser.add_argument('-r', '--remove', type=int, 334 | help='remove RSS url at index (obtained using --list)') 335 | parser.add_argument('-m', '--magnet', type=str, 336 | help='add magnet to Real-Debrid') 337 | parser.add_argument('-s', '--select', 338 | help='select added files on Real-Debrid', action='store_true') 339 | 340 | args = parser.parse_args() 341 | if args.token: 342 | set_token(args.token) 343 | elif args.list: 344 | list_rss() 345 | elif args.add: 346 | add_rss(args.add) 347 | elif args.remove: 348 | remove_rss(args.remove) 349 | elif args.magnet: 350 | if token_check(): 351 | add_magnet(args.magnet) 352 | elif args.select: 353 | if token_check(): 354 | select_files() 355 | else: 356 | ready_and_parse() 357 | # !SECTION 358 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RDRSS 2 | Python script which takes torrent magnet links from RSS feed and feeds them to your [real-debrid.com](https://real-debrid.com) account. 3 | 4 | ### Features 5 | - Adds magnet links from any RSS feed to [real-debrid.com](https://real-debrid.com) 6 | - Fetches only new links, will ignore already fetched entries. 7 | - Saves all set up data. 8 | 9 | ## Getting Started 10 | 11 | ### Install 12 | 13 | Instalation is done native or using Docker container. 14 | 15 | #### Native 16 | 17 | ##### Prerequisites 18 | - Python (3) 19 | - pip 20 | - Premium [real-debrid.com](https://real-debrid.com) account 21 | 22 | ##### Instructions 23 | 24 | Install the required packages. 25 | ``` 26 | pip install -r requirements.txt 27 | ``` 28 | 29 | Then download [the latest release](https://github.com/CaptainMishan/RDRSS/releases/latest) and save the python file somewhere accessible. 30 | 31 | #### Docker 32 | 33 | Download [the latest release](https://github.com/CaptainMishan/RDRSS/releases/latest). 34 | Build the Docker image from Dockerfile. 35 | 36 | ### Set up 37 | 1. Obtain your [real-debrid api token here](https://real-debrid.com/apitoken) 38 | 2. Run `RDRSS.py --token ""` in your shell to save your api token. 39 | 40 | #### Adding RSS feeds 41 | Run `RDRSS.py --add ""` to add RSS feed with magnet links (in link tag of RSS feed) that should be added to Real-Debrid. 42 | 43 | #### Listing and removing RSSS feeds 44 | - Run `RDRSS.py --list` to see stored RSS feeds and their indexes. 45 | - Run `RDRSS.py --remove ` to remove stored RSS feed. 46 | - Run `RDRSS.py --select` to "select" added files on Real-Debrid. 47 | 48 | The script creates "./RDRSSconfig/rdrss.json" config file, which contains: 49 | - stored token ("authToken" field) 50 | - stored RSS urls ("rssUrls" field) 51 | - timestamp for last entry that was added to Real-Debrid ("updated" field) 52 | 53 | ## Usage 54 | Run `RDRSS.py` to add magnets from new entries in feeds to real-debrid. 55 | For help run `RDRSS.py -h`. 56 | It is recommended to run this regularly, for example at startup, using cron job or Automator. 57 | 58 | ## License 59 | This project is licensed under the MIT License - see the [LICENSE](/LICENSE) file for details 60 | 61 | ## Contributions 62 | All contributions are most welcome. 63 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | feedparser 3 | argparse --------------------------------------------------------------------------------