wi1-bot 1.4.10__py3-none-any.whl → 1.4.12__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
wi1_bot/_version.py CHANGED
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '1.4.10'
16
- __version_tuple__ = version_tuple = (1, 4, 10)
15
+ __version__ = version = '1.4.12'
16
+ __version_tuple__ = version_tuple = (1, 4, 12)
wi1_bot/arr/__init__.py CHANGED
@@ -1,4 +1,5 @@
1
1
  from .radarr import Radarr
2
2
  from .sonarr import Sonarr
3
+ from .util import replace_remote_paths
3
4
 
4
- __all__ = ["Radarr", "Sonarr"]
5
+ __all__ = ["Radarr", "Sonarr", "replace_remote_paths"]
wi1_bot/arr/episode.py CHANGED
@@ -21,8 +21,7 @@ class Episode:
21
21
  self.imdb_id: str = series_imdb_id
22
22
 
23
23
  self.full_title: str = (
24
- f"{self.series_title} S{self.season_num:02d}E{self.ep_num:02d} -"
25
- f" {self.ep_title}"
24
+ f"{self.series_title} S{self.season_num:02d}E{self.ep_num:02d} - {self.ep_title}"
26
25
  )
27
26
 
28
27
  if self.imdb_id:
wi1_bot/arr/radarr.py CHANGED
@@ -33,9 +33,7 @@ class Radarr:
33
33
 
34
34
  user_movie_ids = tag_detail["movieIds"]
35
35
 
36
- return [
37
- Movie(m) for m in possible_movies if "id" in m and m["id"] in user_movie_ids
38
- ]
36
+ return [Movie(m) for m in possible_movies if "id" in m and m["id"] in user_movie_ids]
39
37
 
40
38
  def add_movie(self, movie: Movie, profile: str = "good") -> bool:
41
39
  if self._radarr.get_movie(movie.tmdb_id, tmdb=True):
@@ -149,10 +147,10 @@ class Radarr:
149
147
  raise ValueError(f"no quality profile with the id {profile_id}")
150
148
 
151
149
  def rescan_movie(self, movie_id: int) -> None:
152
- self._radarr.post_command("RescanMovie", movieId=movie_id)
150
+ self._radarr.post_command("RescanMovie", movieId=movie_id) # type: ignore
153
151
 
154
152
  def refresh_movie(self, movie_id: int) -> None:
155
- self._radarr.post_command("RefreshMovie", movieIds=[movie_id])
153
+ self._radarr.post_command("RefreshMovie", movieIds=[movie_id]) # type: ignore
156
154
 
157
155
  def search_missing(self) -> None:
158
156
  self._radarr.post_command(name="MissingMoviesSearch")
wi1_bot/arr/util.py ADDED
@@ -0,0 +1,31 @@
1
+ import logging
2
+ import pathlib
3
+
4
+ from wi1_bot.config import RemotePathMapping, config
5
+
6
+
7
+ def replace_remote_paths(path: pathlib.Path) -> pathlib.Path:
8
+ if "general" not in config or "remote_path_mappings" not in config["general"]:
9
+ return path
10
+
11
+ mappings = config["general"]["remote_path_mappings"]
12
+
13
+ most_specific: RemotePathMapping | None = None
14
+
15
+ for mapping in mappings:
16
+ if path.is_relative_to(mapping["remote"]):
17
+ mapping_len = len(pathlib.Path(mapping["remote"]).parts)
18
+ most_specific_len = (
19
+ len(pathlib.Path(most_specific["remote"]).parts) if most_specific is not None else 0
20
+ )
21
+
22
+ if mapping_len > most_specific_len:
23
+ most_specific = mapping
24
+
25
+ if most_specific is not None:
26
+ remote_path = path
27
+ path = pathlib.Path(most_specific["local"]) / path.relative_to(most_specific["remote"])
28
+
29
+ logging.getLogger(__name__).debug(f"replaced remote path mapping: {remote_path} -> {path}")
30
+
31
+ return path
wi1_bot/discord/bot.py CHANGED
@@ -26,9 +26,7 @@ async def check_channel(ctx: commands.Context[Any]) -> bool:
26
26
 
27
27
 
28
28
  @bot.event
29
- async def on_command_error(
30
- ctx: commands.Context[Any], error: commands.CommandError
31
- ) -> None:
29
+ async def on_command_error(ctx: commands.Context[Any], error: commands.CommandError) -> None:
32
30
  match error:
33
31
  case commands.CommandNotFound() | commands.CheckFailure():
34
32
  pass
@@ -42,9 +40,7 @@ async def on_command_error(
42
40
  await reply(ctx.message, str(error))
43
41
  case _:
44
42
  logger.error(
45
- "".join(
46
- traceback.format_exception(type(error), error, error.__traceback__)
47
- )
43
+ "".join(traceback.format_exception(type(error), error, error.__traceback__))
48
44
  )
49
45
 
50
46
  await reply(
@@ -71,10 +67,10 @@ async def before_invoke(ctx: commands.Context[Any]) -> None:
71
67
  logger.info(f"got command from {ctx.message.author}: {ctx.message.content}")
72
68
 
73
69
 
74
- @commands.cooldown(1, 10) # type: ignore
75
- @bot.command(
70
+ @bot.command( # type: ignore[arg-type]
76
71
  name="downloads", aliases=["queue", "q"], help="see the status of movie downloads"
77
72
  )
73
+ @commands.cooldown(1, 10)
78
74
  async def downloads_cmd(ctx: commands.Context[Any]) -> None:
79
75
  async with ctx.typing():
80
76
  queue = radarr.get_downloads() + sonarr.get_downloads()
@@ -88,8 +84,8 @@ async def downloads_cmd(ctx: commands.Context[Any]) -> None:
88
84
  await reply(ctx.message, "\n\n".join(map(str, queue)), title="download progress")
89
85
 
90
86
 
91
- @commands.cooldown(1, 60, commands.BucketType.user) # type: ignore
92
- @bot.command(name="quota", help="see your used space on the plex")
87
+ @bot.command(name="quota", help="see your used space on the plex") # type: ignore[arg-type]
88
+ @commands.cooldown(1, 60, commands.BucketType.user)
93
89
  async def quota_cmd(ctx: commands.Context[Any]) -> None:
94
90
  async with ctx.typing():
95
91
  used = (
@@ -99,10 +95,8 @@ async def quota_cmd(ctx: commands.Context[Any]) -> None:
99
95
 
100
96
  maximum: float = 0
101
97
 
102
- try:
98
+ if "quotas" in config["discord"]:
103
99
  maximum = config["discord"]["quotas"][ctx.message.author.id]
104
- except KeyError:
105
- pass
106
100
 
107
101
  pct = used / maximum * 100 if maximum != 0 else 100
108
102
 
@@ -114,15 +108,15 @@ async def quota_cmd(ctx: commands.Context[Any]) -> None:
114
108
  await reply(ctx.message, msg)
115
109
 
116
110
 
117
- @commands.cooldown(1, 60) # type: ignore
118
- @bot.command(name="quotas", help="see everyone's used space on the plex")
111
+ @bot.command(name="quotas", help="see everyone's used space on the plex") # type: ignore[arg-type]
112
+ @commands.cooldown(1, 60)
119
113
  async def quotas_cmd(ctx: commands.Context[Any]) -> None:
120
- try:
121
- quotas = config["discord"]["quotas"]
122
- except ValueError:
114
+ if "quotas" not in config["discord"]:
123
115
  await reply(ctx.message, "quotas are not implemented here")
124
116
  return
125
117
 
118
+ quotas = config["discord"]["quotas"]
119
+
126
120
  if not quotas:
127
121
  await reply(ctx.message, "quotas are not implemented here")
128
122
 
@@ -130,9 +124,7 @@ async def quotas_cmd(ctx: commands.Context[Any]) -> None:
130
124
  msg = []
131
125
 
132
126
  for user_id, total in quotas.items():
133
- used = (
134
- radarr.get_quota_amount(user_id) + sonarr.get_quota_amount(user_id)
135
- ) / 1024**3
127
+ used = (radarr.get_quota_amount(user_id) + sonarr.get_quota_amount(user_id)) / 1024**3
136
128
 
137
129
  pct = used / total * 100 if total != 0 else 100
138
130
 
@@ -147,11 +139,9 @@ async def quotas_cmd(ctx: commands.Context[Any]) -> None:
147
139
  )
148
140
 
149
141
 
150
- @bot.command(name="addtag", help="add a user tag") # type: ignore
142
+ @bot.command(name="addtag", help="add a user tag") # type: ignore[arg-type]
151
143
  @commands.has_role("plex-admin")
152
- async def addtag_cmd(
153
- ctx: commands.Context[Any], name: str, user: discord.Member
154
- ) -> None:
144
+ async def addtag_cmd(ctx: commands.Context[Any], name: str, user: discord.Member) -> None:
155
145
  tag = f"{name}: {user.id}"
156
146
 
157
147
  radarr.create_tag(tag)
@@ -18,9 +18,7 @@ class MovieCog(commands.Cog):
18
18
  self.radarr = Radarr(config["radarr"]["url"], config["radarr"]["api_key"])
19
19
 
20
20
  @commands.command(name="addmovie", help="add a movie to the plex")
21
- async def addmovie_cmd(
22
- self, ctx: commands.Context[Any], *, query: str = ""
23
- ) -> None:
21
+ async def addmovie_cmd(self, ctx: commands.Context[Any], *, query: str = "") -> None:
24
22
  if not query:
25
23
  await reply(ctx.message, "usage: !addmovie KEYWORDS...")
26
24
  return
@@ -36,9 +34,7 @@ class MovieCog(commands.Cog):
36
34
  )
37
35
  return
38
36
 
39
- resp, to_add = await select_from_list(
40
- self.bot, ctx.message, "addmovie", potential
41
- )
37
+ resp, to_add = await select_from_list(self.bot, ctx.message, "addmovie", potential)
42
38
 
43
39
  if not to_add:
44
40
  return
@@ -48,16 +44,12 @@ class MovieCog(commands.Cog):
48
44
  for movie in to_add:
49
45
  if not self.radarr.add_movie(movie):
50
46
  if self.radarr.movie_downloaded(movie):
51
- await reply(
52
- resp, f"{movie} is already DOWNLOADED on the plex (idiot)"
53
- )
47
+ await reply(resp, f"{movie} is already DOWNLOADED on the plex (idiot)")
54
48
  else:
55
49
  await reply(resp, f"{movie} is already on the plex (idiot)")
56
50
  continue
57
51
 
58
- self.logger.info(
59
- f"{ctx.message.author.name} has added the movie {movie.full_title}"
60
- )
52
+ self.logger.info(f"{ctx.message.author.name} has added the movie {movie.full_title}")
61
53
 
62
54
  push.send(
63
55
  f"{ctx.message.author.name} has added the movie {movie.full_title}",
@@ -74,18 +66,12 @@ class MovieCog(commands.Cog):
74
66
  await asyncio.sleep(10)
75
67
 
76
68
  if not self.radarr.add_tag(added, ctx.message.author.id):
77
- push.send(
78
- f"get {ctx.message.author.name} a tag", title="tag needed", priority=1
79
- )
69
+ push.send(f"get {ctx.message.author.name} a tag", title="tag needed", priority=1)
80
70
 
81
- await ctx.send(
82
- f"hey <@!{config['discord']['admin_id']}> get this guy a tag"
83
- )
71
+ await ctx.send(f"hey <@!{config['discord']['admin_id']}> get this guy a tag")
84
72
 
85
73
  @commands.command(name="delmovie", help="delete a movie from the plex")
86
- async def delmovie_cmd(
87
- self, ctx: commands.Context[Any], *, query: str = ""
88
- ) -> None:
74
+ async def delmovie_cmd(self, ctx: commands.Context[Any], *, query: str = "") -> None:
89
75
  if not query:
90
76
  await reply(ctx.message, "usage: !delmovie KEYWORDS...")
91
77
  return
@@ -102,9 +88,7 @@ class MovieCog(commands.Cog):
102
88
  )
103
89
  return
104
90
  else:
105
- potential = self.radarr.lookup_user_library(
106
- query, ctx.message.author.id
107
- )[:50]
91
+ potential = self.radarr.lookup_user_library(query, ctx.message.author.id)[:50]
108
92
 
109
93
  if not potential:
110
94
  await reply(
@@ -114,9 +98,7 @@ class MovieCog(commands.Cog):
114
98
  )
115
99
  return
116
100
 
117
- resp, to_delete = await select_from_list(
118
- self.bot, ctx.message, "delmovie", potential
119
- )
101
+ resp, to_delete = await select_from_list(self.bot, ctx.message, "delmovie", potential)
120
102
 
121
103
  if not to_delete:
122
104
  return
@@ -124,9 +106,7 @@ class MovieCog(commands.Cog):
124
106
  for movie in to_delete:
125
107
  self.radarr.del_movie(movie)
126
108
 
127
- self.logger.info(
128
- f"{ctx.message.author.name} has deleted the movie {movie.full_title}"
129
- )
109
+ self.logger.info(f"{ctx.message.author.name} has deleted the movie {movie.full_title}")
130
110
 
131
111
  push.send(
132
112
  f"{ctx.message.author.name} has deleted the movie {movie.full_title}",
@@ -46,9 +46,7 @@ class SeriesCog(commands.Cog):
46
46
  )
47
47
  return
48
48
 
49
- resp, to_add = await select_from_list(
50
- self.bot, ctx.message, "addshow", potential
51
- )
49
+ resp, to_add = await select_from_list(self.bot, ctx.message, "addshow", potential)
52
50
 
53
51
  if not to_add:
54
52
  return
@@ -58,16 +56,12 @@ class SeriesCog(commands.Cog):
58
56
  for series in to_add:
59
57
  if not self.sonarr.add_series(series):
60
58
  if self.sonarr.series_downloaded(series):
61
- await reply(
62
- resp, f"{series} is already DOWNLOADED on the plex (idiot)"
63
- )
59
+ await reply(resp, f"{series} is already DOWNLOADED on the plex (idiot)")
64
60
  else:
65
61
  await reply(resp, f"{series} is already on the plex (idiot)")
66
62
  continue
67
63
 
68
- self.logger.info(
69
- f"{ctx.message.author.name} has added the show {series.full_title}"
70
- )
64
+ self.logger.info(f"{ctx.message.author.name} has added the show {series.full_title}")
71
65
 
72
66
  push.send(
73
67
  f"{ctx.message.author.name} has added the show {series.full_title}",
@@ -91,17 +85,13 @@ class SeriesCog(commands.Cog):
91
85
  priority=1,
92
86
  )
93
87
 
94
- await ctx.send(
95
- f"hey <@!{config['discord']['admin_id']}> get this guy a tag"
96
- )
88
+ await ctx.send(f"hey <@!{config['discord']['admin_id']}> get this guy a tag")
97
89
 
98
90
  return
99
91
 
100
92
  @commands.command(name="delshow", help="delete a show from the plex")
101
93
  @commands.has_any_role("plex-admin", "plex-shows")
102
- async def delshow_command(
103
- self, ctx: commands.Context[Any], *, query: str = ""
104
- ) -> None:
94
+ async def delshow_command(self, ctx: commands.Context[Any], *, query: str = "") -> None:
105
95
  if not query:
106
96
  await reply(ctx.message, "usage: !delshow KEYWORDS...")
107
97
  return
@@ -118,9 +108,7 @@ class SeriesCog(commands.Cog):
118
108
  )
119
109
  return
120
110
  else:
121
- potential = self.sonarr.lookup_user_library(
122
- query, ctx.message.author.id
123
- )[:50]
111
+ potential = self.sonarr.lookup_user_library(query, ctx.message.author.id)[:50]
124
112
 
125
113
  if not potential:
126
114
  await reply(
@@ -130,9 +118,7 @@ class SeriesCog(commands.Cog):
130
118
  )
131
119
  return
132
120
 
133
- resp, to_delete = await select_from_list(
134
- self.bot, ctx.message, "delshow", potential
135
- )
121
+ resp, to_delete = await select_from_list(self.bot, ctx.message, "delshow", potential)
136
122
 
137
123
  if not to_delete:
138
124
  return
@@ -140,9 +126,7 @@ class SeriesCog(commands.Cog):
140
126
  for series in to_delete:
141
127
  self.sonarr.del_series(series)
142
128
 
143
- self.logger.info(
144
- f"{ctx.message.author.name} has deleted the show {series.full_title}"
145
- )
129
+ self.logger.info(f"{ctx.message.author.name} has deleted the show {series.full_title}")
146
130
 
147
131
  push.send(
148
132
  f"{ctx.message.author.name} has deleted the show {series.full_title}",
@@ -13,9 +13,7 @@ async def member_has_role(member: discord.Member | discord.User, role: str) -> b
13
13
  return False
14
14
 
15
15
 
16
- async def reply(
17
- msg: discord.Message, content: str, title: str = "", error: bool = False
18
- ) -> None:
16
+ async def reply(msg: discord.Message, content: str, title: str = "", error: bool = False) -> None:
19
17
  if len(content) > 2048:
20
18
  while len(content) > 2048 - len("\n..."):
21
19
  content = content[: content.rfind("\n")].rstrip()
wi1_bot/push.py CHANGED
@@ -3,9 +3,7 @@ import requests
3
3
  from wi1_bot.config import config
4
4
 
5
5
 
6
- def send(
7
- msg: str, title: str | None = None, url: str | None = None, priority: int = 0
8
- ) -> None:
6
+ def send(msg: str, title: str | None = None, url: str | None = None, priority: int = 0) -> None:
9
7
  if "pushover" not in config:
10
8
  return
11
9
 
wi1_bot/scripts/rescan.py CHANGED
@@ -38,9 +38,7 @@ def rescan_sonarr() -> None:
38
38
  def main() -> None:
39
39
  parser = argparse.ArgumentParser(description="Rescan all movies/shows")
40
40
 
41
- parser.add_argument(
42
- "service", nargs="?", choices=["radarr", "sonarr"], help="radarr or sonarr"
43
- )
41
+ parser.add_argument("service", nargs="?", choices=["radarr", "sonarr"], help="radarr or sonarr")
44
42
 
45
43
  args = parser.parse_args()
46
44
 
wi1_bot/scripts/start.py CHANGED
@@ -25,8 +25,7 @@ def main() -> None:
25
25
  "detailed": {
26
26
  "class": "logging.Formatter",
27
27
  "format": (
28
- "[%(asctime)s] %(levelname)s [%(name)s.%(funcName)s:%(lineno)d]"
29
- " %(message)s"
28
+ "[%(asctime)s] %(levelname)s [%(name)s.%(funcName)s:%(lineno)d] %(message)s"
30
29
  ),
31
30
  },
32
31
  },
@@ -9,7 +9,7 @@ from time import sleep
9
9
  from typing import Any
10
10
 
11
11
  from wi1_bot import push
12
- from wi1_bot.arr import Radarr, Sonarr
12
+ from wi1_bot.arr import Radarr, Sonarr, replace_remote_paths
13
13
  from wi1_bot.config import config
14
14
 
15
15
  from .transcode_queue import TranscodeItem, queue
@@ -51,9 +51,7 @@ class Transcoder:
51
51
  if remove:
52
52
  queue.remove(item)
53
53
  except Exception:
54
- self.logger.warning(
55
- "got exception when trying to transcode", exc_info=True
56
- )
54
+ self.logger.warning("got exception when trying to transcode", exc_info=True)
57
55
 
58
56
  sleep(3)
59
57
 
@@ -108,35 +106,25 @@ class Transcoder:
108
106
  try:
109
107
  transcode_to.unlink(missing_ok=True)
110
108
  except Exception:
111
- self.logger.debug(
112
- f"failed to delete transcoded file: {transcode_to}"
113
- )
109
+ self.logger.debug(f"failed to delete transcoded file: {transcode_to}")
114
110
 
115
111
  if (
116
112
  "Error opening input files" in last_output
117
113
  or "No such file or directory" in last_output
118
114
  ):
119
- self.logger.info(
120
- f"file does not exist: {path}, skipping transcoding"
121
- )
115
+ self.logger.info(f"file does not exist: {path}, skipping transcoding")
122
116
  return True
123
117
 
124
118
  if "File name too long" in last_output:
125
- self.logger.info(
126
- f"file name is too long: {path}, skipping transcoding"
127
- )
119
+ self.logger.info(f"file name is too long: {path}, skipping transcoding")
128
120
  return True
129
121
 
130
122
  if "received signal 15" in last_output:
131
- self.logger.info(
132
- f"transcoding interrupted by signal: {path}, will retry"
133
- )
123
+ self.logger.info(f"transcoding interrupted by signal: {path}, will retry")
134
124
  return False
135
125
 
136
126
  if "cannot open shared object file" in last_output:
137
- self.logger.error(
138
- "ffmpeg error: missing shared object file, will retry"
139
- )
127
+ self.logger.error("ffmpeg error: missing shared object file, will retry")
140
128
  push.send(f"ffmpeg error: {last_output}", title="ffmpeg error")
141
129
  return False
142
130
 
@@ -161,9 +149,7 @@ class Transcoder:
161
149
  return True
162
150
 
163
151
  if not path.exists():
164
- self.logger.debug(
165
- f"file doesn't exist: {item.path}, deleting transcoded file"
166
- )
152
+ self.logger.debug(f"file doesn't exist: {item.path}, deleting transcoded file")
167
153
 
168
154
  transcode_to.unlink(missing_ok=True)
169
155
  return True
@@ -172,24 +158,26 @@ class Transcoder:
172
158
  shutil.move(transcode_to, new_path)
173
159
  path.unlink()
174
160
 
175
- self._rescan_content(item, str(new_path))
161
+ self._rescan_content(item, new_path)
176
162
 
177
163
  self.logger.info(f"transcoded: {path.name} -> {new_path.name}")
178
164
  # push.send(f"{path.name} -> {new_path.name}", title="file transcoded")
179
165
 
180
166
  return True
181
167
 
182
- def _rescan_content(self, item: TranscodeItem, new_path: str) -> None:
168
+ def _rescan_content(self, item: TranscodeItem, new_path: pathlib.Path) -> None:
183
169
  if item.content_id is not None:
184
- if new_path.startswith(config["radarr"]["root_folder"]):
170
+ if new_path.is_relative_to(
171
+ replace_remote_paths(pathlib.Path(config["radarr"]["root_folder"]))
172
+ ):
173
+ # have to rescan the movie twice: Radarr/Radarr#7668
174
+ # TODO: create function that waits for comand to finish
185
175
  self.radarr.rescan_movie(item.content_id)
186
- # radarr bug that it doesn't see the deleted file and the new file
187
- # in one rescan?
188
- # have to sleep in between to ensure initial command finishes
189
- # or use pyarr.get_command() to see command status
190
176
  sleep(5)
191
177
  self.radarr.rescan_movie(item.content_id)
192
- elif new_path.startswith(config["sonarr"]["root_folder"]):
178
+ elif new_path.is_relative_to(
179
+ replace_remote_paths(pathlib.Path(config["sonarr"]["root_folder"]))
180
+ ):
193
181
  self.sonarr.rescan_series(item.content_id)
194
182
 
195
183
  def _get_duration(self, path: str) -> timedelta:
@@ -212,20 +200,16 @@ class Transcoder:
212
200
 
213
201
  return duration
214
202
 
215
- def _build_ffmpeg_command(
216
- self, item: TranscodeItem, transcode_to: pathlib.Path
217
- ) -> list[str]:
203
+ def _build_ffmpeg_command(self, item: TranscodeItem, transcode_to: pathlib.Path) -> list[str]:
218
204
  command: list[Any] = [
219
205
  "ffmpeg",
220
206
  "-hide_banner",
221
207
  "-y",
222
208
  ]
223
209
 
224
- try:
210
+ if "transcoding" in config and "hwaccel" in config["transcoding"]:
225
211
  command.extend(["-hwaccel", config["transcoding"]["hwaccel"]])
226
212
  command.extend(["-hwaccel_output_format", config["transcoding"]["hwaccel"]])
227
- except KeyError:
228
- pass
229
213
 
230
214
  command.extend(["-probesize", "100M"])
231
215
  command.extend(["-analyzeduration", "250M"])
wi1_bot/webhook.py CHANGED
@@ -7,8 +7,8 @@ from typing import Any
7
7
  from flask import Flask, request
8
8
 
9
9
  from wi1_bot import push, transcoder
10
- from wi1_bot.arr import Radarr, Sonarr
11
- from wi1_bot.config import RemotePathMapping, config
10
+ from wi1_bot.arr import Radarr, Sonarr, replace_remote_paths
11
+ from wi1_bot.config import config
12
12
 
13
13
  app = Flask(__name__)
14
14
 
@@ -21,40 +21,7 @@ sonarr = Sonarr(config["sonarr"]["url"], config["sonarr"]["api_key"])
21
21
 
22
22
 
23
23
  def on_grab(req: dict[str, Any]) -> None:
24
- push.send(
25
- req["release"]["releaseTitle"], title=f"file grabbed ({req['downloadClient']})"
26
- )
27
-
28
-
29
- def replace_remote_paths(path: pathlib.Path) -> pathlib.Path:
30
- if "general" not in config or "remote_path_mappings" not in config["general"]:
31
- return path
32
-
33
- mappings = config["general"]["remote_path_mappings"]
34
-
35
- most_specific: RemotePathMapping | None = None
36
-
37
- for mapping in mappings:
38
- if path.is_relative_to(mapping["remote"]):
39
- mapping_len = len(pathlib.Path(mapping["remote"]).parts)
40
- most_specific_len = (
41
- len(pathlib.Path(most_specific["remote"]).parts)
42
- if most_specific is not None
43
- else 0
44
- )
45
-
46
- if mapping_len > most_specific_len:
47
- most_specific = mapping
48
-
49
- if most_specific is not None:
50
- remote_path = path
51
- path = pathlib.Path(most_specific["local"]) / path.relative_to(
52
- most_specific["remote"]
53
- )
54
-
55
- logger.debug(f"replaced remote path mapping: {remote_path} -> {path}")
56
-
57
- return path
24
+ push.send(req["release"]["releaseTitle"], title=f"file grabbed ({req['downloadClient']})")
58
25
 
59
26
 
60
27
  def on_download(req: dict[str, Any]) -> None:
@@ -66,9 +33,7 @@ def on_download(req: dict[str, Any]) -> None:
66
33
  movie_json = radarr._radarr.get_movie(content_id)
67
34
  assert isinstance(movie_json, dict)
68
35
 
69
- quality_profile = radarr.get_quality_profile_name(
70
- movie_json["qualityProfileId"]
71
- )
36
+ quality_profile = radarr.get_quality_profile_name(movie_json["qualityProfileId"])
72
37
 
73
38
  movie_folder = req["movie"]["folderPath"]
74
39
  relative_path = req["movieFile"]["relativePath"]
@@ -82,9 +47,7 @@ def on_download(req: dict[str, Any]) -> None:
82
47
  series_json = sonarr._sonarr.get_series(content_id)
83
48
  assert isinstance(series_json, dict)
84
49
 
85
- quality_profile = sonarr.get_quality_profile_name(
86
- series_json["qualityProfileId"]
87
- )
50
+ quality_profile = sonarr.get_quality_profile_name(series_json["qualityProfileId"])
88
51
 
89
52
  series_folder = req["series"]["path"]
90
53
  relative_path = req["episodeFile"]["relativePath"]
@@ -96,12 +59,12 @@ def on_download(req: dict[str, Any]) -> None:
96
59
  else:
97
60
  raise ValueError("unknown download request")
98
61
 
62
+ if "transcoding" not in config:
63
+ return
64
+
99
65
  path = replace_remote_paths(path)
100
66
 
101
- try:
102
- quality_options = config["transcoding"]["profiles"][quality_profile]
103
- except KeyError:
104
- return
67
+ quality_options = config["transcoding"]["profiles"][quality_profile]
105
68
 
106
69
  # python 3.11: TranscodingProfile and typing.LiteralString
107
70
  def get_key(d: Any, k: str) -> Any:
@@ -142,9 +105,7 @@ def index() -> Any:
142
105
  if request.json["eventType"] == "Download":
143
106
  on_download(request.json)
144
107
  except Exception:
145
- logger.warning(
146
- f"error handling request: {request.data.decode()}", exc_info=True
147
- )
108
+ logger.warning(f"error handling request: {request.data.decode()}", exc_info=True)
148
109
 
149
110
  return "", 200
150
111
 
@@ -1,8 +1,7 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: wi1-bot
3
- Version: 1.4.10
3
+ Version: 1.4.12
4
4
  Summary: Discord bot for Radarr/Sonarr integration
5
- Home-page: https://github.com/wthueb/wi1-bot
6
5
  Author-email: William Huebner <wilhueb@gmail.com>
7
6
  License: MIT License
8
7
 
@@ -27,25 +26,24 @@ License: MIT License
27
26
  SOFTWARE.
28
27
 
29
28
  Project-URL: Homepage, https://github.com/wthueb/wi1-bot
30
- Platform: UNKNOWN
31
29
  Classifier: Programming Language :: Python :: 3
32
30
  Classifier: Programming Language :: Python :: 3 :: Only
33
31
  Classifier: Programming Language :: Python :: 3.10
34
32
  Requires-Python: >=3.10
35
33
  Description-Content-Type: text/markdown
36
34
  License-File: LICENSE
37
- Requires-Dist: discord.py ==2.3.2
38
- Requires-Dist: Flask ==3.0.2
39
- Requires-Dist: mongoengine ==0.28.1
40
- Requires-Dist: pyarr ==5.2.0
41
- Requires-Dist: PyYAML ==6.0.1
42
- Requires-Dist: requests ==2.31.0
35
+ Requires-Dist: discord.py==2.3.2
36
+ Requires-Dist: Flask==3.0.2
37
+ Requires-Dist: mongoengine==0.29.1
38
+ Requires-Dist: pyarr==5.2.0
39
+ Requires-Dist: PyYAML==6.0.1
40
+ Requires-Dist: requests==2.31.0
43
41
  Provides-Extra: dev
44
- Requires-Dist: mongo-types ==0.15.1 ; extra == 'dev'
45
- Requires-Dist: mypy ==1.3.0 ; extra == 'dev'
46
- Requires-Dist: pre-commit ==3.6.2 ; extra == 'dev'
47
- Requires-Dist: ruff ==0.3.0 ; extra == 'dev'
48
- Requires-Dist: types-PyYAML ==6.0.12.12 ; extra == 'dev'
42
+ Requires-Dist: mongo-types==0.15.1; extra == "dev"
43
+ Requires-Dist: mypy==1.3.0; extra == "dev"
44
+ Requires-Dist: pre-commit==3.6.2; extra == "dev"
45
+ Requires-Dist: ruff==0.3.0; extra == "dev"
46
+ Requires-Dist: types-PyYAML==6.0.12.12; extra == "dev"
49
47
 
50
48
  # wi1-bot
51
49
 
@@ -103,5 +101,3 @@ Requires Python >=3.10.
103
101
  - Tautulli API (get_history) to show who has already seen the movie
104
102
  - User leaderboard
105
103
  - movies/shows added, Tautulli watch counts
106
-
107
-
@@ -0,0 +1,32 @@
1
+ wi1_bot/__init__.py,sha256=11ozJKiUsqDCZ3_mcAHhGYUyGK_Unl54djVSBBExFB4,59
2
+ wi1_bot/_version.py,sha256=Zv4TpC5V6FOmlNNowuZivqOrr2E0lJ1Gpdzj7o5gDYw,413
3
+ wi1_bot/config.py,sha256=AtzOXvCeat_lb-w_Wy37XyWIpV3LmKh38OGigluJ2nM,3139
4
+ wi1_bot/push.py,sha256=6EwQ1eXcJnQimAi0dOm1_t7Am056zob-1-EOkytGZWg,783
5
+ wi1_bot/webhook.py,sha256=11rzmxhXnDeXWlgEnOLh26WP79rLeG71HaDvKUXuUcA,3665
6
+ wi1_bot/arr/__init__.py,sha256=-6UE81yY6OhD6-nbLindChE_HlwsNRuL8JijUmo_Q6k,149
7
+ wi1_bot/arr/download.py,sha256=02AYFglnFdWSG8xj_TaJc6l2wjybyhUW6F97CnoyUFw,1381
8
+ wi1_bot/arr/episode.py,sha256=0K_auUYwkBBd_Ludc4-4z7lSW8HRQ3mchFQ7FR10JSU,1041
9
+ wi1_bot/arr/movie.py,sha256=9cpJZcoW3r4op6X8Fkr4Cv0BsiWfqKD8MkBfEpTcT8k,741
10
+ wi1_bot/arr/radarr.py,sha256=jxgNuCZY7aGMWK3nTLt8KLntiYcLaAF7g9dJoThsCvc,5721
11
+ wi1_bot/arr/sonarr.py,sha256=OouuorRHBMFAOy5oDeJZ5H6WZ-95-N01EHtpOTVMTCo,5970
12
+ wi1_bot/arr/util.py,sha256=y4_Dotm-pSJJT8xvPWy7ZoGMGVI-YOU4zK2LNTSoTB0,1025
13
+ wi1_bot/discord/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ wi1_bot/discord/bot.py,sha256=KcluVIF1erA3jv5fqE9sLxZaNi_4Zrg9Mgx7OACNypM,5034
15
+ wi1_bot/discord/helpers.py,sha256=7Qr5Kr8H2CQoLiGG7Oqh388jqUCMJH0EBvF8fC-IbJo,2300
16
+ wi1_bot/discord/cogs/__init__.py,sha256=9nA47jEyuGG7s1UJYz5SJASJcs0Xa3M1rNz9BiyCico,95
17
+ wi1_bot/discord/cogs/movie.py,sha256=Gr8xbsdNDlKkOb7boiTE5n3YHz7FR0ajSRANV8EM3XY,3950
18
+ wi1_bot/discord/cogs/series.py,sha256=bPA3LtuK78qkYvyN38ZdNQ_6I3j4qVN9L5JHV8KX_GA,4597
19
+ wi1_bot/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
+ wi1_bot/scripts/add_tag.py,sha256=mWwo8egk2Y5XRiQCpfkA11-3rcxZoD0JOJKxV0LguLk,586
21
+ wi1_bot/scripts/rescan.py,sha256=97oGK1Gr3jBjfX-Eil4C0hDIwtpZ34E_rwTYCI8Z7r4,1474
22
+ wi1_bot/scripts/start.py,sha256=TyT29yZcWyQ4YqZeISYE8JB6fXv_y7siGdBX353GqqA,2454
23
+ wi1_bot/scripts/transcode_item.py,sha256=21MeeIZ9wIRhAY-FOEgOdPsg6YOLlSUj59r8NkTfixw,1155
24
+ wi1_bot/transcoder/__init__.py,sha256=B4xr82UtIFc3tyy_MEZdZKMukYW0yejPnfsGowaTIM0,105
25
+ wi1_bot/transcoder/transcode_queue.py,sha256=W7r2I7OD8QuxseCEb7_ddOvYXiBBLmKLvCs2IgJJ92I,1856
26
+ wi1_bot/transcoder/transcoder.py,sha256=2wIgDPkrrXcrSjA1HpYGd0RX5wonw1zfltJqGzCaorM,9163
27
+ wi1_bot-1.4.12.dist-info/LICENSE,sha256=6V4_mQoPoLJl77_WMsQRQMDG2mnIhDUdfCmMkqM9Qwc,1072
28
+ wi1_bot-1.4.12.dist-info/METADATA,sha256=twF68ewC8o9PcBMCUInNwSGz3SBnJwU9bHXWMBDHcWU,4750
29
+ wi1_bot-1.4.12.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
30
+ wi1_bot-1.4.12.dist-info/entry_points.txt,sha256=pF5EawAQsUNMHs5exynpNdRq9g4dODg-RaPkx2MyYLU,184
31
+ wi1_bot-1.4.12.dist-info/top_level.txt,sha256=Q7mTnPLk80Td82YbjlBMO5tvXJoTFHhheHdmwc-c-7I,8
32
+ wi1_bot-1.4.12.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.43.0)
2
+ Generator: setuptools (75.6.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,31 +0,0 @@
1
- wi1_bot/__init__.py,sha256=11ozJKiUsqDCZ3_mcAHhGYUyGK_Unl54djVSBBExFB4,59
2
- wi1_bot/_version.py,sha256=PSfQzSIY-TCmDuz8UpHfMXvNhO-iyRNDmP7WVYUSfx8,413
3
- wi1_bot/config.py,sha256=AtzOXvCeat_lb-w_Wy37XyWIpV3LmKh38OGigluJ2nM,3139
4
- wi1_bot/push.py,sha256=-Az8c21R3IZAkJVRQmWsbNXMfvBzzpc9pFTWsDy1mJE,789
5
- wi1_bot/webhook.py,sha256=cVp6USg1vbfL05TPQSDIUUYPHWDjuPGb1IGsqImao6s,4713
6
- wi1_bot/arr/__init__.py,sha256=ZIgkW24GBdS4sJmaiEIhueHOB6s2L8y2s9ahgp1USRI,86
7
- wi1_bot/arr/download.py,sha256=02AYFglnFdWSG8xj_TaJc6l2wjybyhUW6F97CnoyUFw,1381
8
- wi1_bot/arr/episode.py,sha256=j25ljy9hyTXFAEBeUQ4iozKP2YXpZyzauphaXa2oqh8,1057
9
- wi1_bot/arr/movie.py,sha256=9cpJZcoW3r4op6X8Fkr4Cv0BsiWfqKD8MkBfEpTcT8k,741
10
- wi1_bot/arr/radarr.py,sha256=sZ2zVhSDir3T3THW2vvIgWPdsvoEsYZLualeY8UCIr4,5711
11
- wi1_bot/arr/sonarr.py,sha256=OouuorRHBMFAOy5oDeJZ5H6WZ-95-N01EHtpOTVMTCo,5970
12
- wi1_bot/discord/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
- wi1_bot/discord/bot.py,sha256=puBlprcGqoC-m98DDZuM7Q7zhfTHSsqJIw2ptNrE4_M,5080
14
- wi1_bot/discord/helpers.py,sha256=X8ym8CTmuN0TKXxWEmvXMOKVOLK7Fe-Fj6iiaDpWhT8,2306
15
- wi1_bot/discord/cogs/__init__.py,sha256=9nA47jEyuGG7s1UJYz5SJASJcs0Xa3M1rNz9BiyCico,95
16
- wi1_bot/discord/cogs/movie.py,sha256=J-1wahJLdIk9WhgQlva4zp9AK__2Vw1M8JoPvhcwzbw,4226
17
- wi1_bot/discord/cogs/series.py,sha256=AfUfWuU-vUlID-gW7GWu9w-GiwWzZ4Cxm49_FjgvJik,4837
18
- wi1_bot/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
- wi1_bot/scripts/add_tag.py,sha256=mWwo8egk2Y5XRiQCpfkA11-3rcxZoD0JOJKxV0LguLk,586
20
- wi1_bot/scripts/rescan.py,sha256=2V7a3GSP8owLxdJ0J6nG98M-PqPWfRRhfJuDOgWQgnU,1488
21
- wi1_bot/scripts/start.py,sha256=vNa_iHkx10D5YWonyRW0f5nG8uE3_JtwJ-XZ-c0hWCs,2477
22
- wi1_bot/scripts/transcode_item.py,sha256=21MeeIZ9wIRhAY-FOEgOdPsg6YOLlSUj59r8NkTfixw,1155
23
- wi1_bot/transcoder/__init__.py,sha256=B4xr82UtIFc3tyy_MEZdZKMukYW0yejPnfsGowaTIM0,105
24
- wi1_bot/transcoder/transcode_queue.py,sha256=W7r2I7OD8QuxseCEb7_ddOvYXiBBLmKLvCs2IgJJ92I,1856
25
- wi1_bot/transcoder/transcoder.py,sha256=h1If_1FZz7aVvhpZ7G5tLwR88BojrO1EUuDy3lDdVb0,9409
26
- wi1_bot-1.4.10.dist-info/LICENSE,sha256=6V4_mQoPoLJl77_WMsQRQMDG2mnIhDUdfCmMkqM9Qwc,1072
27
- wi1_bot-1.4.10.dist-info/METADATA,sha256=wVcpUomwJ605vkSs5gUKWddBkIWwI7RbfKG2JNWZd5k,4831
28
- wi1_bot-1.4.10.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
29
- wi1_bot-1.4.10.dist-info/entry_points.txt,sha256=pF5EawAQsUNMHs5exynpNdRq9g4dODg-RaPkx2MyYLU,184
30
- wi1_bot-1.4.10.dist-info/top_level.txt,sha256=Q7mTnPLk80Td82YbjlBMO5tvXJoTFHhheHdmwc-c-7I,8
31
- wi1_bot-1.4.10.dist-info/RECORD,,