Skip to content

Commit 0a7b3ed

Browse files
authored
Merge pull request #403 from zSeriesGuy/PlayHistory
Support history for specific ratingKeys
2 parents ba1f3a2 + 59ce923 commit 0a7b3ed

File tree

7 files changed

+248
-1
lines changed

7 files changed

+248
-1
lines changed

plexapi/base.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,15 @@ def delete(self):
421421
'havnt allowed items to be deleted' % self.key)
422422
raise
423423

424+
def history(self, maxresults=9999999, mindate=None):
425+
""" Get Play History for a media item.
426+
Parameters:
427+
maxresults (int): Only return the specified number of results (optional).
428+
mindate (datetime): Min datetime to return results from.
429+
"""
430+
return self._server.history(maxresults=maxresults, mindate=mindate, ratingKey=self.ratingKey)
431+
432+
424433
# The photo tag cant be built atm. TODO
425434
# def arts(self):
426435
# part = '%s/arts' % self.key

plexapi/library.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,17 @@ def add(self, name='', type='', agent='', scanner='', location='', language='en'
294294
part += urlencode(kwargs)
295295
return self._server.query(part, method=self._server._session.post)
296296

297+
def history(self, maxresults=9999999, mindate=None):
298+
""" Get Play History for all library Sections for the owner.
299+
Parameters:
300+
maxresults (int): Only return the specified number of results (optional).
301+
mindate (datetime): Min datetime to return results from.
302+
"""
303+
hist = []
304+
for section in self.sections():
305+
hist.extend(section.history(maxresults=maxresults, mindate=mindate))
306+
return hist
307+
297308

298309
class LibrarySection(PlexObject):
299310
""" Base class for a single library section.
@@ -633,6 +644,14 @@ def sync(self, policy, mediaSettings, client=None, clientId=None, title=None, so
633644

634645
return myplex.sync(client=client, clientId=clientId, sync_item=sync_item)
635646

647+
def history(self, maxresults=9999999, mindate=None):
648+
""" Get Play History for this library Section for the owner.
649+
Parameters:
650+
maxresults (int): Only return the specified number of results (optional).
651+
mindate (datetime): Min datetime to return results from.
652+
"""
653+
return self._server.history(maxresults=maxresults, mindate=mindate, librarySectionID=self.key, accountID=1)
654+
636655

637656
class MovieSection(LibrarySection):
638657
""" Represents a :class:`~plexapi.library.LibrarySection` section containing movies.

plexapi/myplex.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,19 @@ def claimToken(self):
600600
raise BadRequest('(%s) %s %s; %s' % (response.status_code, codename, response.url, errtext))
601601
return response.json()['token']
602602

603+
def history(self, maxresults=9999999, mindate=None):
604+
""" Get Play History for all library sections on all servers for the owner.
605+
Parameters:
606+
maxresults (int): Only return the specified number of results (optional).
607+
mindate (datetime): Min datetime to return results from.
608+
"""
609+
servers = [x for x in self.resources() if x.provides == 'server' and x.owned]
610+
hist = []
611+
for server in servers:
612+
conn = server.connect()
613+
hist.extend(conn.history(maxresults=maxresults, mindate=mindate, accountID=1))
614+
return hist
615+
603616

604617
class MyPlexUser(PlexObject):
605618
""" This object represents non-signed in users such as friends and linked
@@ -654,6 +667,8 @@ def _loadData(self, data):
654667
self.title = data.attrib.get('title', '')
655668
self.username = data.attrib.get('username', '')
656669
self.servers = self.findItems(data, MyPlexServerShare)
670+
for server in self.servers:
671+
server.accountID = self.id
657672

658673
def get_token(self, machineIdentifier):
659674
try:
@@ -663,6 +678,29 @@ def get_token(self, machineIdentifier):
663678
except Exception:
664679
log.exception('Failed to get access token for %s' % self.title)
665680

681+
def server(self, name):
682+
""" Returns the :class:`~plexapi.myplex.MyPlexServerShare` that matches the name specified.
683+
684+
Parameters:
685+
name (str): Name of the server to return.
686+
"""
687+
for server in self.servers:
688+
if name.lower() == server.name.lower():
689+
return server
690+
691+
raise NotFound('Unable to find server %s' % name)
692+
693+
def history(self, maxresults=9999999, mindate=None):
694+
""" Get all Play History for a user in all shared servers.
695+
Parameters:
696+
maxresults (int): Only return the specified number of results (optional).
697+
mindate (datetime): Min datetime to return results from.
698+
"""
699+
hist = []
700+
for server in self.servers:
701+
hist.extend(server.history(maxresults=maxresults, mindate=mindate))
702+
return hist
703+
666704

667705
class Section(PlexObject):
668706
""" This refers to a shared section. The raw xml for the data presented here
@@ -689,6 +727,16 @@ def _loadData(self, data):
689727
self.type = data.attrib.get('type')
690728
self.shared = utils.cast(bool, data.attrib.get('shared'))
691729

730+
def history(self, maxresults=9999999, mindate=None):
731+
""" Get all Play History for a user for this section in this shared server.
732+
Parameters:
733+
maxresults (int): Only return the specified number of results (optional).
734+
mindate (datetime): Min datetime to return results from.
735+
"""
736+
server = self._server._server.resource(self._server.name).connect()
737+
return server.history(maxresults=maxresults, mindate=mindate,
738+
accountID=self._server.accountID, librarySectionID=self.sectionKey)
739+
692740

693741
class MyPlexServerShare(PlexObject):
694742
""" Represents a single user's server reference. Used for library sharing.
@@ -711,6 +759,7 @@ def _loadData(self, data):
711759
""" Load attribute values from Plex XML response. """
712760
self._data = data
713761
self.id = utils.cast(int, data.attrib.get('id'))
762+
self.accountID = utils.cast(int, data.attrib.get('accountID'))
714763
self.serverId = utils.cast(int, data.attrib.get('serverId'))
715764
self.machineIdentifier = data.attrib.get('machineIdentifier')
716765
self.name = data.attrib.get('name')
@@ -720,7 +769,21 @@ def _loadData(self, data):
720769
self.owned = utils.cast(bool, data.attrib.get('owned'))
721770
self.pending = utils.cast(bool, data.attrib.get('pending'))
722771

772+
def section(self, name):
773+
""" Returns the :class:`~plexapi.myplex.Section` that matches the name specified.
774+
775+
Parameters:
776+
name (str): Name of the section to return.
777+
"""
778+
for section in self.sections():
779+
if name.lower() == section.title.lower():
780+
return section
781+
782+
raise NotFound('Unable to find section %s' % name)
783+
723784
def sections(self):
785+
""" Returns a list of all :class:`~plexapi.myplex.Section` objects shared with this user.
786+
"""
724787
url = MyPlexAccount.FRIENDSERVERS.format(machineId=self.machineIdentifier, serverId=self.id)
725788
data = self._server.query(url)
726789
sections = []
@@ -731,6 +794,15 @@ def sections(self):
731794

732795
return sections
733796

797+
def history(self, maxresults=9999999, mindate=None):
798+
""" Get all Play History for a user in this shared server.
799+
Parameters:
800+
maxresults (int): Only return the specified number of results (optional).
801+
mindate (datetime): Min datetime to return results from.
802+
"""
803+
server = self._server.resource(self.name).connect()
804+
return server.history(maxresults=maxresults, mindate=mindate, accountID=self.accountID)
805+
734806

735807
class MyPlexResource(PlexObject):
736808
""" This object represents resources connected to your Plex server that can provide

plexapi/server.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ def installUpdate(self):
322322
# figure out what method this is..
323323
return self.query(part, method=self._session.put)
324324

325-
def history(self, maxresults=9999999, mindate=None):
325+
def history(self, maxresults=9999999, mindate=None, ratingKey=None, accountID=None, librarySectionID=None):
326326
""" Returns a list of media items from watched history. If there are many results, they will
327327
be fetched from the server in batches of X_PLEX_CONTAINER_SIZE amounts. If you're only
328328
looking for the first <num> results, it would be wise to set the maxresults option to that
@@ -332,9 +332,18 @@ def history(self, maxresults=9999999, mindate=None):
332332
maxresults (int): Only return the specified number of results (optional).
333333
mindate (datetime): Min datetime to return results from. This really helps speed
334334
up the result listing. For example: datetime.now() - timedelta(days=7)
335+
ratingKey (int/str) Request history for a specific ratingKey item.
336+
accountID (int/str) Request history for a specific account ID.
337+
librarySectionID (int/str) Request history for a specific library section ID.
335338
"""
336339
results, subresults = [], '_init'
337340
args = {'sort': 'viewedAt:desc'}
341+
if ratingKey:
342+
args['metadataItemID'] = ratingKey
343+
if accountID:
344+
args['accountID'] = accountID
345+
if librarySectionID:
346+
args['librarySectionID'] = librarySectionID
338347
if mindate:
339348
args['viewedAt>'] = int(mindate.timestamp())
340349
args['X-Plex-Container-Start'] = 0

tests/test_audio.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ def test_audio_Artist_get(artist, music):
3232
artist.title == 'Infinite State'
3333

3434

35+
def test_audio_Artist_history(artist):
36+
history = artist.history()
37+
assert len(history)
38+
39+
3540
def test_audio_Artist_track(artist):
3641
track = artist.track('Holy Moment')
3742
assert track.title == 'Holy Moment'
@@ -80,6 +85,16 @@ def test_audio_Album_attrs(album):
8085
assert album.artUrl is None
8186

8287

88+
def test_audio_Album_history(album):
89+
history = album.history()
90+
assert len(history)
91+
92+
93+
def test_audio_Track_history(track):
94+
history = track.history()
95+
assert len(history)
96+
97+
8398
def test_audio_Album_tracks(album):
8499
tracks = album.tracks()
85100
track = tracks[0]

tests/test_history.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# -*- coding: utf-8 -*-
2+
import pytest
3+
from datetime import datetime
4+
from plexapi.exceptions import BadRequest, NotFound
5+
from . import conftest as utils
6+
7+
8+
def test_history_Movie(movie):
9+
movie.markWatched()
10+
history = movie.history()
11+
assert len(history)
12+
movie.markUnwatched()
13+
14+
15+
def test_history_Show(show):
16+
show.markWatched()
17+
history = show.history()
18+
assert len(history)
19+
show.markUnwatched()
20+
21+
22+
def test_history_Season(show):
23+
season = show.season('Season 1')
24+
season.markWatched()
25+
history = season.history()
26+
assert len(history)
27+
season.markUnwatched()
28+
29+
30+
def test_history_Episode(episode):
31+
episode.markWatched()
32+
history = episode.history()
33+
assert len(history)
34+
episode.markUnwatched()
35+
36+
37+
def test_history_Artist(artist):
38+
history = artist.history()
39+
40+
41+
def test_history_Album(album):
42+
history = album.history()
43+
44+
45+
def test_history_Track(track):
46+
history = track.history()
47+
48+
49+
def test_history_MyAccount(account, movie, show):
50+
movie.markWatched()
51+
show.markWatched()
52+
history = account.history()
53+
assert len(history)
54+
movie.markUnwatched()
55+
show.markUnwatched()
56+
57+
58+
def test_history_MyLibrary(plex, movie, show):
59+
movie.markWatched()
60+
show.markWatched()
61+
history = plex.library.history()
62+
assert len(history)
63+
movie.markUnwatched()
64+
show.markUnwatched()
65+
66+
67+
def test_history_MySection(plex, movie):
68+
movie.markWatched()
69+
history = plex.library.section('Movies').history()
70+
assert len(history)
71+
movie.markUnwatched()
72+
73+
74+
def test_history_MyServer(plex, movie):
75+
movie.markWatched()
76+
history = plex.history()
77+
assert len(history)
78+
movie.markUnwatched()
79+
80+
81+
def test_history_User(account, shared_username):
82+
user = account.user(shared_username)
83+
history = user.history()
84+
85+
86+
def test_history_UserServer(account, shared_username, plex):
87+
userSharedServer = account.user(shared_username).server(plex.friendlyName)
88+
history = userSharedServer.history()
89+
90+
91+
def test_history_UserSection(account, shared_username, plex):
92+
userSharedServerSection = account.user(shared_username).server(plex.friendlyName).section('Movies')
93+
history = userSharedServerSection.history()
94+

tests/test_video.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,13 @@ def test_video_Movie_attrs(movies):
296296
assert stream2.type == 2
297297

298298

299+
def test_video_Movie_history(movie):
300+
movie.markWatched()
301+
history = movie.history()
302+
assert len(history)
303+
movie.markUnwatched()
304+
305+
299306
def test_video_Show(show):
300307
assert show.title == 'Game of Thrones'
301308

@@ -362,6 +369,13 @@ def test_video_Show_attrs(show):
362369
assert show.url(None) is None
363370

364371

372+
def test_video_Show_history(show):
373+
show.markWatched()
374+
history = show.history()
375+
assert len(history)
376+
show.markUnwatched()
377+
378+
365379
def test_video_Show_watched(tvshows):
366380
show = tvshows.get('The 100')
367381
show.episodes()[0].markWatched()
@@ -468,6 +482,13 @@ def test_video_Episode(show):
468482
show.episode(season=1337, episode=1337)
469483

470484

485+
def test_video_Episode_history(episode):
486+
episode.markWatched()
487+
history = episode.history()
488+
assert len(history)
489+
episode.markUnwatched()
490+
491+
471492
# Analyze seems to fail intermittently
472493
@pytest.mark.xfail
473494
def test_video_Episode_analyze(tvshows):
@@ -544,6 +565,14 @@ def test_video_Season(show):
544565
assert show.season('Season 1') == seasons[0]
545566

546567

568+
def test_video_Season_history(show):
569+
season = show.season('Season 1')
570+
season.markWatched()
571+
history = season.history()
572+
assert len(history)
573+
season.markUnwatched()
574+
575+
547576
def test_video_Season_attrs(show):
548577
season = show.season('Season 1')
549578
assert utils.is_datetime(season.addedAt)

0 commit comments

Comments
 (0)