4.3. Howdy Core API
This document describes the lower level Plex core API, upon which all the command line and GUI tools are based. It lives in howdy.core
.
4.3.1. howdy.core module
This module implements the lower-level functionality that does or has the following:
access and retrieve configuration and other data from an SQLite3 database using SQLAlchemy object relational mapping (ORM) classes. The SQLite3 database is stored in
~/.config/howdy/app.db
.PlexConfig
is an ORM class that stores configuration information.PlexGuestEmailMapping
is an ORM class that stores all the email addresses that will receive Howdy email notifications.LastNewsletterDate
is an ORM class that store one member (or row) – thedatetime
of when the Howdy newsletter was last updated.create_all
instantiates necessary SQLite3 tables in the configuration table if they don’t already exist.low level PyQt5 derived widgets used for the other GUIs in Howdy:
ProgressDialog
,QDialogWithPrinting
, andQLabelWithSave
.initialization, in order to check for necessary prerequisites (see Prerequisites) and to install missing Python modules and packages (see Installation). This initialization is handled via a
HowdyInitialization
singleton object.
- class howdy.core.HtmlView(parent, htmlString='')
A convenient PyQt5 widget that displays rich and interactive HTML (HTML with CSS and Javascript). This extends
QWebEngineView
.
- class howdy.core.LastNewsletterDate(**kwargs)
This SQLAlchemy ORM class contains the date at which the last newsletter was sent. It is not used much, and now that Tautulli has newsletter functionality, I very likely won’t use this at all. Stored into the
lastnewsletterdate
table in the SQLite3 configuration database.
- class howdy.core.PlexConfig(**kwargs)
This SQLAlchemy ORM class contains the configuration data used for running all the Howdy tools. Stored into the
plexconfig
table in the SQLite3 configuration database.- Variables:
service – the name of the configuration service we store. Index on this unique key. This is a
Column
containing aString
of size 65536.data –
the JSON formatted information on the data stored here. For instance, username and password can be stored in the following way
{ 'username' : USERNAME, 'password' : PASSWORD }
- class howdy.core.PlexExcludedTrackerStubs(**kwargs)
This SQLAlchemy ORM class contains all the excluded trackers (as of 7 AUGUST 2022 I am looking at
stealth.si
) when reconstructing magnet links. Stored in theplexexcludedtrackerstubs
table in the SQlite3 configuration database.- Variables:
trackerstub – the torrent tracker stub to exclude. This is a
Column
containing aString
object of size 65536.
- class howdy.core.PlexGuestEmailMapping(**kwargs)
This SQLAlchemy ORM class contains mapping of emails of Plex server users, to other email addresses. This is used to determine other email addresses to which Howdy one-off or newsletter emails are delivered. Stored in the
plexguestemailmapping
table in the SQLite3 configuration database. The structure of each row in this table is straightforward. Each column in the table is a member of the object in this ORM class.- Variables:
plexemail – this is the main column, which must be the email of a Plex user who has access to the Plex server. This is a
Column
containing aString
.plexmapping – this is a collection of different email addresses, to which the emails are sent. For example, if a Plex user with email address
A@email.com
would like to send email toB@mail.com
andC@mail.com
, the plexmapping column would beB@mail.com,C@mail.com
. NONE of the mapped emails will match email. This is aColumn
containing aString
of size 65536.plexreplaceexisting – this is a boolean that determines whether email also goes to the Plex user’s email address. From the example above, if
True
then a Plex user atA@mail.com
will have email delivered ONLY toB@mail.com
andC@mail.com
. IfFalse
, then that same Plex user will have email delivered to all three email addresses (A@mail.com
,B@mail.com
, andC@mail.com
). This is aColumn
containing aBoolean
.
- class howdy.core.ProgressDialog(parent, windowTitle='', doQuit=True)
A convenient PyQt5 widget, inheriting from
QDialogWithPrinting
, that acts as a GUI blocking progress window for longer lasting operations. Like its parent class, this dialog widget is also resizable. This shows the passage of the underlying slow process in 5 second increments.This progress dialog exposes three methods –
addText
,stopDialog
, andstartDialog
– to which a customQThread
object can connect.startDialog
is triggered on long operation start, sometimes with an initial message.addText
is triggered when some intermediate progress text must be returned.stopDialog
is triggered on process end.
- Parameters:
parent (
QWidget
) – the parentQWidget
on which this dialog widget blocks.windowTitle (str) – the label to put on this progress dialog in an internal
QLabel
.doQuit (bool) – if
True
, then using the quit shortcuts (Esc
orCtrl+Shift+Q
) will cause the underlying program to exit. Otherwise, hide the progress dialog.
- Variables:
mainDialog (
QTextEdit
) – the main dialog widget in this GUI.parsedHTML (
BeautifulSoup
) – theBeautifulSoup
structure that contains the indexable tree of progress dialogs.elapsedTime (
QLabel
) – the bottomQLabel
widget that displays how much time (in seconds) has passed.timer (
QTimer
) – theQTimer
sub-thread that listens every 5 seconds before emitting a signal.t0 (float) – the UNIX time, in seconds with resolution of microseconds.
- addText(text)
adds some text to this progress dialog window.
- Parameters:
text (str) – the text to add.
- showTime()
method connected to the internal
timer
that prints out how many seconds have passed, on the underlyingelapsedTime
QLabel
.
- startDialog(initString='')
starts running the progress dialog, with an optional labeling string, and starts the timer.
- Parameters:
initString (str) – optional internal labeling string.
- stopDialog()
stops running, and hides, this progress dialog.
- class howdy.core.ProgressDialogThread(parent, title)
This subclassing of
QThread
provides a convenient scaffolding to run, in a non-blocking fashion, some long-running processes with an asssociatedProgressDialog
widget.Subclasses of this object need to have a particular structure for their
__init__
method. The first three arguments MUST beself
,parent
,self
is a reference to this object.parent
is the parentQWidget
to which theProgressDialog
attribute, namedprogress_dialog
, is the child.title
is the title ofprogress_dialog
. Here is an example, where an example class namedProgressDialogThreadChildClass
inherits fromProgressDialogThread
.def __init__( self, parent, *args, **kwargs ): super( ProgressDialogThreadChildClass, self ).__init__( parent, title ) # own code to initialize based on *args and **kwargs
This thing has an associated
run
method that is expected to be partially implemented in the following manner for subclasses ofProgressDialogThread
.It must start with
self.progress_dialog.show( )
to show the progress dialog widget.It must end with this command,
self.stopDialog.emit( )
to hide the progress dialog widget.
Here is an example.
def run( self ): self.progress_dialog.show( ) # run its own way self.stopDialog.emit( )
In the
run
method, if one wants to print out something intoprogess_dialog
, then there should be these types of commands inrun
:self.emitString.emit( mystr )
, wheremystr
is astr
message to show inprogress_dialog
, andemitString
is apyqtsignal
connected to theprogress_dialog
object’saddText( )
.- Parameters:
- Variables:
emitString – the signal, with
str
signature, that is triggered to send progress messages intoprogress_dialog
.stopDialog – the signal that is triggered to stop the
progress_dialog
, callingstopDialog
.startDialog – the signal, with
str
signature, that is triggered to restart theprogress_dialog
widget, callingstartDialog
.progress_dialog – the GUI that shows, in a non-blocking fashion, the progress on some longer-running method.
time0 (int) – a convenience attribute, the UTC time at which the
progress_dialog
object was first instantiated. Can be used to determine the time each submethod takes (for example,time.time( ) - self.time0
).
See also
- class howdy.core.QDialogWithPrinting(parent, isIsolated=True, doQuit=True)
A convenient PyQt5 widget, inheriting from
QDialog
, that allows for screen grabs and keyboard shortcuts to either hide this dialog window or quit the underlying program. This PyQt5 widget is also resizable, in relative increments of 5% larger or smaller, to a maximum of \(1.05^5\) times the initial size, and to a minimum of \(1.05^{-5}\) times the initial size.- Parameters:
parent (
QWidget
) – the parentQWidget
to this dialog widget.isIsolated (bool) – If
True
, then this widget is detached from its parent. IfFalse
, then this widget is embedded into a layout in the parent widget.doQuit (bool) – if
True
, then using the quit shortcuts (Esc
orCtrl+Shift+Q
) will cause the underlying program to exit. Otherwise, hide the progress dialog.
- Variables:
indexScalingSignal – a
pyqtSignal
that can be connected to other PyQt5 events or methods, if resize events want to be recorded.initWidth (int) – the initial width of the GUI in pixels.
initHeight (int) – the initial heigth of the GUI in pixels.
- makeBigger()
makes the widget incrementally 5% larger, for a maximum of \(1.05^5\), or approximately 28% larger than, the initial size.
- makeSmaller()
makes the widget incrementally 5% smaller, for a minimum of \(1.05^{-5}\), or approximately 28% smaller than, the initial size.
- resetSize()
reset the widget size to the initial size.
- reset_sizes()
Sets the default widget size to the current size.
- screenGrab()
take a screen shot of itself and saver to a PNG file through a
QFileDialog
widget.See also
- class howdy.core.QLabelWithSave(parent=None)
A convenient PyQt5 widget that inherits from
QLabel
, but allows screen shots.- contextMenuEvent(event)
Constructs a context menu with a single action, Save Pixmap, that takes a screen shot of this widget, using
screenGrab
.- Parameters:
event (QEvent) – default
QEvent
argument needed to create a context menu. Is not used in this reimplementation.
- screenGrab()
take a screen shot of itself and save to a PNG file through a
QFileDialog
widget.See also
- howdy.core.check_valid_RST(myString)
Checks to see whether the input string is valid reStructuredText.
- Parameters:
myString (str) – the candidate reStructuredText input.
- Returns:
True
if valid, otherwiseFalse
.- Return type:
See also
- howdy.core.convert_string_RST(myString)
Converts a valid reStructuredText input string into rich HTML.
- Parameters:
myString (str) – the candidate reStructuredText input.
- Returns:
If the input string is valid reStructuredText, returns the rich HTML as a
string
. Otherwise emits alogging error message
and returnsNone
.- Return type:
See also
- howdy.core.create_all()
creates the necessary SQLite3 tables into the database file
~/.config/howdy/app.db
if they don’t already exist, but only if not building documentation in Read the docs.
- howdy.core.geoip_reader = <geoip2.database.Reader object>
This contains an on-disk MaxMind database, of type
geoip2.database.Reader
, containing location information for IP addresses.
- howdy.core.get_formatted_duration(totdur)
This routine spits out a nice, formatted string representation of the duration, which is of type
datetime
.
- howdy.core.get_formatted_size(totsizebytes)
This routine spits out a nice, formatted string representation of a file size, which is represented in int.
This method works like this.
get_formatted_size( int(2e3) ) = '1.953 kB' # kilobytes get_formatted_size( int(2e6) ) = '1.907 MB' # megabytes get_formatted_size( int(2e9) ) = '1.863 GB' # gigabytes
- Parameters:
totsizebytes (int) – size of a file in bytes.
- Returns:
Formatted representation of that file size.
- Return type:
See also
- howdy.core.get_formatted_size_MB(totsizeMB)
Same as
get_formatted_size
, except this operates on file sizes in units of megabytes rather than bytes.- Parameters:
totsizeMB (int) – size of the file in megabytes.
- Returns:
Formatted representation of that file size.
- Return type:
See also
- howdy.core.get_lastupdated_string(dt=datetime.datetime(2024, 10, 14, 14, 50, 18, 538250))
Returns a string representation of a
datetime
object.- Parameters:
dt – the date and time, of type
datetime
.- Returns:
a
str
with this format,Saturday, 28 September 2019, at 3:41 AM
.- Return type:
See also
- howdy.core.get_maximum_matchval(check_string, input_string)
Returns the Levenshtein distance of two strings, implemented using A perfect match is a score of
100.0
.- Parameters:
- Returns:
the Levenshtein distance between the two strings.
- Return type:
- howdy.core.get_popularity_color(hpop, alpha=1.0)
Get a color that represents some darkish cool looking color interpolated between 0 and 1.
- howdy.core.returnQAppWithFonts()
returns a customized
QApplication
with all custom fonts loaded.
- howdy.core.splitall(path_init)
This routine is used by
get_path_data_on_tvshow
to split a TV show file path into separate directory delimited tokens.
4.3.2. howdy.core.core module
This module implements the functionality to do the following:
access your Plex server, and determine those Plex servers to which you have access.
retrieve and summarize data in the Plex libraries.
miscellaneous functionalities, such as the following: getting formatted date strings from a
date
object.
- howdy.core.core.add_mapping(plex_email, plex_emails, new_emails, replace_existing)
Changes the mapping of one member of the Plex server’s emails from an old set of emails to a new set of emails. The command line tool, howdy_core_cli, is a front end to the lower-level functionality implemented here.
- howdy.core.core.checkServerCredentials(doLocal=False, verify=True, checkWorkingServer=False)
Returns get a local or remote URL and Plex access token to allow for API access to the server. If there is already a VALID token in the SQLite3 configuration database, then uses that. Otherwise, tries to acquire a Plex access token.
- Parameters:
doLocal (bool) – optional argument, whether to get a local (
http://localhost:32400
) or remote URL. Default isFalse
(look for the remote URL).verify (bool) – optional argument, whether to verify SSL connections. Default is
True
.checkWorkingServer (bool) – optional argument, whether to check if the server is working. Default is
True
.
- Returns:
a tuple of server URL and Plex access token.
- Return type:
See also
- howdy.core.core.check_imgurl_credentials(clientID, clientSECRET, clientREFRESHTOKEN, verify=True)
validate the Imgur API credentials with the provided API client ID, secret, and refresh token.
- Parameters:
- Returns:
whether the new credentials are correct.
- Return type:
See also
- howdy.core.core.check_jackett_credentials(url, apikey, verify=True)
validate the Jackett server credentials with the provided URL and API key.
- Parameters:
- Returns:
a
tuple
. If successful, this is a tuple of the server’s URL and the string"SUCCESS"
. If unsuccessful, this is a tuple ofNoneType
and error message string.- Return type:
See also
- howdy.core.core.fill_out_movies_stuff(token, fullURL='http://localhost:32400', verify=True)
Creates a
tuple
. The first element of thetuple
is alist
of movies from this Plex server. Each element in that list is adict
with the following structure with 12 keys and values, as shown in this example{ 'title': 'Blue Collar', 'rating': 10.0, 'contentrating': 'R', 'picurl': 'https://24.5.231.186:32400/library/metadata/46001/art/1569656413', 'releasedate': datetime.date(1978, 2, 10), 'addedat': datetime.date(2019, 6, 6), 'summary': "Fed up with mistreatment at the hands of both management and union brass, and coupled with financial hardships on each man's end, three auto assembly line workers hatch a plan to rob a safe at union headquarters.", 'duration': 6820.394, 'totsize': 935506414.0, 'localpic': True, 'imdb_id': 'tt0077248', 'genre': 'drama' }
The second element of the
tuple
is alist
of movie genres on the Plex server.
- howdy.core.core.getCredentials(verify=True, checkWorkingServer=True)
Returns the Plex user account information stored in
~/.config/howdy/app.db
.- Parameters:
- Returns:
the Plex account tuple of
(username, password)
.- Return type:
See also
- howdy.core.core.getTokenForUsernamePassword(username, password, verify=True)
get the Plex access token for the Plex server given an username and password for the user account.
- Parameters:
- Returns:
the Plex access token.
- Return type:
See also
- howdy.core.core.get_all_servers(token, verify=True)
Find all the Plex servers for which you have access.
- Parameters:
- Returns:
a dictionary of servers accessible to you. Each key is the Plex server’s name, and the value is a
dict
that looks like this.
{ 'owned' : OWNED, # boolean on whether server owned by you 'access token' : TOKEN, # string access token to server 'url' : URL # remote URL of the form https://IP-ADDRESS:PORT }
- Return type:
See also
- howdy.core.core.get_cloudconvert_credentials()
retrieves the CloudConvert API V2 credentials from the SQLite3 configuration database.
- Returns:
a
dict
of the CloudConvert API V2 credentials. Its structure is,
{ 'clientID': XXXX, 'clientSECRET': XXXX }
- Return type:
See also
- howdy.core.core.get_current_date_newsletter()
the last date and time at which the Howdy email newsletter was updated.
- Returns:
the date and time of the most recent previous email newsletter.
- Return type:
See also
- howdy.core.core.get_date_from_datestring(dstring)
Returns a
date
object from a date string with the format, “January 1, 2000”.
- howdy.core.core.get_email_contacts(token, fullURL='https://localhost:32400', verify=True)
list of all email addresses of friends who have stream access to your Plex server.
- howdy.core.core.get_imgurl_credentials()
retrieves the Imgur API credentials from the SQLite3 configuration database.
{ 'clientID': XXXX, 'clientSECRET': XXXX, 'clientREFRESHTOKEN': XXXX, 'mainALBUMID': XXXX, 'mainALBUMTITLE': XXXX }
- Return type:
See also
- howdy.core.core.get_jackett_credentials()
retrieves the Jackett server’s API credentials from the SQLite3 configuration database.
- howdy.core.core.get_lastN_movies(lastN, token, fullURL='http://localhost:32400', useLastNewsletterDate=True)
Returns the last \(N\) movies that were uploaded to the Plex server, either after the last date at which a newsletter was sent out or not.
- Parameters:
- Returns:
a
list
of Plex movies. Each element in the list istuple
of the movie: title, year,datetime
, and The Movie Database <TMDB_> URL of the movie.- Return type:
dict
.
- howdy.core.core.get_libraries(token, fullURL='http://localhost:32400', do_full=False, timeout=None)
Gets the
dict
of libraries on the Plex server. The key is the library number, while the value can be either the name of the library or atuple
of library name and library type
- howdy.core.core.get_library_data(title, token, fullURL='http://localhost:32400', num_threads=24, timeout=None, mainPath=None)
Returns the data on the specific Plex library, as a
dict
. This lower level functionality lives in the same space as PlexAPI. Three types of library data can be returned: movies, TV shows, and music.Movie data has this JSON like structure.
moviedata
is an example movie data dictionary.moviedata
is adict
whose keys (of typestr
) are the main movie genres in the Plex library, such ascomedy
.Each value in
moviedata[<genre>]
is alist
of movies of that (main) genre.Each
movie
inmoviedata[<genre>]
is adict
with the following ten keys and values.title
:str
movie’s name.rating
:float
the movie’s quality rating as a number between0.0
and10.0
.contentrating
:str
the MPAA content rating for the movie (such asPG
,PG-13
,R
, orNR
).releasedate
:date
of when the movie was first released.addedat
:date
of when the movie was added to the Plex server.summary
:str
plot summary of the movie.duration
:float
movie’s duration in seconds.totsize
:int
size of the movie file in bytes.localpic
:bool
currently unused variable, alwaysTrue
.
An example
moviedata
dict
with one genre (comedy
) and ten highly rated movies can be found inmoviedata example
in JSON format.
TV data has this JSON like structure.
tvdata
is an example TV data dictionary.tvdata
is adict
whose keys are the individual TV shows.Each value in
tvdata[<showname>]
is a dictionary with four keys:title
(str
name of the show, <showname>),summary
(str
description of the show),picturl
(str
URL of the poster for the show), andseasons
(dict
whose keys are the seasons of the show).tvdata[<showname>]['seasons']
is adict
whose keys are the seasons. If the show has specials, then those episodes are in season 0.this
dict
has two keys:seasonpicurl
(str
URL of the poster for the season), andepisodes
(dict
of the episodes for that season).tvdata[<showname>]['seasons']['episodes']
is adict
whose keys are the episode numbers, and whose value is adict
with the following nine keys and values.title
:str
title of the episode.episodepicurl
:str
URL of the poster of the episode.date aired
:date
of when the episode first aired.summary
:str
summary of the episode’s plot.duration:
:float
episode duration in seconds.size
:int
size of the episode file in bytes.director
:list
of the episode’s directors.writer
:list
of the episode’s writers.
An example
tvdata
dict
with one finished HBO show, The Brink, can be found intvdata example
in JSON format.
Music data has this JSON like structure.
musicdata
is an example music data dictionary.musicdata
is adict
whose keys are the individual artists or groups (such as The Beatles).Each value in
musicdata[<artistname>]
is adict
of albums associated with<artistname>
on the Plex server.The key is the album name, and the value is another
dict
with three keys.musicdata[<artistname>][<albumname>]['tracks']
is alist
ofdict
s representing each song that is on the Plex server. Each songdict
has the following keys and values.An example
musicdata
dict
with one artist, The Beatles, can be found inmusicdata example
. Sincedate
is not JSON-serializable, I have represented eachcurdate
as a string in this format: DAY MONTH YEAR (such as13 January 2020
).
- Parameters:
title (str) – the name of the library.
num_threads (int) – the number of concurrent threads used to access the Plex server and get the library data.
timeout (int) – optional time, in seconds, to wait for an HTTP conection to the Plex server.
mainPath (str) – optional prefix directory to put in front of all the file paths on the Plex server.
- Returns:
- Return type:
- howdy.core.core.get_library_stats(key, token, fullURL='http://localhost:32400', sinceDate=None)
Gets summary data on a specific library on the Plex server. returned as a
dict
. The Plex librarymediatype
can be one ofmovie
(Movies),show
(TV shows), orartist
(Music). The common part of thedict
looks like the following.{ 'fullURL' : FULLURL, # URL of the Plex server in the form of https://IP-ADDRESS:PORT 'title' : TITLE, # name of the Plex library 'mediatype' : MEDIATYPE, # one of "movie", "show", or "artist" }
Here are what the extra parts of this dictionary look like.
If the library is a
movie
(Movies), then{ 'num_movies' : num_movies, # total number of movies in this library. 'totdur' : totdur, # total duration in seconds of all movies. 'totsize' : totsize, # total size in bytes of all movies. 'genres' : sorted_by_genre # another dictionary subdivided by, showing # movies, size, and duration by genre. }
sorted_by_genre
is also adict
, whose keys are the separate movie genres in this Plex library (such asaction
,horror
,comedy
). Each value in this dictionary is another dictionary that looks like this.{ 'num_movies' : num_movies_gen, # total number of movies in this genre. 'totdur' : totdur_gen, # total duration in seconds of movies in this genre. 'totsize' : totsize_gen, # total size in bytes of movies in this genre. }
If the library is a
show
(TV Shows), then{ 'num_tveps' : num_tveps, # total number of TV episodes in this library. 'num_tvshows' : num_tvshows, # total number of TV shows in this library. 'totdur' : totdur, # total duration in seconds of all TV shows. 'totsize' : totsize # otal size in bytes of all TV shows. }
If the library is an
artist
(Music), then{ 'num_songs' : num_songs, # total number of songs in this library. 'num_albums' : num_albums, # total number of albums in this library. 'num_artists' : num_artists, # total number of unique artists in this library. 'totdur' : totdur, # total duration in seconds of all songs. 'totsize' : totsize # total size in bytes of all songs. }
- Parameters:
- Returns:
- Return type:
See also
- howdy.core.core.get_mapped_email_contacts(token, fullURL='https://localhost:32400', verify=True)
list of all email addresses (including Plex server friends and mapped emails) to send Howdy related emails.
- howdy.core.core.get_movies_libraries(token, fullURL='http://localhost:32400')
Returns a
list
of the key numbers of all Plex movie libraries on the Plex server.
- howdy.core.core.get_pic_data(plexPICURL, token=None)
Get the PNG data as a
Response
object, from a movie picture URL on the Plex server.
- howdy.core.core.get_updated_at(token, fullURL='https://localhost:32400')
Get the date and time at which the Plex server was last updated, as a
datetime
object.
- howdy.core.core.oauthCheckGoogleCredentials(bypass=False)
Checks whether the Google OAuth2 authentication settings exist in the SQLite3 configuration database. The format of the authentication data in the configuration database is,
{ 'access_token': XXXX, 'client_id': YYYY, 'client_secret': ZZZZ, 'refresh_token': AAAA, 'token_expiry': BBBB, 'token_uri': 'https://accounts.google.com/o/oauth2/token', 'user_agent': None, 'revoke_uri': 'https://oauth2.googleapis.com/revoke', 'id_token': None, 'id_token_jwt': None, 'token_response': { 'access_token': XXXX, 'expires_in': 3600, 'refresh_token': AAAA, 'scope': 'https://www.googleapis.com/auth/gmail.send https://www.googleapis.com/auth/contacts.readonly https://www.googleapis.com/auth/youtube.readonly https://www.googleapis.com/auth/musicmanager https://www.googleapis.com/auth/spreadsheets', 'token_type': 'Bearer' }, 'scopes': ['https://www.googleapis.com/auth/gmail.send', 'https://spreadsheets.google.com/feeds', 'https://www.googleapis.com/auth/youtube.readonly', 'https://www.googleapis.com/auth/contacts.readonly', 'https://www.googleapis.com/auth/musicmanager'], 'token_info_uri': 'https://oauth2.googleapis.com/tokeninfo', 'invalid': False, '_class': 'OAuth2Credentials', '_module': 'oauth2client.client' }
If
data
is this dictionary, note the scopes defined in thedata['scopes']
anddata['token_response']['scope']
.- Parameters:
bypass (bool) – if
True
, then ignore what exists in the database under google crdentials.- Returns:
a
tuple
of status and message. If the settings are in the database, returns( True, 'SUCCESS' )
. If they are not, returns( False, 'GOOGLE AUTHENTICATION CREDENTIALS DO NOT EXIST.' )
.- Return type:
tuple.
- howdy.core.core.oauthGetGoogleCredentials(verify=True)
Gets the Google Oauth2 credentials, stored in the SQLite3 configuration database, in the form of a refreshed
Credentials
object. This OAuth2 authentication method is used for ALL the services accessed by Howdy.- Parameters:
verify (bool) – optional argument, whether to verify SSL connections. Default is
True
.- Returns:
a
Credentials
form of the Google Oauth2 credentials for various Oauth2 services.- Return type:
- howdy.core.core.oauthGetOauth2ClientGoogleCredentials()
Gets the Google Oauth2 credentials, stored in the SQLite3 configuration database, in the form of a refreshed
AccessTokenCredentials
object. This OAuth2 authentication method IS used for all the services accessed by Howdy.- Returns:
a
AccessTokenCredentials
form of the Google Oauth2 credentials for various Oauth2 services.- Return type:
- howdy.core.core.oauth_generate_google_permission_url()
Generates a Google OAuth2 web-based flow for all the Google services used in Howdy. The authentication process that uses this flow is described in this subsection. Here are the programmatic steps to finally generate an
AccessTokenCredentials
object.Get the
OAuth2WebServerFlow
and authentication URI.flow, auth_uri = oauth_generate_google_permission_url( )
Go to the URL,
auth_uri
, in a browser, grant permissions, and copy the authorization code in the browser window. This authorization code is referred to asauthorization_code
.Create the
AccessTokenCredentials
usingauthorization_code
.credentials = flow.step2_exchange( authorization_code )
- Returns:
a
tuple
of two elements. The first element is anOAuth2WebServerFlow
web server flow object. The second element is the redirection URIstring
that redirects the user to begin the authorization flow.- Return type:
- howdy.core.core.oauth_store_google_credentials(credentials)
Store the Google OAuth2 credentials, in the form of a
AccessTokenCredentials
object, into the SQLite3 configuration database.- Parameters:
credentials – the
AccessTokenCredentials
object to store into the database.
- howdy.core.core.processValidHTMLWithPNG(html, pngDataDict, doEmbed=False)
Returns a prettified HTML document, using BeautifulSoup, including all PNG image data (whether URLs or Base 64 encoded data) in
<img>
tags.- Parameters:
html (str) – the initial HTML document into which images are to be embedded.
pngDataDict (dict) – dictionary of PNG data. Key is the name of the PNG file (must end in .png). Value is a tuple of type
(b64data, widthInCM, url)
.b64data
is the Base 64 encoded binary representation of the PNG image.widthInCm
is the image width in cm.url
is the URL address of the image.doEmbed (bool) – If
True
, then the image source tag uses the Base 64 encoded data. IfFalse
, the image source tag is the URL.
- Returns:
prettified HTML document with the images located in it.
- Return type:
- howdy.core.core.pushCredentials(username, password)
replace the Plex server credentials, located in
~/.config/howdy/app.db
, with a newusername
andpassword
.- Parameters:
- Returns:
if successful, return a string,
SUCCESS
. If unsuccessful, returns a string reason of why it failed.- Return type:
See also
- howdy.core.core.refresh_library(key, library_dict, token, fullURL='http://localhost:32400')
Lower level front-end to
plex_resynclibs.py
that refreshes a Plex server library. Here I use instructions found in this Plex forum article on URL commands.
- howdy.core.core.rstToHTML(rstString)
Converts a reStructuredText string into HTML, then prettifies the intermediate HTML using BeautifulSoup.
- Parameters:
rstString (str) – the initial restructuredText string.
- Returns:
the final prettified, formatted HTML
string
.- Return type:
- howdy.core.core.store_cloudconvert_credentials(clientID, clientTOKEN)
stores the CloudConvert API V2 credentials into the SQLite3 configuration database.
- Parameters:
clientID (str) – the CloudConvert API V2 client ID. This is the name on the CloudConvert dashboard’s API V2 key name. For example, for myself it is named
TanimIslamCloudConvert
.clientTOKEN (str) – the CloudConvert API V2 client token.
- Returns:
the string
"SUCCESS"
if could store the new CloudConvert credentials. Otherwise, the string'ERROR, COULD NOT STORE CLOUDCONVERT API V2 CREDENTIALS.'
.- Return type:
See also
- howdy.core.core.store_imgurl_credentials(clientID, clientSECRET, clientREFRESHTOKEN, verify=True, mainALBUMID=None, mainALBUMNAME=None)
stores the Imgur API credentials into the SQLite3 configuration database.
- Parameters:
- Returns:
the string
"SUCCESS"
if could store the new Imgur credentials. Otherwise, the string'ERROR, COULD NOT STORE IMGURL CREDENTIALS.'
.- Return type:
See also
- howdy.core.core.store_jackett_credentials(url, apikey, verify=True)
stores the Jackett server’s API credentials into the SQLite3 configuration database.
- Parameters:
- Returns:
the string
"SUCCESS"
if could store the new Jackett server credentials. Otherwise, some illuminating error message.- Return type:
See also
4.3.3. howdy.core.core_deluge module
This module implements the functionality to interact with a Seedhost seedbox Deluge torrent server, by copying a minimal set of the functionality of a Deluge torrent client. The data formatting in this module is largely or wholly copied from the Deluge SDK. The much reduced Deluge torrent client, howdy_deluge_console, is a CLI front-end to this module.
- howdy.core.core_deluge.create_deluge_client(url, port, username, password)
Creates a minimal Deluge torrent client to the Deluge seedbox server.
- Parameters:
- Returns:
previously a lightweight Deluge RPC client, although now it is a
DelugeRPCClient
.
- howdy.core.core_deluge.deluge_add_magnet_file(client, magnet_uri)
Uploads a Magnet URI to the Deluge server through the Deluge RPC client.
- Parameters:
client – the Deluge RPC client.
magnet_uri (str) – the Magnet URI to upload.
- Returns:
if successful, returns the MD5 hash of the uploaded torrent as a
str
. If unsuccessful, returnsNone
.
- howdy.core.core_deluge.deluge_add_torrent_file(client, torrent_file_name)
Higher level method that takes a torrent file on disk and uploads to the Deluge server through the Deluge RPC client.
- Parameters:
client – the Deluge RPC client.
torrent_file_name (str) – name of the candidate file.
- Returns:
if successful, returns the MD5 hash of the uploaded torrent as a
str
. If unsuccessful, returnsNone
.
See also
- howdy.core.core_deluge.deluge_add_torrent_file_as_data(client, torrent_file_name, torrent_file_data)
Higher level method that takes a torrent file name, and its byte data representation, and uploads to the Deluge server through the Deluge RPC client.
- Parameters:
client – the Deluge RPC client.
torrent_file_name (str) – name of the candidate file.
torrent_file_data (byte) – byte representation of the torrent file data.
- Returns:
if successful, returns the MD5 hash of the uploaded torrent as a
str
. If unsuccessful, returnsNone
.
See also
- howdy.core.core_deluge.deluge_add_url(client, torrent_url)
Adds a torrent file via URL to the Deluge server through the Deluge RPC client. If the URL is valid, then added. If the URL is invalid, then nothing happens.
- Parameters:
client – the Deluge RPC client.
torrent_url (str) – candidate URL.
- howdy.core.core_deluge.deluge_format_info(status, torrent_id)
Returns a nicely formatted representation of the status of a torrent.
>>> print( '%s' % deluge_format_info( status, 'ed53ba61555cab24946ebf2f346752805601a7fb' ) ) Name: ubuntu-19.10-beta-desktop-amd64.iso ID: ed53ba61555cab24946ebf2f346752805601a7fb State: Downloading Down Speed: 73.4 MiB/s Up Speed: 0.0 KiB/s ETA: 0 days 00:00:23 Seeds: 24 (67) Peers: 1 (4) Availability: 24.22 Size: 474.5 MiB/2.1 GiB Ratio: 0.000 Seed time: 0 days 00:00:00 Active: 0 days 00:00:05 Tracker status: ubuntu.com: Announce OK Progress: 21.64% [##################################~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]
- Parameters:
status (dict) – the status
dict
for a given torrent, generated fromdeluge_get_torrents_info
.torrent_id (str) – the MD5 hash of that torrent.
- Returns:
a nicely formatted representation of that torrent on the Deluge server.
- Return type:
See also
- howdy.core.core_deluge.deluge_get_matching_torrents(client, torrent_id_strings)
Given a
list
of possibly truncated MD5 hashes of candidate torrents on the Deluge server, returns alist
of MD5 sums of torrents that match what was provided.- Parameters:
client – the Deluge RPC client.
torrent_id_strings – the candidate
list
of truncated MD5 hashes on the Deluge server. The[ '*' ]
input means to look for all torrents on the Deluge server.
- Returns:
a
list
of candidate torrents, as their MD5 hash, tat matchtorrent_id_strings
. Iftorrent_id_strings == ['*']
, then return all the torrents (as MD5 hashes) on the Deluge server.- Return type:
- howdy.core.core_deluge.deluge_get_torrents_info(client)
Returns a
dict
of status info for every torrent on the Deluge server through the Deluge RPC client. The key in thisdict
is the MD5 hash of the torrent, and its value is a statusdict
. For each torrent, here are the keys in the statusdict
:active_time
,all_time_download
,distributed_copies
,download_location
,download_payload_rate
,eta
,file_priorities
,file_progress
,files
,is_finished
,is_seed
,last_seen_complete
,name
,next_announce
,num_peers
,num_pieces
,num_seeds
,peers
,piece_length
,progress
,ratio
,seed_rank
,seeding_time
,state
,time_added
,time_since_transfer
,total_done
,total_payload_download
,total_payload_upload
,total_peers
,total_seeds
,total_size
,total_uploaded
,tracker_host
,tracker_status
,upload_payload_rate
.- Parameters:
client – the Deluge RPC client.
- Returns:
a
dict
of statusdict
for each torrent on the Deluge server.- Return type:
- howdy.core.core_deluge.deluge_is_torrent_file(torrent_file_name)
Check if a file is a torrent file.
- howdy.core.core_deluge.deluge_is_url(torrent_url)
Checks whether an URL is valid, following this prescription.
- howdy.core.core_deluge.deluge_pause_torrent(client, torrent_ids)
Pauses torrents on the Deluge server. Unlike other methods here, this does not use the Deluge RPC client lower-level RPC calls, but system command line calls to the deluge-console client. If the deluge-console executable cannot be found, then this does nothing. I do not know the Deluge RPC client is not working.
- Parameters:
client – the Deluge RPC client. In this case, only the configuration info (username, password, URL, and port) are used.
torrent_ids –
list
of MD5 hashes on the Deluge server.
- howdy.core.core_deluge.deluge_remove_torrent(client, torrent_ids, remove_data=False)
Remove torrents from the Deluge server through the Deluge RPC client.
- Parameters:
client – the Deluge RPC client.
torrent_ids –
list
of MD5 hashes on the Deluge server.remove_data (bool) – if
True
, remove the torrent and delete all data associated with the torrent on disk. IfFalse
, just remove the torrent.
- howdy.core.core_deluge.deluge_resume_torrent(client, torrent_ids)
Resumes torrents on the Deluge server. Unlike other methods here, this does not use the Deluge RPC client lower-level RPC calls, but system command line calls to the deluge-console client. If the deluge-console executable cannot be found, then this does nothing. I do not know the Deluge RPC client is not working.
- Parameters:
client – the Deluge RPC client. In this case, only the configuration info (username, password, URL, and port) are used.
torrent_ids –
list
of MD5 hashes on the Deluge server.
- howdy.core.core_deluge.format_progressbar(progress, width)
Returns a string of a progress bar. This code has been copied from
deluge's format_progressbar
.- Parameters:
progress (float) – a value between 0-100.
- Returns:
str, a progress bar based on width.
- Return type:
Usage
>>> format_progressbar( 87.6, 100 ) '[######################################################################################~~~~~~~~~~~~]'
- howdy.core.core_deluge.format_size(fsize_b)
Formats the bytes value into a string with KiB, MiB or GiB units. This code has been copied from
deluge's format_size
.- Parameters:
fsize_b (int) – the filesize in bytes.
- Returns:
formatted string in KiB, MiB or GiB units.
- Return type:
Usage
>>> format_size( 112245 ) '109.6 KiB'
- howdy.core.core_deluge.format_speed(bps)
Formats a string to display a transfer speed utilizing
fsize()
. This is code has been copied fromdeluge's format_speed
.- Parameters:
bps (int) – bytes per second.
- Returns:
a formatted string representing transfer speed
- Return type:
Usage
>>> format_speed( 43134 ) '42.1 KiB/s'
- howdy.core.core_deluge.format_time(seconds)
Formats the time, in seconds, to a nice format. Unfortunately, the
datetime
class is too unwieldy for this type of formatting. This code is copied fromdeluge's format_time
.- Parameters:
seconds (int) – number of seconds.
- Returns:
formatted string in the form of
1 days 03:05:04
.- Return type:
Usage
>>> format_time( 97262 ) '1 days 03:01:02'
- howdy.core.core_deluge.get_deluge_client()
Using a minimal Deluge torrent client from server credentials stored in the SQLite3 configuration database.
- Returns:
a
tuple
. If successful, the first element is a lightweight Deluge RPC client and the second element is the string'SUCCESS'
. If unsuccessful, the first element isNone
and the second element is an error string.- Return type:
- howdy.core.core_deluge.get_deluge_credentials()
Gets the Deluge server credentials from the SQLite3 configuration database. The data looks like this.
{ 'url': XXXX, 'port': YYYY, 'username': AAAA, 'password': BBBB }
- Returns:
dictionary of Deluge server settings.
- Return type:
- howdy.core.core_deluge.push_deluge_credentials(url, port, username, password)
Stores the Deluge server credentials into the SQLite3 configuration database.
- Parameters:
- Returns:
if successful (due to correct Deluge server settings), returns
'SUCCESS'
. If unsuccessful, returns'ERROR, INVALID SETTINGS FOR DELUGE CLIENT.'
- Return type:
4.3.4. howdy.core.core_rsync module
This module implements the functionality to interact with a Seedhost seedbox SSH server to download or upload files and directories using the rsync protocol tunneled through SSH. rsync_subproc is a CLI front-end to this module.
- howdy.core.core_rsync.check_credentials(local_dir, sshpath, password, subdir=None)
Checks whether one can download to (or upload from) the directory
local_dir
on the Plex server to the remote SSH server.- Parameters:
local_dir (str) – the local directory, on the Plex server, into which to download files from the remote SSH server.
sshpath (str) – the full path with username and host name for the SSH server. Format is
'username@hostname'
.password (str) – the password to connect as the SSH server.
subdir (str) – if not
None
, the subdirectory on the remote SSH server from which to download files.
- Returns:
if everything works, return
'SUCCESS'
. If fails, return specific illuminating error messages.- Return type:
See also
- howdy.core.core_rsync.download_upload_files(glob_string, numtries=10, debug_string=False, do_reverse=False, do_local_rsync=False)
Run the system process, using rsync, to download files and directories from, or upload to, the remote SSH server. On completion, the source files are all deleted.
- Parameters:
glob_string (str) – the description of files and directories (such as
'*.mkv'
to represent all MKV files) to upload/download.numtries (int) – the number of attempts to run rsync before giving up on uploading or downloading.
debug_string (bool) – if
True
, then print out the password-stripped rsync command that is being run.do_reverse (bool) – if
True
, then upload files to remote SSH server. IfFalse
(the default), then download files from the SSH server.do_local_rsync (bool) – if
True
, then instead of SSH-ing do an effectively move command from path =os.path.expanduser( "~", data[ 'subdir' ], mystr )
intodata['local_dir']
via rsync. This requires that data[‘subdir’] is notNone
.
- Returns:
a
tuple
of “SUCCESS” and the log of rsync command, otherwise “FAILURE” and specific reason for failure.- Return type:
- howdy.core.core_rsync.get_credentials()
Returns the rsync’ing setup from the SQLite3 configuration database as a
dict
in the following form.{ 'local_dir' : XXXX, 'sshpath' : YYYY@ZZZZ, 'password' : AAAA, 'subdir' : BBBB }
- Returns:
the
dict
of the rsync’ing setup if in the SQLite3 configuration database, otherwiseNone
.- Return type:
See also
- howdy.core.core_rsync.get_rsync_command(data, mystr, do_download=True, do_local_rsync=False)
Returns a
tuple
of the actual rsync command, and the command with password information obscured, to download from or upload to the remote SSH server from the Plex server. We require that sshpass_ exists and is accessible on this system.- Parameters:
data – the
dict
of rsync’ing configuration, described inget_credentials
.mystr (str) – the specific rsync syntax describing those files/folders to upload or download. See, e.g., this website for some practical examples of file or directory listings.
do_download (bool) – if
True
, then download from remote SSH server. IfFalse
, then upload to remote SSH server.do_local_rsync (bool) – if
True
, then instead of SSH-ing do an effectively move command from path =os.path.expanduser( os.path.join( "~", data[ 'subdir' ], mystr ) )
intodata['local_dir']
via rsync. This requires that data[‘subdir’] is notNone
.
- Returns:
a
tuple
of the actual rsync command, and the command with password information obscured, to download from or upload to the remote SSH server from the Plex server.- Return type:
- howdy.core.core_rsync.push_credentials(local_dir, sshpath, password, subdir=None)
Push the rsync’ing setup (
local_dir
,sshpath
,password
, andsubdir
), if working, into the SQLite3 configuration database.- Parameters:
local_dir (str) – the local directory, on the Plex server, into which to download files from the remote SSH server.
sshpath (str) – the full path with username and host name for the SSH server. Format is
'username@hostname'
.password (str) – the password to connect as the SSH server.
subdir (str) – if not
None
, the subdirectory on the remote SSH server from which to download files.
- Returns:
if successful, return
'SUCCESS'
. If not, return error messages.- Return type:
See also
4.3.5. howdy.core.core_torrents module
This is a newer module. It implements higher level interfaces to the Jackett torrent searching server to search for ebooks. get_book_tor is its only CLI front-end.
- howdy.core.core_torrents.deconfuse_magnet_link(magnet_string, excluded_tracker_stubs=['stealth.si'])
First functional implementation that returns a magnet string given a
list
of torrent tracker stubs to ignore.If one has an invalid magnet_string, then return
None
.- Parameters:
magnet_string (str) – the initial magnet string.
excluded_tracker_stubs (list) – the
list
of torrent tracker stubs to ignore. Default isget_trackers_to_exclude
.
:returns a
str
of a magnet string with the problematic torrent tracker stubs inexcluded_tracker_stubs
removed. Ifmagnet_string
is not a valid magnet string, returnNone
. :rtype: str
- howdy.core.core_torrents.get_book_torrent_jackett(name, maxnum=10, keywords=[], verify=True)
Returns a
tuple
of candidate book Magnet links found using the main Jackett torrent searching service and the string"SUCCESS"
, if successful.- Parameters:
name (str) – the book to search for.
maxnum (int) – optional argumeent, the maximum number of magnet links to return. Default is 10. Must be \(\ge 5\).
keywords (list) – optional argument. If not empty, the title of the candidate element must have at least one of the keywords in
keywords
.verify (bool) – optional argument, whether to verify SSL connections. Default is
True
.
- Returns:
if successful, then returns a two member
tuple
the first member is alist
of elements that match the searched episode, ordered from most seeds and leechers to least. The second element is the string"SUCCESS"
. The keys in each element of the list are,title
is the name of the candidate book to download, and in parentheses the size of the candidate in MB or GB.rawtitle
also the name of the candidate episode to download.seeders
is the number of seeds for this Magnet link.leechers
is the number of leeches for this Magnet link.link
is the Magnet URI link.torrent_size
is the size of this torrent in Megabytes.
If this is unsuccessful, then returns an error
tuple
of the form returned byreturn_error_raw
.- Return type:
- howdy.core.core_torrents.get_trackers_to_exclude()
Returns the
set
of torrent tracker stubs to exclude from the reconstruction of magnet links.
- howdy.core.core_torrents.push_trackers_to_exclude(tracker_stubs)
Adds a collection of new torrent tracker stubs to the
PlexExcludedTrackerStubs
database.- Parameters:
tracker_stubs (list) – the collection of torrent tracker stubs to add to the database.
- howdy.core.core_torrents.remove_trackers_to_exclude(tracker_stubs)
Removes a collection of candidate torrent tracker stubs to the
PlexExcludedTrackerStubs
database.- Parameters:
tracker_stubs (list) – the collection of torrent tracker stubs to remove from the database.
4.3.6. howdy.core.core_admin module
This is a newer module. It implements the low-level tooling to monitor the Tautulli server that looks at the Plex server, and to check for updates, and download updates to, the current Plex server.
- howdy.core.core_admin.get_tautulli_activity(endpoint, apikey)
- howdy.core.core_admin.get_tautulli_apikey(username, password, endpoint)
Gets the tautulli API key with provided Tautulli username and password.
- howdy.core.core_admin.plex_check_for_update(token, fullURL='http://localhost:32400')
Determines whether there are any new Plex server releases.
- howdy.core.core_admin.plex_download_release(release, token, destination_dir='/mnt/software/sources/pythonics/howdy/docsrc', do_progress=False)
Downloads the Plex update into a specific directory, with optional progress bar.
- Parameters:
- Returns:
If unsuccessful an error message. If successful, the full path of the downloaded file.
- Return type:
4.3.7. howdy.core.deluge_client_tanim module
This is a newer module. This is a copy of the deluge-client repository and module. Instead of creating a special branch, I had to modify its DelugeRPCClient
object to work in a custom way, because of much stricter SSL policies in Python 3.10.
The relevant code that I modify now lives in my DelugeRPCClient
. I followed advice from this StackOverflow article on how to set ciphers in SSL Python sockets. I created an SSL context, allowed all ciphers, and then passed the all-ciphers list into the low-level socket that performs RPC communications.
- exception howdy.core.deluge_client_tanim.client.CallTimeoutException
- exception howdy.core.deluge_client_tanim.client.ConnectionLostException
- exception howdy.core.deluge_client_tanim.client.DelugeClientException
Base exception for all deluge client exceptions
- exception howdy.core.deluge_client_tanim.client.FailedToReconnectException
- exception howdy.core.deluge_client_tanim.client.InvalidHeaderException
- class howdy.core.deluge_client_tanim.client.LocalDelugeRPCClient(host='127.0.0.1', port=58846, username='', password='', decode_utf8=True, automatic_reconnect=True)
Client with auto discovery for the default local credentials
- exception howdy.core.deluge_client_tanim.client.RemoteException
4.3.7.1. howdy.core.deluge_client_tanim.rencode submodule
rencode – Web safe object pickling/unpickling.
Public domain, Connelly Barnes 2006-2007.
The rencode module is a modified version of bencode from the BitTorrent project. For complex, heterogeneous data structures with many small elements, r-encodings take up significantly less space than b-encodings:
>>> len(rencode.dumps({'a':0, 'b':[1,2], 'c':99}))
13
>>> len(bencode.bencode({'a':0, 'b':[1,2], 'c':99}))
26
The rencode format is not standardized, and may change with different rencode module versions, so you should check that you are using the same rencode version throughout your project.
- howdy.core.deluge_client_tanim.rencode.dumps(x, float_bits=32)
Dump data structure to str.
Here float_bits is either 32 or 64.