episoder 0.11.0__tar.gz → 0.11.1__tar.gz

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.
Files changed (78) hide show
  1. {episoder-0.11.0 → episoder-0.11.1}/.gitlab-ci.yml +17 -0
  2. {episoder-0.11.0 → episoder-0.11.1}/CHANGELOG +3 -0
  3. {episoder-0.11.0 → episoder-0.11.1}/PKG-INFO +2 -2
  4. {episoder-0.11.0 → episoder-0.11.1}/episoder/database.py +1 -1
  5. {episoder-0.11.0 → episoder-0.11.1}/episoder/main.py +9 -29
  6. episoder-0.11.1/episoder/output.py +169 -0
  7. {episoder-0.11.0 → episoder-0.11.1}/episoder/version.py +1 -1
  8. {episoder-0.11.0 → episoder-0.11.1}/episoder.egg-info/PKG-INFO +2 -2
  9. {episoder-0.11.0 → episoder-0.11.1}/test/test_notify.py +6 -45
  10. {episoder-0.11.0 → episoder-0.11.1}/test/test_output.py +52 -79
  11. episoder-0.11.0/episoder/output.py +0 -186
  12. {episoder-0.11.0 → episoder-0.11.1}/.gitignore +0 -0
  13. {episoder-0.11.0 → episoder-0.11.1}/COPYING +0 -0
  14. {episoder-0.11.0 → episoder-0.11.1}/README.md +0 -0
  15. {episoder-0.11.0 → episoder-0.11.1}/episoder/__init__.py +0 -0
  16. {episoder-0.11.0 → episoder-0.11.1}/episoder/episoder.py +0 -0
  17. {episoder-0.11.0 → episoder-0.11.1}/episoder/sources/__init__.py +0 -0
  18. {episoder-0.11.0 → episoder-0.11.1}/episoder/sources/epguides.py +0 -0
  19. {episoder-0.11.0 → episoder-0.11.1}/episoder/sources/parser.py +0 -0
  20. {episoder-0.11.0 → episoder-0.11.1}/episoder/sources/thetvdb.py +0 -0
  21. {episoder-0.11.0 → episoder-0.11.1}/episoder/sources/tvcom.py +0 -0
  22. {episoder-0.11.0 → episoder-0.11.1}/episoder.1 +0 -0
  23. {episoder-0.11.0 → episoder-0.11.1}/episoder.egg-info/SOURCES.txt +0 -0
  24. {episoder-0.11.0 → episoder-0.11.1}/episoder.egg-info/dependency_links.txt +0 -0
  25. {episoder-0.11.0 → episoder-0.11.1}/episoder.egg-info/entry_points.txt +0 -0
  26. {episoder-0.11.0 → episoder-0.11.1}/episoder.egg-info/requires.txt +0 -0
  27. {episoder-0.11.0 → episoder-0.11.1}/episoder.egg-info/top_level.txt +0 -0
  28. {episoder-0.11.0 → episoder-0.11.1}/examples/home.episoder +0 -0
  29. {episoder-0.11.0 → episoder-0.11.1}/pyproject.toml +0 -0
  30. {episoder-0.11.0 → episoder-0.11.1}/setup.cfg +0 -0
  31. {episoder-0.11.0 → episoder-0.11.1}/setup.py +0 -0
  32. {episoder-0.11.0 → episoder-0.11.1}/test/__init__.py +0 -0
  33. {episoder-0.11.0 → episoder-0.11.1}/test/fixtures/epguides_48_hours_mistery.html +0 -0
  34. {episoder-0.11.0 → episoder-0.11.1}/test/fixtures/epguides_american_idol.html +0 -0
  35. {episoder-0.11.0 → episoder-0.11.1}/test/fixtures/epguides_bsg.html +0 -0
  36. {episoder-0.11.0 → episoder-0.11.1}/test/fixtures/epguides_buzzcocks.html +0 -0
  37. {episoder-0.11.0 → episoder-0.11.1}/test/fixtures/epguides_doctor_who.html +0 -0
  38. {episoder-0.11.0 → episoder-0.11.1}/test/fixtures/epguides_eureka.html +0 -0
  39. {episoder-0.11.0 → episoder-0.11.1}/test/fixtures/epguides_futurama.html +0 -0
  40. {episoder-0.11.0 → episoder-0.11.1}/test/fixtures/epguides_house.html +0 -0
  41. {episoder-0.11.0 → episoder-0.11.1}/test/fixtures/epguides_kr2008.html +0 -0
  42. {episoder-0.11.0 → episoder-0.11.1}/test/fixtures/epguides_lost.html +0 -0
  43. {episoder-0.11.0 → episoder-0.11.1}/test/fixtures/epguides_lost_html.html +0 -0
  44. {episoder-0.11.0 → episoder-0.11.1}/test/fixtures/epguides_lower_decks.html +0 -0
  45. {episoder-0.11.0 → episoder-0.11.1}/test/fixtures/epguides_midsomer_murders.html +0 -0
  46. {episoder-0.11.0 → episoder-0.11.1}/test/fixtures/epguides_picard.html +0 -0
  47. {episoder-0.11.0 → episoder-0.11.1}/test/fixtures/epguides_test_iso_8859_1.html +0 -0
  48. {episoder-0.11.0 → episoder-0.11.1}/test/fixtures/tvdb_error_404.json +0 -0
  49. {episoder-0.11.0 → episoder-0.11.1}/test/fixtures/tvdb_search_frasier.json +0 -0
  50. {episoder-0.11.0 → episoder-0.11.1}/test/fixtures/tvdb_search_friends.json +0 -0
  51. {episoder-0.11.0 → episoder-0.11.1}/test/fixtures/tvdb_test_show_260.json +0 -0
  52. {episoder-0.11.0 → episoder-0.11.1}/test/fixtures/tvdb_test_show_260_1_eps.json +0 -0
  53. {episoder-0.11.0 → episoder-0.11.1}/test/fixtures/tvdb_test_show_261.json +0 -0
  54. {episoder-0.11.0 → episoder-0.11.1}/test/fixtures/tvdb_test_show_261_1_eps.json +0 -0
  55. {episoder-0.11.0 → episoder-0.11.1}/test/fixtures/tvdb_test_show_261_2_eps.json +0 -0
  56. {episoder-0.11.0 → episoder-0.11.1}/test/fixtures/tvdb_test_show_261_3_eps.json +0 -0
  57. {episoder-0.11.0 → episoder-0.11.1}/test/fixtures/tvdb_test_show_262.json +0 -0
  58. {episoder-0.11.0 → episoder-0.11.1}/test/fixtures/tvdb_test_show_262_1_eps.json +0 -0
  59. {episoder-0.11.0 → episoder-0.11.1}/test/fixtures/tvdb_test_show_263.json +0 -0
  60. {episoder-0.11.0 → episoder-0.11.1}/test/fixtures/tvdb_test_show_268156.json +0 -0
  61. {episoder-0.11.0 → episoder-0.11.1}/test/fixtures/tvdb_test_show_268156_1_eps.json +0 -0
  62. {episoder-0.11.0 → episoder-0.11.1}/test/fixtures/tvdb_test_show_295640.json +0 -0
  63. {episoder-0.11.0 → episoder-0.11.1}/test/fixtures/tvdb_test_show_295640_1_eps.json +0 -0
  64. {episoder-0.11.0 → episoder-0.11.1}/test/fixtures/tvdb_test_show_295640_2_eps.json +0 -0
  65. {episoder-0.11.0 → episoder-0.11.1}/test/fixtures/tvdb_test_show_73739.json +0 -0
  66. {episoder-0.11.0 → episoder-0.11.1}/test/fixtures/tvdb_test_show_73739_1_eps.json +0 -0
  67. {episoder-0.11.0 → episoder-0.11.1}/test/fixtures/tvdb_test_show_75397.json +0 -0
  68. {episoder-0.11.0 → episoder-0.11.1}/test/fixtures/tvdb_test_show_75397_1_eps.json +0 -0
  69. {episoder-0.11.0 → episoder-0.11.1}/test/test_database.py +0 -0
  70. {episoder-0.11.0 → episoder-0.11.1}/test/test_epguides_parser.py +0 -0
  71. {episoder-0.11.0 → episoder-0.11.1}/test/test_episode.py +0 -0
  72. {episoder-0.11.0 → episoder-0.11.1}/test/test_metainfo.py +0 -0
  73. {episoder-0.11.0 → episoder-0.11.1}/test/test_migration.py +0 -0
  74. {episoder-0.11.0 → episoder-0.11.1}/test/test_show.py +0 -0
  75. {episoder-0.11.0 → episoder-0.11.1}/test/test_sources.py +0 -0
  76. {episoder-0.11.0 → episoder-0.11.1}/test/test_tvcom.py +0 -0
  77. {episoder-0.11.0 → episoder-0.11.1}/test/test_tvdb.py +0 -0
  78. {episoder-0.11.0 → episoder-0.11.1}/test/util.py +0 -0
@@ -8,6 +8,9 @@ stages:
8
8
  .bookworm_image: &bookworm
9
9
  image: registry.gitlab.com/schabe/episoder:debian-bookworm
10
10
 
11
+ .trixie_image: &trixie
12
+ image: registry.gitlab.com/schabe/episoder:debian-trixie
13
+
11
14
  .noble_image: &noble
12
15
  image: registry.gitlab.com/schabe/episoder:ubuntu-noble
13
16
 
@@ -105,3 +108,17 @@ py312-noble-pip:deploy:
105
108
  needs: ["py312-noble:test"]
106
109
  <<: *noble
107
110
  <<: *deploy_pip_venv
111
+
112
+ py313-trixie:test:
113
+ <<: *trixie
114
+ <<: *test
115
+
116
+ py313-trixie-setuptools:deploy:
117
+ needs: ["py313-trixie:test"]
118
+ <<: *trixie
119
+ <<: *deploy_setuptools
120
+
121
+ py313-trixie-pip:deploy:
122
+ needs: ["py313-trixie:test"]
123
+ <<: *trixie
124
+ <<: *deploy_pip_venv
@@ -1,3 +1,6 @@
1
+ == 0.11.1 (8 Apr 2025) ==
2
+ * Use Python's EmailMessage class when sending e-mail
3
+
1
4
  == 0.11.0 (20 Jul 2024) ==
2
5
  * Port to sqlalchemy 2.0
3
6
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: episoder
3
- Version: 0.11.0
3
+ Version: 0.11.1
4
4
  Summary: TV episode notifier
5
5
  Home-page: https://code.ott.net/episoder/
6
6
  Author: Stefan Ott
@@ -83,7 +83,7 @@ class Episode(Base):
83
83
  notified = Column(Date)
84
84
  show = relationship(Show, backref=backref("episodes", cascade="all"))
85
85
 
86
- # pylint: disable=too-many-arguments
86
+ # pylint: disable=too-many-arguments,too-many-positional-arguments
87
87
  def __init__(self, name, season, episode, aired, prodnum, total):
88
88
 
89
89
  self.title = name
@@ -18,6 +18,7 @@
18
18
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
19
19
 
20
20
  import logging
21
+ import sys
21
22
 
22
23
  from argparse import ArgumentParser
23
24
  from datetime import date, timedelta
@@ -30,7 +31,7 @@ from episoder.database import Show
30
31
  from episoder.episoder import Database
31
32
  from episoder.sources import parser_for, setup_sources
32
33
  from episoder.sources.thetvdb import TVDBShowNotFoundError
33
- from episoder.output import ConsoleRenderer, EmailNotifier
34
+ from episoder.output import ColorfulRenderer, ColorlessRenderer, EmailNotifier
34
35
  from episoder.output import NewEpisodesNotification
35
36
  from episoder.version import __version__
36
37
 
@@ -38,7 +39,6 @@ from episoder.version import __version__
38
39
  class EpisoderOptions:
39
40
 
40
41
  def __init__(self):
41
-
42
42
  self._parser = ArgumentParser()
43
43
  self._add_options()
44
44
  self._args = None
@@ -46,7 +46,6 @@ class EpisoderOptions:
46
46
  self.loglevel = logging.WARNING
47
47
 
48
48
  def _add_options(self):
49
-
50
49
  yesterday = date.today() - timedelta(1)
51
50
 
52
51
  self._parser.set_defaults(func=None)
@@ -162,7 +161,6 @@ class EpisoderOptions:
162
161
  help="show version information")
163
162
 
164
163
  def _replace_parsed_date(self, args):
165
-
166
164
  if args.date.isdigit():
167
165
  daysback = int(args.date)
168
166
  args.date = date.today() - timedelta(daysback)
@@ -173,7 +171,6 @@ class EpisoderOptions:
173
171
  self._parser.error(f"{args.date}: Wrong date format")
174
172
 
175
173
  def _setup_logging(self):
176
-
177
174
  if self._args.debug:
178
175
  self.loglevel = logging.DEBUG
179
176
  elif self._args.verbose:
@@ -188,14 +185,13 @@ class EpisoderOptions:
188
185
  logging.basicConfig(level=level)
189
186
 
190
187
  def parse_args(self):
191
-
192
188
  self._args = self._parser.parse_args()
193
189
 
194
190
  # this happens automatically in python 2 but not in 3.4
195
191
  if self._args.func is None:
196
192
  self._parser.print_usage()
197
193
  stderr.write("error: too few arguments\n")
198
- exit(1)
194
+ sys.exit(1)
199
195
 
200
196
  # turn our date string into a proper date object
201
197
  if hasattr(self._args, "date"):
@@ -206,11 +202,9 @@ class EpisoderOptions:
206
202
  def load_rcfile(self, file_):
207
203
 
208
204
  def strip_comments(line):
209
-
210
205
  return line.split("#")[0]
211
206
 
212
207
  def valid(line):
213
-
214
208
  return "=" in line
215
209
 
216
210
  self._log.info("Loading config file")
@@ -221,7 +215,6 @@ class EpisoderOptions:
221
215
  return dict(line.split("=") for line in lines)
222
216
 
223
217
  def load_config(self):
224
-
225
218
  if path.exists(self.args.c):
226
219
  with open(self.args.c, "rb") as file_:
227
220
  cfg = self.load_rcfile(file_)
@@ -251,21 +244,17 @@ class EpisoderOptions:
251
244
  self._log.info("Loaded configuration")
252
245
 
253
246
  def _get_args(self):
254
-
255
247
  return self._args
256
248
 
257
249
  args = property(_get_args)
258
250
 
259
251
 
260
252
  class Episoder:
261
-
262
253
  def __init__(self, args):
263
-
264
254
  self._args = args
265
255
  self._log = logging.getLogger("Episoder")
266
256
 
267
257
  def update(self):
268
-
269
258
  database = Database(self._args.datafile)
270
259
  database.migrate()
271
260
 
@@ -283,7 +272,6 @@ class Episoder:
283
272
  shows = database.get_expired_shows()
284
273
 
285
274
  for show in shows[0:self._args.num]:
286
-
287
275
  try:
288
276
  parser = parser_for(show.url)
289
277
  parser.parse(show, database)
@@ -300,7 +288,6 @@ class Episoder:
300
288
  self._log.info("None of your shows need to be updated")
301
289
 
302
290
  def add(self):
303
-
304
291
  url = self._args.show
305
292
 
306
293
  database = Database(self._args.datafile)
@@ -322,14 +309,12 @@ class Episoder:
322
309
  database.commit()
323
310
 
324
311
  def remove(self):
325
-
326
312
  database = Database(self._args.datafile)
327
313
  database.migrate()
328
314
  database.remove_show(self._args.show)
329
315
  database.commit()
330
316
 
331
317
  def _set_show_enabled(self, database, enabled):
332
-
333
318
  show_id = self._args.show
334
319
  show = database.get_show_by_id(show_id)
335
320
 
@@ -341,19 +326,16 @@ class Episoder:
341
326
  database.commit()
342
327
 
343
328
  def enable(self):
344
-
345
329
  database = Database(self._args.datafile)
346
330
  database.migrate()
347
331
  self._set_show_enabled(database, True)
348
332
 
349
333
  def disable(self):
350
-
351
334
  database = Database(self._args.datafile)
352
335
  database.migrate()
353
336
  self._set_show_enabled(database, False)
354
337
 
355
338
  def shows(self):
356
-
357
339
  database = Database(self._args.datafile)
358
340
  database.migrate()
359
341
 
@@ -374,7 +356,6 @@ class Episoder:
374
356
  print(f" Episodes: {len(show.episodes)}")
375
357
 
376
358
  def notify(self):
377
-
378
359
  opt = self._args
379
360
 
380
361
  if opt.email_to is None:
@@ -406,9 +387,11 @@ class Episoder:
406
387
  database.commit()
407
388
 
408
389
  def episodes(self):
409
-
410
390
  database = Database(self._args.datafile)
411
- out = ConsoleRenderer(self._args.format, self._args.dateformat)
391
+ if self._args.nocolor:
392
+ out = ColorlessRenderer(self._args.format, self._args.dateformat)
393
+ else:
394
+ out = ConsoleRenderer(self._args.format, self._args.dateformat)
412
395
 
413
396
  if self._args.nodate:
414
397
  startdate = date(1900, 1, 1)
@@ -422,10 +405,9 @@ class Episoder:
422
405
  else:
423
406
  episodes = database.get_episodes(startdate, n_days)
424
407
 
425
- out.render(episodes, self._args.date, not self._args.nocolor)
408
+ out.render(episodes, self._args.date)
426
409
 
427
410
  def search(self):
428
-
429
411
  tvdb = parser_for('123') # numeric IDs are TVDB entries
430
412
 
431
413
  try:
@@ -437,7 +419,6 @@ class Episoder:
437
419
  print("Nothing found")
438
420
 
439
421
  def check_data_file(self):
440
-
441
422
  filename = self._args.datafile
442
423
  if not path.exists(filename):
443
424
  return
@@ -448,11 +429,10 @@ class Episoder:
448
429
  if data != b"SQLite":
449
430
  message = "%s: Please remove old data file\n"
450
431
  stderr.write(message % filename)
451
- exit(4)
432
+ sys.exit(4)
452
433
 
453
434
 
454
435
  def main():
455
-
456
436
  options = EpisoderOptions()
457
437
  options.parse_args()
458
438
  options.load_config()
@@ -0,0 +1,169 @@
1
+ # episoder, https://code.ott.net/episoder
2
+ #
3
+ # Copyright (C) 2004-2025 Stefan Ott. All rights reserved.
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ from datetime import date, timedelta
19
+ from email.message import EmailMessage
20
+ from smtplib import SMTP
21
+ from sys import stdout
22
+ from typing import Optional
23
+
24
+ from .database import Episode
25
+
26
+
27
+ # pylint: disable=too-few-public-methods
28
+ class EpisodeRenderer:
29
+ def __init__(self, episode_format: str, date_format: str) -> None:
30
+ self._format = episode_format
31
+ self._date_format = date_format
32
+
33
+ def format(self, episode: Episode) -> str:
34
+ string = self._format
35
+
36
+ airdate = episode.airdate.strftime(self._date_format)
37
+ string = string.replace("%airdate", airdate)
38
+ string = string.replace("%show", str(episode.show.name))
39
+ string = string.replace("%season", str(episode.season))
40
+ string = string.replace("%epnum", f"{episode.episode:02d}")
41
+ string = string.replace("%eptitle", str(episode.title))
42
+ string = string.replace("%totalep", str(episode.totalnum))
43
+ string = string.replace("%prodnum", str(episode.prodnum))
44
+
45
+ return string
46
+
47
+
48
+ class ColorfulRenderer(EpisodeRenderer):
49
+ RED = "\033[31;1m"
50
+ CYAN = "\033[36;1m"
51
+ GREY = "\033[30;0m"
52
+ GREEN = "\033[32;1m"
53
+ YELLOW = "\033[33;1m"
54
+
55
+ def __init__(self, fmt: str, date_format: str, dest=stdout) -> None:
56
+ super().__init__(fmt, date_format)
57
+ self._dest = dest
58
+
59
+ def _color(self, episode: Episode, yesterday: date, today: date,
60
+ tomorrow: date) -> str:
61
+ if episode.airdate == yesterday:
62
+ return ColorfulRenderer.RED
63
+ if episode.airdate == today:
64
+ return ColorfulRenderer.YELLOW
65
+ if episode.airdate == tomorrow:
66
+ return ColorfulRenderer.GREEN
67
+ if episode.airdate > tomorrow:
68
+ return ColorfulRenderer.CYAN
69
+
70
+ return ColorfulRenderer.GREY
71
+
72
+ def render(self, episodes: list[Episode], today: date) -> None:
73
+ yesterday = today - timedelta(1)
74
+ tomorrow = today + timedelta(1)
75
+
76
+ for episode in episodes:
77
+ text = self.format(episode)
78
+ color = self._color(episode, yesterday, today, tomorrow)
79
+ self._dest.write(f'{color}{text}{ColorfulRenderer.GREY}\n')
80
+
81
+
82
+ class ColorlessRenderer(EpisodeRenderer):
83
+ def __init__(self, fmt: str, date_format: str, dest=stdout) -> None:
84
+ super().__init__(fmt, date_format)
85
+ self._dest = dest
86
+
87
+ def render(self, episodes: list[Episode], _: date) -> None:
88
+ for episode in episodes:
89
+ text = self.format(episode)
90
+ self._dest.write(f'{text}\n')
91
+
92
+
93
+ class EmailNotifier:
94
+ def __init__(self, server: str, port: int, smtp=SMTP) -> None:
95
+ self._server = server
96
+ self._port = port
97
+ self._smtp = smtp
98
+ self._user: Optional[str] = None
99
+ self._password: Optional[str] = None
100
+
101
+ self.use_tls = False
102
+
103
+ def __str__(self) -> str:
104
+ return "EmailNotifier"
105
+
106
+ def __repr__(self) -> str:
107
+ fmt = 'EmailNotifier("%s", %d, %s)'
108
+ return fmt % (self._server, self._port, str(self._smtp))
109
+
110
+ def login(self, user: str, password: str) -> None:
111
+ self._user = user
112
+ self._password = password
113
+
114
+ def _dump(self, msg: EmailMessage) -> None:
115
+ print(f'From: {msg["to"]}')
116
+ print(f'To: {msg["to"]}')
117
+ print(f'Subject: {msg["Subject"]}')
118
+ print(msg.get_content())
119
+
120
+ def send(self, body: str, to: str, pretend: bool = False) -> None:
121
+ message = EmailMessage()
122
+ message.set_content(body)
123
+ message['Subject'] = 'Upcoming TV episodes'
124
+ message['From'] = to
125
+ message['To'] = to
126
+
127
+ if pretend:
128
+ self._dump(message)
129
+ return
130
+
131
+ server = self._smtp(self._server, self._port)
132
+
133
+ if self.use_tls:
134
+ server.starttls()
135
+ if self._user:
136
+ server.login(self._user, self._password)
137
+
138
+ server.send_message(message)
139
+ server.quit()
140
+
141
+
142
+ class NewEpisodesNotification(EpisodeRenderer):
143
+ def __init__(self, episodes: list[Episode], fmt: str,
144
+ date_format: str) -> None:
145
+ super().__init__(fmt, date_format)
146
+ self._episodes = episodes
147
+
148
+ def __str__(self) -> str:
149
+ return f'Notification: {len(self._episodes)} new episode(s)'
150
+
151
+ def __repr__(self) -> str:
152
+ eps = f"<({len(self._episodes)} x Episode)"
153
+ fmt = 'NewEpisodesNotification(%s, "%s", "%s")'
154
+ return fmt % (eps, self._format, self._date_format)
155
+
156
+ def get(self) -> str:
157
+ return ''.join(f'{self.format(e)}x\n' for e in self._episodes)
158
+
159
+ def send(self, notifier: EmailNotifier, to: str,
160
+ pretend: bool = False) -> None:
161
+ body = "Your upcoming episodes:\n\n"
162
+
163
+ for episode in self._episodes:
164
+ body += f"{self.format(episode)}\n"
165
+
166
+ if not pretend:
167
+ episode.notified = date.today()
168
+
169
+ notifier.send(body, to, pretend)
@@ -15,4 +15,4 @@
15
15
  # You should have received a copy of the GNU General Public License
16
16
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
17
 
18
- __version__ = "0.11.0"
18
+ __version__ = "0.11.1"
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: episoder
3
- Version: 0.11.0
3
+ Version: 0.11.1
4
4
  Summary: TV episode notifier
5
5
  Home-page: https://code.ott.net/episoder/
6
6
  Author: Stefan Ott
@@ -31,11 +31,9 @@ from episoder.output import EmailNotifier, NewEpisodesNotification
31
31
 
32
32
  # pylint: disable=too-many-instance-attributes
33
33
  class MockSMTPInstance():
34
-
35
34
  instance = None
36
35
 
37
36
  def __init__(self, server, port):
38
-
39
37
  self.server = server
40
38
  self.port = port
41
39
 
@@ -49,99 +47,78 @@ class MockSMTPInstance():
49
47
  self.msg = None
50
48
 
51
49
  def reset(self):
52
-
53
50
  self.count = 0
54
51
  self.data = {}
55
52
  self.tls = 0
56
53
  self.user = None
57
54
  self.password = None
58
55
 
59
- def sendmail(self, from_addr, to_addr, msg):
60
-
61
- self.data["from"] = from_addr
62
- self.data["to"] = to_addr
63
- self.data["msg"] = msg
56
+ def send_message(self, msg):
57
+ self.data['from'] = msg['from']
58
+ self.data['to'] = msg['to']
59
+ self.data['msg'] = msg.get_content()
64
60
 
65
61
  def quit(self):
66
-
67
62
  self.from_addr = self.data.get("from")
68
63
  self.to_addr = self.data.get("to")
69
64
  self.msg = self.data.get("msg")
70
65
  self.count += 1
71
66
 
72
67
  def starttls(self):
73
-
74
68
  self.tls = 1
75
69
 
76
70
  def login(self, user, password):
77
-
78
71
  self.user = user
79
72
  self.password = password
80
73
 
81
74
 
82
75
  class MockSMTP():
83
-
84
76
  instance = None
85
77
 
86
78
  def __init__(self, server, port):
87
-
88
79
  if not MockSMTP.instance:
89
80
  MockSMTP.instance = MockSMTPInstance(server, port)
90
81
 
91
82
  def __str__(self):
92
-
93
83
  return "MockSMTP"
94
84
 
95
- def sendmail(self, from_addr, to_addr, msg):
96
-
97
- self.instance.sendmail(from_addr, to_addr, msg)
85
+ def send_message(self, msg):
86
+ self.instance.send_message(msg)
98
87
 
99
88
  def quit(self):
100
-
101
89
  self.instance.quit()
102
90
 
103
91
  def _get_count(self):
104
-
105
92
  return MockSMTP.instance.count
106
93
 
107
94
  def _set_count(self, num):
108
-
109
95
  MockSMTP.instance.count = num
110
96
 
111
97
  def _get_from(self):
112
-
113
98
  return MockSMTP.instance.from_addr
114
99
 
115
100
  def _get_to(self):
116
-
117
101
  return MockSMTP.instance.to_addr
118
102
 
119
103
  def _get_msg(self):
120
-
121
104
  return MockSMTP.instance.msg
122
105
 
123
106
  def _get_tls(self):
124
-
125
107
  return MockSMTP.instance.tls
126
108
 
127
109
  def _get_user(self):
128
-
129
110
  return MockSMTP.instance.user
130
111
 
131
112
  def _get_password(self):
132
-
133
113
  return MockSMTP.instance.password
134
114
 
135
115
  def starttls(self):
136
-
137
116
  MockSMTP.instance.starttls()
138
117
 
139
118
  def login(self, user, password):
140
-
141
119
  MockSMTP.instance.login(user, password)
142
120
 
143
121
  def reset(self):
144
-
145
122
  MockSMTP.instance.reset()
146
123
 
147
124
  count = property(_get_count, _set_count)
@@ -154,9 +131,7 @@ class MockSMTP():
154
131
 
155
132
 
156
133
  class TestNotification(TestCase):
157
-
158
134
  def setUp(self):
159
-
160
135
  self.show = Show("Test show 36")
161
136
  self.show.show_id = 36
162
137
 
@@ -168,7 +143,6 @@ class TestNotification(TestCase):
168
143
  self.smtp.reset()
169
144
 
170
145
  def test_str_and_repr(self):
171
-
172
146
  notify = EmailNotifier("localhost", 25, MockSMTP)
173
147
  self.assertEqual(str(notify), "EmailNotifier")
174
148
  self.assertEqual(repr(notify), 'EmailNotifier("localhost", 25, '
@@ -186,7 +160,6 @@ class TestNotification(TestCase):
186
160
  self.assertEqual(repr(message), expect)
187
161
 
188
162
  def test_send_mail_pretend(self):
189
-
190
163
  show1 = Show("Columbo", "")
191
164
  ep1 = Episode("Episode 1", 1, 1, date(2014, 8, 10), "x", 1)
192
165
  ep2 = Episode("Episode 2", 1, 2, date(2014, 12, 1), "x", 2)
@@ -218,7 +191,6 @@ class TestNotification(TestCase):
218
191
  "[2014-12-01] Columbo 1x02 - Episode 2\n\n")
219
192
 
220
193
  def test_send_mail(self):
221
-
222
194
  show1 = Show("Columbo", "")
223
195
  ep1 = Episode("Episode 1", 1, 1, date(2014, 8, 10), "x", 1)
224
196
  ep2 = Episode("Episode 2", 1, 2, date(2014, 12, 1), "x", 2)
@@ -242,9 +214,6 @@ class TestNotification(TestCase):
242
214
  self.assertEqual(self.smtp.from_addr, "person@example.org")
243
215
  self.assertEqual(self.smtp.to_addr, "person@example.org")
244
216
  self.assertEqual(self.smtp.msg,
245
- "From: person@example.org\n"
246
- "To: person@example.org\n"
247
- "Subject: Upcoming TV episodes\n"
248
217
  "Your upcoming episodes:\n\n"
249
218
  "[2014-08-10] Columbo 1x01 - Episode 1\n"
250
219
  "[2014-12-01] Columbo 1x02 - Episode 2\n")
@@ -253,7 +222,6 @@ class TestNotification(TestCase):
253
222
  self.assertIsNotNone(ep2.notified)
254
223
 
255
224
  def test_send_mail_tls(self):
256
-
257
225
  show1 = Show("Columbo", "")
258
226
  ep1 = Episode("Episode 1", 1, 1, date(2014, 8, 10), "x", 1)
259
227
  ep2 = Episode("Episode 2", 1, 2, date(2014, 12, 1), "x", 2)
@@ -276,15 +244,11 @@ class TestNotification(TestCase):
276
244
  self.assertEqual(self.smtp.from_addr, "xy@example.org")
277
245
  self.assertEqual(self.smtp.to_addr, "xy@example.org")
278
246
  self.assertEqual(self.smtp.msg,
279
- "From: xy@example.org\n"
280
- "To: xy@example.org\n"
281
- "Subject: Upcoming TV episodes\n"
282
247
  "Your upcoming episodes:\n\n"
283
248
  "[2014-08-10] Columbo 1x01 - Episode 1\n"
284
249
  "[2014-12-01] Columbo 1x02 - Episode 2\n")
285
250
 
286
251
  def test_send_mail_auth(self):
287
-
288
252
  message = NewEpisodesNotification([], "", "")
289
253
 
290
254
  notifier = EmailNotifier("localhost", 97, MockSMTP)
@@ -298,7 +262,4 @@ class TestNotification(TestCase):
298
262
  self.assertEqual(self.smtp.from_addr, "xy@example.org")
299
263
  self.assertEqual(self.smtp.to_addr, "xy@example.org")
300
264
  self.assertEqual(self.smtp.msg,
301
- "From: xy@example.org\n"
302
- "To: xy@example.org\n"
303
- "Subject: Upcoming TV episodes\n"
304
265
  "Your upcoming episodes:\n\n")