Checks available MAME Torrents on PleasureDome and updates the local versions if more recent versions are detected
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

353 lines
11 KiB

  1. #!/usr/bin/python3
  2. '''
  3. Checks available Torrents on PleasureDome
  4. and updates the local versions if more recent
  5. versions are detected
  6. Basically what it does is:
  7. * Get all torrents in a directory torrents
  8. * Get all torrents from PleasureDome RSS
  9. * Get all torrents currently active in Transmission
  10. * Intersect the first two lists to get updatable torrents
  11. * And for each updatable torrent:
  12. - remove the old torrent from Transmission (if needed),
  13. - rename the local directory,
  14. - add the new torrent
  15. Supported Torrents:
  16. * MAME
  17. * HBMAME
  18. * No-Intro
  19. Work in progress…
  20. * TODO: implement some error handling
  21. Requirements:
  22. * Transmission for Bitorrent
  23. * A PleasureDome account
  24. * A proper PDMameUpdate.json file (see PDMameUpdate.template.json)
  25. * Python3 with the libraries below
  26. - feedparser
  27. - transmission-clutch
  28. - tabulate
  29. * Linux (untested on other OS, but it might work)
  30. Notes
  31. * This script logs in PleasureDome to get the proper cookies.
  32. It seems you can also set your cookies in Transmission using
  33. a cookies.txt file in the .config/transmission directory
  34. See: https://forum.transmissionbt.com/viewtopic.php?t=7468
  35. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  36. !!! Provided with no warranty whatsoever. !!!
  37. !!! Make sure you understand what the script !!!
  38. !!! does and adapt it to your context !!!
  39. !!! Use with caution. !!!
  40. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  41. '''
  42. import argparse
  43. import feedparser
  44. import json
  45. import logging
  46. import os
  47. import re
  48. import requests
  49. import time
  50. import pathlib
  51. from clutch.core import Client
  52. from tabulate import tabulate
  53. from collections import defaultdict
  54. from pprint import pformat
  55. from urllib.parse import quote
  56. def open_config_file():
  57. """Reads configuration from PDMameUpdate.json file"""
  58. locations = (
  59. os.environ.get("PDMAMEUPDATE_CONF"),
  60. "/etc",
  61. os.path.expanduser("~"),
  62. os.curdir
  63. )
  64. for loc in locations:
  65. if loc is not None:
  66. logging.info("Searching for config file in '%s'", loc)
  67. config_path = loc + '/PDMameUpdate.json'
  68. if pathlib.Path(config_path).is_file():
  69. config_file_loc = config_path
  70. break
  71. if 'config_file_loc' not in locals():
  72. logging.error("Config file not found")
  73. raise FileNotFoundError("Config file not found")
  74. logging.info('Opening config file: '+config_file_loc)
  75. config_file = None
  76. with open(config_file_loc, 'r') as config_file:
  77. config = json.load(config_file)
  78. parameters = [
  79. "mame-directory",
  80. "pleasuredome-password",
  81. "pleasuredome-user",
  82. "torrent-directory",
  83. "transmission-password",
  84. "transmission-port",
  85. "transmission-user"
  86. ]
  87. for key in parameters:
  88. if key not in config:
  89. logging.error('Missing key in config file: %s', key)
  90. raise ValueError('Invalid config file.')
  91. return config
  92. def fetch_local_torrents():
  93. """Fetches local torrents versions"""
  94. logging.info('Fetching current MAME versions')
  95. directories = os.listdir(config['mame-directory'])
  96. for directory in directories:
  97. for regexp in regexps:
  98. match = regexp.search(directory)
  99. if match:
  100. version = match.group(0)
  101. name = regexp.sub('#', directory)
  102. torrents[name]['local-version'] = version
  103. torrents[name]['local-name'] = directory
  104. logging.debug('Found the local torrent versions: %s', pformat(torrents))
  105. def fetch_remote_torrents():
  106. """Fetches PleasureDome torrents versions"""
  107. logging.info('Opening PleasureDome RSS feed')
  108. d = feedparser.parse('http://www.pleasuredome.org.uk/rss.xml')
  109. for post in d.entries:
  110. for regexp in regexps:
  111. match = regexp.search(post.title)
  112. if match:
  113. matched_version = match.group(0)
  114. matched_torrent = torrents[regexp.sub('#', post.title)]
  115. if matched_version > matched_torrent.get('remote-version', ''):
  116. matched_torrent['remote-version'] = matched_version
  117. matched_torrent['remote-link'] = post.link
  118. matched_torrent['remote-name'] = post.title
  119. else:
  120. logging.info("Skipping '{}' version '{}'".format(
  121. match.group(0),
  122. matched_version
  123. ))
  124. logging.debug('Found the remote torrent versions: %s', pformat(torrents))
  125. def filter_updatable_torrents():
  126. """Checks if newer versions are available and prompt for update"""
  127. for torrent, data in list(torrents.items()):
  128. keys_to_check = {'local-version', 'remote-version'}
  129. if (
  130. keys_to_check.issubset(data.keys())
  131. and
  132. data['local-version'] < data['remote-version']
  133. ):
  134. check_and_rewrite_download_url(data)
  135. else:
  136. del torrents[torrent]
  137. logging.debug(
  138. 'The following torrents can be updated: %s',
  139. [t for t in torrents.keys()]
  140. )
  141. def check_and_rewrite_download_url(torrent_data):
  142. url_match = re.compile(
  143. r"https?://www.pleasuredome.org.uk/details.php\?id=(.*)"
  144. )
  145. match = url_match.match(torrent_data['remote-link'])
  146. if match:
  147. url = ("http://www.pleasuredome.org.uk/download.php"
  148. "?id={}&f={}.torrent&secure=no").format(
  149. match.group(1),
  150. quote('+'.join(torrent_data['remote-name'].split(' ')), safe='+')
  151. )
  152. logging.info('Changed url {} to {}'.format(
  153. torrent_data['remote-link'],
  154. url
  155. ))
  156. torrent_data['remote-link'] = url
  157. def prompt_for_update():
  158. """Ask for user confirmation before updating"""
  159. output_header = ["Torrent", "From", "To", "Transmission ID"]
  160. output = []
  161. if len(torrents) > 0:
  162. for torrent, data in torrents.items():
  163. output.append([
  164. torrent,
  165. data['local-version'],
  166. data['remote-version'],
  167. data.get('transmission-id', 'N/A')
  168. ])
  169. print(tabulate(output, headers=output_header, tablefmt="simple"))
  170. print('Should I update the torrents listed above? (y/N)')
  171. answer = input()
  172. if answer.lower() != 'y':
  173. logging.info('Quitting: user cancelled update')
  174. print('Quitting…')
  175. exit(0)
  176. else:
  177. logging.info('Quitting: no update candidate')
  178. print('No update found…')
  179. exit(0)
  180. logging.info('User chose to update torrents')
  181. def get_cookies_from_pleasuredome():
  182. """Connects to PleasureDome to retrieve Cookies"""
  183. logging.info('Logging in PleasureDome')
  184. data = {
  185. 'uid': config['pleasuredome-user'],
  186. 'pwd': config['pleasuredome-password']
  187. }
  188. r = requests.post('http://www.pleasuredome.org.uk/login3.php', data=data)
  189. if r.status_code == 200:
  190. logging.info('Connected to PleasureDome')
  191. logging.info('Logging out')
  192. requests.get('http://www.pleasuredome.org.uk/logout.php')
  193. else:
  194. logging.error(
  195. 'Connection to PleasureDome failed with status %s',
  196. r.status_code
  197. )
  198. exit(1)
  199. return {k: r.cookies[k] for k in ('uid', 'pass')}
  200. def connect_to_transmission():
  201. """Connects to Transmission and return a Client object"""
  202. logging.info('Connecting to Transmission Remote Control')
  203. return Client(
  204. username=config['transmission-user'],
  205. password=config['transmission-password'],
  206. port=config['transmission-port']
  207. )
  208. def fetch_transmission_torrents():
  209. """Gets the torrents id from Transmission"""
  210. logging.info('Listing Transmission torrents')
  211. for torrent in client.list().values():
  212. for regexp in regexps:
  213. match = regexp.search(torrent['name'])
  214. if match:
  215. name = regexp.sub('#', torrent['name'])
  216. torrents[name]['transmission-id'] = torrent['id']
  217. logging.debug('Found the Transmission torrent ids: %s', pformat(torrents))
  218. def update_torrents():
  219. """
  220. Updates torrents:
  221. * remove it from Transmission,
  222. * rename the local directory,
  223. * and add the new torrent
  224. """
  225. logging.info('Updating torrents')
  226. for torrent in torrents.values():
  227. logging.info('Updating torrent : %s', torrent['remote-name'])
  228. old_name = os.path.join(
  229. config['mame-directory'],
  230. torrent['local-name']
  231. )
  232. new_name = os.path.join(
  233. config['mame-directory'],
  234. torrent['remote-name']
  235. )
  236. new_torrent = os.path.join(
  237. config['torrent-directory'],
  238. torrent['remote-name']+'.torrent'
  239. )
  240. if 'transmission-id' in torrent:
  241. logging.debug('Removing from transmission : %s',torrent['transmission-id'])
  242. client.torrent.remove(torrent['transmission-id'])
  243. logging.debug('Renaming %s to %s',old_name, new_name)
  244. os.rename(old_name, new_name)
  245. logging.debug('Adding to transmission : %s', torrent['remote-link'])
  246. client.torrent.add(
  247. filename=torrent['remote-link'],
  248. download_dir=config['mame-directory'],
  249. cookies=cookies,
  250. paused=False
  251. )
  252. if args.keep:
  253. logging.debug('Downloading torrent : %s', new_torrent)
  254. t = requests.get(torrent['remote-link'], verify=False, cookies=cookies)
  255. open(new_torrent, 'wb').write(t.content)
  256. if __name__ == '__main__':
  257. logging.basicConfig(
  258. level=logging.WARNING,
  259. format=' %(asctime)s - %(levelname)s - %(message)s'
  260. )
  261. parser = argparse.ArgumentParser(
  262. description='Update PleasureDome MAME Torrents'
  263. )
  264. parser.add_argument(
  265. '-l', '--log',
  266. action='store_true',
  267. help='Display more log messages'
  268. )
  269. parser.add_argument(
  270. '-d', '--debug',
  271. action='store_true',
  272. help='Display debugging messages'
  273. )
  274. parser.add_argument(
  275. '-k', '--keep',
  276. action='store_true',
  277. help='Keep torrent files localy'
  278. )
  279. parser.add_argument(
  280. '-c', '--countdown',
  281. action='store_true',
  282. help='Start with a 5 second countdown'
  283. )
  284. args = parser.parse_args()
  285. if args.log:
  286. logging.getLogger().setLevel(logging.INFO)
  287. if args.debug:
  288. logging.getLogger().setLevel(logging.DEBUG)
  289. if args.countdown:
  290. print('PDMameUpdate is about to start')
  291. # Useful if you run this script when your machine boots
  292. for i in range(5, 0, -1):
  293. print('{}\r'.format(i), end=''),
  294. time.sleep(1)
  295. regexps = [
  296. re.compile(r'(?<=MAME )[\d.]+'),
  297. re.compile(r'(?<=HBMAME )[\d.]+'),
  298. re.compile(r'(?<=No-Intro \()[\d-]+')
  299. ]
  300. config = open_config_file()
  301. torrents = defaultdict(dict)
  302. client = connect_to_transmission()
  303. cookies = get_cookies_from_pleasuredome()
  304. fetch_local_torrents()
  305. fetch_remote_torrents()
  306. fetch_transmission_torrents()
  307. filter_updatable_torrents()
  308. prompt_for_update()
  309. update_torrents()