From 8114079eacda0588f9ac9b1083d067a876634e98 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Thu, 11 Aug 2022 09:46:15 -0700 Subject: [PATCH 001/583] Fix album count showing 0 --- plexpy/pmsconnect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py index 45597b18..7149836d 100644 --- a/plexpy/pmsconnect.py +++ b/plexpy/pmsconnect.py @@ -2740,7 +2740,7 @@ class PmsConnect(object): return [] elif str(section_id).isdigit() or section_type == 'album': - if section_type == 'album': + if section_type == 'album' and rating_key: sort_type += '&artist.id=' + str(rating_key) xml_head = self.fetch_library_list( From 85dda97e52c828a86b67e75b7e38f77a5dd265c2 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Thu, 18 Aug 2022 10:02:32 -0700 Subject: [PATCH 002/583] Fix library stats not shown for libraries without history * Fixes #1818 --- plexpy/datafactory.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plexpy/datafactory.py b/plexpy/datafactory.py index eb4c18ae..09b26a45 100644 --- a/plexpy/datafactory.py +++ b/plexpy/datafactory.py @@ -1048,8 +1048,8 @@ class DataFactory(object): 'shm.art, sh.media_type, shm.content_rating, shm.labels, shm.live, shm.guid, ' \ 'MAX(sh.started) AS last_watch ' \ 'FROM library_sections AS ls ' \ - 'JOIN session_history AS sh ON ls.section_id = sh.section_id ' \ - 'JOIN session_history_metadata AS shm ON sh.id = shm.id ' \ + 'LEFT OUTER JOIN session_history AS sh ON ls.section_id = sh.section_id ' \ + 'LEFT OUTER JOIN session_history_metadata AS shm ON sh.id = shm.id ' \ 'WHERE ls.section_id IN (%s) AND ls.deleted_section = 0 ' \ 'GROUP BY ls.id ' \ 'ORDER BY ls.section_type, ls.count DESC, ls.parent_count DESC, ls.child_count DESC ' % ','.join(library_cards) @@ -1084,9 +1084,9 @@ class DataFactory(object): 'count': item['count'], 'child_count': item['parent_count'], 'grandchild_count': item['child_count'], - 'thumb': thumb, - 'grandparent_thumb': item['grandparent_thumb'], - 'art': item['art'], + 'thumb': thumb or '', + 'grandparent_thumb': item['grandparent_thumb'] or '', + 'art': item['art'] or '', 'title': item['full_title'], 'grandparent_title': item['grandparent_title'], 'grandchild_title': item['title'], From c3572f3212f31c45a9b194785ea1823ecde05e06 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Thu, 18 Aug 2022 10:11:47 -0700 Subject: [PATCH 003/583] Add workflow dispatch to workflows [skip ci] --- .github/workflows/publish-docker.yml | 1 + .github/workflows/publish-installers.yml | 1 + .github/workflows/publish-snap.yml | 1 + 3 files changed, 3 insertions(+) diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml index 8fc0be89..39533cb6 100644 --- a/.github/workflows/publish-docker.yml +++ b/.github/workflows/publish-docker.yml @@ -1,6 +1,7 @@ name: Publish Docker on: + workflow_dispatch: ~ push: branches: [master, beta, nightly] tags: [v*] diff --git a/.github/workflows/publish-installers.yml b/.github/workflows/publish-installers.yml index 1098ee01..ffb1f2ee 100644 --- a/.github/workflows/publish-installers.yml +++ b/.github/workflows/publish-installers.yml @@ -1,6 +1,7 @@ name: Publish Installers on: + workflow_dispatch: ~ push: branches: [master, beta, nightly] tags: [v*] diff --git a/.github/workflows/publish-snap.yml b/.github/workflows/publish-snap.yml index df3c3475..65fb9b17 100644 --- a/.github/workflows/publish-snap.yml +++ b/.github/workflows/publish-snap.yml @@ -1,6 +1,7 @@ name: Publish Snap on: + workflow_dispatch: ~ push: branches: [master, beta, nightly] tags: [v*] From b73f8cc30e31e06fce6d2b7fce408fede40083c4 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Thu, 18 Aug 2022 10:14:14 -0700 Subject: [PATCH 004/583] Remove unused architectures in snap workflow --- .github/workflows/publish-snap.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/publish-snap.yml b/.github/workflows/publish-snap.yml index 65fb9b17..26fb174c 100644 --- a/.github/workflows/publish-snap.yml +++ b/.github/workflows/publish-snap.yml @@ -15,12 +15,9 @@ jobs: fail-fast: false matrix: architecture: - - i386 - amd64 - arm64 - armhf - - ppc64el - #- s390x # broken at the moment steps: - name: Checkout Code uses: actions/checkout@v3.0.2 From 84fb1a2dc24d342b64a7170eaa104c9581eddd2c Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Thu, 18 Aug 2022 16:35:06 -0700 Subject: [PATCH 005/583] Add quality profile tooltip --- data/interfaces/default/current_activity_instance.html | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/data/interfaces/default/current_activity_instance.html b/data/interfaces/default/current_activity_instance.html index 3ce0fb05..2c24c233 100644 --- a/data/interfaces/default/current_activity_instance.html +++ b/data/interfaces/default/current_activity_instance.html @@ -160,7 +160,8 @@ DOCUMENTATION :: END
  • Quality
    -
    +
    + % if data['media_type'] != 'photo' and data['quality_profile'] != 'Unknown': <% br = cast_to_int(data['stream_bitrate']) or '' @@ -174,6 +175,8 @@ DOCUMENTATION :: END % else: ${data['quality_profile']} % endif + +
  • % if data['optimized_version'] == 1: From b2777e30f2d9d5f76e16a6ec9c7d7036a12bbc45 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Thu, 18 Aug 2022 16:40:38 -0700 Subject: [PATCH 006/583] Add git safe directory to snapcraft.yml --- snap/snapcraft.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 5cdeeeb9..82171392 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -28,6 +28,7 @@ parts: - git override-pull: | snapcraftctl pull + git config --global --add safe.directory /data/parts/tautulli/src TAG_FULL=$(git describe --tag) TAG=$(echo $TAG_FULL | grep -oP '(v\d+\.\d+\.\d+(?>-beta)?)') BRANCH=$(git rev-parse --abbrev-ref HEAD) From 3c40f83738a4c74b12a4374c0cfb5802e04f24f2 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Fri, 19 Aug 2022 10:36:10 -0700 Subject: [PATCH 007/583] Update filterer.jquery.js --- data/interfaces/default/js/filterer.jquery.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/data/interfaces/default/js/filterer.jquery.js b/data/interfaces/default/js/filterer.jquery.js index efab9afa..16458f13 100644 --- a/data/interfaces/default/js/filterer.jquery.js +++ b/data/interfaces/default/js/filterer.jquery.js @@ -1,9 +1,10 @@ -!function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return e[r].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var n={};return t.m=e,t.c=n,t.p="/filterer/",t(0)}(function(e){for(var t in e)if(Object.prototype.hasOwnProperty.call(e,t))switch(typeof e[t]){case"function":break;case"object":e[t]=function(t){var n=t.slice(1),r=e[t[0]];return function(e,t,o){r.apply(this,[e,t,o].concat(n))}}(e[t]);break;default:e[t]=e[e[t]]}return e}([function(e,t,n){n(181),e.exports=n(95)},function(e,t,n){"use strict";function r(e,t,n,r,i,a,s,u){if(o(t),!e){var l;if(void 0===t)l=new Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.");else{var c=[n,r,i,a,s,u],p=0;l=new Error(t.replace(/%s/g,function(){return c[p++]})),l.name="Invariant Violation"}throw l.framesToPop=1,l}}var o=function(e){};e.exports=r},function(e,t,n){"use strict";var r=n(8),o=r;e.exports=o},function(e,t){"use strict";function n(e){for(var t=arguments.length-1,n="Minified React error #"+e+"; visit http://facebook.github.io/react/docs/error-decoder.html?invariant="+e,r=0;r1?function(){var i=o?o.concat():[];return n=t?n||this:this,i.push.apply(i,arguments)1?function(){var i=o?o.concat():[];return n=t?n||this:this,i.push.apply(i,arguments)0?r({},e[n]):e[n],t[n])})(o),e))}),P=t(function(e,t,n){var r,o,i;return r=t[0],o=N.call(t,1),o.length>0?(e[r]=null!=(i=e[r])?i:{},P(e[r],o,n)):(e[r]=n,e)}),k=function(e){return d(function(t){return d(function(e){return e[t]})(e)})(f(e[0]))},S=t(function(e,n,r){var o;return(o=t(function(e,t,n,r,i){return s(function(i){var a,s;return a=i[0],s=i[1],n1){for(var m=Array(v),g=0;g1){for(var b=Array(y),C=0;C]/;e.exports=r},function(e,t,n){"use strict";var r,o=n(7),i=n(38),a=/^[ \r\n\t\f]/,s=/<(!--|link|noscript|meta|script|style)[ \r\n\t\f\/>]/,u=n(46),l=u(function(e,t){if(e.namespaceURI!==i.svg||"innerHTML"in e)e.innerHTML=t;else{r=r||document.createElement("div"),r.innerHTML=""+t+"";for(var n=r.firstChild;n.firstChild;)e.appendChild(n.firstChild)}});if(o.canUseDOM){var c=document.createElement("div");c.innerHTML=" ",""===c.innerHTML&&(l=function(e,t){if(e.parentNode&&e.parentNode.replaceChild(e,e),a.test(t)||"<"===t[0]&&s.test(t)){e.innerHTML=String.fromCharCode(65279)+t;var n=e.firstChild;1===n.data.length?e.removeChild(n):n.deleteData(0,1)}else e.innerHTML=t}),c=null}e.exports=l},function(e,t){function n(){throw new Error("setTimeout has not been defined")}function r(){throw new Error("clearTimeout has not been defined")}function o(e){if(c===setTimeout)return setTimeout(e,0);if((c===n||!c)&&setTimeout)return c=setTimeout,setTimeout(e,0);try{return c(e,0)}catch(t){try{return c.call(null,e,0)}catch(t){return c.call(this,e,0)}}}function i(e){if(p===clearTimeout)return clearTimeout(e);if((p===r||!p)&&clearTimeout)return p=clearTimeout,clearTimeout(e);try{return p(e)}catch(t){try{return p.call(null,e)}catch(t){return p.call(this,e)}}}function a(){v&&d&&(v=!1,d.length?h=d.concat(h):m=-1,h.length&&s())}function s(){if(!v){var e=o(a);v=!0;for(var t=h.length;t;){for(d=h,h=[];++m1)for(var n=1;n-1?void 0:a("96",e),!l.plugins[n]){t.extractEvents?void 0:a("97",e),l.plugins[n]=t;var r=t.eventTypes;for(var i in r)o(r[i],t,i)?void 0:a("98",i,e)}}}function o(e,t,n){l.eventNameDispatchConfigs.hasOwnProperty(n)?a("99",n):void 0,l.eventNameDispatchConfigs[n]=e;var r=e.phasedRegistrationNames;if(r){for(var o in r)if(r.hasOwnProperty(o)){var s=r[o];i(s,t,n)}return!0}return!!e.registrationName&&(i(e.registrationName,t,n),!0)}function i(e,t,n){l.registrationNameModules[e]?a("100",e):void 0,l.registrationNameModules[e]=t,l.registrationNameDependencies[e]=t.eventTypes[n].dependencies}var a=n(3),s=(n(1),null),u={},l={plugins:[],eventNameDispatchConfigs:{},registrationNameModules:{},registrationNameDependencies:{},possibleRegistrationNames:null,injectEventPluginOrder:function(e){s?a("101"):void 0,s=Array.prototype.slice.call(e),r()},injectEventPluginsByName:function(e){var t=!1;for(var n in e)if(e.hasOwnProperty(n)){var o=e[n];u.hasOwnProperty(n)&&u[n]===o||(u[n]?a("102",n):void 0,u[n]=o,t=!0)}t&&r()},getPluginModuleForEvent:function(e){var t=e.dispatchConfig;if(t.registrationName)return l.registrationNameModules[t.registrationName]||null;if(void 0!==t.phasedRegistrationNames){var n=t.phasedRegistrationNames;for(var r in n)if(n.hasOwnProperty(r)){var o=l.registrationNameModules[n[r]];if(o)return o}}return null},_resetEventPlugins:function(){s=null;for(var e in u)u.hasOwnProperty(e)&&delete u[e];l.plugins.length=0;var t=l.eventNameDispatchConfigs;for(var n in t)t.hasOwnProperty(n)&&delete t[n];var r=l.registrationNameModules;for(var o in r)r.hasOwnProperty(o)&&delete r[o]}};e.exports=l},function(e,t,n){"use strict";function r(e){return"topMouseUp"===e||"topTouchEnd"===e||"topTouchCancel"===e}function o(e){return"topMouseMove"===e||"topTouchMove"===e}function i(e){return"topMouseDown"===e||"topTouchStart"===e}function a(e,t,n,r){var o=e.type||"unknown-event";e.currentTarget=g.getNodeFromInstance(r),t?v.invokeGuardedCallbackWithCatch(o,n,e):v.invokeGuardedCallback(o,n,e),e.currentTarget=null}function s(e,t){var n=e._dispatchListeners,r=e._dispatchInstances;if(Array.isArray(n))for(var o=0;o0&&r.length<20?n+" (keys: "+r.join(", ")+")":n}function i(e,t){var n=s.get(e);if(!n){return null}return n}var a=n(3),s=(n(13),n(27)),u=(n(9),n(10)),l=(n(1),n(2),{isMounted:function(e){var t=s.get(e);return!!t&&!!t._renderedComponent},enqueueCallback:function(e,t,n){l.validateCallback(t,n);var o=i(e);return o?(o._pendingCallbacks?o._pendingCallbacks.push(t):o._pendingCallbacks=[t],void r(o)):null},enqueueCallbackInternal:function(e,t){e._pendingCallbacks?e._pendingCallbacks.push(t):e._pendingCallbacks=[t],r(e)},enqueueForceUpdate:function(e){var t=i(e,"forceUpdate");t&&(t._pendingForceUpdate=!0,r(t))},enqueueReplaceState:function(e,t,n){var o=i(e,"replaceState");o&&(o._pendingStateQueue=[t],o._pendingReplaceState=!0,void 0!==n&&null!==n&&(l.validateCallback(n,"replaceState"),o._pendingCallbacks?o._pendingCallbacks.push(n):o._pendingCallbacks=[n]),r(o))},enqueueSetState:function(e,t){var n=i(e,"setState");if(n){var o=n._pendingStateQueue||(n._pendingStateQueue=[]);o.push(t),r(n)}},enqueueElementInternal:function(e,t,n){e._pendingElement=t,e._context=n,r(e)},validateCallback:function(e,t){e&&"function"!=typeof e?a("122",t,o(e)):void 0}});e.exports=l},function(e,t){"use strict";var n=function(e){return"undefined"!=typeof MSApp&&MSApp.execUnsafeLocalFunction?function(t,n,r,o){MSApp.execUnsafeLocalFunction(function(){return e(t,n,r,o)})}:e};e.exports=n},function(e,t){"use strict";function n(e){var t,n=e.keyCode;return"charCode"in e?(t=e.charCode,0===t&&13===n&&(t=13)):t=n,t>=32||13===t?t:0}e.exports=n},function(e,t){"use strict";function n(e){var t=this,n=t.nativeEvent;if(n.getModifierState)return n.getModifierState(e);var r=o[e];return!!r&&!!n[r]}function r(e){return n}var o={Alt:"altKey",Control:"ctrlKey",Meta:"metaKey",Shift:"shiftKey"};e.exports=r},function(e,t){"use strict";function n(e){var t=e.target||e.srcElement||window;return t.correspondingUseElement&&(t=t.correspondingUseElement),3===t.nodeType?t.parentNode:t}e.exports=n},function(e,t,n){"use strict";function r(e,t){if(!i.canUseDOM||t&&!("addEventListener"in document))return!1;var n="on"+e,r=n in document;if(!r){var a=document.createElement("div");a.setAttribute(n,"return;"),r="function"==typeof a[n]}return!r&&o&&"wheel"===e&&(r=document.implementation.hasFeature("Events.wheel","3.0")),r}var o,i=n(7);i.canUseDOM&&(o=document.implementation&&document.implementation.hasFeature&&document.implementation.hasFeature("","")!==!0),e.exports=r},function(e,t){"use strict";function n(e,t){var n=null===e||e===!1,r=null===t||t===!1;if(n||r)return n===r;var o=typeof e,i=typeof t;return"string"===o||"number"===o?"string"===i||"number"===i:"object"===i&&e.type===t.type&&e.key===t.key}e.exports=n},function(e,t,n){"use strict";var r=(n(4),n(8)),o=(n(2),r);e.exports=o},function(e,t,n){(function(){function t(e,t){var n={}.hasOwnProperty;for(var r in t)n.call(t,r)&&(e[r]=t[r]);return e}function r(e,t){for(var n=-1,r=t.length>>>0;++n0&&!this.props.hideResetButton?x({className:"react-selectize-reset-button-container",onClick:function(e){return function(){return a.props.onValuesChange([],function(){return a.props.onSearchChange("",function(){return a.highlightAndFocus()})})}(),j(e)}},this.props.renderResetButton()):void 0,x({className:"react-selectize-toggle-button-container",onMouseDown:function(e){return a.props.open?a.onOpenChange(!1,function(){}):a.props.onAnchorChange(p(a.props.values),function(){return a.onOpenChange(!0,function(){})}),j(e)}},this.props.renderToggleButton({open:this.props.open,flipped:r}))),D((o=t({},this.props),o.ref="dropdownMenu",o.className=B((i={"react-selectize":1},i[this.props.className+""]=1,i)),o.theme=this.props.theme,o.scrollLock=this.props.scrollLock,o.onScrollChange=this.props.onScrollChange,o.bottomAnchor=function(){return M(a.refs.control)},o.tetherProps=(i=t({},this.props.tetherProps),i.target=function(){return M(a.refs.control)},i),o.highlightedUid=this.props.highlightedUid,o.onHighlightedUidChange=this.props.onHighlightedUidChange,o.onOptionClick=function(t){a.selectHighlightedUid(e,function(){})},o)))},handleKeydown:function(e,t){var n,o,i,a=this;switch(n=e.anchorIndex,t.persist(),t.which){case 8:if(this.props.search.length>0||n===-1)return;!function(){var e,t,r,o;return e=n,t=n-1<0?void 0:a.props.values[n-1],r=a.props.values[n],a.props.onValuesChange(null!=(o=v(function(e){return a.isEqualToObject(e,r)})(a.props.values))?o:[],function(){return function(){return function(e){return"undefined"==typeof s(function(e){return a.isEqualToObject(e,r)},a.props.values)?a.props.restoreOnBackspace?a.props.onSearchChange(a.props.restoreOnBackspace(r),function(){return e(!0)}):e(!0):e(!1)}}()(function(r){if(r&&(a.highlightAndScrollToSelectableOption(a.props.firstOptionIndexToHighlight(a.props.options),1),n===e&&("undefined"==typeof t||s(function(e){return a.isEqualToObject(e,t)})(a.props.values))))return a.props.onAnchorChange(t,function(){})})})}(),j(t);break;case 27:!function(){return a.props.open?function(e){return a.onOpenChange(!1,e)}:function(e){return a.props.onValuesChange([],e)}}()(function(){return a.props.onSearchChange("",function(){return a.focusOnInput()})})}if(this.props.open&&r(t.which,[13].concat(this.props.delimiters))&&!(null!=t&&t.metaKey||null!=t&&t.ctrlKey||null!=t&&t.shiftKey)&&(o=this.selectHighlightedUid(n,function(e){if("undefined"==typeof e)return a.props.onKeyboardSelectionFailed(t.which)}),o&&this.props.cancelKeyboardEventOnSelection))return j(t);if(0===this.props.search.length)switch(t.which){case 37:this.props.onAnchorChange(n-1<0||t.metaKey?void 0:this.props.values[_(n-1,0,this.props.values.length-1)],function(){});break;case 39:this.props.onAnchorChange(t.metaKey?p(this.props.values):this.props.values[_(n+1,0,this.props.values.length-1)],function(){})}switch(t.which){case 38:return this.props.onScrollLockChange(!0),i=function(){switch(!1){case"undefined"!=typeof this.props.highlightedUid:return 0;default:return-1+this.optionIndexFromUid(this.props.highlightedUid)}}.call(this),this.highlightAndScrollToSelectableOption(i,-1,function(e){if(!e)return a.highlightAndScrollToSelectableOption(a.props.options.length-1,-1)});case 40:return this.props.onScrollLockChange(!0),i=function(){switch(!1){case"undefined"!=typeof this.props.highlightedUid:return 0;default:return 1+this.optionIndexFromUid(this.props.highlightedUid)}}.call(this),this.highlightAndScrollToSelectableOption(i,1,function(e){if(!e)return a.highlightAndScrollToSelectableOption(0,1)})}},componentDidMount:function(){this.props.autofocus&&this.focus(),this.props.open&&this.highlightAndFocus()},componentDidUpdate:function(e){this.props.open&&!e.open&&void 0===this.props.highlightedUid&&this.highlightAndFocus(),!this.props.open&&e.open&&this.props.onHighlightedUidChange(void 0,function(){})},componentWillReceiveProps:function(e){"undefined"!=typeof this.props.disabled&&this.props.disabled!==!1||"undefined"==typeof e.disabled||e.disabled!==!0||this.onOpenChange(!1,function(){})},optionIndexFromUid:function(e){var t=this;return u(function(n){return w(e,t.props.uid(n))})(this.props.options)},closeDropdown:function(e){var t=this;this.onOpenChange(!1,function(){return t.props.onAnchorChange(p(t.props.values),e)})},blur:function(){this.refs.search.blur()},focus:function(){this.refs.search.focus()},focusOnInput:function(){var e;e=M(this.refs.search),e!==document.activeElement&&(this.focusLock=!0,e.focus(),e.value=e.value)},highlightAndFocus:function(){this.highlightAndScrollToSelectableOption(this.props.firstOptionIndexToHighlight(this.props.options),1),this.focusOnInput()},highlightAndScrollToOption:function(e,t){null==t&&(t=function(){}),this.refs.dropdownMenu.highlightAndScrollToOption(e,t)},highlightAndScrollToSelectableOption:function(e,t,n){var r=this;null==n&&(n=function(){}),function(){return r.props.open?function(e){return e()}:function(e){return r.onOpenChange(!0,e)}}()(function(){return r.refs.dropdownMenu.highlightAndScrollToSelectableOption(e,t,n)})},isEqualToObject:function(){return w(this.props.uid(arguments[0]),this.props.uid(arguments[1]))},onOpenChange:function(e,t){return this.props.onOpenChange(!this.props.disabled&&e,t)},selectHighlightedUid:function(e,t){var n,r,o=this;return void 0===this.props.highlightedUid?(t(),!1):(n=this.optionIndexFromUid(this.props.highlightedUid),"number"!=typeof n?(t(),!1):(r=this.props.options[n],function(){return o.props.onValuesChange(f(function(e){return o.props.values[e]},function(){var t,n,r=[];for(t=0,n=e;t<=n;++t)r.push(t);return r}()).concat([r],f(function(e){return o.props.values[e]},function(){var t,n,r=[];for(t=e+1,n=this.props.values.length;t.":"function"==typeof t?" Instead of passing a class like Foo, pass React.createElement(Foo) or .":null!=t&&void 0!==t.props?" This may be caused by unintentionally loading two independent copies of React.":"");var a,s=m.createElement(F,{child:t});if(e){var u=w.get(e);a=u._processChildContext(u._context)}else a=P;var c=f(n);if(c){var p=c._currentElement,h=p.props.child;if(N(h,t)){var v=c._renderedComponent.getPublicInstance(),g=r&&function(){r.call(v)};return j._updateRootComponent(c,s,a,n,g),v}j.unmountComponentAtNode(n)}var y=o(n),b=y&&!!i(y),C=l(n),_=b&&!c&&!C,E=j._renderNewRootComponent(s,n,_,a)._renderedComponent.getPublicInstance();return r&&r.call(E),E},render:function(e,t,n){return j._renderSubtreeIntoContainer(null,e,t,n)},unmountComponentAtNode:function(e){c(e)?void 0:d("40");var t=f(e);if(!t){l(e),1===e.nodeType&&e.hasAttribute(A);return!1}return delete L[t._instance.rootID],O.batchedUpdates(u,t,e,!1),!0},_mountImageIntoNode:function(e,t,n,i,a){if(c(t)?void 0:d("41"),i){var s=o(t);if(E.canReuseMarkup(e,s))return void y.precacheNode(n,s);var u=s.getAttribute(E.CHECKSUM_ATTR_NAME);s.removeAttribute(E.CHECKSUM_ATTR_NAME);var l=s.outerHTML;s.setAttribute(E.CHECKSUM_ATTR_NAME,u);var p=e,f=r(p,l),v=" (client) "+p.substring(f-20,f+20)+"\n (server) "+l.substring(f-20,f+20);t.nodeType===D?d("42",v):void 0}if(t.nodeType===D?d("43"):void 0,a.useCreateElement){for(;t.lastChild;)t.removeChild(t.lastChild);h.insertTreeBefore(t,e,null)}else S(t,e),y.precacheNode(n,t.firstChild)}};e.exports=j},function(e,t,n){"use strict";var r=n(3),o=n(12),i=(n(1),{HOST:0,COMPOSITE:1,EMPTY:2,getType:function(e){return null===e||e===!1?i.EMPTY:o.isValidElement(e)?"function"==typeof e.type?i.COMPOSITE:i.HOST:void r("26",e)}});e.exports=i},function(e,t){"use strict";var n={currentScrollLeft:0,currentScrollTop:0,refreshScrollValues:function(e){n.currentScrollLeft=e.x,n.currentScrollTop=e.y}};e.exports=n},function(e,t,n){"use strict";function r(e,t){return null==t?o("30"):void 0,null==e?t:Array.isArray(e)?Array.isArray(t)?(e.push.apply(e,t),e):(e.push(t),e):Array.isArray(t)?[e].concat(t):[e,t]}var o=n(3);n(1);e.exports=r},function(e,t){"use strict";function n(e,t,n){Array.isArray(e)?e.forEach(t,n):e&&t.call(n,e)}e.exports=n},function(e,t,n){"use strict";function r(e){for(var t;(t=e._renderedNodeType)===o.COMPOSITE;)e=e._renderedComponent;return t===o.HOST?e._renderedComponent:t===o.EMPTY?null:void 0}var o=n(73);e.exports=r},function(e,t,n){"use strict";function r(){return!i&&o.canUseDOM&&(i="textContent"in document.documentElement?"textContent":"innerText"),i}var o=n(7),i=null;e.exports=r},function(e,t,n){"use strict";function r(e,t){var n={};return n[e.toLowerCase()]=t.toLowerCase(),n["Webkit"+e]="webkit"+t,n["Moz"+e]="moz"+t,n["ms"+e]="MS"+t,n["O"+e]="o"+t.toLowerCase(),n}function o(e){if(s[e])return s[e];if(!a[e])return e;var t=a[e];for(var n in t)if(t.hasOwnProperty(n)&&n in u)return s[e]=t[n];return""}var i=n(7),a={animationend:r("Animation","AnimationEnd"),animationiteration:r("Animation","AnimationIteration"),animationstart:r("Animation","AnimationStart"),transitionend:r("Transition","TransitionEnd")},s={},u={};i.canUseDOM&&(u=document.createElement("div").style,"AnimationEvent"in window||(delete a.animationend.animation,delete a.animationiteration.animation,delete a.animationstart.animation),"TransitionEvent"in window||delete a.transitionend.transition),e.exports=o},function(e,t,n){"use strict";function r(e){if(e){var t=e.getName();if(t)return" Check the render method of `"+t+"`."}return""}function o(e){return"function"==typeof e&&"undefined"!=typeof e.prototype&&"function"==typeof e.prototype.mountComponent&&"function"==typeof e.prototype.receiveComponent}function i(e,t){var n;if(null===e||e===!1)n=l.create(i);else if("object"==typeof e){var s=e,u=s.type;if("function"!=typeof u&&"string"!=typeof u){var f="";f+=r(s._owner),a("130",null==u?u:typeof u,f)}"string"==typeof s.type?n=c.createInternalComponent(s):o(s.type)?(n=new s.type(s),n.getHostNode||(n.getHostNode=n.getNativeNode)):n=new p(s)}else"string"==typeof e||"number"==typeof e?n=c.createInstanceForText(e):a("131",typeof e);return n._mountIndex=0,n._mountImage=null,n}var a=n(3),s=n(4),u=n(132),l=n(68),c=n(70),p=(n(209),n(1),n(2),function(e){this.construct(e)});s(p.prototype,u,{_instantiateReactComponent:i}),e.exports=i},function(e,t){"use strict";function n(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return"input"===t?!!r[e.type]:"textarea"===t}var r={color:!0,date:!0,datetime:!0,"datetime-local":!0,email:!0,month:!0,number:!0,password:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0};e.exports=n},function(e,t,n){"use strict";var r=n(7),o=n(34),i=n(35),a=function(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&3===n.nodeType)return void(n.nodeValue=t)}e.textContent=t};r.canUseDOM&&("textContent"in document.documentElement||(a=function(e,t){return 3===e.nodeType?void(e.nodeValue=t):void i(e,o(t))})),e.exports=a},function(e,t,n){"use strict";function r(e,t){return e&&"object"==typeof e&&null!=e.key?l.escape(e.key):t.toString(36)}function o(e,t,n,i){var f=typeof e;if("undefined"!==f&&"boolean"!==f||(e=null),null===e||"string"===f||"number"===f||"object"===f&&e.$$typeof===s)return n(i,e,""===t?c+r(e,0):t),1;var d,h,v=0,m=""===t?c:t+p;if(Array.isArray(e))for(var g=0;gc){for(var t=0,n=s.length-l;t-1}).map(function(e,t){return l.default.createElement("option",{key:t,value:e.name},e.name)})}},{key:"getValues",value:function(e){return e?e.map(function(e){return{label:e,value:e}}):[]}},{key:"render",value:function(){var e=this,t=this.props.parameters.find(function(t){return t.value===e.props.condition.parameter});return this.props.condition.type=t?t.type:null,l.default.createElement("div",{className:this.props.classes.filterLineRow},l.default.createElement("div",{className:this.props.classes.filterLineParameter},l.default.createElement("select",{className:this.props.classes.filterLineInput,name:"parameter",value:this.props.condition.parameter,onChange:this.handleInputChange},l.default.createElement("option",{value:""},"-- Parameter --"),this.getCoefficients(this.props.parameters))),l.default.createElement("div",{className:this.props.classes.filterLineOperator},l.default.createElement("select",{className:this.props.classes.filterLineInput,name:"operator",value:this.props.condition.operator,onChange:this.handleInputChange},l.default.createElement("option",{disabled:!0,value:""},"-- Operator --"),this.getOperators(this.props.operators,this.props.parameters.find(function(t){return t.value===e.props.condition.parameter})))),l.default.createElement("div",{className:this.props.classes.filterLineValue},l.default.createElement(c.MultiSelect,{style:{width:"100%"},placeholder:"-- Value --",theme:"bootstrap3",values:this.getValues(this.props.condition.value),onValuesChange:this.handleValueChange,uid:function(e){return e.value},restoreOnBackspace:function(e){return e.label.toString()},createFromSearch:function(t,n,r){return e.labels=n.map(function(e){return e.label}),0===r.trim().length||e.labels.indexOf(r.trim())!==-1?null:{label:r.trim(),value:r.trim()}},renderNoResultsFound:function(e,t){return l.default.createElement("div",{className:"no-results-found"},function(){return 0===t.trim().length?"Enter a new value":e.map(function(e){return e.label}).indexOf(t.trim())!==-1?"Value already exists":void 0}())}})))}}]),t}(u.Component);t.default=p},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var s=function(){function e(e,t){for(var n=0;n1){var t=this.state.conditions;t.splice(e,1),this.setState({conditions:t})}}},{key:"componentDidUpdate",value:function(e,t){t!==this.state&&this.props.config.updateConditions(this.state.conditions)}},{key:"render",value:function(){var e=this,t=this.state.conditions.map(function(t,n){return l.default.createElement("div",{key:n},l.default.createElement(d.default,{index:n,classes:e.props.config.classes,addCondition:e.addCondition,removeCondition:e.removeCondition}),l.default.createElement(p.default,{parameters:e.props.config.parameters,operators:e.props.config.operators,condition:t,index:n,classes:e.props.config.classes,onChange:e.updateCondition}))});return l.default.createElement("div",{className:"form-horizontal"},t)}}]),t}(u.Component);t.default=h},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}var o=n(6),i=r(o),a=n(17),s=r(a),u=n(94),l=r(u),c=window.$;if(c.fn.filterer=function(e){e.operators=[{name:"contains",types:["string","str"]},{name:"does not contain",types:["string","str"]},{name:"is",types:["string","str","number","int","float"]},{name:"is not",types:["string","str","number","int","float"]},{name:"begins with",types:["string","str"]},{name:"ends with",types:["string","str"]},{name:"is greater than",types:["number","int","float"]},{name:"is less than",types:["number","int","float"]}],e.classes=Object.assign({plusIcon:"fa fa-fw fa-plus",minusIcon:"fa fa-fw fa-minus",filterLineRow:"form-group",filterLineParameter:"col-sm-4",filterLineOperator:"col-sm-3",filterLineValue:"col-sm-5",filterLineInput:"form-control",filterLineLabelRow:"row",filterLineLabelCondition:"col-sm-10",filterLineLabelControls:"col-sm-2 text-right"},e.classes),this.each(function(){s.default.render(i.default.createElement(l.default,{id:"filterer",config:e}),this)})},window.wcomartin_filterer_demo){var p={parameters:[{name:"Title",type:"string",value:"title"},{name:"Year",type:"number",value:"year"}],conditions:[{parameter:"year",operator:"is",value:[2017]}]};p.updateConditions=function(e){console.log(JSON.stringify(e))},c("#root").filterer(p)}},function(e,t,n){"use strict";function r(e,t){for(var n=e;n.parentNode;)n=n.parentNode;var r=n.querySelectorAll(t);return Array.prototype.indexOf.call(r,e)!==-1}var o=n(1),i={addClass:function(e,t){return/\s/.test(t)?o(!1):void 0,t&&(e.classList?e.classList.add(t):i.hasClass(e,t)||(e.className=e.className+" "+t)),e},removeClass:function(e,t){return/\s/.test(t)?o(!1):void 0,t&&(e.classList?e.classList.remove(t):i.hasClass(e,t)&&(e.className=e.className.replace(new RegExp("(^|\\s)"+t+"(?:\\s|$)","g"),"$1").replace(/\s+/g," ").replace(/^\s*|\s*$/g,""))),e},conditionClass:function(e,t,n){return(n?i.addClass:i.removeClass)(e,t)},hasClass:function(e,t){return/\s/.test(t)?o(!1):void 0,e.classList?!!t&&e.classList.contains(t):(" "+e.className+" ").indexOf(" "+t+" ")>-1},matchesSelector:function(e,t){var n=e.matches||e.webkitMatchesSelector||e.mozMatchesSelector||e.msMatchesSelector||function(t){return r(e,t)};return n.call(e,t)}};e.exports=i},function(e,t){"use strict";function n(e){return e.replace(r,function(e,t){return t.toUpperCase()})}var r=/-(.)/g;e.exports=n},function(e,t,n){"use strict";function r(e){return o(e.replace(i,"ms-"))}var o=n(97),i=/^-ms-/;e.exports=r},function(e,t,n){"use strict";function r(e,t){return!(!e||!t)&&(e===t||!o(e)&&(o(t)?r(e,t.parentNode):"contains"in e?e.contains(t):!!e.compareDocumentPosition&&!!(16&e.compareDocumentPosition(t))))}var o=n(107);e.exports=r},function(e,t,n){"use strict";function r(e){var t=e.length;if(Array.isArray(e)||"object"!=typeof e&&"function"!=typeof e?a(!1):void 0,"number"!=typeof t?a(!1):void 0,0===t||t-1 in e?void 0:a(!1),"function"==typeof e.callee?a(!1):void 0,e.hasOwnProperty)try{return Array.prototype.slice.call(e)}catch(e){}for(var n=Array(t),r=0;r":a.innerHTML="<"+e+">",s[e]=!a.firstChild),s[e]?f[e]:null}var o=n(7),i=n(1),a=o.canUseDOM?document.createElement("div"):null,s={},u=[1,'"],l=[1,"","
    "],c=[3,"","
    "],p=[1,'',""],f={"*":[1,"?
    ","
    "],area:[1,"",""],col:[2,"","
    "],legend:[1,"
    ","
    "],param:[1,"",""],tr:[2,"","
    "],optgroup:u,option:u,caption:l,colgroup:l,tbody:l,tfoot:l,thead:l,td:c,th:c},d=["circle","clipPath","defs","ellipse","g","image","line","linearGradient","mask","path","pattern","polygon","polyline","radialGradient","rect","stop","text","tspan"];d.forEach(function(e){f[e]=p,s[e]=!0}),e.exports=r},function(e,t){"use strict";function n(e){return e.Window&&e instanceof e.Window?{x:e.pageXOffset||e.document.documentElement.scrollLeft,y:e.pageYOffset||e.document.documentElement.scrollTop}:{x:e.scrollLeft,y:e.scrollTop}}e.exports=n},function(e,t){"use strict";function n(e){return e.replace(r,"-$1").toLowerCase()}var r=/([A-Z])/g;e.exports=n},function(e,t,n){"use strict";function r(e){return o(e).replace(i,"-ms-")}var o=n(104),i=/^ms-/;e.exports=r},function(e,t){"use strict";function n(e){var t=e?e.ownerDocument||e:document,n=t.defaultView||window;return!(!e||!("function"==typeof n.Node?e instanceof n.Node:"object"==typeof e&&"number"==typeof e.nodeType&&"string"==typeof e.nodeName))}e.exports=n},function(e,t,n){"use strict";function r(e){return o(e)&&3==e.nodeType}var o=n(106);e.exports=r},function(e,t){"use strict";function n(e){var t={};return function(n){return t.hasOwnProperty(n)||(t[n]=e.call(this,n)),t[n]}}e.exports=n},function(e,t){function n(e,t){var n,r=function(o){return e.length>1?function(){var i=o?o.concat():[];return n=t?n||this:this,i.push.apply(i,arguments)1?function(){var i=o?o.concat():[];return n=t?n||this:this,i.push.apply(i,arguments)>>0;++n=0;--r)o=n[r],t=e(o,t);return t}),P=n(function(e,t){return O(e,t[t.length-1],t.slice(0,-1))}),k=n(function(e,t){var n,r,o;for(n=[],r=t;null!=(o=e(r));)n.push(o[0]),r=o[1];return n}),S=function(e){return[].concat.apply([],e)},N=n(function(e,t){var n;return[].concat.apply([],function(){var r,o,i,a=[];for(r=0,i=(o=t).length;rt?1:ee(n)?1:e(t)t&&(t=i);return t},G=function(e){var t,n,r,o,i;for(t=e[0],n=0,o=(r=e.slice(1)).length;ne(n)&&(n=a);return n}),$=n(function(e,t){var n,r,o,i,a;for(n=t[0],r=0,i=(o=t.slice(1)).length;r1?function(){var i=o?o.concat():[];return n=t?n||this:this,i.push.apply(i,arguments)t?e:t}),o=n(function(e,t){return e0?1:0},u=n(function(e,t){return~~(e/t)}),l=n(function(e,t){return e%t}),c=n(function(e,t){return Math.floor(e/t)}),p=n(function(e,t){var n;return(e%(n=t)+n)%n}),f=function(e){return 1/e},d=Math.PI,h=2*d,v=Math.exp,m=Math.sqrt,g=Math.log,y=n(function(e,t){return Math.pow(e,t)}),b=Math.sin,C=Math.tan,_=Math.cos,w=Math.asin,E=Math.acos,x=Math.atan,T=n(function(e,t){return Math.atan2(e,t)}),O=function(e){return~~e},P=Math.round,k=Math.ceil,S=Math.floor,N=function(e){return e!==e},M=function(e){return e%2===0},A=function(e){return e%2!==0},I=n(function(e,t){var n;for(e=Math.abs(e),t=Math.abs(t);0!==t;)n=e%t,e=t,t=n;return e}),D=n(function(e,t){return Math.abs(Math.floor(e/I(e,t)*t))}),e.exports={max:r,min:o,negate:i,abs:a,signum:s,quot:u,rem:l,div:c,mod:p,recip:f,pi:d,tau:h,exp:v,sqrt:m,ln:g,pow:y,sin:b,tan:C,cos:_,acos:E,asin:w,atan:x,atan2:T,truncate:O,round:P,ceiling:k,floor:S,isItNaN:N,even:M,odd:A,gcd:I,lcm:D}},function(e,t){function n(e,t){var n,r=function(o){return e.length>1?function(){var i=o?o.concat():[];return n=t?n||this:this,i.push.apply(i,arguments)1?function(){var i=o?o.concat():[];return n=t?n||this:this,i.push.apply(i,arguments)1?n:n.toLowerCase())}).replace(/^([A-Z]+)/,function(e,t){return t.length>1?t+"-":t.toLowerCase()})},e.exports={split:r,join:o,lines:i,unlines:a,words:s,unwords:u,chars:l,unchars:c,reverse:p,repeat:f,capitalize:d,camelize:h,dasherize:v}},function(e,t,n){"use strict";function r(e){var t=new o(o._61);return t._81=1,t._65=e,t}var o=n(60);e.exports=o;var i=r(!0),a=r(!1),s=r(null),u=r(void 0),l=r(0),c=r("");o.resolve=function(e){if(e instanceof o)return e;if(null===e)return s;if(void 0===e)return u;if(e===!0)return i;if(e===!1)return a;if(0===e)return l;if(""===e)return c;if("object"==typeof e||"function"==typeof e)try{var t=e.then;if("function"==typeof t)return new o(t.bind(e))}catch(e){return new o(function(t,n){n(e)})}return r(e)},o.all=function(e){var t=Array.prototype.slice.call(e);return new o(function(e,n){function r(a,s){if(s&&("object"==typeof s||"function"==typeof s)){if(s instanceof o&&s.then===o.prototype.then){for(;3===s._81;)s=s._65;return 1===s._81?r(a,s._65):(2===s._81&&n(s._65),void s.then(function(e){r(a,e)},n))}var u=s.then;if("function"==typeof u){var l=new o(u.bind(s));return void l.then(function(e){r(a,e)},n)}}t[a]=s,0===--i&&e(t)}if(0===t.length)return e([]);for(var i=t.length,a=0;a8&&_<=11),x=32,T=String.fromCharCode(x),O={beforeInput:{phasedRegistrationNames:{bubbled:"onBeforeInput",captured:"onBeforeInputCapture"},dependencies:["topCompositionEnd","topKeyPress","topTextInput","topPaste"]},compositionEnd:{phasedRegistrationNames:{bubbled:"onCompositionEnd",captured:"onCompositionEndCapture"},dependencies:["topBlur","topCompositionEnd","topKeyDown","topKeyPress","topKeyUp","topMouseDown"]},compositionStart:{phasedRegistrationNames:{bubbled:"onCompositionStart",captured:"onCompositionStartCapture"},dependencies:["topBlur","topCompositionStart","topKeyDown","topKeyPress","topKeyUp","topMouseDown"]},compositionUpdate:{phasedRegistrationNames:{bubbled:"onCompositionUpdate",captured:"onCompositionUpdateCapture"},dependencies:["topBlur","topCompositionUpdate","topKeyDown","topKeyPress","topKeyUp","topMouseDown"]}},P=!1,k=null,S={eventTypes:O,extractEvents:function(e,t,n,r){return[l(e,t,n,r),f(e,t,n,r)]}};e.exports=S},function(e,t,n){"use strict";var r=n(62),o=n(7),i=(n(9),n(98),n(173)),a=n(105),s=n(108),u=(n(2),s(function(e){return a(e)})),l=!1,c="cssFloat";if(o.canUseDOM){var p=document.createElement("div").style;try{p.font=""}catch(e){l=!0}void 0===document.documentElement.style.cssFloat&&(c="styleFloat")}var f={createMarkupForStyles:function(e,t){var n="";for(var r in e)if(e.hasOwnProperty(r)){var o=e[r];null!=o&&(n+=u(r)+":",n+=i(r,o,t)+";")}return n||null},setValueForStyles:function(e,t,n){var o=e.style;for(var a in t)if(t.hasOwnProperty(a)){var s=i(a,t[a],n);if("float"!==a&&"cssFloat"!==a||(a=c),s)o[a]=s;else{var u=l&&r.shorthandPropertyExpansions[a];if(u)for(var p in u)o[p]="";else o[a]=""}}}};e.exports=f},function(e,t,n){"use strict";function r(e){var t=e.nodeName&&e.nodeName.toLowerCase();return"select"===t||"input"===t&&"file"===e.type}function o(e){var t=x.getPooled(k.change,N,e,T(e));C.accumulateTwoPhaseDispatches(t),E.batchedUpdates(i,t)}function i(e){b.enqueueEvents(e),b.processEventQueue(!1)}function a(e,t){S=e,N=t,S.attachEvent("onchange",o)}function s(){S&&(S.detachEvent("onchange",o),S=null,N=null)}function u(e,t){if("topChange"===e)return t}function l(e,t,n){"topFocus"===e?(s(),a(t,n)):"topBlur"===e&&s()}function c(e,t){S=e,N=t,M=e.value,A=Object.getOwnPropertyDescriptor(e.constructor.prototype,"value"),Object.defineProperty(S,"value",R),S.attachEvent?S.attachEvent("onpropertychange",f):S.addEventListener("propertychange",f,!1)}function p(){S&&(delete S.value,S.detachEvent?S.detachEvent("onpropertychange",f):S.removeEventListener("propertychange",f,!1),S=null,N=null,M=null,A=null)}function f(e){if("value"===e.propertyName){var t=e.srcElement.value;t!==M&&(M=t,o(e))}}function d(e,t){if("topInput"===e)return t}function h(e,t,n){"topFocus"===e?(p(),c(t,n)):"topBlur"===e&&p()}function v(e,t){if(("topSelectionChange"===e||"topKeyUp"===e||"topKeyDown"===e)&&S&&S.value!==M)return M=S.value,N}function m(e){return e.nodeName&&"input"===e.nodeName.toLowerCase()&&("checkbox"===e.type||"radio"===e.type)}function g(e,t){if("topClick"===e)return t}function y(e,t){if(null!=e){var n=e._wrapperState||t._wrapperState;if(n&&n.controlled&&"number"===t.type){var r=""+t.value;t.getAttribute("value")!==r&&t.setAttribute("value",r)}}}var b=n(25),C=n(26),_=n(7),w=n(5),E=n(10),x=n(11),T=n(49),O=n(50),P=n(81),k={change:{phasedRegistrationNames:{bubbled:"onChange",captured:"onChangeCapture"},dependencies:["topBlur","topChange","topClick","topFocus","topInput","topKeyDown","topKeyUp","topSelectionChange"]}},S=null,N=null,M=null,A=null,I=!1;_.canUseDOM&&(I=O("change")&&(!document.documentMode||document.documentMode>8));var D=!1;_.canUseDOM&&(D=O("input")&&(!document.documentMode||document.documentMode>11));var R={get:function(){return A.get.call(this)},set:function(e){M=""+e,A.set.call(this,e)}},L={eventTypes:k,extractEvents:function(e,t,n,o){var i,a,s=t?w.getNodeFromInstance(t):window;if(r(s)?I?i=u:a=l:P(s)?D?i=d:(i=v,a=h):m(s)&&(i=g),i){var c=i(e,t);if(c){var p=x.getPooled(k.change,c,n,o);return p.type="change",C.accumulateTwoPhaseDispatches(p),p}}a&&a(e,s,t),"topBlur"===e&&y(t,s)}};e.exports=L},function(e,t,n){"use strict";var r=n(3),o=n(18),i=n(7),a=n(101),s=n(8),u=(n(1),{dangerouslyReplaceNodeWithMarkup:function(e,t){if(i.canUseDOM?void 0:r("56"),t?void 0:r("57"),"HTML"===e.nodeName?r("58"):void 0,"string"==typeof t){var n=a(t,s)[0];e.parentNode.replaceChild(n,e)}else o.replaceChildWithTree(e,t)}});e.exports=u},function(e,t){"use strict";var n=["ResponderEventPlugin","SimpleEventPlugin","TapEventPlugin","EnterLeaveEventPlugin","ChangeEventPlugin","SelectEventPlugin","BeforeInputEventPlugin"];e.exports=n},function(e,t,n){"use strict";var r=n(26),o=n(5),i=n(32),a={mouseEnter:{registrationName:"onMouseEnter",dependencies:["topMouseOut","topMouseOver"]},mouseLeave:{registrationName:"onMouseLeave",dependencies:["topMouseOut","topMouseOver"]}},s={eventTypes:a,extractEvents:function(e,t,n,s){if("topMouseOver"===e&&(n.relatedTarget||n.fromElement))return null;if("topMouseOut"!==e&&"topMouseOver"!==e)return null;var u;if(s.window===s)u=s;else{var l=s.ownerDocument;u=l?l.defaultView||l.parentWindow:window}var c,p;if("topMouseOut"===e){c=t;var f=n.relatedTarget||n.toElement;p=f?o.getClosestInstanceFromNode(f):null}else c=null,p=t;if(c===p)return null;var d=null==c?u:o.getNodeFromInstance(c),h=null==p?u:o.getNodeFromInstance(p),v=i.getPooled(a.mouseLeave,c,n,s);v.type="mouseleave",v.target=d,v.relatedTarget=h;var m=i.getPooled(a.mouseEnter,p,n,s);return m.type="mouseenter",m.target=h,m.relatedTarget=d,r.accumulateEnterLeaveDispatches(v,m,c,p),[v,m]}};e.exports=s},function(e,t,n){"use strict";function r(e){this._root=e,this._startText=this.getText(),this._fallbackText=null}var o=n(4),i=n(15),a=n(78);o(r.prototype,{destructor:function(){this._root=null,this._startText=null,this._fallbackText=null},getText:function(){return"value"in this._root?this._root.value:this._root[a()]},getData:function(){if(this._fallbackText)return this._fallbackText;var e,t,n=this._startText,r=n.length,o=this.getText(),i=o.length;for(e=0;e1?1-t:void 0;return this._fallbackText=o.slice(e,s),this._fallbackText}}),i.addPoolingTo(r),e.exports=r},function(e,t,n){"use strict";var r=n(19),o=r.injection.MUST_USE_PROPERTY,i=r.injection.HAS_BOOLEAN_VALUE,a=r.injection.HAS_NUMERIC_VALUE,s=r.injection.HAS_POSITIVE_NUMERIC_VALUE,u=r.injection.HAS_OVERLOADED_BOOLEAN_VALUE,l={isCustomAttribute:RegExp.prototype.test.bind(new RegExp("^(data|aria)-["+r.ATTRIBUTE_NAME_CHAR+"]*$")),Properties:{accept:0,acceptCharset:0,accessKey:0,action:0,allowFullScreen:i,allowTransparency:0,alt:0,as:0,async:i,autoComplete:0,autoPlay:i,capture:i,cellPadding:0,cellSpacing:0,charSet:0,challenge:0,checked:o|i,cite:0,classID:0,className:0,cols:s,colSpan:0,content:0,contentEditable:0,contextMenu:0,controls:i,coords:0,crossOrigin:0,data:0,dateTime:0,default:i,defer:i,dir:0,disabled:i,download:u,draggable:0,encType:0,form:0,formAction:0,formEncType:0,formMethod:0,formNoValidate:i,formTarget:0,frameBorder:0,headers:0,height:0,hidden:i,high:0,href:0,hrefLang:0,htmlFor:0,httpEquiv:0,icon:0,id:0,inputMode:0,integrity:0,is:0,keyParams:0,keyType:0,kind:0,label:0,lang:0,list:0,loop:i,low:0,manifest:0,marginHeight:0,marginWidth:0,max:0,maxLength:0,media:0,mediaGroup:0,method:0,min:0,minLength:0,multiple:o|i,muted:o|i,name:0,nonce:0,noValidate:i,open:i,optimum:0,pattern:0,placeholder:0,playsInline:i,poster:0,preload:0,profile:0,radioGroup:0,readOnly:i,referrerPolicy:0,rel:0,required:i,reversed:i,role:0,rows:s,rowSpan:a,sandbox:0,scope:0,scoped:i,scrolling:0,seamless:i,selected:o|i,shape:0,size:s,sizes:0,span:s,spellCheck:0,src:0,srcDoc:0,srcLang:0,srcSet:0,start:a,step:0,style:0,summary:0,tabIndex:0,target:0,title:0,type:0,useMap:0,value:0,width:0,wmode:0,wrap:0,about:0,datatype:0,inlist:0,prefix:0,property:0,resource:0,typeof:0,vocab:0,autoCapitalize:0,autoCorrect:0,autoSave:0,color:0,itemProp:0,itemScope:i,itemType:0,itemID:0,itemRef:0,results:0,security:0,unselectable:0},DOMAttributeNames:{acceptCharset:"accept-charset",className:"class",htmlFor:"for",httpEquiv:"http-equiv"},DOMPropertyNames:{},DOMMutationMethods:{value:function(e,t){return null==t?e.removeAttribute("value"):void("number"!==e.type||e.hasAttribute("value")===!1?e.setAttribute("value",""+t):e.validity&&!e.validity.badInput&&e.ownerDocument.activeElement!==e&&e.setAttribute("value",""+t))}}};e.exports=l},function(e,t,n){(function(t){"use strict";function r(e,t,n,r){var o=void 0===e[n];null!=t&&o&&(e[n]=i(t,!0))}var o=n(20),i=n(80),a=(n(41),n(51)),s=n(83),u=(n(2),{instantiateChildren:function(e,t,n,o){if(null==e)return null;var i={};return s(e,r,i),i},updateChildren:function(e,t,n,r,s,u,l,c,p){if(t||e){var f,d;for(f in t)if(t.hasOwnProperty(f)){d=e&&e[f];var h=d&&d._currentElement,v=t[f];if(null!=d&&a(h,v))o.receiveComponent(d,v,s,c),t[f]=d;else{d&&(r[f]=o.getHostNode(d),o.unmountComponent(d,!1));var m=i(v,!0);t[f]=m;var g=o.mountComponent(m,s,u,l,c,p);n.push(g)}}for(f in e)!e.hasOwnProperty(f)||t&&t.hasOwnProperty(f)||(d=e[f],r[f]=o.getHostNode(d),o.unmountComponent(d,!1))}},unmountChildren:function(e,t){for(var n in e)if(e.hasOwnProperty(n)){var r=e[n];o.unmountComponent(r,t)}}});e.exports=u}).call(t,n(36))},function(e,t,n){"use strict";var r=n(37),o=n(137),i={processChildrenUpdates:o.dangerouslyProcessChildrenUpdates,replaceNodeWithMarkup:r.dangerouslyReplaceNodeWithMarkup};e.exports=i},function(e,t,n){"use strict";function r(e){}function o(e,t){}function i(e){return!(!e.prototype||!e.prototype.isReactComponent)}function a(e){return!(!e.prototype||!e.prototype.isPureReactComponent)}var s=n(3),u=n(4),l=n(12),c=n(43),p=n(13),f=n(44),d=n(27),h=(n(9),n(73)),v=n(20),m=n(23),g=(n(1),n(30)),y=n(51),b=(n(2),{ImpureClass:0,PureClass:1,StatelessFunctional:2});r.prototype.render=function(){var e=d.get(this)._currentElement.type,t=e(this.props,this.context,this.updater);return o(e,t),t};var C=1,_={construct:function(e){this._currentElement=e,this._rootNodeID=0,this._compositeType=null,this._instance=null,this._hostParent=null,this._hostContainerInfo=null,this._updateBatchNumber=null,this._pendingElement=null,this._pendingStateQueue=null,this._pendingReplaceState=!1,this._pendingForceUpdate=!1,this._renderedNodeType=null,this._renderedComponent=null,this._context=null,this._mountOrder=0,this._topLevelWrapper=null,this._pendingCallbacks=null,this._calledComponentWillUnmount=!1},mountComponent:function(e,t,n,u){this._context=u,this._mountOrder=C++,this._hostParent=t,this._hostContainerInfo=n;var c,p=this._currentElement.props,f=this._processContext(u),h=this._currentElement.type,v=e.getUpdateQueue(),g=i(h),y=this._constructComponent(g,p,f,v);g||null!=y&&null!=y.render?a(h)?this._compositeType=b.PureClass:this._compositeType=b.ImpureClass:(c=y,o(h,c),null===y||y===!1||l.isValidElement(y)?void 0:s("105",h.displayName||h.name||"Component"),y=new r(h),this._compositeType=b.StatelessFunctional);y.props=p,y.context=f,y.refs=m,y.updater=v,this._instance=y,d.set(y,this);var _=y.state;void 0===_&&(y.state=_=null),"object"!=typeof _||Array.isArray(_)?s("106",this.getName()||"ReactCompositeComponent"):void 0,this._pendingStateQueue=null,this._pendingReplaceState=!1,this._pendingForceUpdate=!1;var w;return w=y.unstable_handleError?this.performInitialMountWithErrorHandling(c,t,n,e,u):this.performInitialMount(c,t,n,e,u),y.componentDidMount&&e.getReactMountReady().enqueue(y.componentDidMount,y),w},_constructComponent:function(e,t,n,r){return this._constructComponentWithoutOwner(e,t,n,r)},_constructComponentWithoutOwner:function(e,t,n,r){var o=this._currentElement.type;return e?new o(t,n,r):o(t,n,r); -},performInitialMountWithErrorHandling:function(e,t,n,r,o){var i,a=r.checkpoint();try{i=this.performInitialMount(e,t,n,r,o)}catch(s){r.rollback(a),this._instance.unstable_handleError(s),this._pendingStateQueue&&(this._instance.state=this._processPendingState(this._instance.props,this._instance.context)),a=r.checkpoint(),this._renderedComponent.unmountComponent(!0),r.rollback(a),i=this.performInitialMount(e,t,n,r,o)}return i},performInitialMount:function(e,t,n,r,o){var i=this._instance,a=0;i.componentWillMount&&(i.componentWillMount(),this._pendingStateQueue&&(i.state=this._processPendingState(i.props,i.context))),void 0===e&&(e=this._renderValidatedComponent());var s=h.getType(e);this._renderedNodeType=s;var u=this._instantiateReactComponent(e,s!==h.EMPTY);this._renderedComponent=u;var l=v.mountComponent(u,r,t,n,this._processChildContext(o),a);return l},getHostNode:function(){return v.getHostNode(this._renderedComponent)},unmountComponent:function(e){if(this._renderedComponent){var t=this._instance;if(t.componentWillUnmount&&!t._calledComponentWillUnmount)if(t._calledComponentWillUnmount=!0,e){var n=this.getName()+".componentWillUnmount()";f.invokeGuardedCallback(n,t.componentWillUnmount.bind(t))}else t.componentWillUnmount();this._renderedComponent&&(v.unmountComponent(this._renderedComponent,e),this._renderedNodeType=null,this._renderedComponent=null,this._instance=null),this._pendingStateQueue=null,this._pendingReplaceState=!1,this._pendingForceUpdate=!1,this._pendingCallbacks=null,this._pendingElement=null,this._context=null,this._rootNodeID=0,this._topLevelWrapper=null,d.remove(t)}},_maskContext:function(e){var t=this._currentElement.type,n=t.contextTypes;if(!n)return m;var r={};for(var o in n)r[o]=e[o];return r},_processContext:function(e){var t=this._maskContext(e);return t},_processChildContext:function(e){var t,n=this._currentElement.type,r=this._instance;if(r.getChildContext&&(t=r.getChildContext()),t){"object"!=typeof n.childContextTypes?s("107",this.getName()||"ReactCompositeComponent"):void 0;for(var o in t)o in n.childContextTypes?void 0:s("108",this.getName()||"ReactCompositeComponent",o);return u({},e,t)}return e},_checkContextTypes:function(e,t,n){},receiveComponent:function(e,t,n){var r=this._currentElement,o=this._context;this._pendingElement=null,this.updateComponent(t,r,e,o,n)},performUpdateIfNecessary:function(e){null!=this._pendingElement?v.receiveComponent(this,this._pendingElement,e,this._context):null!==this._pendingStateQueue||this._pendingForceUpdate?this.updateComponent(e,this._currentElement,this._currentElement,this._context,this._context):this._updateBatchNumber=null},updateComponent:function(e,t,n,r,o){var i=this._instance;null==i?s("136",this.getName()||"ReactCompositeComponent"):void 0;var a,u=!1;this._context===o?a=i.context:(a=this._processContext(o),u=!0);var l=t.props,c=n.props;t!==n&&(u=!0),u&&i.componentWillReceiveProps&&i.componentWillReceiveProps(c,a);var p=this._processPendingState(c,a),f=!0;this._pendingForceUpdate||(i.shouldComponentUpdate?f=i.shouldComponentUpdate(c,p,a):this._compositeType===b.PureClass&&(f=!g(l,c)||!g(i.state,p))),this._updateBatchNumber=null,f?(this._pendingForceUpdate=!1,this._performComponentUpdate(n,c,p,a,e,o)):(this._currentElement=n,this._context=o,i.props=c,i.state=p,i.context=a)},_processPendingState:function(e,t){var n=this._instance,r=this._pendingStateQueue,o=this._pendingReplaceState;if(this._pendingReplaceState=!1,this._pendingStateQueue=null,!r)return n.state;if(o&&1===r.length)return r[0];for(var i=u({},o?r[0]:n.state),a=o?1:0;a=0||null!=t.is}function h(e){var t=e.type;f(t),this._currentElement=e,this._tag=t.toLowerCase(),this._namespaceURI=null,this._renderedChildren=null,this._previousStyle=null,this._previousStyleCopy=null,this._hostNode=null,this._hostParent=null,this._rootNodeID=0,this._domID=0,this._hostContainerInfo=null,this._wrapperState=null,this._topLevelWrapper=null,this._flags=0}var v=n(3),m=n(4),g=n(121),y=n(123),b=n(18),C=n(38),_=n(19),w=n(64),E=n(25),x=n(39),T=n(31),O=n(66),P=n(5),k=n(138),S=n(139),N=n(67),M=n(142),A=(n(9),n(151)),I=n(156),D=(n(8),n(34)),R=(n(1),n(50),n(30),n(52),n(2),O),L=E.deleteListener,U=P.getNodeFromInstance,F=T.listenTo,j=x.registrationNameModules,B={string:!0,number:!0},V="style",W="__html",H={children:null,dangerouslySetInnerHTML:null,suppressContentEditableWarning:null},q=11,z={topAbort:"abort",topCanPlay:"canplay",topCanPlayThrough:"canplaythrough",topDurationChange:"durationchange",topEmptied:"emptied",topEncrypted:"encrypted",topEnded:"ended",topError:"error",topLoadedData:"loadeddata",topLoadedMetadata:"loadedmetadata",topLoadStart:"loadstart",topPause:"pause",topPlay:"play",topPlaying:"playing",topProgress:"progress",topRateChange:"ratechange",topSeeked:"seeked",topSeeking:"seeking",topStalled:"stalled",topSuspend:"suspend",topTimeUpdate:"timeupdate",topVolumeChange:"volumechange",topWaiting:"waiting"},K={area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0},Y={listing:!0,pre:!0,textarea:!0},X=m({menuitem:!0},K),G=/^[a-zA-Z][a-zA-Z:_\.\-\d]*$/,Q={},$={}.hasOwnProperty,Z=1;h.displayName="ReactDOMComponent",h.Mixin={mountComponent:function(e,t,n,r){this._rootNodeID=Z++,this._domID=n._idCounter++,this._hostParent=t,this._hostContainerInfo=n;var i=this._currentElement.props;switch(this._tag){case"audio":case"form":case"iframe":case"img":case"link":case"object":case"source":case"video":this._wrapperState={listeners:null},e.getReactMountReady().enqueue(c,this);break;case"input":k.mountWrapper(this,i,t),i=k.getHostProps(this,i),e.getReactMountReady().enqueue(c,this);break;case"option":S.mountWrapper(this,i,t),i=S.getHostProps(this,i);break;case"select":N.mountWrapper(this,i,t),i=N.getHostProps(this,i),e.getReactMountReady().enqueue(c,this);break;case"textarea":M.mountWrapper(this,i,t),i=M.getHostProps(this,i),e.getReactMountReady().enqueue(c,this)}o(this,i);var a,p;null!=t?(a=t._namespaceURI,p=t._tag):n._tag&&(a=n._namespaceURI,p=n._tag),(null==a||a===C.svg&&"foreignobject"===p)&&(a=C.html),a===C.html&&("svg"===this._tag?a=C.svg:"math"===this._tag&&(a=C.mathml)),this._namespaceURI=a;var f;if(e.useCreateElement){var d,h=n._ownerDocument;if(a===C.html)if("script"===this._tag){var v=h.createElement("div"),m=this._currentElement.type;v.innerHTML="<"+m+">",d=v.removeChild(v.firstChild)}else d=i.is?h.createElement(this._currentElement.type,i.is):h.createElement(this._currentElement.type);else d=h.createElementNS(a,this._currentElement.type);P.precacheNode(this,d),this._flags|=R.hasCachedChildNodes,this._hostParent||w.setAttributeForRoot(d),this._updateDOMProperties(null,i,e);var y=b(d);this._createInitialChildren(e,i,r,y),f=y}else{var _=this._createOpenTagMarkupAndPutListeners(e,i),E=this._createContentMarkup(e,i,r);f=!E&&K[this._tag]?_+"/>":_+">"+E+""}switch(this._tag){case"input":e.getReactMountReady().enqueue(s,this),i.autoFocus&&e.getReactMountReady().enqueue(g.focusDOMComponent,this);break;case"textarea":e.getReactMountReady().enqueue(u,this),i.autoFocus&&e.getReactMountReady().enqueue(g.focusDOMComponent,this);break;case"select":i.autoFocus&&e.getReactMountReady().enqueue(g.focusDOMComponent,this);break;case"button":i.autoFocus&&e.getReactMountReady().enqueue(g.focusDOMComponent,this);break;case"option":e.getReactMountReady().enqueue(l,this)}return f},_createOpenTagMarkupAndPutListeners:function(e,t){var n="<"+this._currentElement.type;for(var r in t)if(t.hasOwnProperty(r)){var o=t[r];if(null!=o)if(j.hasOwnProperty(r))o&&i(this,r,o,e);else{r===V&&(o&&(o=this._previousStyleCopy=m({},t.style)),o=y.createMarkupForStyles(o,this));var a=null;null!=this._tag&&d(this._tag,t)?H.hasOwnProperty(r)||(a=w.createMarkupForCustomAttribute(r,o)):a=w.createMarkupForProperty(r,o),a&&(n+=" "+a)}}return e.renderToStaticMarkup?n:(this._hostParent||(n+=" "+w.createMarkupForRoot()),n+=" "+w.createMarkupForID(this._domID))},_createContentMarkup:function(e,t,n){var r="",o=t.dangerouslySetInnerHTML;if(null!=o)null!=o.__html&&(r=o.__html);else{var i=B[typeof t.children]?t.children:null,a=null!=i?null:t.children;if(null!=i)r=D(i);else if(null!=a){var s=this.mountChildren(a,e,n);r=s.join("")}}return Y[this._tag]&&"\n"===r.charAt(0)?"\n"+r:r},_createInitialChildren:function(e,t,n,r){var o=t.dangerouslySetInnerHTML;if(null!=o)null!=o.__html&&b.queueHTML(r,o.__html);else{var i=B[typeof t.children]?t.children:null,a=null!=i?null:t.children;if(null!=i)""!==i&&b.queueText(r,i);else if(null!=a)for(var s=this.mountChildren(a,e,n),u=0;u"},receiveComponent:function(){},getHostNode:function(){return i.getNodeFromInstance(this)},unmountComponent:function(){i.uncacheNode(this)}}),e.exports=a},function(e,t){"use strict";var n={useCreateElement:!0,useFiber:!1};e.exports=n},function(e,t,n){"use strict";var r=n(37),o=n(5),i={dangerouslyProcessChildrenUpdates:function(e,t){var n=o.getNodeFromInstance(e);r.processUpdates(n,t)}};e.exports=i},function(e,t,n){"use strict";function r(){this._rootNodeID&&f.updateWrapper(this)}function o(e){var t="checkbox"===e.type||"radio"===e.type;return t?null!=e.checked:null!=e.value}function i(e){var t=this._currentElement.props,n=l.executeOnChange(t,e);p.asap(r,this);var o=t.name;if("radio"===t.type&&null!=o){for(var i=c.getNodeFromInstance(this),s=i;s.parentNode;)s=s.parentNode;for(var u=s.querySelectorAll("input[name="+JSON.stringify(""+o)+'][type="radio"]'),f=0;ft.end?(n=t.end,r=t.start):(n=t.start,r=t.end),o.moveToElementText(e),o.moveStart("character",n),o.setEndPoint("EndToStart",o),o.moveEnd("character",r-n),o.select()}function s(e,t){if(window.getSelection){var n=window.getSelection(),r=e[c()].length,o=Math.min(t.start,r),i=void 0===t.end?o:Math.min(t.end,r);if(!n.extend&&o>i){var a=i;i=o,o=a}var s=l(e,o),u=l(e,i);if(s&&u){var p=document.createRange();p.setStart(s.node,s.offset),n.removeAllRanges(),o>i?(n.addRange(p),n.extend(u.node,u.offset)):(p.setEnd(u.node,u.offset),n.addRange(p))}}}var u=n(7),l=n(178),c=n(78),p=u.canUseDOM&&"selection"in document&&!("getSelection"in window),f={getOffsets:p?o:i,setOffsets:p?a:s};e.exports=f},function(e,t,n){"use strict";var r=n(3),o=n(4),i=n(37),a=n(18),s=n(5),u=n(34),l=(n(1),n(52),function(e){this._currentElement=e,this._stringText=""+e,this._hostNode=null,this._hostParent=null,this._domID=0,this._mountIndex=0,this._closingComment=null,this._commentNodes=null});o(l.prototype,{mountComponent:function(e,t,n,r){var o=n._idCounter++,i=" react-text: "+o+" ",l=" /react-text ";if(this._domID=o,this._hostParent=t,e.useCreateElement){var c=n._ownerDocument,p=c.createComment(i),f=c.createComment(l),d=a(c.createDocumentFragment());return a.queueChild(d,a(p)),this._stringText&&a.queueChild(d,a(c.createTextNode(this._stringText))),a.queueChild(d,a(f)),s.precacheNode(this,p),this._closingComment=f,d}var h=u(this._stringText);return e.renderToStaticMarkup?h:""+h+""},receiveComponent:function(e,t){if(e!==this._currentElement){this._currentElement=e;var n=""+e;if(n!==this._stringText){this._stringText=n;var r=this.getHostNode();i.replaceDelimitedText(r[0],r[1],n)}}},getHostNode:function(){var e=this._commentNodes;if(e)return e;if(!this._closingComment)for(var t=s.getNodeFromInstance(this),n=t.nextSibling;;){if(null==n?r("67",this._domID):void 0,8===n.nodeType&&" /react-text "===n.nodeValue){this._closingComment=n;break}n=n.nextSibling}return e=[this._hostNode,this._closingComment],this._commentNodes=e,e},unmountComponent:function(){this._closingComment=null,this._commentNodes=null,s.uncacheNode(this)}}),e.exports=l},function(e,t,n){"use strict";function r(){this._rootNodeID&&c.updateWrapper(this)}function o(e){var t=this._currentElement.props,n=s.executeOnChange(t,e);return l.asap(r,this),n}var i=n(3),a=n(4),s=n(42),u=n(5),l=n(10),c=(n(1),n(2),{getHostProps:function(e,t){null!=t.dangerouslySetInnerHTML?i("91"):void 0;var n=a({},t,{value:void 0,defaultValue:void 0,children:""+e._wrapperState.initialValue,onChange:e._wrapperState.onChange});return n},mountWrapper:function(e,t){var n=s.getValue(t),r=n;if(null==n){var a=t.defaultValue,u=t.children;null!=u&&(null!=a?i("92"):void 0,Array.isArray(u)&&(u.length<=1?void 0:i("93"),u=u[0]),a=""+u),null==a&&(a=""),r=a}e._wrapperState={initialValue:""+r,listeners:null,onChange:o.bind(e)}},updateWrapper:function(e){var t=e._currentElement.props,n=u.getNodeFromInstance(e),r=s.getValue(t);if(null!=r){var o=""+r;o!==n.value&&(n.value=o),null==t.defaultValue&&(n.defaultValue=o)}null!=t.defaultValue&&(n.defaultValue=t.defaultValue)},postMountWrapper:function(e){var t=u.getNodeFromInstance(e),n=t.textContent;n===e._wrapperState.initialValue&&(t.value=n)}});e.exports=c},function(e,t,n){"use strict";function r(e,t){"_hostNode"in e?void 0:u("33"),"_hostNode"in t?void 0:u("33");for(var n=0,r=e;r;r=r._hostParent)n++;for(var o=0,i=t;i;i=i._hostParent)o++;for(;n-o>0;)e=e._hostParent,n--;for(;o-n>0;)t=t._hostParent,o--;for(var a=n;a--;){if(e===t)return e;e=e._hostParent,t=t._hostParent}return null}function o(e,t){"_hostNode"in e?void 0:u("35"),"_hostNode"in t?void 0:u("35");for(;t;){if(t===e)return!0;t=t._hostParent}return!1}function i(e){return"_hostNode"in e?void 0:u("36"),e._hostParent}function a(e,t,n){for(var r=[];e;)r.push(e),e=e._hostParent;var o;for(o=r.length;o-- >0;)t(r[o],"captured",n);for(o=0;o0;)n(u[l],"captured",i)}var u=n(3);n(1);e.exports={isAncestor:o,getLowestCommonAncestor:r,getParentInstance:i,traverseTwoPhase:a,traverseEnterLeave:s}},function(e,t,n){"use strict";function r(){this.reinitializeTransaction()}var o=n(4),i=n(10),a=n(33),s=n(8),u={initialize:s,close:function(){f.isBatchingUpdates=!1}},l={initialize:s,close:i.flushBatchedUpdates.bind(i)},c=[l,u];o(r.prototype,a,{getTransactionWrappers:function(){return c}});var p=new r,f={isBatchingUpdates:!1,batchedUpdates:function(e,t,n,r,o,i){var a=f.isBatchingUpdates;return f.isBatchingUpdates=!0,a?e(t,n,r,o,i):p.perform(e,null,t,n,r,o,i)}};e.exports=f},function(e,t,n){"use strict";function r(){E||(E=!0,y.EventEmitter.injectReactEventListener(g),y.EventPluginHub.injectEventPluginOrder(s),y.EventPluginUtils.injectComponentTree(f),y.EventPluginUtils.injectTreeTraversal(h),y.EventPluginHub.injectEventPluginsByName({SimpleEventPlugin:w,EnterLeaveEventPlugin:u,ChangeEventPlugin:a,SelectEventPlugin:_,BeforeInputEventPlugin:i}),y.HostComponent.injectGenericComponentClass(p),y.HostComponent.injectTextComponentClass(v),y.DOMProperty.injectDOMPropertyConfig(o),y.DOMProperty.injectDOMPropertyConfig(l),y.DOMProperty.injectDOMPropertyConfig(C),y.EmptyComponent.injectEmptyComponentFactory(function(e){return new d(e)}),y.Updates.injectReconcileTransaction(b),y.Updates.injectBatchingStrategy(m),y.Component.injectEnvironment(c))}var o=n(120),i=n(122),a=n(124),s=n(126),u=n(127),l=n(129),c=n(131),p=n(133),f=n(5),d=n(135),h=n(143),v=n(141),m=n(144),g=n(148),y=n(149),b=n(154),C=n(159),_=n(160),w=n(161),E=!1;e.exports={inject:r}},87,function(e,t,n){"use strict";function r(e){o.enqueueEvents(e),o.processEventQueue(!1)}var o=n(25),i={handleTopLevel:function(e,t,n,i){var a=o.extractEvents(e,t,n,i);r(a)}};e.exports=i},function(e,t,n){"use strict";function r(e){for(;e._hostParent;)e=e._hostParent;var t=p.getNodeFromInstance(e),n=t.parentNode;return p.getClosestInstanceFromNode(n)}function o(e,t){this.topLevelType=e,this.nativeEvent=t,this.ancestors=[]}function i(e){var t=d(e.nativeEvent),n=p.getClosestInstanceFromNode(t),o=n;do e.ancestors.push(o),o=o&&r(o);while(o);for(var i=0;i/,i=/^<\!\-\-/,a={CHECKSUM_ATTR_NAME:"data-react-checksum",addChecksumToMarkup:function(e){var t=r(e);return i.test(e)?e:e.replace(o," "+a.CHECKSUM_ATTR_NAME+'="'+t+'"$&')},canReuseMarkup:function(e,t){var n=t.getAttribute(a.CHECKSUM_ATTR_NAME);n=n&&parseInt(n,10);var o=r(e);return o===n}};e.exports=a},function(e,t,n){"use strict";function r(e,t,n){return{type:"INSERT_MARKUP",content:e,fromIndex:null,fromNode:null,toIndex:n,afterNode:t}}function o(e,t,n){return{type:"MOVE_EXISTING",content:null,fromIndex:e._mountIndex,fromNode:f.getHostNode(e),toIndex:n,afterNode:t}}function i(e,t){return{type:"REMOVE_NODE",content:null,fromIndex:e._mountIndex,fromNode:t,toIndex:null,afterNode:null}}function a(e){return{type:"SET_MARKUP",content:e,fromIndex:null,fromNode:null,toIndex:null,afterNode:null}}function s(e){return{type:"TEXT_CONTENT",content:e,fromIndex:null,fromNode:null,toIndex:null,afterNode:null}}function u(e,t){return t&&(e=e||[],e.push(t)),e}function l(e,t){p.processChildrenUpdates(e,t)}var c=n(3),p=n(43),f=(n(27),n(9),n(13),n(20)),d=n(130),h=(n(8),n(175)),v=(n(1),{Mixin:{_reconcilerInstantiateChildren:function(e,t,n){return d.instantiateChildren(e,t,n)},_reconcilerUpdateChildren:function(e,t,n,r,o,i){var a,s=0;return a=h(t,s),d.updateChildren(e,a,n,r,o,this,this._hostContainerInfo,i,s),a},mountChildren:function(e,t,n){var r=this._reconcilerInstantiateChildren(e,t,n);this._renderedChildren=r;var o=[],i=0;for(var a in r)if(r.hasOwnProperty(a)){var s=r[a],u=0,l=f.mountComponent(s,t,this,this._hostContainerInfo,n,u);s._mountIndex=i++,o.push(l)}return o},updateTextContent:function(e){var t=this._renderedChildren;d.unmountChildren(t,!1);for(var n in t)t.hasOwnProperty(n)&&c("118");var r=[s(e)];l(this,r)},updateMarkup:function(e){var t=this._renderedChildren;d.unmountChildren(t,!1);for(var n in t)t.hasOwnProperty(n)&&c("118");var r=[a(e)];l(this,r)},updateChildren:function(e,t,n){this._updateChildren(e,t,n)},_updateChildren:function(e,t,n){var r=this._renderedChildren,o={},i=[],a=this._reconcilerUpdateChildren(r,e,i,o,t,n);if(a||r){var s,c=null,p=0,d=0,h=0,v=null;for(s in a)if(a.hasOwnProperty(s)){var m=r&&r[s],g=a[s];m===g?(c=u(c,this.moveChild(m,v,p,d)),d=Math.max(m._mountIndex,d),m._mountIndex=p):(m&&(d=Math.max(m._mountIndex,d)),c=u(c,this._mountChildAtIndex(g,i[h],v,p,t,n)),h++),p++,v=f.getHostNode(g)}for(s in o)o.hasOwnProperty(s)&&(c=u(c,this._unmountChild(r[s],o[s])));c&&l(this,c),this._renderedChildren=a}},unmountChildren:function(e){var t=this._renderedChildren;d.unmountChildren(t,e),this._renderedChildren=null},moveChild:function(e,t,n,r){if(e._mountIndex=t)return{node:o,offset:t-i};i=a}o=n(r(o))}}e.exports=o},function(e,t,n){"use strict";function r(e){return'"'+o(e)+'"'}var o=n(34);e.exports=r},function(e,t,n){"use strict";var r=n(72);e.exports=r.renderSubtreeIntoContainer},function(e,t,n){"use strict";"undefined"==typeof Promise&&(n(115).enable(),window.Promise=n(114)),n(211),Object.assign=n(4)},function(e,t,n){(function(){var t,r,o;t=n(6),r=t.createClass,o=t.DOM.div,e.exports=r({getDefaultProps:function(){return{className:"",onHeightChange:function(){}}},render:function(){return o({className:this.props.className,ref:"dropdown"},this.props.children)},componentDidMount:function(){this.props.onHeightChange(this.refs.dropdown.offsetHeight)},componentDidUpdate:function(){this.props.onHeightChange(this.refs.dropdown.offsetHeight)},componentWillUnmount:function(){this.props.onHeightChange(0)}})}).call(this)},function(e,t,n){(function(){function t(e,t){var n={}.hasOwnProperty;for(var r in t)n.call(t,r)&&(e[r]=t[r]);return e}var r,o,i,a,s,u,l,c,p,f,d,h,v,m,g,y,b,C;r=n(14),o=r.filter,i=r.id,a=r.map,s=n(16).isEqualToObject,u=n(6),r=u.DOM,l=r.div,c=r.input,p=r.span,f=u.createClass,d=u.createFactory,h=n(17).findDOMNode,v=d(n(61)),m=d(n(186)),g=d(n(182)),y=d(n(84)),r=n(29),b=r.cancelEvent,C=r.classNameFromObject,e.exports=f({displayName:"DropdownMenu",getDefaultProps:function(){return{className:"",dropdownDirection:1,groupId:function(e){return e.groupId},groupsAsColumns:!1,highlightedUid:void 0,onHighlightedUidChange:function(e,t){},onOptionClick:function(e){},onScrollLockChange:function(e){},options:[],renderNoResultsFound:function(){return l({className:"no-results-found"},"No results found")},renderGroupTitle:function(e,t){var n,r;return null!=t&&(n=t.groupId,r=t.title),l({className:"simple-group-title",key:n},r)},renderOption:function(e){var t,n,r,o;return null!=e&&(t=e.label,n=e.newOption,r=e.selectable),o="undefined"==typeof r||r,l({className:"simple-option "+(o?"":"not-selectable")},p(null,n?"Add "+t+" ...":t))},scrollLock:!1,style:{},tether:!1,tetherProps:{},theme:"default",transitionEnter:!1,transitionLeave:!1,transitionEnterTimeout:200,transitionLeaveTimeout:200,uid:i}},render:function(){var e,n;return e=C((n={},n[this.props.theme+""]=1,n[this.props.className+""]=1,n.flipped=this.props.dropdownDirection===-1,n.tethered=this.props.tether,n)),this.props.tether?m((n=t({},this.props.tetherProps),n.options={attachment:"top left",targetAttachment:"bottom left",constraints:[{to:"scrollParent"}]},n),this.renderAnimatedDropdown({dynamicClassName:e})):this.renderAnimatedDropdown({dynamicClassName:e})},renderAnimatedDropdown:function(e){var t;return t=e.dynamicClassName,this.props.transitionEnter||this.props.transitionLeave?v({component:"div",transitionName:"custom",transitionEnter:this.props.transitionEnter,transitionLeave:this.props.transitionLeave,transitionEnterTimeout:this.props.transitionEnterTimeout,transitionLeaveTimeout:this.props.transitionLeaveTimeout,className:"dropdown-menu-wrapper "+t,ref:"dropdownMenuWrapper"},this.renderDropdown(e)):this.renderDropdown(e)},renderOptions:function(e){var n=this;return a(function(r){var o,i;return o=e[r],i=n.props.uid(o),y(t({uid:i,ref:"option-"+n.uidToString(i),key:n.uidToString(i),item:o,highlight:s(n.props.highlightedUid,i),selectable:null!=o?o.selectable:void 0,onMouseMove:function(e){var t;t=e.currentTarget,n.props.scrollLock&&n.props.onScrollLockChange(!1)},onMouseOut:function(){n.props.scrollLock||n.props.onHighlightedUidChange(void 0,function(){})},renderItem:n.props.renderOption},function(){switch(!1){case!("boolean"==typeof(null!=o?o.selectable:void 0)&&!o.selectable):return{onClick:b};default:return{onClick:function(){n.props.onOptionClick(n.props.highlightedUid)},onMouseOver:function(e){var t;t=e.currentTarget,n.props.scrollLock||n.props.onHighlightedUidChange(i,function(){})}}}}()))})(function(){var t,n,r=[];for(t=0,n=e.length;t0?(i=a(function(e){var t,n,r;return t=s.props.groups[e],n=t.groupId,r=o(function(e){return s.props.groupId(e)===n})(s.props.options),{index:e,group:t,options:r}})(function(){var e,t,n=[];for(e=0,t=this.props.groups.length;e0})(i)))):this.renderOptions(this.props.options)):null},componentDidUpdate:function(){var e,t,n;e=t=h(null!=(n=this.refs.dropdownMenuWrapper)?n:this.refs.dropdownMenu),null!=e&&(e.style.bottom=function(){switch(!1){case this.props.dropdownDirection!==-1:return this.props.bottomAnchor().offsetHeight+t.style.marginBottom+"px";default:return""}}.call(this))},highlightAndScrollToOption:function(e,t){var n,r=this;null==t&&(t=function(){}),n=this.props.uid(this.props.options[e]),this.props.onHighlightedUidChange(n,function(){var e,o,i,a,s;return null!=(e=h(null!=(o=r.refs)?o["option-"+r.uidToString(n)]:void 0))&&(i=e),i&&(a=h(r.refs.dropdownMenu),s=i.offsetHeight-1,i.offsetTop-a.scrollTop>=a.offsetHeight?a.scrollTop=i.offsetTop-a.offsetHeight+s:i.offsetTop-a.scrollTop+s<=0&&(a.scrollTop=i.offsetTop)),t()})},highlightAndScrollToSelectableOption:function(e,t,n){var r,o,i;null==n&&(n=function(){}),e<0||e>=this.props.options.length?this.props.onHighlightedUidChange(void 0,function(){return n(!1)}):(r=null!=(o=this.props)&&null!=(i=o.options)?i[e]:void 0,"boolean"!=typeof(null!=r?r.selectable:void 0)||r.selectable?this.highlightAndScrollToOption(e,function(){return n(!0)}):this.highlightAndScrollToSelectableOption(e+t,t,n))},uidToString:function(e){return("object"==typeof e?JSON.stringify:i)(e)}})}).call(this)},function(e,t,n){(function(){var t,r,o,i,a,s;t=n(6),r=t.createClass,o=t.DOM,i=o.div,a=o.span,s=n(14).map,e.exports=r({getDefaultProps:function(){return{partitions:[],text:"",style:{},highlightStyle:{}}},render:function(){var e=this;return i({className:"highlighted-text",style:this.props.style},s(function(t){var n,r,o;return n=t[0],r=t[1],o=t[2],a({key:e.props.text+""+n+r+o,className:o?"highlight":"",style:o?e.props.highlightStyle:{}},e.props.text.substring(n,r))})(this.props.partitions))}})}).call(this)},function(e,t,n){(function(){function t(e,t){for(var n=-1,r=t.length>>>0;++n1?function(){var i=o?o.concat():[];return n=t?n||this:this,i.push.apply(i,arguments)-1})(g(function(e){return t(e.label.trim(),m(function(e){return e.label.trim()},null!=n?n:[]))})(e))}),firstOptionIndexToHighlight:h,onBlur:function(e){},onFocus:function(e){},onPaste:function(e){},serialize:m(function(e){return null!=e?e.value:void 0}),tether:!1}},render:function(){var e,t,n,r,i,a,s,u,l,c,p,f,d,h,m,g,y,b,C,_,w,E,O,P,k,S,N,M,A,I,D,R,L,U,F,j,B,V,W=this;return e=this.getComputedState(),t=e.anchor,n=e.filteredOptions,r=e.highlightedUid,i=e.onAnchorChange,a=e.onOpenChange,s=e.onHighlightedUidChange,u=e.onSearchChange,l=e.onValuesChange,c=e.search,p=e.open,f=e.options,d=e.values,null!=(e=this.props)&&(h=e.autofocus,m=e.autosize,g=e.cancelKeyboardEventOnSelection,y=e.delimiters,b=e.disabled,C=e.dropdownDirection,_=e.groupId,w=e.groups,E=e.groupsAsColumns,O=e.hideResetButton,P=e.inputProps,k=e.name,S=e.onKeyboardSelectionFailed,N=e.renderToggleButton,M=e.renderGroupTitle,A=e.renderResetButton,I=e.serialize,D=e.tether,R=e.tetherProps,L=e.theme,U=e.transitionEnter,F=e.transitionLeave,j=e.transitionEnterTimeout,B=e.transitionLeaveTimeout,V=e.uid),x(o(o({autofocus:h,autosize:m,cancelKeyboardEventOnSelection:g,className:"multi-select "+this.props.className,delimiters:y,disabled:b,dropdownDirection:C,groupId:_,groups:w,groupsAsColumns:E,hideResetButton:O,highlightedUid:r,onHighlightedUidChange:s,inputProps:P,name:k,onKeyboardSelectionFailed:S,renderGroupTitle:M,renderResetButton:A,renderToggleButton:N,scrollLock:this.state.scrollLock,onScrollLockChange:function(e){return W.setState({scrollLock:e})},tether:D,tetherProps:R,theme:L,transitionEnter:U,transitionEnterTimeout:j,transitionLeave:F,transitionLeaveTimeout:B,uid:V,ref:"select",anchor:t,onAnchorChange:i,open:p,onOpenChange:a,options:f,renderOption:this.props.renderOption,firstOptionIndexToHighlight:function(){return W.firstOptionIndexToHighlight(f)},search:c,onSearchChange:function(e,t){return u(W.props.maxValues&&d.length>=W.props.maxValues?"":e,t)},values:d,onValuesChange:function(e,t){return l(e,function(){if(t(),W.props.closeOnSelect||W.props.maxValues&&W.values().length>=W.props.maxValues)return a(!1,function(){})})},renderValue:this.props.renderValue,serialize:I,onBlur:function(e){u("",function(){return W.props.onBlur({open:p,values:d,originalEvent:e})})},onFocus:function(e){W.props.onFocus({open:p,values:d,originalEvent:e})},onPaste:function(){var e;switch(!1){case"undefined"!=typeof(null!=(e=this.props)?e.valuesFromPaste:void 0):return this.props.onPaste;default:return function(e){var t;return t=e.clipboardData,function(){var e;return e=d.concat(W.props.valuesFromPaste(f,d,t.getData("text"))),l(e,function(){return i(v(e))})}(),T(e)}}}.call(this),placeholder:this.props.placeholder,style:this.props.style},function(){switch(!1){case"function"!=typeof this.props.restoreOnBackspace:return{restoreOnBackspace:this.props.restoreOnBackspace};default:return{}}}.call(this)),function(){switch(!1){case"function"!=typeof this.props.renderNoResultsFound:return{renderNoResultsFound:function(){return W.props.renderNoResultsFound(d,c)}};default:return{}}}.call(this)))},getComputedState:function(){var e,t,n,r,i,a,s,l,c,p,f,d,h,v,g,y,b=this;return e=this.props.hasOwnProperty("anchor")?this.props.anchor:this.state.anchor,t=this.props.hasOwnProperty("highlightedUid")?this.props.highlightedUid:this.state.highlightedUid,n=this.isOpen(),r=this.props.hasOwnProperty("search")?this.props.search:this.state.search,i=this.values(),a=m(function(e){switch(!1){case!(b.props.hasOwnProperty(e)&&b.props.hasOwnProperty(u("on-"+e+"-change"))):return function(t,n){return b.props[u("on-"+e+"-change")](t,function(){}),b.setState({},n)};case!(b.props.hasOwnProperty(e)&&!b.props.hasOwnProperty(u("on-"+e+"-change"))):return function(e,t){return t()};case!(!b.props.hasOwnProperty(e)&&b.props.hasOwnProperty(u("on-"+e+"-change"))):return function(t,n){var r;return b.setState((r={},r[e+""]=t,r),function(){return n(),b.props[u("on-"+e+"-change")](t,function(){})})};case!(!b.props.hasOwnProperty(e)&&!b.props.hasOwnProperty(u("on-"+e+"-change"))):return function(t,n){var r;return b.setState((r={},r[e+""]=t,r),n)}}})(["anchor","highlightedUid","open","search","values"]),s=a[0],l=a[1],c=a[2],p=a[3],f=a[4],d=function(){var e;switch(!1){case!(null!=(e=this.props)&&e.children):return m(function(e){var t,n,r;return null!=e&&(t=e.props),null!=t&&(n=t.value,r=t.children),{label:r,value:n}})("Array"===O.call(this.props.children).slice(8,-1)?this.props.children:[this.props.children]);default:return[]}}.call(this),h=this.props.hasOwnProperty("options")?null!=(a=this.props.options)?a:[]:d,v=this.props.filterOptions(h,i,r),g=function(){switch(!1){case"function"!=typeof this.props.createFromSearch:return this.props.createFromSearch(v,i,r);default:return null}}.call(this),y=(g?[(a=o({},g),a.newOption=!0,a)]:[]).concat(v),{anchor:e,highlightedUid:t,search:r,values:i,onAnchorChange:s,onHighlightedUidChange:l,open:n,onOpenChange:function(e,t){c(function(){switch(!1){case!("undefined"!=typeof this.props.maxValues&&this.values().length>=this.props.maxValues):return!1;default:return e}}.call(b),t)},onSearchChange:p,onValuesChange:f,filteredOptions:v,options:y}},getInitialState:function(){return{anchor:this.props.values?v(this.props.values):void 0,highlightedUid:void 0,open:!1,scrollLock:!1,search:"",values:this.props.defaultValues}},firstOptionIndexToHighlight:function(e){var t,n;return t=function(){var t;switch(!1){case 1!==e.length:return 0;case"undefined"!=typeof(null!=(t=e[0])?t.newOption:void 0):return 0;default:return a(function(e){return"boolean"==typeof e.selectable&&!e.selectable})(c(1)(e))?0:1}}(),n=this.props.hasOwnProperty("search")?this.props.search:this.state.search,this.props.firstOptionIndexToHighlight(t,e,this.values(),n)},focus:function(){this.refs.select.focus()},blur:function(){this.refs.select.blur()},highlightFirstSelectableOption:function(){this.state.open&&this.refs.select.highlightAndScrollToSelectableOption(this.firstOptionIndexToHighlight(this.getComputedState().options),1)},values:function(){return this.props.hasOwnProperty("values")?this.props.values:this.state.values},isOpen:function(){return this.props.hasOwnProperty("open")?this.props.open:this.state.open}})}).call(this)},function(e,t,n){(function(){function t(e,t){var n={}.hasOwnProperty;for(var r in t)n.call(t,r)&&(e[r]=t[r]);return e}var r,o,i,a,s,u;r=n(6).createClass,o=n(17),i=o.render,a=o.unmountComponentAtNode,s=n(119),u=n(210),e.exports=r({getDefaultProps:function(){return{parentElement:function(){return document.body}}},render:function(){return null},initTether:function(e){var n=this;this.node=document.createElement("div"),this.props.parentElement().appendChild(this.node),this.tether=new u(t({element:this.node,target:e.target()},e.options)),i(e.children,this.node,function(){return n.tether.position()})},destroyTether:function(){this.tether&&this.tether.destroy(),this.node&&(a(this.node),this.node.parentElement.removeChild(this.node)),this.node=this.tether=void 0},componentDidMount:function(){this.props.children&&this.initTether(this.props)},componentWillReceiveProps:function(e){var n=this;this.props.children&&!e.children?this.destroyTether():e.children&&!this.props.children?this.initTether(e):e.children&&(this.tether.setOptions(t({element:this.node,target:e.target() -},e.options)),i(e.children,this.node,function(){return n.tether.position()}))},shouldComponentUpdate:function(e,t){return s(this,e,t)},componentWillUnmount:function(){this.destroyTether()}})}).call(this)},function(e,t,n){(function(){var t,r,o,i,a;t=n(6),r=t.createClass,o=t.createFactory,i=t.DOM.path,a=o(n(85)),e.exports=r({render:function(){return a({className:"react-selectize-reset-button",style:{width:8,height:8}},i({d:"M0 0 L8 8 M8 0 L 0 8"}))}})}).call(this)},function(e,t,n){(function(){function t(e,t){var n={}.hasOwnProperty;for(var r in t)n.call(t,r)&&(e[r]=t[r]);return e}var r,o,i,a,s,u,l,c;r=n(14),o=r.each,i=r.objToPairs,a=n(6),s=a.DOM.input,u=a.createClass,l=a.createFactory,c=n(17).findDOMNode,e.exports=u({displayName:"ResizableInput",render:function(){var e;return s((e=t({},this.props),e.type="input",e.className="resizable-input",e))},autosize:function(){var e,t,n,r,a;return e=t=c(this),e.style.width="0px",0===t.value.length?t.style.width=null!=t&&t.currentStyle?"4px":"2px":t.scrollWidth>0?t.style.width=2+t.scrollWidth+"px":(n=r=document.createElement("div"),n.innerHTML=t.value,function(){var e;return e=r.style,e.display="inline-block",e.width="",e}(o(function(e){var t,n;return t=e[0],n=e[1],r.style[t]=n})(i(t.currentStyle?t.currentStyle:null!=(a=document.defaultView)?a:window.getComputedStyle(t)))),document.body.appendChild(r),t.style.width=4+r.clientWidth+"px",document.body.removeChild(r))},componentDidMount:function(){this.autosize()},componentDidUpdate:function(){this.autosize()},blur:function(){return c(this).blur()},focus:function(){return c(this).focus()}})}).call(this)},function(e,t,n){(function(){function t(e,t){var n,r=function(o){return e.length>1?function(){var i=o?o.concat():[];return n=t?n||this:this,i.push.apply(i,arguments)-1})(e)}),firstOptionIndexToHighlight:d,onBlur:function(e){},onBlurResetsInput:!0,onFocus:function(e){},onKeyboardSelectionFailed:function(e){},onPaste:function(e){},placeholder:"",renderValue:function(e){var t;return t=e.label,C({className:"simple-value"},w(null,t))},serialize:function(e){return null!=e?e.value:void 0},style:{},tether:!1,uid:d}},render:function(){var e,t,n,o,i,a,s,u,l,c,p,f,d,v,m,y,b,C,_,w,T,O,P,k,S,N,M,A,I,D,R,L,U,F,j,B,V,W=this;return e=this.getComputedState(),t=e.filteredOptions,n=e.highlightedUid,o=e.onHighlightedUidChange,i=e.onOpenChange,a=e.onSearchChange,s=e.onValueChange,u=e.open,l=e.options,c=e.search,p=e.value,f=e.values,null!=(e=this.props)&&(d=e.autofocus,v=e.autosize,m=e.cancelKeyboardEventOnSelection,y=e.delimiters,b=e.disabled,C=e.dropdownDirection,_=e.groupId,w=e.groups,T=e.groupsAsColumns,O=e.hideResetButton,P=e.name,k=e.inputProps,S=e.onBlurResetsInput,N=e.renderToggleButton,M=e.renderGroupTitle,A=e.renderResetButton,I=e.serialize,D=e.tether,R=e.tetherProps,L=e.theme,U=e.transitionEnter,F=e.transitionLeave,j=e.transitionEnterTimeout,B=e.transitionLeaveTimeout,V=e.uid),E(r(r({autofocus:d,autosize:v,cancelKeyboardEventOnSelection:m,className:"simple-select"+(this.props.className?" "+this.props.className:""),delimiters:y,disabled:b,dropdownDirection:C,groupId:_,groups:w,groupsAsColumns:T,hideResetButton:O,highlightedUid:n,onHighlightedUidChange:o,inputProps:k,name:P,onBlurResetsInput:S,renderGroupTitle:M,renderResetButton:A,renderToggleButton:N,scrollLock:this.state.scrollLock,onScrollLockChange:function(e){return W.setState({scrollLock:e})},tether:D,tetherProps:R,theme:L,transitionEnter:U,transitionEnterTimeout:j,transitionLeave:F,transitionLeaveTimeout:B,ref:"select",anchor:h(f),onAnchorChange:function(e,t){return t()},open:u,onOpenChange:i,firstOptionIndexToHighlight:function(){return W.firstOptionIndexToHighlight(l,p)},options:l,renderOption:this.props.renderOption,renderNoResultsFound:this.props.renderNoResultsFound,search:c,onSearchChange:function(e,t){return a(e,t)},values:f,onValuesChange:function(e,t){var n,r;return 0===e.length?s(void 0,function(){return t()}):(n=h(e),r=!g(n,p),function(){return function(e){return r?s(n,e):e()}}()(function(){return t(),i(!1,function(){})}))},renderValue:function(e){return u&&(W.props.editable||c.length>0)?null:W.props.renderValue(e)},onKeyboardSelectionFailed:function(e){return a("",function(){return i(!1,function(){return W.props.onKeyboardSelectionFailed(e)})})},uid:function(e){return{uid:W.props.uid(e),open:u,search:c}},serialize:function(e){return I(e[0])},onBlur:function(e){var t;t=W.props.onBlurResetsInput,function(){return function(e){return c.length>0&&t?a("",e):e()}}()(function(){return W.props.onBlur({value:p,open:u,originalEvent:e})})},onFocus:function(e){W.props.onFocus({value:p,open:u,originalEvent:e})},onPaste:function(){var e;switch(!1){case"undefined"!=typeof(null!=(e=this.props)?e.valueFromPaste:void 0):return this.props.onPaste;default:return function(e){var t,n;if(t=e.clipboardData,n=W.props.valueFromPaste(l,p,t.getData("text")))return function(){return s(n,function(){return a("",function(){return i(!1)})})}(),x(e)}}}.call(this),placeholder:this.props.placeholder,style:this.props.style},function(){switch(!1){case"function"!=typeof this.props.restoreOnBackspace:return{restoreOnBackspace:this.props.restoreOnBackspace};default:return{}}}.call(this)),function(){switch(!1){case"function"!=typeof this.props.renderNoResultsFound:return{renderNoResultsFound:function(){return W.props.renderNoResultsFound(p,c)}};default:return{}}}.call(this)))},getComputedState:function(){var e,t,n,o,i,a,s,l,c,p,f,d,h,m,g,y=this;return e=this.props.hasOwnProperty("highlightedUid")?this.props.highlightedUid:this.state.highlightedUid,t=this.isOpen(),n=this.props.hasOwnProperty("search")?this.props.search:this.state.search,o=this.value(),i=o||0===o?[o]:[],a=v(function(e){var t;return t=function(){switch(!1){case!(this.props.hasOwnProperty(e)&&this.props.hasOwnProperty(u("on-"+e+"-change"))):return function(t,n){return y.props[u("on-"+e+"-change")](t,function(){}),y.setState({},n)};case!(this.props.hasOwnProperty(e)&&!this.props.hasOwnProperty(u("on-"+e+"-change"))):return function(e,t){return t()};case!(!this.props.hasOwnProperty(e)&&this.props.hasOwnProperty(u("on-"+e+"-change"))):return function(t,n){var r;return y.setState((r={},r[e+""]=t,r),function(){return n(),y.props[u("on-"+e+"-change")](t,function(){})})};case!(!this.props.hasOwnProperty(e)&&!this.props.hasOwnProperty(u("on-"+e+"-change"))):return function(t,n){var r;return y.setState((r={},r[e+""]=t,r),n)}}}.call(y)})(["highlightedUid","open","search","value"]),s=a[0],l=a[1],c=a[2],p=a[3],f=function(){var e;switch(!1){case!(null!=(e=this.props)&&e.children):return v(function(e){var t,n,r;return null!=(t=null!=e?e.props:void 0)&&(n=t.value,r=t.children),{label:r,value:n}})("Array"===T.call(this.props.children).slice(8,-1)?this.props.children:[this.props.children]);default:return[]}}.call(this),d=this.props.hasOwnProperty("options")?null!=(a=this.props.options)?a:[]:f,h=this.props.filterOptions(d,n),m=function(){switch(!1){case"function"!=typeof this.props.createFromSearch:return this.props.createFromSearch(h,n);default:return null}}.call(this),g=(m?[(a=r({},m),a.newOption=!0,a)]:[]).concat(h),{highlightedUid:e,open:t,search:n,value:o,values:i,onHighlightedUidChange:s,onOpenChange:function(e,t){l(e,function(){if(t(),y.props.editable&&y.isOpen()&&o)return c(y.props.editable(o)+""+(1===n.length?n:""),function(){return y.highlightFirstSelectableOption(function(){})})})},onSearchChange:c,onValueChange:p,filteredOptions:h,options:g}},getInitialState:function(){var e;return{highlightedUid:void 0,open:!1,scrollLock:!1,search:"",value:null!=(e=this.props)?e.defaultValue:void 0}},firstOptionIndexToHighlight:function(e,t){var n,r,o;return n=t?f(function(e){return g(e,t)},e):void 0,r=function(){var t;switch(!1){case"undefined"==typeof n:return n;case 1!==e.length:return 0;case"undefined"!=typeof(null!=(t=e[0])?t.newOption:void 0):return 0;default:return i(function(e){return"boolean"==typeof e.selectable&&!e.selectable})(s(1)(e))?0:1}}(),o=this.props.hasOwnProperty("search")?this.props.search:this.state.search,this.props.firstOptionIndexToHighlight(r,e,t,o)},focus:function(){this.refs.select.focus()},blur:function(){this.refs.select.blur()},highlightFirstSelectableOption:function(e){var t,n,r;null==e&&(e=function(){}),this.state.open?(t=this.getComputedState(),n=t.options,r=t.value,this.refs.select.highlightAndScrollToSelectableOption(this.firstOptionIndexToHighlight(n,r),1,e)):e()},value:function(){return this.props.hasOwnProperty("value")?this.props.value:this.state.value},isOpen:function(){return this.props.hasOwnProperty("open")?this.props.open:this.state.open}})}).call(this)},function(e,t,n){(function(){var t,r,o,i,a;t=n(6),r=t.createClass,o=t.createFactory,i=t.DOM.path,a=o(n(85)),e.exports=r({getDefaultProps:function(){return{open:!1,flipped:!1}},render:function(){return a({className:"react-selectize-toggle-button",style:{width:10,height:8}},i({d:function(){switch(!1){case!(this.props.open&&!this.props.flipped||!this.props.open&&this.props.flipped):return"M0 6 L5 1 L10 6 Z";default:return"M0 1 L5 6 L10 1 Z"}}.call(this)}))}})}).call(this)},function(e,t,n){(function(){var t,r,o,i;t=n(6),r=t.createClass,o=t.DOM.div,i=n(16).isEqualToObject,e.exports=r({getDefaultProps:function(){return{}},render:function(){return o({className:"value-wrapper"},this.props.renderItem(this.props.item))},shouldComponentUpdate:function(e){var t;return!i(null!=e?e.uid:void 0,null!=(t=this.props)?t.uid:void 0)}})}).call(this)},function(e,t,n){(function(){var t,r,o,i;t=n(184),r=n(189),o=n(185),i=n(53),e.exports={HighlightedText:t,SimpleSelect:r,MultiSelect:o,ReactSelectize:i}}).call(this)},[212,22],function(e,t,n){"use strict";var r=n(65);t.getReactDOM=function(){return r}},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function a(e){var t="transition"+e+"Timeout",n="transition"+e;return function(e){if(e[n]){if(null==e[t])return new Error(t+" wasn't supplied to ReactCSSTransitionGroup: this can cause unreliable animations and won't be supported in a future version of React. See https://fb.me/react-animation-transition-group-timeout for more information.");if("number"!=typeof e[t])return new Error(t+" must be a number (in milliseconds)")}}}var s=n(4),u=n(12),l=n(24),c=l(u.isValidElement),p=n(205),f=n(196),d=function(e){function t(){var n,i,a;r(this,t);for(var s=arguments.length,l=Array(s),c=0;c=0)&&r.push(o)}return r.push(e.ownerDocument.body),e.ownerDocument!==document&&r.push(e.ownerDocument.defaultView),r}function a(){T&&document.body.removeChild(T),T=null}function s(e){var t=void 0;e===document?(t=document,e=document.documentElement):t=e.ownerDocument;var n=t.documentElement,r=o(e),i=k();return r.top-=i.top,r.left-=i.left,"undefined"==typeof r.width&&(r.width=document.body.scrollWidth-r.left-r.right),"undefined"==typeof r.height&&(r.height=document.body.scrollHeight-r.top-r.bottom),r.top=r.top-n.clientTop,r.left=r.left-n.clientLeft,r.right=t.body.clientWidth-r.width-r.left,r.bottom=t.body.clientHeight-r.height-r.top,r}function u(e){return e.offsetParent||document.documentElement}function l(){if(S)return S;var e=document.createElement("div");e.style.width="100%",e.style.height="200px";var t=document.createElement("div");c(t.style,{position:"absolute",top:0,left:0,pointerEvents:"none",visibility:"hidden",width:"200px",height:"150px",overflow:"hidden"}),t.appendChild(e),document.body.appendChild(t);var n=e.offsetWidth;t.style.overflow="scroll";var r=e.offsetWidth;n===r&&(r=t.clientWidth),document.body.removeChild(t);var o=n-r;return S={width:o,height:o}}function c(){var e=arguments.length<=0||void 0===arguments[0]?{}:arguments[0],t=[];return Array.prototype.push.apply(t,arguments),t.slice(1).forEach(function(t){if(t)for(var n in t)({}).hasOwnProperty.call(t,n)&&(e[n]=t[n])}),e}function p(e,t){if("undefined"!=typeof e.classList)t.split(" ").forEach(function(t){t.trim()&&e.classList.remove(t)});else{var n=new RegExp("(^| )"+t.split(" ").join("|")+"( |$)","gi"),r=h(e).replace(n," ");v(e,r)}}function f(e,t){if("undefined"!=typeof e.classList)t.split(" ").forEach(function(t){t.trim()&&e.classList.add(t)});else{p(e,t);var n=h(e)+(" "+t);v(e,n)}}function d(e,t){if("undefined"!=typeof e.classList)return e.classList.contains(t);var n=h(e);return new RegExp("(^| )"+t+"( |$)","gi").test(n)}function h(e){return e.className instanceof e.ownerDocument.defaultView.SVGAnimatedString?e.className.baseVal:e.className}function v(e,t){e.setAttribute("class",t)}function m(e,t,n){n.forEach(function(n){t.indexOf(n)===-1&&d(e,n)&&p(e,n)}),t.forEach(function(t){d(e,t)||f(e,t)})}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function g(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function y(e,t){var n=arguments.length<=2||void 0===arguments[2]?1:arguments[2];return e+n>=t&&t>=e-n}function b(){return"undefined"!=typeof performance&&"undefined"!=typeof performance.now?performance.now():+new Date}function C(){for(var e={top:0,left:0},t=arguments.length,n=Array(t),r=0;r1?n-1:0),o=1;o16?(t=Math.min(t-16,250),void(n=setTimeout(r,250))):void("undefined"!=typeof e&&b()-e<10||(null!=n&&(clearTimeout(n),n=null),e=b(),j(),t=b()-e))};"undefined"!=typeof window&&"undefined"!=typeof window.addEventListener&&["resize","scroll","touchmove"].forEach(function(e){window.addEventListener(e,r)})}();var B={center:"center",left:"right",right:"left"},V={middle:"middle",top:"bottom",bottom:"top"},W={top:0,left:0,middle:"50%",center:"50%",bottom:"100%",right:"100%"},H=function(e,t){var n=e.left,r=e.top;return"auto"===n&&(n=B[t.left]),"auto"===r&&(r=V[t.top]),{left:n,top:r}},q=function(e){var t=e.left,n=e.top;return"undefined"!=typeof W[e.left]&&(t=W[e.left]),"undefined"!=typeof W[e.top]&&(n=W[e.top]),{left:t,top:n}},z=function(e){var t=e.split(" "),n=D(t,2),r=n[0],o=n[1];return{top:r,left:o}},K=z,Y=function(e){function t(e){var n=this;r(this,t),R(Object.getPrototypeOf(t.prototype),"constructor",this).call(this),this.position=this.position.bind(this),F.push(this),this.history=[],this.setOptions(e,!1),x.modules.forEach(function(e){"undefined"!=typeof e.initialize&&e.initialize.call(n)}),this.position()}return g(t,e),E(t,[{key:"getClass",value:function(){var e=arguments.length<=0||void 0===arguments[0]?"":arguments[0],t=this.options.classes;return"undefined"!=typeof t&&t[e]?this.options.classes[e]:this.options.classPrefix?this.options.classPrefix+"-"+e:e}},{key:"setOptions",value:function(e){var t=this,n=arguments.length<=1||void 0===arguments[1]||arguments[1],r={offset:"0 0",targetOffset:"0 0",targetAttachment:"auto auto",classPrefix:"tether"};this.options=c(r,e);var o=this.options,a=o.element,s=o.target,u=o.targetModifier;if(this.element=a,this.target=s,this.targetModifier=u,"viewport"===this.target?(this.target=document.body,this.targetModifier="visible"):"scroll-handle"===this.target&&(this.target=document.body,this.targetModifier="scroll-handle"),["element","target"].forEach(function(e){if("undefined"==typeof t[e])throw new Error("Tether Error: Both element and target must be defined");"undefined"!=typeof t[e].jquery?t[e]=t[e][0]:"string"==typeof t[e]&&(t[e]=document.querySelector(t[e]))}),f(this.element,this.getClass("element")),this.options.addTargetClasses!==!1&&f(this.target,this.getClass("target")),!this.options.attachment)throw new Error("Tether Error: You must provide an attachment");this.targetAttachment=K(this.options.targetAttachment),this.attachment=K(this.options.attachment),this.offset=z(this.options.offset),this.targetOffset=z(this.options.targetOffset),"undefined"!=typeof this.scrollParents&&this.disable(),"scroll-handle"===this.targetModifier?this.scrollParents=[this.target]:this.scrollParents=i(this.target),this.options.enabled!==!1&&this.enable(n)}},{key:"getTargetBounds",value:function(){if("undefined"==typeof this.targetModifier)return s(this.target);if("visible"===this.targetModifier){if(this.target===document.body)return{top:pageYOffset,left:pageXOffset,height:innerHeight,width:innerWidth};var e=s(this.target),t={height:e.height,width:e.width,top:e.top,left:e.left};return t.height=Math.min(t.height,e.height-(pageYOffset-e.top)),t.height=Math.min(t.height,e.height-(e.top+e.height-(pageYOffset+innerHeight))),t.height=Math.min(innerHeight,t.height),t.height-=2,t.width=Math.min(t.width,e.width-(pageXOffset-e.left)),t.width=Math.min(t.width,e.width-(e.left+e.width-(pageXOffset+innerWidth))),t.width=Math.min(innerWidth,t.width),t.width-=2,t.topn.clientWidth||[r.overflow,r.overflowX].indexOf("scroll")>=0||this.target!==document.body,i=0;o&&(i=15);var a=e.height-parseFloat(r.borderTopWidth)-parseFloat(r.borderBottomWidth)-i,t={width:15,height:.975*a*(a/n.scrollHeight),left:e.left+e.width-parseFloat(r.borderLeftWidth)-15},u=0;a<408&&this.target===document.body&&(u=-11e-5*Math.pow(a,2)-.00727*a+22.58),this.target!==document.body&&(t.height=Math.max(t.height,24));var l=this.target.scrollTop/(n.scrollHeight-a);return t.top=l*(a-t.height-u)+e.top+parseFloat(r.borderTopWidth),this.target===document.body&&(t.height=Math.max(t.height,24)),t}}},{key:"clearCache",value:function(){this._cache={}}},{key:"cache",value:function(e,t){return"undefined"==typeof this._cache&&(this._cache={}),"undefined"==typeof this._cache[e]&&(this._cache[e]=t.call(this)),this._cache[e]}},{key:"enable",value:function(){var e=this,t=arguments.length<=0||void 0===arguments[0]||arguments[0];this.options.addTargetClasses!==!1&&f(this.target,this.getClass("enabled")),f(this.element,this.getClass("enabled")),this.enabled=!0,this.scrollParents.forEach(function(t){t!==e.target.ownerDocument&&t.addEventListener("scroll",e.position)}),t&&this.position()}},{key:"disable",value:function(){var e=this;p(this.target,this.getClass("enabled")),p(this.element,this.getClass("enabled")),this.enabled=!1,"undefined"!=typeof this.scrollParents&&this.scrollParents.forEach(function(t){t.removeEventListener("scroll",e.position)})}},{key:"destroy",value:function(){var e=this;this.disable(),F.forEach(function(t,n){t===e&&F.splice(n,1)}),0===F.length&&a()}},{key:"updateAttachClasses",value:function(e,t){var n=this;e=e||this.attachment,t=t||this.targetAttachment;var r=["left","top","bottom","right","middle","center"];"undefined"!=typeof this._addAttachClasses&&this._addAttachClasses.length&&this._addAttachClasses.splice(0,this._addAttachClasses.length),"undefined"==typeof this._addAttachClasses&&(this._addAttachClasses=[]);var o=this._addAttachClasses;e.top&&o.push(this.getClass("element-attached")+"-"+e.top),e.left&&o.push(this.getClass("element-attached")+"-"+e.left),t.top&&o.push(this.getClass("target-attached")+"-"+t.top),t.left&&o.push(this.getClass("target-attached")+"-"+t.left);var i=[];r.forEach(function(e){i.push(n.getClass("element-attached")+"-"+e),i.push(n.getClass("target-attached")+"-"+e)}),M(function(){"undefined"!=typeof n._addAttachClasses&&(m(n.element,n._addAttachClasses,i),n.options.addTargetClasses!==!1&&m(n.target,n._addAttachClasses,i),delete n._addAttachClasses)})}},{key:"position",value:function(){var e=this,t=arguments.length<=0||void 0===arguments[0]||arguments[0];if(this.enabled){this.clearCache();var n=H(this.targetAttachment,this.attachment);this.updateAttachClasses(this.attachment,n);var r=this.cache("element-bounds",function(){return s(e.element)}),o=r.width,i=r.height;if(0===o&&0===i&&"undefined"!=typeof this.lastSize){var a=this.lastSize;o=a.width,i=a.height}else this.lastSize={width:o,height:i};var c=this.cache("target-bounds",function(){return e.getTargetBounds()}),p=c,f=_(q(this.attachment),{width:o,height:i}),d=_(q(n),p),h=_(this.offset,{width:o,height:i}),v=_(this.targetOffset,p);f=C(f,h),d=C(d,v);for(var m=c.left+d.left-f.left,g=c.top+d.top-f.top,y=0;yT.documentElement.clientHeight&&(P=this.cache("scrollbar-size",l),E.viewport.bottom-=P.height),O.innerWidth>T.documentElement.clientWidth&&(P=this.cache("scrollbar-size",l),E.viewport.right-=P.width),["","static"].indexOf(T.body.style.position)!==-1&&["","static"].indexOf(T.body.parentElement.style.position)!==-1||(E.page.bottom=T.body.scrollHeight-g-i,E.page.right=T.body.scrollWidth-m-o),"undefined"!=typeof this.options.optimizations&&this.options.optimizations.moveElement!==!1&&"undefined"==typeof this.targetModifier&&!function(){var t=e.cache("target-offsetparent",function(){return u(e.target)}),n=e.cache("target-offsetparent-bounds",function(){return s(t)}),r=getComputedStyle(t),o=n,i={};if(["Top","Left","Bottom","Right"].forEach(function(e){i[e.toLowerCase()]=parseFloat(r["border"+e+"Width"])}),n.right=T.body.scrollWidth-n.left-o.width+i.right,n.bottom=T.body.scrollHeight-n.top-o.height+i.bottom,E.page.top>=n.top+i.top&&E.page.bottom>=n.bottom&&E.page.left>=n.left+i.left&&E.page.right>=n.right){var a=t.scrollTop,l=t.scrollLeft;E.offset={top:E.page.top-n.top+a-i.top,left:E.page.left-n.left+l-i.left}}}(),this.move(E),this.history.unshift(E),this.history.length>3&&this.history.pop(),t&&A(),!0}}},{key:"move",value:function(e){var t=this;if("undefined"!=typeof this.element.parentNode){var n={};for(var r in e){n[r]={};for(var o in e[r]){for(var i=!1,a=0;a=0){var h=s.split(" "),m=D(h,2);p=m[0],c=m[1]}else c=p=s;var b=w(t,i);"target"!==p&&"both"!==p||(nb[3]&&"bottom"===g.top&&(n-=f,g.top="top")),"together"===p&&("top"===g.top&&("bottom"===y.top&&nb[3]&&n-(a-f)>=b[1]&&(n-=a-f,g.top="bottom",y.top="bottom")),"bottom"===g.top&&("top"===y.top&&n+a>b[3]?(n-=f,g.top="top",n-=a,y.top="bottom"):"bottom"===y.top&&nb[3]&&"top"===y.top?(n-=a,y.top="bottom"):nb[2]&&"right"===g.left&&(r-=d,g.left="left")),"together"===c&&(rb[2]&&"right"===g.left?"left"===y.left?(r-=d,g.left="left",r-=u,y.left="right"):"right"===y.left&&(r-=d,g.left="left",r+=u,y.left="left"):"center"===g.left&&(r+u>b[2]&&"left"===y.left?(r-=u,y.left="right"):rb[3]&&"top"===y.top&&(n-=a,y.top="bottom")),"element"!==c&&"both"!==c||(rb[2]&&("left"===y.left?(r-=u,y.left="right"):"center"===y.left&&(r-=u/2,y.left="right"))),"string"==typeof l?l=l.split(",").map(function(e){return e.trim()}):l===!0&&(l=["top","left","right","bottom"]),l=l||[];var C=[],_=[];n=0?(n=b[1],C.push("top")):_.push("top")),n+a>b[3]&&(l.indexOf("bottom")>=0?(n=b[3]-a,C.push("bottom")):_.push("bottom")),r=0?(r=b[0],C.push("left")):_.push("left")),r+u>b[2]&&(l.indexOf("right")>=0?(r=b[2]-u,C.push("right")):_.push("right")),C.length&&!function(){var e=void 0;e="undefined"!=typeof t.options.pinnedClass?t.options.pinnedClass:t.getClass("pinned"),v.push(e),C.forEach(function(t){v.push(e+"-"+t)})}(),_.length&&!function(){var e=void 0;e="undefined"!=typeof t.options.outOfBoundsClass?t.options.outOfBoundsClass:t.getClass("out-of-bounds"),v.push(e),_.forEach(function(t){v.push(e+"-"+t)})}(),(C.indexOf("left")>=0||C.indexOf("right")>=0)&&(y.left=g.left=!1),(C.indexOf("top")>=0||C.indexOf("bottom")>=0)&&(y.top=g.top=!1),g.top===o.top&&g.left===o.left&&y.top===t.attachment.top&&y.left===t.attachment.left||(t.updateAttachClasses(y,g),t.trigger("update",{attachment:y,targetAttachment:g}))}),M(function(){t.options.addTargetClasses!==!1&&m(t.target,v,h),m(t.element,v,h)}),{top:n,left:r}}});var L=x.Utils,s=L.getBounds,m=L.updateClasses,M=L.defer;x.modules.push({position:function(e){var t=this,n=e.top,r=e.left,o=this.cache("element-bounds",function(){return s(t.element)}),i=o.height,a=o.width,u=this.getTargetBounds(),l=n+i,c=r+a,p=[];n<=u.bottom&&l>=u.top&&["left","right"].forEach(function(e){var t=u[e];t!==r&&t!==c||p.push(e)}),r<=u.right&&c>=u.left&&["top","bottom"].forEach(function(e){var t=u[e];t!==n&&t!==l||p.push(e)});var f=[],d=[],h=["left","top","right","bottom"];return f.push(this.getClass("abutted")),h.forEach(function(e){f.push(t.getClass("abutted")+"-"+e)}),p.length&&d.push(this.getClass("abutted")),p.forEach(function(e){d.push(t.getClass("abutted")+"-"+e)}),M(function(){t.options.addTargetClasses!==!1&&m(t.target,d,f),m(t.element,d,f)}),!0}});var D=function(){function e(e,t){var n=[],r=!0,o=!1,i=void 0;try{for(var a,s=e[Symbol.iterator]();!(r=(a=s.next()).done)&&(n.push(a.value),!t||n.length!==t);r=!0);}catch(e){o=!0,i=e}finally{try{!r&&s.return&&s.return()}finally{if(o)throw i}}return n}return function(t,n){if(Array.isArray(t))return t;if(Symbol.iterator in Object(t))return e(t,n);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}();return x.modules.push({position:function(e){var t=e.top,n=e.left;if(this.options.shift){var r=this.options.shift;"function"==typeof this.options.shift&&(r=this.options.shift.call(this,{top:t,left:n}));var o=void 0,i=void 0;if("string"==typeof r){r=r.split(" "),r[1]=r[1]||r[0];var a=r,s=D(a,2);o=s[0],i=s[1],o=parseFloat(o,10),i=parseFloat(i,10)}else o=r.top,i=r.left;return t+=o,n+=i,{top:t,left:n}}}}),X})},function(e,t){!function(e){"use strict";function t(e){if("string"!=typeof e&&(e=String(e)),/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(e))throw new TypeError("Invalid character in header field name");return e.toLowerCase()}function n(e){return"string"!=typeof e&&(e=String(e)),e}function r(e){var t={next:function(){var t=e.shift();return{done:void 0===t,value:t}}};return g.iterable&&(t[Symbol.iterator]=function(){return t}),t}function o(e){this.map={},e instanceof o?e.forEach(function(e,t){this.append(t,e)},this):e&&Object.getOwnPropertyNames(e).forEach(function(t){this.append(t,e[t])},this)}function i(e){return e.bodyUsed?Promise.reject(new TypeError("Already read")):void(e.bodyUsed=!0)}function a(e){return new Promise(function(t,n){e.onload=function(){t(e.result)},e.onerror=function(){n(e.error)}})}function s(e){var t=new FileReader,n=a(t);return t.readAsArrayBuffer(e),n}function u(e){var t=new FileReader,n=a(t);return t.readAsText(e),n}function l(e){for(var t=new Uint8Array(e),n=new Array(t.length),r=0;r-1?t:e}function d(e,t){t=t||{};var n=t.body;if(e instanceof d){if(e.bodyUsed)throw new TypeError("Already read");this.url=e.url,this.credentials=e.credentials,t.headers||(this.headers=new o(e.headers)),this.method=e.method,this.mode=e.mode,n||null==e._bodyInit||(n=e._bodyInit,e.bodyUsed=!0)}else this.url=String(e);if(this.credentials=t.credentials||this.credentials||"omit",!t.headers&&this.headers||(this.headers=new o(t.headers)),this.method=f(t.method||this.method||"GET"),this.mode=t.mode||this.mode||null,this.referrer=null,("GET"===this.method||"HEAD"===this.method)&&n)throw new TypeError("Body not allowed for GET or HEAD requests");this._initBody(n)}function h(e){var t=new FormData;return e.trim().split("&").forEach(function(e){if(e){var n=e.split("="),r=n.shift().replace(/\+/g," "),o=n.join("=").replace(/\+/g," ");t.append(decodeURIComponent(r),decodeURIComponent(o))}}),t}function v(e){var t=new o;return e.split(/\r?\n/).forEach(function(e){var n=e.split(":"),r=n.shift().trim();if(r){var o=n.join(":").trim();t.append(r,o)}}),t}function m(e,t){t||(t={}),this.type="default",this.status="status"in t?t.status:200,this.ok=this.status>=200&&this.status<300,this.statusText="statusText"in t?t.statusText:"OK",this.headers=new o(t.headers),this.url=t.url||"",this._initBody(e)}if(!e.fetch){var g={searchParams:"URLSearchParams"in e,iterable:"Symbol"in e&&"iterator"in Symbol,blob:"FileReader"in e&&"Blob"in e&&function(){try{return new Blob,!0}catch(e){return!1}}(),formData:"FormData"in e,arrayBuffer:"ArrayBuffer"in e};if(g.arrayBuffer)var y=["[object Int8Array]","[object Uint8Array]","[object Uint8ClampedArray]","[object Int16Array]","[object Uint16Array]","[object Int32Array]","[object Uint32Array]","[object Float32Array]","[object Float64Array]"],b=function(e){return e&&DataView.prototype.isPrototypeOf(e)},C=ArrayBuffer.isView||function(e){return e&&y.indexOf(Object.prototype.toString.call(e))>-1};o.prototype.append=function(e,r){e=t(e),r=n(r);var o=this.map[e];this.map[e]=o?o+","+r:r},o.prototype.delete=function(e){delete this.map[t(e)]},o.prototype.get=function(e){return e=t(e),this.has(e)?this.map[e]:null},o.prototype.has=function(e){return this.map.hasOwnProperty(t(e))},o.prototype.set=function(e,r){this.map[t(e)]=n(r)},o.prototype.forEach=function(e,t){for(var n in this.map)this.map.hasOwnProperty(n)&&e.call(t,this.map[n],n,this)},o.prototype.keys=function(){var e=[];return this.forEach(function(t,n){e.push(n)}),r(e)},o.prototype.values=function(){var e=[];return this.forEach(function(t){e.push(t)}),r(e)},o.prototype.entries=function(){var e=[];return this.forEach(function(t,n){e.push([n,t])}),r(e)},g.iterable&&(o.prototype[Symbol.iterator]=o.prototype.entries);var _=["DELETE","GET","HEAD","OPTIONS","POST","PUT"];d.prototype.clone=function(){return new d(this,{body:this._bodyInit})},p.call(d.prototype),p.call(m.prototype),m.prototype.clone=function(){return new m(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new o(this.headers),url:this.url})},m.error=function(){var e=new m(null,{status:0,statusText:""});return e.type="error",e};var w=[301,302,303,307,308];m.redirect=function(e,t){if(w.indexOf(t)===-1)throw new RangeError("Invalid status code");return new m(null,{status:t,headers:{location:e}})},e.Headers=o,e.Request=d,e.Response=m,e.fetch=function(e,t){return new Promise(function(n,r){var o=new d(e,t),i=new XMLHttpRequest;i.onload=function(){var e={status:i.status,statusText:i.statusText,headers:v(i.getAllResponseHeaders()||"")};e.url="responseURL"in i?i.responseURL:e.headers.get("X-Request-URL");var t="response"in i?i.response:i.responseText;n(new m(t,e))},i.onerror=function(){r(new TypeError("Network request failed"))},i.ontimeout=function(){r(new TypeError("Network request failed"))},i.open(o.method,o.url,!0),"include"===o.credentials&&(i.withCredentials=!0),"responseType"in i&&g.blob&&(i.responseType="blob"),o.headers.forEach(function(e,t){i.setRequestHeader(t,e)}),i.send("undefined"==typeof o._bodyInit?null:o._bodyInit)})},e.fetch.polyfill=!0}}("undefined"!=typeof self?self:this)},function(e,t,n,r){"use strict";var o=n(r),i=(n(1),function(e){var t=this;if(t.instancePool.length){var n=t.instancePool.pop();return t.call(n,e),n}return new t(e)}),a=function(e,t){var n=this;if(n.instancePool.length){var r=n.instancePool.pop();return n.call(r,e,t),r}return new n(e,t)},s=function(e,t,n){var r=this;if(r.instancePool.length){var o=r.instancePool.pop();return r.call(o,e,t,n),o}return new r(e,t,n)},u=function(e,t,n,r){var o=this;if(o.instancePool.length){var i=o.instancePool.pop();return o.call(i,e,t,n,r),i}return new o(e,t,n,r)},l=function(e){var t=this;e instanceof t?void 0:o("25"),e.destructor(),t.instancePool.length1?function(){var i=o?o.concat():[];return n=t?n||this:this,i.push.apply(i,arguments)0?r({},e[n]):e[n],t[n])})(o),e))}),P=t(function(e,t,n){var r,o,i;return r=t[0],o=N.call(t,1),o.length>0?(e[r]=null!=(i=e[r])?i:{},P(e[r],o,n)):(e[r]=n,e)}),k=function(e){return d(function(t){return d(function(e){return e[t]})(e)})(f(e[0]))},S=t(function(e,n,r){var o;return(o=t(function(e,t,n,r,i){return s(function(i){var a,s;return a=i[0],s=i[1],n1){for(var v=Array(m),g=0;g1){for(var b=Array(y),C=0;C]/;e.exports=r},function(e,t,n){"use strict";var r,o=n(7),i=n(38),a=/^[ \r\n\t\f]/,s=/<(!--|link|noscript|meta|script|style)[ \r\n\t\f\/>]/,u=n(46),l=u(function(e,t){if(e.namespaceURI!==i.svg||"innerHTML"in e)e.innerHTML=t;else{r=r||document.createElement("div"),r.innerHTML=""+t+"";for(var n=r.firstChild;n.firstChild;)e.appendChild(n.firstChild)}});if(o.canUseDOM){var c=document.createElement("div");c.innerHTML=" ",""===c.innerHTML&&(l=function(e,t){if(e.parentNode&&e.parentNode.replaceChild(e,e),a.test(t)||"<"===t[0]&&s.test(t)){e.innerHTML=String.fromCharCode(65279)+t;var n=e.firstChild;1===n.data.length?e.removeChild(n):n.deleteData(0,1)}else e.innerHTML=t}),c=null}e.exports=l},function(e,t,n){e.exports=n(208)()},function(e,t){function n(e){return e&&e.__esModule?e:{default:e}}e.exports=n,e.exports.__esModule=!0,e.exports.default=e.exports},function(e,t){"use strict";function n(e,t){return e===t?0!==e||0!==t||1/e===1/t:e!==e&&t!==t}function r(e,t){if(n(e,t))return!0;if("object"!=typeof e||null===e||"object"!=typeof t||null===t)return!1;var r=Object.keys(e),i=Object.keys(t);if(r.length!==i.length)return!1;for(var a=0;a-1?void 0:a("96",e),!l.plugins[n]){t.extractEvents?void 0:a("97",e),l.plugins[n]=t;var r=t.eventTypes;for(var i in r)o(r[i],t,i)?void 0:a("98",i,e)}}}function o(e,t,n){l.eventNameDispatchConfigs.hasOwnProperty(n)?a("99",n):void 0,l.eventNameDispatchConfigs[n]=e;var r=e.phasedRegistrationNames;if(r){for(var o in r)if(r.hasOwnProperty(o)){var s=r[o];i(s,t,n)}return!0}return!!e.registrationName&&(i(e.registrationName,t,n),!0)}function i(e,t,n){l.registrationNameModules[e]?a("100",e):void 0,l.registrationNameModules[e]=t,l.registrationNameDependencies[e]=t.eventTypes[n].dependencies}var a=n(3),s=(n(1),null),u={},l={plugins:[],eventNameDispatchConfigs:{},registrationNameModules:{},registrationNameDependencies:{},possibleRegistrationNames:null,injectEventPluginOrder:function(e){s?a("101"):void 0,s=Array.prototype.slice.call(e),r()},injectEventPluginsByName:function(e){var t=!1;for(var n in e)if(e.hasOwnProperty(n)){var o=e[n];u.hasOwnProperty(n)&&u[n]===o||(u[n]?a("102",n):void 0,u[n]=o,t=!0)}t&&r()},getPluginModuleForEvent:function(e){var t=e.dispatchConfig;if(t.registrationName)return l.registrationNameModules[t.registrationName]||null;if(void 0!==t.phasedRegistrationNames){var n=t.phasedRegistrationNames;for(var r in n)if(n.hasOwnProperty(r)){var o=l.registrationNameModules[n[r]];if(o)return o}}return null},_resetEventPlugins:function(){s=null;for(var e in u)u.hasOwnProperty(e)&&delete u[e];l.plugins.length=0;var t=l.eventNameDispatchConfigs;for(var n in t)t.hasOwnProperty(n)&&delete t[n];var r=l.registrationNameModules;for(var o in r)r.hasOwnProperty(o)&&delete r[o]}};e.exports=l},function(e,t,n){"use strict";function r(e){return"topMouseUp"===e||"topTouchEnd"===e||"topTouchCancel"===e}function o(e){return"topMouseMove"===e||"topTouchMove"===e}function i(e){return"topMouseDown"===e||"topTouchStart"===e}function a(e,t,n,r){var o=e.type||"unknown-event";e.currentTarget=g.getNodeFromInstance(r),t?m.invokeGuardedCallbackWithCatch(o,n,e):m.invokeGuardedCallback(o,n,e),e.currentTarget=null}function s(e,t){var n=e._dispatchListeners,r=e._dispatchInstances;if(Array.isArray(n))for(var o=0;o0&&r.length<20?n+" (keys: "+r.join(", ")+")":n}function i(e,t){var n=s.get(e);if(!n){return null}return n}var a=n(3),s=(n(12),n(26)),u=(n(9),n(10)),l=(n(1),n(2),{isMounted:function(e){var t=s.get(e);return!!t&&!!t._renderedComponent},enqueueCallback:function(e,t,n){l.validateCallback(t,n);var o=i(e);return o?(o._pendingCallbacks?o._pendingCallbacks.push(t):o._pendingCallbacks=[t],void r(o)):null},enqueueCallbackInternal:function(e,t){e._pendingCallbacks?e._pendingCallbacks.push(t):e._pendingCallbacks=[t],r(e)},enqueueForceUpdate:function(e){var t=i(e,"forceUpdate");t&&(t._pendingForceUpdate=!0,r(t))},enqueueReplaceState:function(e,t,n){var o=i(e,"replaceState");o&&(o._pendingStateQueue=[t],o._pendingReplaceState=!0,void 0!==n&&null!==n&&(l.validateCallback(n,"replaceState"),o._pendingCallbacks?o._pendingCallbacks.push(n):o._pendingCallbacks=[n]),r(o))},enqueueSetState:function(e,t){var n=i(e,"setState");if(n){var o=n._pendingStateQueue||(n._pendingStateQueue=[]);o.push(t),r(n)}},enqueueElementInternal:function(e,t,n){e._pendingElement=t,e._context=n,r(e)},validateCallback:function(e,t){e&&"function"!=typeof e?a("122",t,o(e)):void 0}});e.exports=l},function(e,t){"use strict";var n=function(e){return"undefined"!=typeof MSApp&&MSApp.execUnsafeLocalFunction?function(t,n,r,o){MSApp.execUnsafeLocalFunction(function(){return e(t,n,r,o)})}:e};e.exports=n},function(e,t){"use strict";function n(e){var t,n=e.keyCode;return"charCode"in e?(t=e.charCode,0===t&&13===n&&(t=13)):t=n,t>=32||13===t?t:0}e.exports=n},function(e,t){"use strict";function n(e){var t=this,n=t.nativeEvent;if(n.getModifierState)return n.getModifierState(e);var r=o[e];return!!r&&!!n[r]}function r(e){return n}var o={Alt:"altKey",Control:"ctrlKey",Meta:"metaKey",Shift:"shiftKey"};e.exports=r},function(e,t){"use strict";function n(e){var t=e.target||e.srcElement||window;return t.correspondingUseElement&&(t=t.correspondingUseElement),3===t.nodeType?t.parentNode:t}e.exports=n},function(e,t,n){"use strict";function r(e,t){if(!i.canUseDOM||t&&!("addEventListener"in document))return!1;var n="on"+e,r=n in document;if(!r){var a=document.createElement("div");a.setAttribute(n,"return;"),r="function"==typeof a[n]}return!r&&o&&"wheel"===e&&(r=document.implementation.hasFeature("Events.wheel","3.0")),r}var o,i=n(7);i.canUseDOM&&(o=document.implementation&&document.implementation.hasFeature&&document.implementation.hasFeature("","")!==!0),e.exports=r},function(e,t){"use strict";function n(e,t){var n=null===e||e===!1,r=null===t||t===!1;if(n||r)return n===r;var o=typeof e,i=typeof t;return"string"===o||"number"===o?"string"===i||"number"===i:"object"===i&&e.type===t.type&&e.key===t.key}e.exports=n},function(e,t,n){"use strict";var r=(n(4),n(8)),o=(n(2),r);e.exports=o},function(e,t,n){(function(){function t(e,t){var n={}.hasOwnProperty;for(var r in t)n.call(t,r)&&(e[r]=t[r]);return e}function r(e,t){for(var n=-1,r=t.length>>>0;++n0&&!this.props.hideResetButton?T({className:"react-selectize-reset-button-container",onClick:function(e){return function(){return a.props.onValuesChange([],function(){return a.props.onSearchChange("",function(){return a.highlightAndFocus()})})}(),j(e)}},this.props.renderResetButton()):void 0,T({className:"react-selectize-toggle-button-container",onMouseDown:function(e){return a.props.open?a.onOpenChange(!1,function(){}):a.props.onAnchorChange(p(a.props.values),function(){return a.onOpenChange(!0,function(){})}),j(e)}},this.props.renderToggleButton({open:this.props.open,flipped:r}))),D((o=t({},this.props),o.ref="dropdownMenu",o.className=B((i={"react-selectize":1},i[this.props.className+""]=1,i)),o.theme=this.props.theme,o.scrollLock=this.props.scrollLock,o.onScrollChange=this.props.onScrollChange,o.bottomAnchor=function(){return M(a.refs.control)},o.tetherProps=(i=t({},this.props.tetherProps),i.target=function(){return M(a.refs.control)},i),o.highlightedUid=this.props.highlightedUid,o.onHighlightedUidChange=this.props.onHighlightedUidChange,o.onOptionClick=function(t){a.selectHighlightedUid(e,function(){})},o)))},handleKeydown:function(e,t){var n,o,i,a=this;switch(n=e.anchorIndex,t.persist(),t.which){case 8:if(this.props.search.length>0||n===-1)return;!function(){var e,t,r,o;return e=n,t=n-1<0?void 0:a.props.values[n-1],r=a.props.values[n],a.props.onValuesChange(null!=(o=m(function(e){return a.isEqualToObject(e,r)})(a.props.values))?o:[],function(){return function(){return function(e){return"undefined"==typeof s(function(e){return a.isEqualToObject(e,r)},a.props.values)?a.props.restoreOnBackspace?a.props.onSearchChange(a.props.restoreOnBackspace(r),function(){return e(!0)}):e(!0):e(!1)}}()(function(r){if(r&&(a.highlightAndScrollToSelectableOption(a.props.firstOptionIndexToHighlight(a.props.options),1),n===e&&("undefined"==typeof t||s(function(e){return a.isEqualToObject(e,t)})(a.props.values))))return a.props.onAnchorChange(t,function(){})})})}(),j(t);break;case 27:!function(){return a.props.open?function(e){return a.onOpenChange(!1,e)}:function(e){return a.props.onValuesChange([],e)}}()(function(){return a.props.onSearchChange("",function(){return a.focusOnInput()})})}if(this.props.open&&r(t.which,[13].concat(this.props.delimiters))&&!(null!=t&&t.metaKey||null!=t&&t.ctrlKey||null!=t&&t.shiftKey)&&(o=this.selectHighlightedUid(n,function(e){if("undefined"==typeof e)return a.props.onKeyboardSelectionFailed(t.which)}),o&&this.props.cancelKeyboardEventOnSelection))return j(t);if(0===this.props.search.length)switch(t.which){case 37:this.props.onAnchorChange(n-1<0||t.metaKey?void 0:this.props.values[_(n-1,0,this.props.values.length-1)],function(){});break;case 39:this.props.onAnchorChange(t.metaKey?p(this.props.values):this.props.values[_(n+1,0,this.props.values.length-1)],function(){})}switch(t.which){case 38:return this.props.onScrollLockChange(!0),i=function(){switch(!1){case"undefined"!=typeof this.props.highlightedUid:return 0;default:return-1+this.optionIndexFromUid(this.props.highlightedUid)}}.call(this),this.highlightAndScrollToSelectableOption(i,-1,function(e){if(!e)return a.highlightAndScrollToSelectableOption(a.props.options.length-1,-1)});case 40:return this.props.onScrollLockChange(!0),i=function(){switch(!1){case"undefined"!=typeof this.props.highlightedUid:return 0;default:return 1+this.optionIndexFromUid(this.props.highlightedUid)}}.call(this),this.highlightAndScrollToSelectableOption(i,1,function(e){if(!e)return a.highlightAndScrollToSelectableOption(0,1)})}},componentDidMount:function(){this.props.autofocus&&this.focus(),this.props.open&&this.highlightAndFocus()},componentDidUpdate:function(e){this.props.open&&!e.open&&void 0===this.props.highlightedUid&&this.highlightAndFocus(),!this.props.open&&e.open&&this.props.onHighlightedUidChange(void 0,function(){})},componentWillReceiveProps:function(e){"undefined"!=typeof this.props.disabled&&this.props.disabled!==!1||"undefined"==typeof e.disabled||e.disabled!==!0||this.onOpenChange(!1,function(){})},optionIndexFromUid:function(e){var t=this;return u(function(n){return w(e,t.props.uid(n))})(this.props.options)},closeDropdown:function(e){var t=this;this.onOpenChange(!1,function(){return t.props.onAnchorChange(p(t.props.values),e)})},blur:function(){this.refs.search.blur()},focus:function(){this.refs.search.focus()},focusOnInput:function(){var e;e=M(this.refs.search),e!==document.activeElement&&(this.focusLock=!0,e.focus(),e.value=e.value)},highlightAndFocus:function(){this.highlightAndScrollToSelectableOption(this.props.firstOptionIndexToHighlight(this.props.options),1),this.focusOnInput()},highlightAndScrollToOption:function(e,t){null==t&&(t=function(){}),this.refs.dropdownMenu.highlightAndScrollToOption(e,t)},highlightAndScrollToSelectableOption:function(e,t,n){var r=this;null==n&&(n=function(){}),function(){return r.props.open?function(e){return e()}:function(e){return r.onOpenChange(!0,e)}}()(function(){return r.refs.dropdownMenu.highlightAndScrollToSelectableOption(e,t,n)})},isEqualToObject:function(){return w(this.props.uid(arguments[0]),this.props.uid(arguments[1]))},onOpenChange:function(e,t){return this.props.onOpenChange(!this.props.disabled&&e,t)},selectHighlightedUid:function(e,t){var n,r,o=this;return void 0===this.props.highlightedUid?(t(),!1):(n=this.optionIndexFromUid(this.props.highlightedUid),"number"!=typeof n?(t(),!1):(r=this.props.options[n],function(){return o.props.onValuesChange(f(function(e){return o.props.values[e]},function(){var t,n,r=[];for(t=0,n=e;t<=n;++t)r.push(t);return r}()).concat([r],f(function(e){return o.props.values[e]},function(){var t,n,r=[];for(t=e+1,n=this.props.values.length;t1)for(var n=1;n.":"function"==typeof t?" Instead of passing a class like Foo, pass React.createElement(Foo) or .":null!=t&&void 0!==t.props?" This may be caused by unintentionally loading two independent copies of React.":"");var a,s=v.createElement(F,{child:t});if(e){var u=w.get(e);a=u._processChildContext(u._context)}else a=P;var c=f(n);if(c){var p=c._currentElement,h=p.props.child;if(N(h,t)){var m=c._renderedComponent.getPublicInstance(),g=r&&function(){r.call(m)};return j._updateRootComponent(c,s,a,n,g),m}j.unmountComponentAtNode(n)}var y=o(n),b=y&&!!i(y),C=l(n),_=b&&!c&&!C,E=j._renderNewRootComponent(s,n,_,a)._renderedComponent.getPublicInstance();return r&&r.call(E),E},render:function(e,t,n){return j._renderSubtreeIntoContainer(null,e,t,n)},unmountComponentAtNode:function(e){c(e)?void 0:d("40");var t=f(e);if(!t){l(e),1===e.nodeType&&e.hasAttribute(A);return!1}return delete L[t._instance.rootID],O.batchedUpdates(u,t,e,!1),!0},_mountImageIntoNode:function(e,t,n,i,a){if(c(t)?void 0:d("41"),i){var s=o(t);if(E.canReuseMarkup(e,s))return void y.precacheNode(n,s);var u=s.getAttribute(E.CHECKSUM_ATTR_NAME);s.removeAttribute(E.CHECKSUM_ATTR_NAME);var l=s.outerHTML;s.setAttribute(E.CHECKSUM_ATTR_NAME,u);var p=e,f=r(p,l),m=" (client) "+p.substring(f-20,f+20)+"\n (server) "+l.substring(f-20,f+20);t.nodeType===D?d("42",m):void 0}if(t.nodeType===D?d("43"):void 0,a.useCreateElement){for(;t.lastChild;)t.removeChild(t.lastChild);h.insertTreeBefore(t,e,null)}else S(t,e),y.precacheNode(n,t.firstChild)}};e.exports=j},function(e,t,n){"use strict";var r=n(3),o=n(20),i=(n(1),{HOST:0,COMPOSITE:1,EMPTY:2,getType:function(e){return null===e||e===!1?i.EMPTY:o.isValidElement(e)?"function"==typeof e.type?i.COMPOSITE:i.HOST:void r("26",e)}});e.exports=i},function(e,t){"use strict";var n={currentScrollLeft:0,currentScrollTop:0,refreshScrollValues:function(e){n.currentScrollLeft=e.x,n.currentScrollTop=e.y}};e.exports=n},function(e,t,n){"use strict";function r(e,t){return null==t?o("30"):void 0,null==e?t:Array.isArray(e)?Array.isArray(t)?(e.push.apply(e,t),e):(e.push(t),e):Array.isArray(t)?[e].concat(t):[e,t]}var o=n(3);n(1);e.exports=r},function(e,t){"use strict";function n(e,t,n){Array.isArray(e)?e.forEach(t,n):e&&t.call(n,e)}e.exports=n},function(e,t,n){"use strict";function r(e){for(var t;(t=e._renderedNodeType)===o.COMPOSITE;)e=e._renderedComponent;return t===o.HOST?e._renderedComponent:t===o.EMPTY?null:void 0}var o=n(74);e.exports=r},function(e,t,n){"use strict";function r(){return!i&&o.canUseDOM&&(i="textContent"in document.documentElement?"textContent":"innerText"),i}var o=n(7),i=null;e.exports=r},function(e,t,n){"use strict";function r(e){if(e){var t=e.getName();if(t)return" Check the render method of `"+t+"`."}return""}function o(e){return"function"==typeof e&&"undefined"!=typeof e.prototype&&"function"==typeof e.prototype.mountComponent&&"function"==typeof e.prototype.receiveComponent}function i(e,t){var n;if(null===e||e===!1)n=l.create(i);else if("object"==typeof e){var s=e,u=s.type;if("function"!=typeof u&&"string"!=typeof u){var f="";f+=r(s._owner),a("130",null==u?u:typeof u,f)}"string"==typeof s.type?n=c.createInternalComponent(s):o(s.type)?(n=new s.type(s),n.getHostNode||(n.getHostNode=n.getNativeNode)):n=new p(s)}else"string"==typeof e||"number"==typeof e?n=c.createInstanceForText(e):a("131",typeof e);return n._mountIndex=0,n._mountImage=null,n}var a=n(3),s=n(4),u=n(137),l=n(69),c=n(71),p=(n(221),n(1),n(2),function(e){this.construct(e)});s(p.prototype,u,{_instantiateReactComponent:i}),e.exports=i},function(e,t){"use strict";function n(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return"input"===t?!!r[e.type]:"textarea"===t}var r={color:!0,date:!0,datetime:!0,"datetime-local":!0,email:!0,month:!0,number:!0,password:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0};e.exports=n},function(e,t,n){"use strict";var r=n(7),o=n(32),i=n(33),a=function(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&3===n.nodeType)return void(n.nodeValue=t)}e.textContent=t};r.canUseDOM&&("textContent"in document.documentElement||(a=function(e,t){return 3===e.nodeType?void(e.nodeValue=t):void i(e,o(t))})),e.exports=a},function(e,t,n){"use strict";function r(e,t){return e&&"object"==typeof e&&null!=e.key?l.escape(e.key):t.toString(36)}function o(e,t,n,i){var f=typeof e;if("undefined"!==f&&"boolean"!==f||(e=null),null===e||"string"===f||"number"===f||"object"===f&&e.$$typeof===s)return n(i,e,""===t?c+r(e,0):t),1;var d,h,m=0,v=""===t?c:t+p;if(Array.isArray(e))for(var g=0;gc){for(var t=0,n=s.length-l;t-1}).map(function(e,t){return l.default.createElement("option",{key:t,value:e.name},e.name)})}},{key:"getValues",value:function(e){return e?e.map(function(e){return{label:e,value:e}}):[]}},{key:"render",value:function(){var e=this,t=this.props.parameters.find(function(t){return t.value===e.props.condition.parameter});return this.props.condition.type=t?t.type:null,l.default.createElement("div",{className:this.props.classes.filterLineRow},l.default.createElement("div",{className:this.props.classes.filterLineParameter},l.default.createElement("select",{className:this.props.classes.filterLineInput,name:"parameter",value:this.props.condition.parameter,onChange:this.handleInputChange},l.default.createElement("option",{value:""},"-- Parameter --"),this.getCoefficients(this.props.parameters))),l.default.createElement("div",{className:this.props.classes.filterLineOperator,style:{"padding-left":0,"padding-right":0}},l.default.createElement("select",{className:this.props.classes.filterLineInput,name:"operator",value:this.props.condition.operator,onChange:this.handleInputChange},l.default.createElement("option",{disabled:!0,value:""},"-- Operator --"),this.getOperators(this.props.operators,this.props.parameters.find(function(t){return t.value===e.props.condition.parameter})))),l.default.createElement("div",{className:this.props.classes.filterLineValue},l.default.createElement(c.MultiSelect,{style:{width:"100%"},placeholder:"-- Value --",theme:"bootstrap3",values:this.getValues(this.props.condition.value),onValuesChange:this.handleValueChange,uid:function(e){return e.value},restoreOnBackspace:function(e){return e.label.toString()},createFromSearch:function(t,n,r){return e.labels=n.map(function(e){return e.label}),0===r.trim().length||e.labels.indexOf(r.trim())!==-1?null:{label:r.trim(),value:r.trim()}},renderNoResultsFound:function(e,t){return l.default.createElement("div",{className:"no-results-found"},function(){return 0===t.trim().length?"Enter a new value":e.map(function(e){return e.label}).indexOf(t.trim())!==-1?"Value already exists":void 0}())}})))}}]),t}(u.Component);t.default=p},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var s=function(){function e(e,t){for(var n=0;n1){var t=this.state.conditions;t.splice(e,1),this.setState({conditions:t})}}},{key:"componentDidUpdate",value:function(e,t){t!==this.state&&this.props.config.updateConditions(this.state.conditions)}},{key:"render",value:function(){var e=this,t=this.state.conditions.map(function(t,n){return l.default.createElement("div",{key:n},l.default.createElement(d.default,{index:n,classes:e.props.config.classes,addCondition:e.addCondition,removeCondition:e.removeCondition}),l.default.createElement(p.default,{parameters:e.props.config.parameters,operators:e.props.config.operators,condition:t,index:n,classes:e.props.config.classes,onChange:e.updateCondition}))});return l.default.createElement("div",{className:"form-horizontal"},t)}}]),t}(u.Component);t.default=h},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}var o=n(5),i=r(o),a=n(13),s=r(a),u=n(93),l=r(u),c=window.$;if(c.fn.filterer=function(e){e.operators=[{name:"contains",types:["string","str"]},{name:"does not contain",types:["string","str"]},{name:"is",types:["string","str","number","int","float"]},{name:"is not",types:["string","str","number","int","float"]},{name:"begins with",types:["string","str"]},{name:"does not begin with",types:["string","str"]},{name:"ends with",types:["string","str"]},{name:"does not end with",types:["string","str"]},{name:"is greater than",types:["number","int","float"]},{name:"is less than",types:["number","int","float"]}],e.classes=Object.assign({plusIcon:"fa fa-fw fa-plus",minusIcon:"fa fa-fw fa-minus",filterLineRow:"form-group",filterLineParameter:"col-sm-4",filterLineOperator:"col-sm-3",filterLineValue:"col-sm-5",filterLineInput:"form-control",filterLineLabelRow:"row",filterLineLabelCondition:"col-sm-10",filterLineLabelControls:"col-sm-2 text-right"},e.classes),this.each(function(){s.default.render(i.default.createElement(l.default,{id:"filterer",config:e}),this)})},window.wcomartin_filterer_demo){var p={parameters:[{name:"Title",type:"string",value:"title"},{name:"Year",type:"number",value:"year"}],conditions:[{parameter:"year",operator:"is",value:[2017]}]};p.updateConditions=function(e){console.log(JSON.stringify(e))},c("#root").filterer(p)}},function(e,t){e.exports=function(){for(var e=arguments.length,t=[],n=0;n":a.innerHTML="<"+e+">",s[e]=!a.firstChild),s[e]?f[e]:null}var o=n(7),i=n(1),a=o.canUseDOM?document.createElement("div"):null,s={},u=[1,'"],l=[1,"","
    "],c=[3,"","
    "],p=[1,'',""],f={"*":[1,"?
    ","
    "],area:[1,"",""],col:[2,"","
    "],legend:[1,"
    ","
    "],param:[1,"",""],tr:[2,"","
    "],optgroup:u,option:u,caption:l,colgroup:l,tbody:l,tfoot:l,thead:l,td:c,th:c},d=["circle","clipPath","defs","ellipse","g","image","line","linearGradient","mask","path","pattern","polygon","polyline","radialGradient","rect","stop","text","tspan"];d.forEach(function(e){f[e]=p,s[e]=!0}),e.exports=r},function(e,t){"use strict";function n(e){return e.Window&&e instanceof e.Window?{x:e.pageXOffset||e.document.documentElement.scrollLeft,y:e.pageYOffset||e.document.documentElement.scrollTop}:{x:e.scrollLeft,y:e.scrollTop}}e.exports=n},function(e,t){"use strict";function n(e){return e.replace(r,"-$1").toLowerCase()}var r=/([A-Z])/g;e.exports=n},function(e,t,n){"use strict";function r(e){return o(e).replace(i,"-ms-")}var o=n(108),i=/^ms-/;e.exports=r},function(e,t){"use strict";function n(e){var t=e?e.ownerDocument||e:document,n=t.defaultView||window;return!(!e||!("function"==typeof n.Node?e instanceof n.Node:"object"==typeof e&&"number"==typeof e.nodeType&&"string"==typeof e.nodeName))}e.exports=n},function(e,t,n){"use strict";function r(e){return o(e)&&3==e.nodeType}var o=n(110);e.exports=r},function(e,t){"use strict";function n(e){var t={};return function(n){return t.hasOwnProperty(n)||(t[n]=e.call(this,n)),t[n]}}e.exports=n},function(e,t){function n(e,t){var n,r=function(o){return e.length>1?function(){var i=o?o.concat():[];return n=t?n||this:this,i.push.apply(i,arguments)1?function(){var i=o?o.concat():[];return n=t?n||this:this,i.push.apply(i,arguments)>>0;++n=0;--r)o=n[r],t=e(o,t);return t}),k=n(function(e,t){return P(e,t[t.length-1],t.slice(0,-1))}),S=n(function(e,t){var n,r,o;for(n=[],r=t;null!=(o=e(r));)n.push(o[0]),r=o[1];return n}),N=function(e){return[].concat.apply([],e)},M=n(function(e,t){var n;return[].concat.apply([],function(){ +var r,o,i,a=[];for(r=0,i=(o=t).length;rt?1:ee(n)?1:e(t)t&&(t=i);return t},Q=function(e){var t,n,r,o,i;for(t=e[0],n=0,o=(r=e.slice(1)).length;ne(n)&&(n=a);return n}),Z=n(function(e,t){var n,r,o,i,a;for(n=t[0],r=0,i=(o=t.slice(1)).length;r1?function(){var i=o?o.concat():[];return n=t?n||this:this,i.push.apply(i,arguments)t?e:t}),o=n(function(e,t){return e0?1:0},u=n(function(e,t){return~~(e/t)}),l=n(function(e,t){return e%t}),c=n(function(e,t){return Math.floor(e/t)}),p=n(function(e,t){var n;return(e%(n=t)+n)%n}),f=function(e){return 1/e},d=Math.PI,h=2*d,m=Math.exp,v=Math.sqrt,g=Math.log,y=n(function(e,t){return Math.pow(e,t)}),b=Math.sin,C=Math.tan,_=Math.cos,w=Math.asin,E=Math.acos,T=Math.atan,x=n(function(e,t){return Math.atan2(e,t)}),O=function(e){return~~e},P=Math.round,k=Math.ceil,S=Math.floor,N=function(e){return e!==e},M=function(e){return e%2===0},A=function(e){return e%2!==0},I=n(function(e,t){var n;for(e=Math.abs(e),t=Math.abs(t);0!==t;)n=e%t,e=t,t=n;return e}),D=n(function(e,t){return Math.abs(Math.floor(e/I(e,t)*t))}),e.exports={max:r,min:o,negate:i,abs:a,signum:s,quot:u,rem:l,div:c,mod:p,recip:f,pi:d,tau:h,exp:m,sqrt:v,ln:g,pow:y,sin:b,tan:C,cos:_,acos:E,asin:w,atan:T,atan2:x,truncate:O,round:P,ceiling:k,floor:S,isItNaN:N,even:M,odd:A,gcd:I,lcm:D}},function(e,t){function n(e,t){var n,r=function(o){return e.length>1?function(){var i=o?o.concat():[];return n=t?n||this:this,i.push.apply(i,arguments)1?function(){var i=o?o.concat():[];return n=t?n||this:this,i.push.apply(i,arguments)1?n:n.toLowerCase())}).replace(/^([A-Z]+)/,function(e,t){return t.length>1?t+"-":t.toLowerCase()})},e.exports={split:r,join:o,lines:i,unlines:a,words:s,unwords:u,chars:l,unchars:c,reverse:p,repeat:f,capitalize:d,camelize:h,dasherize:m}},[227,113,114,116,117,115],function(e,t,n){"use strict";function r(e){var t=new o(o._61);return t._81=1,t._65=e,t}var o=n(61);e.exports=o;var i=r(!0),a=r(!1),s=r(null),u=r(void 0),l=r(0),c=r("");o.resolve=function(e){if(e instanceof o)return e;if(null===e)return s;if(void 0===e)return u;if(e===!0)return i;if(e===!1)return a;if(0===e)return l;if(""===e)return c;if("object"==typeof e||"function"==typeof e)try{var t=e.then;if("function"==typeof t)return new o(t.bind(e))}catch(e){return new o(function(t,n){n(e)})}return r(e)},o.all=function(e){var t=Array.prototype.slice.call(e);return new o(function(e,n){function r(a,s){if(s&&("object"==typeof s||"function"==typeof s)){if(s instanceof o&&s.then===o.prototype.then){for(;3===s._81;)s=s._65;return 1===s._81?r(a,s._65):(2===s._81&&n(s._65),void s.then(function(e){r(a,e)},n))}var u=s.then;if("function"==typeof u){var l=new o(u.bind(s));return void l.then(function(e){r(a,e)},n)}}t[a]=s,0===--i&&e(t)}if(0===t.length)return e([]);for(var i=t.length,a=0;a8&&_<=11),T=32,x=String.fromCharCode(T),O={beforeInput:{phasedRegistrationNames:{bubbled:"onBeforeInput",captured:"onBeforeInputCapture"},dependencies:["topCompositionEnd","topKeyPress","topTextInput","topPaste"]},compositionEnd:{phasedRegistrationNames:{bubbled:"onCompositionEnd",captured:"onCompositionEndCapture"},dependencies:["topBlur","topCompositionEnd","topKeyDown","topKeyPress","topKeyUp","topMouseDown"]},compositionStart:{phasedRegistrationNames:{bubbled:"onCompositionStart",captured:"onCompositionStartCapture"},dependencies:["topBlur","topCompositionStart","topKeyDown","topKeyPress","topKeyUp","topMouseDown"]},compositionUpdate:{phasedRegistrationNames:{bubbled:"onCompositionUpdate",captured:"onCompositionUpdateCapture"},dependencies:["topBlur","topCompositionUpdate","topKeyDown","topKeyPress","topKeyUp","topMouseDown"]}},P=!1,k=null,S={eventTypes:O,extractEvents:function(e,t,n,r){return[l(e,t,n,r),f(e,t,n,r)]}};e.exports=S},function(e,t,n){"use strict";var r=n(64),o=n(7),i=(n(9),n(102),n(179)),a=n(109),s=n(112),u=(n(2),s(function(e){return a(e)})),l=!1,c="cssFloat";if(o.canUseDOM){var p=document.createElement("div").style;try{p.font=""}catch(e){l=!0}void 0===document.documentElement.style.cssFloat&&(c="styleFloat")}var f={createMarkupForStyles:function(e,t){var n="";for(var r in e)if(e.hasOwnProperty(r)){var o=e[r];null!=o&&(n+=u(r)+":",n+=i(r,o,t)+";")}return n||null},setValueForStyles:function(e,t,n){var o=e.style;for(var a in t)if(t.hasOwnProperty(a)){var s=i(a,t[a],n);if("float"!==a&&"cssFloat"!==a||(a=c),s)o[a]=s;else{var u=l&&r.shorthandPropertyExpansions[a];if(u)for(var p in u)o[p]="";else o[a]=""}}}};e.exports=f},function(e,t,n){"use strict";function r(e){var t=e.nodeName&&e.nodeName.toLowerCase();return"select"===t||"input"===t&&"file"===e.type}function o(e){var t=T.getPooled(k.change,N,e,x(e));C.accumulateTwoPhaseDispatches(t),E.batchedUpdates(i,t)}function i(e){b.enqueueEvents(e),b.processEventQueue(!1)}function a(e,t){S=e,N=t,S.attachEvent("onchange",o)}function s(){S&&(S.detachEvent("onchange",o),S=null,N=null)}function u(e,t){if("topChange"===e)return t}function l(e,t,n){"topFocus"===e?(s(),a(t,n)):"topBlur"===e&&s()}function c(e,t){S=e,N=t,M=e.value,A=Object.getOwnPropertyDescriptor(e.constructor.prototype,"value"),Object.defineProperty(S,"value",R),S.attachEvent?S.attachEvent("onpropertychange",f):S.addEventListener("propertychange",f,!1)}function p(){S&&(delete S.value,S.detachEvent?S.detachEvent("onpropertychange",f):S.removeEventListener("propertychange",f,!1),S=null,N=null,M=null,A=null)}function f(e){if("value"===e.propertyName){var t=e.srcElement.value;t!==M&&(M=t,o(e))}}function d(e,t){if("topInput"===e)return t}function h(e,t,n){"topFocus"===e?(p(),c(t,n)):"topBlur"===e&&p()}function m(e,t){if(("topSelectionChange"===e||"topKeyUp"===e||"topKeyDown"===e)&&S&&S.value!==M)return M=S.value,N}function v(e){return e.nodeName&&"input"===e.nodeName.toLowerCase()&&("checkbox"===e.type||"radio"===e.type)}function g(e,t){if("topClick"===e)return t}function y(e,t){if(null!=e){var n=e._wrapperState||t._wrapperState;if(n&&n.controlled&&"number"===t.type){var r=""+t.value;t.getAttribute("value")!==r&&t.setAttribute("value",r)}}}var b=n(24),C=n(25),_=n(7),w=n(6),E=n(10),T=n(11),x=n(49),O=n(50),P=n(81),k={change:{phasedRegistrationNames:{bubbled:"onChange",captured:"onChangeCapture"},dependencies:["topBlur","topChange","topClick","topFocus","topInput","topKeyDown","topKeyUp","topSelectionChange"]}},S=null,N=null,M=null,A=null,I=!1;_.canUseDOM&&(I=O("change")&&(!document.documentMode||document.documentMode>8));var D=!1;_.canUseDOM&&(D=O("input")&&(!document.documentMode||document.documentMode>11));var R={get:function(){return A.get.call(this)},set:function(e){M=""+e,A.set.call(this,e)}},L={eventTypes:k,extractEvents:function(e,t,n,o){var i,a,s=t?w.getNodeFromInstance(t):window;if(r(s)?I?i=u:a=l:P(s)?D?i=d:(i=m,a=h):v(s)&&(i=g),i){var c=i(e,t);if(c){var p=T.getPooled(k.change,c,n,o);return p.type="change",C.accumulateTwoPhaseDispatches(p),p}}a&&a(e,s,t),"topBlur"===e&&y(t,s)}};e.exports=L},function(e,t,n){"use strict";var r=n(3),o=n(17),i=n(7),a=n(105),s=n(8),u=(n(1),{dangerouslyReplaceNodeWithMarkup:function(e,t){if(i.canUseDOM?void 0:r("56"),t?void 0:r("57"),"HTML"===e.nodeName?r("58"):void 0,"string"==typeof t){var n=a(t,s)[0];e.parentNode.replaceChild(n,e)}else o.replaceChildWithTree(e,t)}});e.exports=u},function(e,t){"use strict";var n=["ResponderEventPlugin","SimpleEventPlugin","TapEventPlugin","EnterLeaveEventPlugin","ChangeEventPlugin","SelectEventPlugin","BeforeInputEventPlugin"];e.exports=n},function(e,t,n){"use strict";var r=n(25),o=n(6),i=n(30),a={mouseEnter:{registrationName:"onMouseEnter",dependencies:["topMouseOut","topMouseOver"]},mouseLeave:{registrationName:"onMouseLeave",dependencies:["topMouseOut","topMouseOver"]}},s={eventTypes:a,extractEvents:function(e,t,n,s){if("topMouseOver"===e&&(n.relatedTarget||n.fromElement))return null;if("topMouseOut"!==e&&"topMouseOver"!==e)return null;var u;if(s.window===s)u=s;else{var l=s.ownerDocument;u=l?l.defaultView||l.parentWindow:window}var c,p;if("topMouseOut"===e){c=t;var f=n.relatedTarget||n.toElement;p=f?o.getClosestInstanceFromNode(f):null}else c=null,p=t;if(c===p)return null;var d=null==c?u:o.getNodeFromInstance(c),h=null==p?u:o.getNodeFromInstance(p),m=i.getPooled(a.mouseLeave,c,n,s);m.type="mouseleave",m.target=d,m.relatedTarget=h;var v=i.getPooled(a.mouseEnter,p,n,s);return v.type="mouseenter",v.target=h,v.relatedTarget=d,r.accumulateEnterLeaveDispatches(m,v,c,p),[m,v]}};e.exports=s},function(e,t,n){"use strict";function r(e){this._root=e,this._startText=this.getText(),this._fallbackText=null}var o=n(4),i=n(14),a=n(79);o(r.prototype,{destructor:function(){this._root=null,this._startText=null,this._fallbackText=null},getText:function(){return"value"in this._root?this._root.value:this._root[a()]},getData:function(){if(this._fallbackText)return this._fallbackText;var e,t,n=this._startText,r=n.length,o=this.getText(),i=o.length;for(e=0;e1?1-t:void 0;return this._fallbackText=o.slice(e,s),this._fallbackText}}),i.addPoolingTo(r),e.exports=r},function(e,t,n){"use strict";var r=n(18),o=r.injection.MUST_USE_PROPERTY,i=r.injection.HAS_BOOLEAN_VALUE,a=r.injection.HAS_NUMERIC_VALUE,s=r.injection.HAS_POSITIVE_NUMERIC_VALUE,u=r.injection.HAS_OVERLOADED_BOOLEAN_VALUE,l={isCustomAttribute:RegExp.prototype.test.bind(new RegExp("^(data|aria)-["+r.ATTRIBUTE_NAME_CHAR+"]*$")),Properties:{accept:0,acceptCharset:0,accessKey:0,action:0,allowFullScreen:i,allowTransparency:0,alt:0,as:0,async:i,autoComplete:0,autoPlay:i,capture:i,cellPadding:0,cellSpacing:0,charSet:0,challenge:0,checked:o|i,cite:0,classID:0,className:0,cols:s,colSpan:0,content:0,contentEditable:0,contextMenu:0,controls:i,coords:0,crossOrigin:0,data:0,dateTime:0,default:i,defer:i,dir:0,disabled:i,download:u,draggable:0,encType:0,form:0,formAction:0,formEncType:0,formMethod:0,formNoValidate:i,formTarget:0,frameBorder:0,headers:0,height:0,hidden:i,high:0,href:0,hrefLang:0,htmlFor:0,httpEquiv:0,icon:0,id:0,inputMode:0,integrity:0,is:0,keyParams:0,keyType:0,kind:0,label:0,lang:0,list:0,loop:i,low:0,manifest:0,marginHeight:0,marginWidth:0,max:0,maxLength:0,media:0,mediaGroup:0,method:0,min:0,minLength:0,multiple:o|i,muted:o|i,name:0,nonce:0,noValidate:i,open:i,optimum:0,pattern:0,placeholder:0,playsInline:i,poster:0,preload:0,profile:0,radioGroup:0,readOnly:i,referrerPolicy:0,rel:0,required:i,reversed:i,role:0,rows:s,rowSpan:a,sandbox:0,scope:0,scoped:i,scrolling:0,seamless:i,selected:o|i,shape:0,size:s,sizes:0,span:s,spellCheck:0,src:0,srcDoc:0,srcLang:0,srcSet:0,start:a,step:0,style:0,summary:0,tabIndex:0,target:0,title:0,type:0,useMap:0,value:0,width:0,wmode:0,wrap:0,about:0,datatype:0,inlist:0,prefix:0,property:0,resource:0,typeof:0,vocab:0,autoCapitalize:0,autoCorrect:0,autoSave:0,color:0,itemProp:0,itemScope:i,itemType:0,itemID:0,itemRef:0,results:0,security:0,unselectable:0},DOMAttributeNames:{acceptCharset:"accept-charset",className:"class",htmlFor:"for",httpEquiv:"http-equiv"},DOMPropertyNames:{},DOMMutationMethods:{value:function(e,t){return null==t?e.removeAttribute("value"):void("number"!==e.type||e.hasAttribute("value")===!1?e.setAttribute("value",""+t):e.validity&&!e.validity.badInput&&e.ownerDocument.activeElement!==e&&e.setAttribute("value",""+t))}}};e.exports=l},function(e,t,n){(function(t){"use strict";function r(e,t,n,r){var o=void 0===e[n];null!=t&&o&&(e[n]=i(t,!0))}var o=n(19),i=n(80),a=(n(41),n(51)),s=n(83),u=(n(2),{instantiateChildren:function(e,t,n,o){if(null==e)return null;var i={};return s(e,r,i),i},updateChildren:function(e,t,n,r,s,u,l,c,p){if(t||e){var f,d;for(f in t)if(t.hasOwnProperty(f)){d=e&&e[f];var h=d&&d._currentElement,m=t[f];if(null!=d&&a(h,m))o.receiveComponent(d,m,s,c),t[f]=d;else{d&&(r[f]=o.getHostNode(d),o.unmountComponent(d,!1));var v=i(m,!0);t[f]=v;var g=o.mountComponent(v,s,u,l,c,p);n.push(g)}}for(f in e)!e.hasOwnProperty(f)||t&&t.hasOwnProperty(f)||(d=e[f],r[f]=o.getHostNode(d),o.unmountComponent(d,!1))}},unmountChildren:function(e,t){for(var n in e)if(e.hasOwnProperty(n)){var r=e[n];o.unmountComponent(r,t)}}});e.exports=u}).call(t,n(60))},function(e,t,n){"use strict";var r=n(37),o=n(143),i={processChildrenUpdates:o.dangerouslyProcessChildrenUpdates,replaceNodeWithMarkup:r.dangerouslyReplaceNodeWithMarkup};e.exports=i},function(e,t,n){"use strict";function r(e){}function o(e,t){}function i(e){return!(!e.prototype||!e.prototype.isReactComponent)}function a(e){return!(!e.prototype||!e.prototype.isPureReactComponent)}var s=n(3),u=n(4),l=n(20),c=n(43),p=n(12),f=n(44),d=n(26),h=(n(9),n(74)),m=n(19),v=n(23),g=(n(1),n(36)),y=n(51),b=(n(2),{ImpureClass:0,PureClass:1,StatelessFunctional:2});r.prototype.render=function(){var e=d.get(this)._currentElement.type,t=e(this.props,this.context,this.updater);return o(e,t),t};var C=1,_={construct:function(e){this._currentElement=e,this._rootNodeID=0,this._compositeType=null,this._instance=null,this._hostParent=null,this._hostContainerInfo=null,this._updateBatchNumber=null,this._pendingElement=null,this._pendingStateQueue=null,this._pendingReplaceState=!1,this._pendingForceUpdate=!1,this._renderedNodeType=null,this._renderedComponent=null,this._context=null,this._mountOrder=0,this._topLevelWrapper=null,this._pendingCallbacks=null,this._calledComponentWillUnmount=!1},mountComponent:function(e,t,n,u){this._context=u,this._mountOrder=C++,this._hostParent=t,this._hostContainerInfo=n;var c,p=this._currentElement.props,f=this._processContext(u),h=this._currentElement.type,m=e.getUpdateQueue(),g=i(h),y=this._constructComponent(g,p,f,m);g||null!=y&&null!=y.render?a(h)?this._compositeType=b.PureClass:this._compositeType=b.ImpureClass:(c=y,o(h,c),null===y||y===!1||l.isValidElement(y)?void 0:s("105",h.displayName||h.name||"Component"),y=new r(h),this._compositeType=b.StatelessFunctional);y.props=p,y.context=f,y.refs=v,y.updater=m,this._instance=y,d.set(y,this);var _=y.state;void 0===_&&(y.state=_=null),"object"!=typeof _||Array.isArray(_)?s("106",this.getName()||"ReactCompositeComponent"):void 0,this._pendingStateQueue=null,this._pendingReplaceState=!1,this._pendingForceUpdate=!1;var w;return w=y.unstable_handleError?this.performInitialMountWithErrorHandling(c,t,n,e,u):this.performInitialMount(c,t,n,e,u),y.componentDidMount&&e.getReactMountReady().enqueue(y.componentDidMount,y),w},_constructComponent:function(e,t,n,r){return this._constructComponentWithoutOwner(e,t,n,r)},_constructComponentWithoutOwner:function(e,t,n,r){var o=this._currentElement.type;return e?new o(t,n,r):o(t,n,r)},performInitialMountWithErrorHandling:function(e,t,n,r,o){var i,a=r.checkpoint();try{i=this.performInitialMount(e,t,n,r,o)}catch(s){r.rollback(a),this._instance.unstable_handleError(s),this._pendingStateQueue&&(this._instance.state=this._processPendingState(this._instance.props,this._instance.context)),a=r.checkpoint(),this._renderedComponent.unmountComponent(!0),r.rollback(a),i=this.performInitialMount(e,t,n,r,o)}return i},performInitialMount:function(e,t,n,r,o){var i=this._instance,a=0;i.componentWillMount&&(i.componentWillMount(),this._pendingStateQueue&&(i.state=this._processPendingState(i.props,i.context))),void 0===e&&(e=this._renderValidatedComponent());var s=h.getType(e);this._renderedNodeType=s; +var u=this._instantiateReactComponent(e,s!==h.EMPTY);this._renderedComponent=u;var l=m.mountComponent(u,r,t,n,this._processChildContext(o),a);return l},getHostNode:function(){return m.getHostNode(this._renderedComponent)},unmountComponent:function(e){if(this._renderedComponent){var t=this._instance;if(t.componentWillUnmount&&!t._calledComponentWillUnmount)if(t._calledComponentWillUnmount=!0,e){var n=this.getName()+".componentWillUnmount()";f.invokeGuardedCallback(n,t.componentWillUnmount.bind(t))}else t.componentWillUnmount();this._renderedComponent&&(m.unmountComponent(this._renderedComponent,e),this._renderedNodeType=null,this._renderedComponent=null,this._instance=null),this._pendingStateQueue=null,this._pendingReplaceState=!1,this._pendingForceUpdate=!1,this._pendingCallbacks=null,this._pendingElement=null,this._context=null,this._rootNodeID=0,this._topLevelWrapper=null,d.remove(t)}},_maskContext:function(e){var t=this._currentElement.type,n=t.contextTypes;if(!n)return v;var r={};for(var o in n)r[o]=e[o];return r},_processContext:function(e){var t=this._maskContext(e);return t},_processChildContext:function(e){var t,n=this._currentElement.type,r=this._instance;if(r.getChildContext&&(t=r.getChildContext()),t){"object"!=typeof n.childContextTypes?s("107",this.getName()||"ReactCompositeComponent"):void 0;for(var o in t)o in n.childContextTypes?void 0:s("108",this.getName()||"ReactCompositeComponent",o);return u({},e,t)}return e},_checkContextTypes:function(e,t,n){},receiveComponent:function(e,t,n){var r=this._currentElement,o=this._context;this._pendingElement=null,this.updateComponent(t,r,e,o,n)},performUpdateIfNecessary:function(e){null!=this._pendingElement?m.receiveComponent(this,this._pendingElement,e,this._context):null!==this._pendingStateQueue||this._pendingForceUpdate?this.updateComponent(e,this._currentElement,this._currentElement,this._context,this._context):this._updateBatchNumber=null},updateComponent:function(e,t,n,r,o){var i=this._instance;null==i?s("136",this.getName()||"ReactCompositeComponent"):void 0;var a,u=!1;this._context===o?a=i.context:(a=this._processContext(o),u=!0);var l=t.props,c=n.props;t!==n&&(u=!0),u&&i.componentWillReceiveProps&&i.componentWillReceiveProps(c,a);var p=this._processPendingState(c,a),f=!0;this._pendingForceUpdate||(i.shouldComponentUpdate?f=i.shouldComponentUpdate(c,p,a):this._compositeType===b.PureClass&&(f=!g(l,c)||!g(i.state,p))),this._updateBatchNumber=null,f?(this._pendingForceUpdate=!1,this._performComponentUpdate(n,c,p,a,e,o)):(this._currentElement=n,this._context=o,i.props=c,i.state=p,i.context=a)},_processPendingState:function(e,t){var n=this._instance,r=this._pendingStateQueue,o=this._pendingReplaceState;if(this._pendingReplaceState=!1,this._pendingStateQueue=null,!r)return n.state;if(o&&1===r.length)return r[0];for(var i=u({},o?r[0]:n.state),a=o?1:0;a=0||null!=t.is}function h(e){var t=e.type;f(t),this._currentElement=e,this._tag=t.toLowerCase(),this._namespaceURI=null,this._renderedChildren=null,this._previousStyle=null,this._previousStyleCopy=null,this._hostNode=null,this._hostParent=null,this._rootNodeID=0,this._domID=0,this._hostContainerInfo=null,this._wrapperState=null,this._topLevelWrapper=null,this._flags=0}var m=n(3),v=n(4),g=n(126),y=n(128),b=n(17),C=n(38),_=n(18),w=n(66),E=n(24),T=n(39),x=n(29),O=n(67),P=n(6),k=n(144),S=n(145),N=n(68),M=n(148),A=(n(9),n(157)),I=n(162),D=(n(8),n(32)),R=(n(1),n(50),n(36),n(52),n(2),O),L=E.deleteListener,U=P.getNodeFromInstance,F=x.listenTo,j=T.registrationNameModules,B={string:!0,number:!0},V="style",W="__html",H={children:null,dangerouslySetInnerHTML:null,suppressContentEditableWarning:null},q=11,z={topAbort:"abort",topCanPlay:"canplay",topCanPlayThrough:"canplaythrough",topDurationChange:"durationchange",topEmptied:"emptied",topEncrypted:"encrypted",topEnded:"ended",topError:"error",topLoadedData:"loadeddata",topLoadedMetadata:"loadedmetadata",topLoadStart:"loadstart",topPause:"pause",topPlay:"play",topPlaying:"playing",topProgress:"progress",topRateChange:"ratechange",topSeeked:"seeked",topSeeking:"seeking",topStalled:"stalled",topSuspend:"suspend",topTimeUpdate:"timeupdate",topVolumeChange:"volumechange",topWaiting:"waiting"},K={area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0},Y={listing:!0,pre:!0,textarea:!0},X=v({menuitem:!0},K),G=/^[a-zA-Z][a-zA-Z:_\.\-\d]*$/,Q={},$={}.hasOwnProperty,Z=1;h.displayName="ReactDOMComponent",h.Mixin={mountComponent:function(e,t,n,r){this._rootNodeID=Z++,this._domID=n._idCounter++,this._hostParent=t,this._hostContainerInfo=n;var i=this._currentElement.props;switch(this._tag){case"audio":case"form":case"iframe":case"img":case"link":case"object":case"source":case"video":this._wrapperState={listeners:null},e.getReactMountReady().enqueue(c,this);break;case"input":k.mountWrapper(this,i,t),i=k.getHostProps(this,i),e.getReactMountReady().enqueue(c,this);break;case"option":S.mountWrapper(this,i,t),i=S.getHostProps(this,i);break;case"select":N.mountWrapper(this,i,t),i=N.getHostProps(this,i),e.getReactMountReady().enqueue(c,this);break;case"textarea":M.mountWrapper(this,i,t),i=M.getHostProps(this,i),e.getReactMountReady().enqueue(c,this)}o(this,i);var a,p;null!=t?(a=t._namespaceURI,p=t._tag):n._tag&&(a=n._namespaceURI,p=n._tag),(null==a||a===C.svg&&"foreignobject"===p)&&(a=C.html),a===C.html&&("svg"===this._tag?a=C.svg:"math"===this._tag&&(a=C.mathml)),this._namespaceURI=a;var f;if(e.useCreateElement){var d,h=n._ownerDocument;if(a===C.html)if("script"===this._tag){var m=h.createElement("div"),v=this._currentElement.type;m.innerHTML="<"+v+">",d=m.removeChild(m.firstChild)}else d=i.is?h.createElement(this._currentElement.type,i.is):h.createElement(this._currentElement.type);else d=h.createElementNS(a,this._currentElement.type);P.precacheNode(this,d),this._flags|=R.hasCachedChildNodes,this._hostParent||w.setAttributeForRoot(d),this._updateDOMProperties(null,i,e);var y=b(d);this._createInitialChildren(e,i,r,y),f=y}else{var _=this._createOpenTagMarkupAndPutListeners(e,i),E=this._createContentMarkup(e,i,r);f=!E&&K[this._tag]?_+"/>":_+">"+E+""}switch(this._tag){case"input":e.getReactMountReady().enqueue(s,this),i.autoFocus&&e.getReactMountReady().enqueue(g.focusDOMComponent,this);break;case"textarea":e.getReactMountReady().enqueue(u,this),i.autoFocus&&e.getReactMountReady().enqueue(g.focusDOMComponent,this);break;case"select":i.autoFocus&&e.getReactMountReady().enqueue(g.focusDOMComponent,this);break;case"button":i.autoFocus&&e.getReactMountReady().enqueue(g.focusDOMComponent,this);break;case"option":e.getReactMountReady().enqueue(l,this)}return f},_createOpenTagMarkupAndPutListeners:function(e,t){var n="<"+this._currentElement.type;for(var r in t)if(t.hasOwnProperty(r)){var o=t[r];if(null!=o)if(j.hasOwnProperty(r))o&&i(this,r,o,e);else{r===V&&(o&&(o=this._previousStyleCopy=v({},t.style)),o=y.createMarkupForStyles(o,this));var a=null;null!=this._tag&&d(this._tag,t)?H.hasOwnProperty(r)||(a=w.createMarkupForCustomAttribute(r,o)):a=w.createMarkupForProperty(r,o),a&&(n+=" "+a)}}return e.renderToStaticMarkup?n:(this._hostParent||(n+=" "+w.createMarkupForRoot()),n+=" "+w.createMarkupForID(this._domID))},_createContentMarkup:function(e,t,n){var r="",o=t.dangerouslySetInnerHTML;if(null!=o)null!=o.__html&&(r=o.__html);else{var i=B[typeof t.children]?t.children:null,a=null!=i?null:t.children;if(null!=i)r=D(i);else if(null!=a){var s=this.mountChildren(a,e,n);r=s.join("")}}return Y[this._tag]&&"\n"===r.charAt(0)?"\n"+r:r},_createInitialChildren:function(e,t,n,r){var o=t.dangerouslySetInnerHTML;if(null!=o)null!=o.__html&&b.queueHTML(r,o.__html);else{var i=B[typeof t.children]?t.children:null,a=null!=i?null:t.children;if(null!=i)""!==i&&b.queueText(r,i);else if(null!=a)for(var s=this.mountChildren(a,e,n),u=0;u"},receiveComponent:function(){},getHostNode:function(){return i.getNodeFromInstance(this)},unmountComponent:function(){i.uncacheNode(this)}}),e.exports=a},function(e,t){"use strict";var n={useCreateElement:!0,useFiber:!1};e.exports=n},function(e,t,n){"use strict";var r=n(37),o=n(6),i={dangerouslyProcessChildrenUpdates:function(e,t){var n=o.getNodeFromInstance(e);r.processUpdates(n,t)}};e.exports=i},function(e,t,n){"use strict";function r(){this._rootNodeID&&f.updateWrapper(this)}function o(e){var t="checkbox"===e.type||"radio"===e.type;return t?null!=e.checked:null!=e.value}function i(e){var t=this._currentElement.props,n=l.executeOnChange(t,e);p.asap(r,this);var o=t.name;if("radio"===t.type&&null!=o){for(var i=c.getNodeFromInstance(this),s=i;s.parentNode;)s=s.parentNode;for(var u=s.querySelectorAll("input[name="+JSON.stringify(""+o)+'][type="radio"]'),f=0;ft.end?(n=t.end,r=t.start):(n=t.start,r=t.end),o.moveToElementText(e),o.moveStart("character",n),o.setEndPoint("EndToStart",o),o.moveEnd("character",r-n),o.select()}function s(e,t){if(window.getSelection){var n=window.getSelection(),r=e[c()].length,o=Math.min(t.start,r),i=void 0===t.end?o:Math.min(t.end,r);if(!n.extend&&o>i){var a=i;i=o,o=a}var s=l(e,o),u=l(e,i);if(s&&u){var p=document.createRange();p.setStart(s.node,s.offset),n.removeAllRanges(),o>i?(n.addRange(p),n.extend(u.node,u.offset)):(p.setEnd(u.node,u.offset),n.addRange(p))}}}var u=n(7),l=n(184),c=n(79),p=u.canUseDOM&&"selection"in document&&!("getSelection"in window),f={getOffsets:p?o:i,setOffsets:p?a:s};e.exports=f},function(e,t,n){"use strict";var r=n(3),o=n(4),i=n(37),a=n(17),s=n(6),u=n(32),l=(n(1),n(52),function(e){this._currentElement=e,this._stringText=""+e,this._hostNode=null,this._hostParent=null,this._domID=0,this._mountIndex=0,this._closingComment=null,this._commentNodes=null});o(l.prototype,{mountComponent:function(e,t,n,r){var o=n._idCounter++,i=" react-text: "+o+" ",l=" /react-text ";if(this._domID=o,this._hostParent=t,e.useCreateElement){var c=n._ownerDocument,p=c.createComment(i),f=c.createComment(l),d=a(c.createDocumentFragment());return a.queueChild(d,a(p)),this._stringText&&a.queueChild(d,a(c.createTextNode(this._stringText))),a.queueChild(d,a(f)),s.precacheNode(this,p),this._closingComment=f,d}var h=u(this._stringText);return e.renderToStaticMarkup?h:""+h+""},receiveComponent:function(e,t){if(e!==this._currentElement){this._currentElement=e;var n=""+e;if(n!==this._stringText){this._stringText=n;var r=this.getHostNode();i.replaceDelimitedText(r[0],r[1],n)}}},getHostNode:function(){var e=this._commentNodes;if(e)return e;if(!this._closingComment)for(var t=s.getNodeFromInstance(this),n=t.nextSibling;;){if(null==n?r("67",this._domID):void 0,8===n.nodeType&&" /react-text "===n.nodeValue){this._closingComment=n;break}n=n.nextSibling}return e=[this._hostNode,this._closingComment],this._commentNodes=e,e},unmountComponent:function(){this._closingComment=null,this._commentNodes=null,s.uncacheNode(this)}}),e.exports=l},function(e,t,n){"use strict";function r(){this._rootNodeID&&c.updateWrapper(this)}function o(e){var t=this._currentElement.props,n=s.executeOnChange(t,e);return l.asap(r,this),n}var i=n(3),a=n(4),s=n(42),u=n(6),l=n(10),c=(n(1),n(2),{getHostProps:function(e,t){null!=t.dangerouslySetInnerHTML?i("91"):void 0;var n=a({},t,{value:void 0,defaultValue:void 0,children:""+e._wrapperState.initialValue,onChange:e._wrapperState.onChange});return n},mountWrapper:function(e,t){var n=s.getValue(t),r=n;if(null==n){var a=t.defaultValue,u=t.children;null!=u&&(null!=a?i("92"):void 0,Array.isArray(u)&&(u.length<=1?void 0:i("93"),u=u[0]),a=""+u),null==a&&(a=""),r=a}e._wrapperState={initialValue:""+r,listeners:null,onChange:o.bind(e)}},updateWrapper:function(e){var t=e._currentElement.props,n=u.getNodeFromInstance(e),r=s.getValue(t);if(null!=r){var o=""+r;o!==n.value&&(n.value=o),null==t.defaultValue&&(n.defaultValue=o)}null!=t.defaultValue&&(n.defaultValue=t.defaultValue)},postMountWrapper:function(e){var t=u.getNodeFromInstance(e),n=t.textContent;n===e._wrapperState.initialValue&&(t.value=n)}});e.exports=c},function(e,t,n){"use strict";function r(e,t){"_hostNode"in e?void 0:u("33"),"_hostNode"in t?void 0:u("33");for(var n=0,r=e;r;r=r._hostParent)n++;for(var o=0,i=t;i;i=i._hostParent)o++;for(;n-o>0;)e=e._hostParent,n--;for(;o-n>0;)t=t._hostParent,o--;for(var a=n;a--;){if(e===t)return e;e=e._hostParent,t=t._hostParent}return null}function o(e,t){"_hostNode"in e?void 0:u("35"),"_hostNode"in t?void 0:u("35");for(;t;){if(t===e)return!0;t=t._hostParent}return!1}function i(e){return"_hostNode"in e?void 0:u("36"),e._hostParent}function a(e,t,n){for(var r=[];e;)r.push(e),e=e._hostParent;var o;for(o=r.length;o-- >0;)t(r[o],"captured",n);for(o=0;o0;)n(u[l],"captured",i)}var u=n(3);n(1);e.exports={isAncestor:o,getLowestCommonAncestor:r,getParentInstance:i,traverseTwoPhase:a,traverseEnterLeave:s}},function(e,t,n){"use strict";function r(){this.reinitializeTransaction()}var o=n(4),i=n(10),a=n(31),s=n(8),u={initialize:s,close:function(){f.isBatchingUpdates=!1}},l={initialize:s,close:i.flushBatchedUpdates.bind(i)},c=[l,u];o(r.prototype,a,{getTransactionWrappers:function(){return c}});var p=new r,f={isBatchingUpdates:!1,batchedUpdates:function(e,t,n,r,o,i){var a=f.isBatchingUpdates;return f.isBatchingUpdates=!0,a?e(t,n,r,o,i):p.perform(e,null,t,n,r,o,i)}};e.exports=f},function(e,t,n){"use strict";function r(){E||(E=!0,y.EventEmitter.injectReactEventListener(g),y.EventPluginHub.injectEventPluginOrder(s),y.EventPluginUtils.injectComponentTree(f),y.EventPluginUtils.injectTreeTraversal(h),y.EventPluginHub.injectEventPluginsByName({SimpleEventPlugin:w,EnterLeaveEventPlugin:u,ChangeEventPlugin:a,SelectEventPlugin:_,BeforeInputEventPlugin:i}),y.HostComponent.injectGenericComponentClass(p),y.HostComponent.injectTextComponentClass(m),y.DOMProperty.injectDOMPropertyConfig(o),y.DOMProperty.injectDOMPropertyConfig(l),y.DOMProperty.injectDOMPropertyConfig(C),y.EmptyComponent.injectEmptyComponentFactory(function(e){return new d(e)}),y.Updates.injectReconcileTransaction(b),y.Updates.injectBatchingStrategy(v),y.Component.injectEnvironment(c))}var o=n(125),i=n(127),a=n(129),s=n(131),u=n(132),l=n(134),c=n(136),p=n(139),f=n(6),d=n(141),h=n(149),m=n(147),v=n(150),g=n(154),y=n(155),b=n(160),C=n(165),_=n(166),w=n(167),E=!1;e.exports={inject:r}},88,function(e,t,n){"use strict";function r(e){o.enqueueEvents(e),o.processEventQueue(!1)}var o=n(24),i={handleTopLevel:function(e,t,n,i){var a=o.extractEvents(e,t,n,i);r(a)}};e.exports=i},function(e,t,n){"use strict";function r(e){for(;e._hostParent;)e=e._hostParent;var t=p.getNodeFromInstance(e),n=t.parentNode;return p.getClosestInstanceFromNode(n)}function o(e,t){this.topLevelType=e,this.nativeEvent=t,this.ancestors=[]}function i(e){var t=d(e.nativeEvent),n=p.getClosestInstanceFromNode(t),o=n;do e.ancestors.push(o),o=o&&r(o);while(o);for(var i=0;i/,i=/^<\!\-\-/,a={CHECKSUM_ATTR_NAME:"data-react-checksum",addChecksumToMarkup:function(e){var t=r(e);return i.test(e)?e:e.replace(o," "+a.CHECKSUM_ATTR_NAME+'="'+t+'"$&')},canReuseMarkup:function(e,t){var n=t.getAttribute(a.CHECKSUM_ATTR_NAME);n=n&&parseInt(n,10);var o=r(e);return o===n}};e.exports=a},function(e,t,n){"use strict";function r(e,t,n){return{type:"INSERT_MARKUP",content:e,fromIndex:null,fromNode:null,toIndex:n,afterNode:t}}function o(e,t,n){return{type:"MOVE_EXISTING",content:null,fromIndex:e._mountIndex,fromNode:f.getHostNode(e),toIndex:n,afterNode:t}}function i(e,t){return{type:"REMOVE_NODE",content:null,fromIndex:e._mountIndex,fromNode:t,toIndex:null,afterNode:null}}function a(e){return{type:"SET_MARKUP",content:e,fromIndex:null,fromNode:null,toIndex:null,afterNode:null}}function s(e){return{type:"TEXT_CONTENT",content:e,fromIndex:null,fromNode:null,toIndex:null,afterNode:null}}function u(e,t){return t&&(e=e||[],e.push(t)),e}function l(e,t){p.processChildrenUpdates(e,t)}var c=n(3),p=n(43),f=(n(26),n(9),n(12),n(19)),d=n(135),h=(n(8),n(181)),m=(n(1),{Mixin:{_reconcilerInstantiateChildren:function(e,t,n){return d.instantiateChildren(e,t,n)},_reconcilerUpdateChildren:function(e,t,n,r,o,i){var a,s=0;return a=h(t,s),d.updateChildren(e,a,n,r,o,this,this._hostContainerInfo,i,s),a},mountChildren:function(e,t,n){var r=this._reconcilerInstantiateChildren(e,t,n);this._renderedChildren=r;var o=[],i=0;for(var a in r)if(r.hasOwnProperty(a)){var s=r[a],u=0,l=f.mountComponent(s,t,this,this._hostContainerInfo,n,u);s._mountIndex=i++,o.push(l)}return o},updateTextContent:function(e){var t=this._renderedChildren;d.unmountChildren(t,!1);for(var n in t)t.hasOwnProperty(n)&&c("118");var r=[s(e)];l(this,r)},updateMarkup:function(e){var t=this._renderedChildren;d.unmountChildren(t,!1);for(var n in t)t.hasOwnProperty(n)&&c("118");var r=[a(e)];l(this,r)},updateChildren:function(e,t,n){this._updateChildren(e,t,n)},_updateChildren:function(e,t,n){var r=this._renderedChildren,o={},i=[],a=this._reconcilerUpdateChildren(r,e,i,o,t,n);if(a||r){var s,c=null,p=0,d=0,h=0,m=null;for(s in a)if(a.hasOwnProperty(s)){var v=r&&r[s],g=a[s];v===g?(c=u(c,this.moveChild(v,m,p,d)),d=Math.max(v._mountIndex,d),v._mountIndex=p):(v&&(d=Math.max(v._mountIndex,d)),c=u(c,this._mountChildAtIndex(g,i[h],m,p,t,n)),h++),p++,m=f.getHostNode(g)}for(s in o)o.hasOwnProperty(s)&&(c=u(c,this._unmountChild(r[s],o[s])));c&&l(this,c),this._renderedChildren=a}},unmountChildren:function(e){var t=this._renderedChildren;d.unmountChildren(t,e),this._renderedChildren=null},moveChild:function(e,t,n,r){if(e._mountIndex=t)return{node:o,offset:t-i};i=a}o=n(r(o))}}e.exports=o},function(e,t,n){"use strict";function r(e,t){var n={};return n[e.toLowerCase()]=t.toLowerCase(),n["Webkit"+e]="webkit"+t,n["Moz"+e]="moz"+t,n["ms"+e]="MS"+t,n["O"+e]="o"+t.toLowerCase(),n}function o(e){if(s[e])return s[e];if(!a[e])return e;var t=a[e];for(var n in t)if(t.hasOwnProperty(n)&&n in u)return s[e]=t[n];return""}var i=n(7),a={animationend:r("Animation","AnimationEnd"),animationiteration:r("Animation","AnimationIteration"),animationstart:r("Animation","AnimationStart"),transitionend:r("Transition","TransitionEnd")},s={},u={};i.canUseDOM&&(u=document.createElement("div").style,"AnimationEvent"in window||(delete a.animationend.animation,delete a.animationiteration.animation,delete a.animationstart.animation),"TransitionEvent"in window||delete a.transitionend.transition),e.exports=o},function(e,t,n){"use strict";function r(e){return'"'+o(e)+'"'}var o=n(32);e.exports=r},function(e,t,n){"use strict";var r=n(73);e.exports=r.renderSubtreeIntoContainer},function(e,t,n){"use strict";"undefined"==typeof Promise&&(n(120).enable(),window.Promise=n(119)),n(226),Object.assign=n(4)},113,114,115,116,117,function(e,t,n){(function(){var t,r,o;t=n(5),r=t.createClass,o=t.DOM.div,e.exports=r({getDefaultProps:function(){return{className:"",onHeightChange:function(){}}},render:function(){return o({className:this.props.className,ref:"dropdown"},this.props.children)},componentDidMount:function(){this.props.onHeightChange(this.refs.dropdown.offsetHeight)},componentDidUpdate:function(){this.props.onHeightChange(this.refs.dropdown.offsetHeight)},componentWillUnmount:function(){this.props.onHeightChange(0)}})}).call(this)},function(e,t,n){(function(){function t(e,t){var n={}.hasOwnProperty;for(var r in t)n.call(t,r)&&(e[r]=t[r]);return e}var r,o,i,a,s,u,l,c,p,f,d,h,m,v,g,y,b,C;r=n(15),o=r.filter,i=r.id,a=r.map,s=n(16).isEqualToObject,u=n(5),r=u.DOM,l=r.div,c=r.input,p=r.span,f=u.createClass,d=u.createFactory,h=n(13).findDOMNode,m=d(n(63)),v=d(n(198)),g=d(n(194)),y=d(n(84)),r=n(28),b=r.cancelEvent,C=r.classNameFromObject,e.exports=f({displayName:"DropdownMenu",getDefaultProps:function(){return{className:"",dropdownDirection:1,groupId:function(e){return e.groupId},groupsAsColumns:!1,highlightedUid:void 0,onHighlightedUidChange:function(e,t){},onOptionClick:function(e){},onScrollLockChange:function(e){},options:[],renderNoResultsFound:function(){return l({className:"no-results-found"},"No results found")},renderGroupTitle:function(e,t){var n,r;return null!=t&&(n=t.groupId,r=t.title),l({className:"simple-group-title",key:n},r)},renderOption:function(e){var t,n,r,o;return null!=e&&(t=e.label,n=e.newOption,r=e.selectable),o="undefined"==typeof r||r,l({className:"simple-option "+(o?"":"not-selectable")},p(null,n?"Add "+t+" ...":t))},scrollLock:!1,style:{},tether:!1,tetherProps:{},theme:"default",transitionEnter:!1,transitionLeave:!1,transitionEnterTimeout:200,transitionLeaveTimeout:200,uid:i}},render:function(){var e,n;return e=C((n={},n[this.props.theme+""]=1,n[this.props.className+""]=1,n.flipped=this.props.dropdownDirection===-1,n.tethered=this.props.tether,n)),this.props.tether?v((n=t({},this.props.tetherProps),n.options={attachment:"top left",targetAttachment:"bottom left",constraints:[{to:"scrollParent"}]},n),this.renderAnimatedDropdown({dynamicClassName:e})):this.renderAnimatedDropdown({dynamicClassName:e})},renderAnimatedDropdown:function(e){var t;return t=e.dynamicClassName,this.props.transitionEnter||this.props.transitionLeave?m({component:"div",transitionName:"custom",transitionEnter:this.props.transitionEnter,transitionLeave:this.props.transitionLeave,transitionEnterTimeout:this.props.transitionEnterTimeout,transitionLeaveTimeout:this.props.transitionLeaveTimeout,className:"dropdown-menu-wrapper "+t,ref:"dropdownMenuWrapper"},this.renderDropdown(e)):this.renderDropdown(e)},renderOptions:function(e){var n=this;return a(function(r){var o,i;return o=e[r],i=n.props.uid(o),y(t({uid:i,ref:"option-"+n.uidToString(i),key:n.uidToString(i),item:o,highlight:s(n.props.highlightedUid,i),selectable:null!=o?o.selectable:void 0,onMouseMove:function(e){var t;t=e.currentTarget,n.props.scrollLock&&n.props.onScrollLockChange(!1)},onMouseOut:function(){n.props.scrollLock||n.props.onHighlightedUidChange(void 0,function(){})},renderItem:n.props.renderOption},function(){switch(!1){case!("boolean"==typeof(null!=o?o.selectable:void 0)&&!o.selectable):return{onClick:b};default:return{onClick:function(){n.props.onOptionClick(n.props.highlightedUid)},onMouseOver:function(e){var t;t=e.currentTarget,n.props.scrollLock||n.props.onHighlightedUidChange(i,function(){})}}}}()))})(function(){var t,n,r=[];for(t=0,n=e.length;t0?(i=a(function(e){var t,n,r;return t=s.props.groups[e],n=t.groupId,r=o(function(e){return s.props.groupId(e)===n})(s.props.options),{index:e,group:t,options:r}})(function(){var e,t,n=[];for(e=0,t=this.props.groups.length;e0})(i)))):this.renderOptions(this.props.options)):null},componentDidUpdate:function(){var e,t,n;e=t=h(null!=(n=this.refs.dropdownMenuWrapper)?n:this.refs.dropdownMenu),null!=e&&(e.style.bottom=function(){switch(!1){case this.props.dropdownDirection!==-1:return this.props.bottomAnchor().offsetHeight+t.style.marginBottom+"px";default:return""}}.call(this))},highlightAndScrollToOption:function(e,t){var n,r=this;null==t&&(t=function(){}),n=this.props.uid(this.props.options[e]),this.props.onHighlightedUidChange(n,function(){var e,o,i,a,s;return null!=(e=h(null!=(o=r.refs)?o["option-"+r.uidToString(n)]:void 0))&&(i=e),i&&(a=h(r.refs.dropdownMenu),s=i.offsetHeight-1,i.offsetTop-a.scrollTop>=a.offsetHeight?a.scrollTop=i.offsetTop-a.offsetHeight+s:i.offsetTop-a.scrollTop+s<=0&&(a.scrollTop=i.offsetTop)),t()})},highlightAndScrollToSelectableOption:function(e,t,n){var r,o,i;null==n&&(n=function(){}),e<0||e>=this.props.options.length?this.props.onHighlightedUidChange(void 0,function(){return n(!1)}):(r=null!=(o=this.props)&&null!=(i=o.options)?i[e]:void 0,"boolean"!=typeof(null!=r?r.selectable:void 0)||r.selectable?this.highlightAndScrollToOption(e,function(){return n(!0)}):this.highlightAndScrollToSelectableOption(e+t,t,n))},uidToString:function(e){return("object"==typeof e?JSON.stringify:i)(e)}})}).call(this)},function(e,t,n){(function(){var t,r,o,i,a,s;t=n(5),r=t.createClass,o=t.DOM,i=o.div,a=o.span,s=n(15).map,e.exports=r({getDefaultProps:function(){return{partitions:[],text:"",style:{},highlightStyle:{}}},render:function(){var e=this;return i({className:"highlighted-text",style:this.props.style},s(function(t){var n,r,o;return n=t[0],r=t[1],o=t[2],a({key:e.props.text+""+n+r+o,className:o?"highlight":"",style:o?e.props.highlightStyle:{}},e.props.text.substring(n,r))})(this.props.partitions))}})}).call(this)},function(e,t,n){(function(){function t(e,t){for(var n=-1,r=t.length>>>0;++n1?function(){var i=o?o.concat():[];return n=t?n||this:this,i.push.apply(i,arguments)-1})(g(function(e){return t(e.label.trim(),v(function(e){return e.label.trim()},null!=n?n:[]))})(e))}),firstOptionIndexToHighlight:h,onBlur:function(e){},onFocus:function(e){},onPaste:function(e){},serialize:v(function(e){return null!=e?e.value:void 0}),tether:!1}},render:function(){var e,t,n,r,i,a,s,u,l,c,p,f,d,h,v,g,y,b,C,_,w,E,O,P,k,S,N,M,A,I,D,R,L,U,F,j,B,V,W=this;return e=this.getComputedState(),t=e.anchor,n=e.filteredOptions,r=e.highlightedUid,i=e.onAnchorChange,a=e.onOpenChange,s=e.onHighlightedUidChange,u=e.onSearchChange,l=e.onValuesChange,c=e.search,p=e.open,f=e.options,d=e.values,null!=(e=this.props)&&(h=e.autofocus,v=e.autosize,g=e.cancelKeyboardEventOnSelection,y=e.delimiters,b=e.disabled,C=e.dropdownDirection,_=e.groupId,w=e.groups,E=e.groupsAsColumns,O=e.hideResetButton,P=e.inputProps,k=e.name,S=e.onKeyboardSelectionFailed,N=e.renderToggleButton,M=e.renderGroupTitle,A=e.renderResetButton,I=e.serialize,D=e.tether,R=e.tetherProps,L=e.theme,U=e.transitionEnter,F=e.transitionLeave,j=e.transitionEnterTimeout,B=e.transitionLeaveTimeout,V=e.uid),T(o(o({autofocus:h,autosize:v,cancelKeyboardEventOnSelection:g,className:"multi-select "+this.props.className,delimiters:y,disabled:b,dropdownDirection:C,groupId:_,groups:w,groupsAsColumns:E,hideResetButton:O,highlightedUid:r,onHighlightedUidChange:s,inputProps:P,name:k,onKeyboardSelectionFailed:S,renderGroupTitle:M,renderResetButton:A,renderToggleButton:N,scrollLock:this.state.scrollLock,onScrollLockChange:function(e){return W.setState({scrollLock:e})},tether:D,tetherProps:R,theme:L,transitionEnter:U,transitionEnterTimeout:j,transitionLeave:F,transitionLeaveTimeout:B,uid:V,ref:"select",anchor:t,onAnchorChange:i,open:p,onOpenChange:a,options:f,renderOption:this.props.renderOption,firstOptionIndexToHighlight:function(){return W.firstOptionIndexToHighlight(f)},search:c,onSearchChange:function(e,t){return u(W.props.maxValues&&d.length>=W.props.maxValues?"":e,t)},values:d,onValuesChange:function(e,t){return l(e,function(){if(t(),W.props.closeOnSelect||W.props.maxValues&&W.values().length>=W.props.maxValues)return a(!1,function(){})})},renderValue:this.props.renderValue,serialize:I,onBlur:function(e){u("",function(){return W.props.onBlur({open:p,values:d,originalEvent:e})})},onFocus:function(e){W.props.onFocus({open:p,values:d,originalEvent:e})},onPaste:function(){var e;switch(!1){case"undefined"!=typeof(null!=(e=this.props)?e.valuesFromPaste:void 0):return this.props.onPaste;default:return function(e){var t;return t=e.clipboardData,function(){var e;return e=d.concat(W.props.valuesFromPaste(f,d,t.getData("text"))),l(e,function(){return i(m(e))})}(),x(e)}}}.call(this),placeholder:this.props.placeholder,style:this.props.style},function(){switch(!1){case"function"!=typeof this.props.restoreOnBackspace:return{restoreOnBackspace:this.props.restoreOnBackspace};default:return{}}}.call(this)),function(){switch(!1){case"function"!=typeof this.props.renderNoResultsFound:return{renderNoResultsFound:function(){return W.props.renderNoResultsFound(d,c)}};default:return{}}}.call(this)))},getComputedState:function(){var e,t,n,r,i,a,s,l,c,p,f,d,h,m,g,y,b=this;return e=this.props.hasOwnProperty("anchor")?this.props.anchor:this.state.anchor,t=this.props.hasOwnProperty("highlightedUid")?this.props.highlightedUid:this.state.highlightedUid,n=this.isOpen(),r=this.props.hasOwnProperty("search")?this.props.search:this.state.search,i=this.values(),a=v(function(e){switch(!1){case!(b.props.hasOwnProperty(e)&&b.props.hasOwnProperty(u("on-"+e+"-change"))):return function(t,n){return b.props[u("on-"+e+"-change")](t,function(){}),b.setState({},n)};case!(b.props.hasOwnProperty(e)&&!b.props.hasOwnProperty(u("on-"+e+"-change"))):return function(e,t){return t()};case!(!b.props.hasOwnProperty(e)&&b.props.hasOwnProperty(u("on-"+e+"-change"))):return function(t,n){var r;return b.setState((r={},r[e+""]=t,r),function(){return n(),b.props[u("on-"+e+"-change")](t,function(){})})};case!(!b.props.hasOwnProperty(e)&&!b.props.hasOwnProperty(u("on-"+e+"-change"))):return function(t,n){var r;return b.setState((r={},r[e+""]=t,r),n)}}})(["anchor","highlightedUid","open","search","values"]),s=a[0],l=a[1],c=a[2],p=a[3],f=a[4],d=function(){var e;switch(!1){case!(null!=(e=this.props)&&e.children):return v(function(e){var t,n,r;return null!=e&&(t=e.props),null!=t&&(n=t.value,r=t.children),{label:r,value:n}})("Array"===O.call(this.props.children).slice(8,-1)?this.props.children:[this.props.children]);default:return[]}}.call(this),h=this.props.hasOwnProperty("options")?null!=(a=this.props.options)?a:[]:d,m=this.props.filterOptions(h,i,r),g=function(){switch(!1){case"function"!=typeof this.props.createFromSearch:return this.props.createFromSearch(m,i,r);default:return null}}.call(this),y=(g?[(a=o({},g),a.newOption=!0,a)]:[]).concat(m),{anchor:e,highlightedUid:t,search:r,values:i,onAnchorChange:s,onHighlightedUidChange:l,open:n,onOpenChange:function(e,t){c(function(){switch(!1){case!("undefined"!=typeof this.props.maxValues&&this.values().length>=this.props.maxValues):return!1;default:return e}}.call(b),t)},onSearchChange:p,onValuesChange:f,filteredOptions:m,options:y}},getInitialState:function(){return{anchor:this.props.values?m(this.props.values):void 0,highlightedUid:void 0,open:!1,scrollLock:!1,search:"",values:this.props.defaultValues}},firstOptionIndexToHighlight:function(e){var t,n;return t=function(){var t;switch(!1){case 1!==e.length:return 0;case"undefined"!=typeof(null!=(t=e[0])?t.newOption:void 0):return 0;default:return a(function(e){return"boolean"==typeof e.selectable&&!e.selectable})(c(1)(e))?0:1}}(),n=this.props.hasOwnProperty("search")?this.props.search:this.state.search,this.props.firstOptionIndexToHighlight(t,e,this.values(),n)},focus:function(){this.refs.select.focus()},blur:function(){this.refs.select.blur()},highlightFirstSelectableOption:function(){this.state.open&&this.refs.select.highlightAndScrollToSelectableOption(this.firstOptionIndexToHighlight(this.getComputedState().options),1)},values:function(){return this.props.hasOwnProperty("values")?this.props.values:this.state.values},isOpen:function(){return this.props.hasOwnProperty("open")?this.props.open:this.state.open}})}).call(this)},function(e,t,n){(function(){function t(e,t){var n={}.hasOwnProperty;for(var r in t)n.call(t,r)&&(e[r]=t[r]);return e}var r,o,i,a,s,u;r=n(5).createClass,o=n(13),i=o.render,a=o.unmountComponentAtNode,s=n(124),u=n(224),e.exports=r({getDefaultProps:function(){return{parentElement:function(){return document.body}}},render:function(){ +return null},initTether:function(e){var n=this;this.node=document.createElement("div"),this.props.parentElement().appendChild(this.node),this.tether=new u(t({element:this.node,target:e.target()},e.options)),i(e.children,this.node,function(){return n.tether.position()})},destroyTether:function(){this.tether&&this.tether.destroy(),this.node&&(a(this.node),this.node.parentElement.removeChild(this.node)),this.node=this.tether=void 0},componentDidMount:function(){this.props.children&&this.initTether(this.props)},componentWillReceiveProps:function(e){var n=this;this.props.children&&!e.children?this.destroyTether():e.children&&!this.props.children?this.initTether(e):e.children&&(this.tether.setOptions(t({element:this.node,target:e.target()},e.options)),i(e.children,this.node,function(){return n.tether.position()}))},shouldComponentUpdate:function(e,t){return s(this,e,t)},componentWillUnmount:function(){this.destroyTether()}})}).call(this)},function(e,t,n){(function(){var t,r,o,i,a;t=n(5),r=t.createClass,o=t.createFactory,i=t.DOM.path,a=o(n(85)),e.exports=r({render:function(){return a({className:"react-selectize-reset-button",style:{width:8,height:8}},i({d:"M0 0 L8 8 M8 0 L 0 8"}))}})}).call(this)},function(e,t,n){(function(){function t(e,t){var n={}.hasOwnProperty;for(var r in t)n.call(t,r)&&(e[r]=t[r]);return e}var r,o,i,a,s,u,l,c;r=n(15),o=r.each,i=r.objToPairs,a=n(5),s=a.DOM.input,u=a.createClass,l=a.createFactory,c=n(13).findDOMNode,e.exports=u({displayName:"ResizableInput",render:function(){var e;return s((e=t({},this.props),e.type="input",e.className="resizable-input",e))},autosize:function(){var e,t,n,r,a;return e=t=c(this),e.style.width="0px",0===t.value.length?t.style.width=null!=t&&t.currentStyle?"4px":"2px":t.scrollWidth>0?t.style.width=2+t.scrollWidth+"px":(n=r=document.createElement("div"),n.innerHTML=t.value,function(){var e;return e=r.style,e.display="inline-block",e.width="",e}(o(function(e){var t,n;return t=e[0],n=e[1],r.style[t]=n})(i(t.currentStyle?t.currentStyle:null!=(a=document.defaultView)?a:window.getComputedStyle(t)))),document.body.appendChild(r),t.style.width=4+r.clientWidth+"px",document.body.removeChild(r))},componentDidMount:function(){this.autosize()},componentDidUpdate:function(){this.autosize()},blur:function(){return c(this).blur()},focus:function(){return c(this).focus()}})}).call(this)},function(e,t,n){(function(){function t(e,t){var n,r=function(o){return e.length>1?function(){var i=o?o.concat():[];return n=t?n||this:this,i.push.apply(i,arguments)-1})(e)}),firstOptionIndexToHighlight:d,onBlur:function(e){},onBlurResetsInput:!0,onFocus:function(e){},onKeyboardSelectionFailed:function(e){},onPaste:function(e){},placeholder:"",renderValue:function(e){var t;return t=e.label,C({className:"simple-value"},w(null,t))},serialize:function(e){return null!=e?e.value:void 0},style:{},tether:!1,uid:d}},render:function(){var e,t,n,o,i,a,s,u,l,c,p,f,d,m,v,y,b,C,_,w,x,O,P,k,S,N,M,A,I,D,R,L,U,F,j,B,V,W=this;return e=this.getComputedState(),t=e.filteredOptions,n=e.highlightedUid,o=e.onHighlightedUidChange,i=e.onOpenChange,a=e.onSearchChange,s=e.onValueChange,u=e.open,l=e.options,c=e.search,p=e.value,f=e.values,null!=(e=this.props)&&(d=e.autofocus,m=e.autosize,v=e.cancelKeyboardEventOnSelection,y=e.delimiters,b=e.disabled,C=e.dropdownDirection,_=e.groupId,w=e.groups,x=e.groupsAsColumns,O=e.hideResetButton,P=e.name,k=e.inputProps,S=e.onBlurResetsInput,N=e.renderToggleButton,M=e.renderGroupTitle,A=e.renderResetButton,I=e.serialize,D=e.tether,R=e.tetherProps,L=e.theme,U=e.transitionEnter,F=e.transitionLeave,j=e.transitionEnterTimeout,B=e.transitionLeaveTimeout,V=e.uid),E(r(r({autofocus:d,autosize:m,cancelKeyboardEventOnSelection:v,className:"simple-select"+(this.props.className?" "+this.props.className:""),delimiters:y,disabled:b,dropdownDirection:C,groupId:_,groups:w,groupsAsColumns:x,hideResetButton:O,highlightedUid:n,onHighlightedUidChange:o,inputProps:k,name:P,onBlurResetsInput:S,renderGroupTitle:M,renderResetButton:A,renderToggleButton:N,scrollLock:this.state.scrollLock,onScrollLockChange:function(e){return W.setState({scrollLock:e})},tether:D,tetherProps:R,theme:L,transitionEnter:U,transitionEnterTimeout:j,transitionLeave:F,transitionLeaveTimeout:B,ref:"select",anchor:h(f),onAnchorChange:function(e,t){return t()},open:u,onOpenChange:i,firstOptionIndexToHighlight:function(){return W.firstOptionIndexToHighlight(l,p)},options:l,renderOption:this.props.renderOption,renderNoResultsFound:this.props.renderNoResultsFound,search:c,onSearchChange:function(e,t){return a(e,t)},values:f,onValuesChange:function(e,t){var n,r;return 0===e.length?s(void 0,function(){return t()}):(n=h(e),r=!g(n,p),function(){return function(e){return r?s(n,e):e()}}()(function(){return t(),i(!1,function(){})}))},renderValue:function(e){return u&&(W.props.editable||c.length>0)?null:W.props.renderValue(e)},onKeyboardSelectionFailed:function(e){return a("",function(){return i(!1,function(){return W.props.onKeyboardSelectionFailed(e)})})},uid:function(e){return{uid:W.props.uid(e),open:u,search:c}},serialize:function(e){return I(e[0])},onBlur:function(e){var t;t=W.props.onBlurResetsInput,function(){return function(e){return c.length>0&&t?a("",e):e()}}()(function(){return W.props.onBlur({value:p,open:u,originalEvent:e})})},onFocus:function(e){W.props.onFocus({value:p,open:u,originalEvent:e})},onPaste:function(){var e;switch(!1){case"undefined"!=typeof(null!=(e=this.props)?e.valueFromPaste:void 0):return this.props.onPaste;default:return function(e){var t,n;if(t=e.clipboardData,n=W.props.valueFromPaste(l,p,t.getData("text")))return function(){return s(n,function(){return a("",function(){return i(!1)})})}(),T(e)}}}.call(this),placeholder:this.props.placeholder,style:this.props.style},function(){switch(!1){case"function"!=typeof this.props.restoreOnBackspace:return{restoreOnBackspace:this.props.restoreOnBackspace};default:return{}}}.call(this)),function(){switch(!1){case"function"!=typeof this.props.renderNoResultsFound:return{renderNoResultsFound:function(){return W.props.renderNoResultsFound(p,c)}};default:return{}}}.call(this)))},getComputedState:function(){var e,t,n,o,i,a,s,l,c,p,f,d,h,v,g,y=this;return e=this.props.hasOwnProperty("highlightedUid")?this.props.highlightedUid:this.state.highlightedUid,t=this.isOpen(),n=this.props.hasOwnProperty("search")?this.props.search:this.state.search,o=this.value(),i=o||0===o?[o]:[],a=m(function(e){var t;return t=function(){switch(!1){case!(this.props.hasOwnProperty(e)&&this.props.hasOwnProperty(u("on-"+e+"-change"))):return function(t,n){return y.props[u("on-"+e+"-change")](t,function(){}),y.setState({},n)};case!(this.props.hasOwnProperty(e)&&!this.props.hasOwnProperty(u("on-"+e+"-change"))):return function(e,t){return t()};case!(!this.props.hasOwnProperty(e)&&this.props.hasOwnProperty(u("on-"+e+"-change"))):return function(t,n){var r;return y.setState((r={},r[e+""]=t,r),function(){return n(),y.props[u("on-"+e+"-change")](t,function(){})})};case!(!this.props.hasOwnProperty(e)&&!this.props.hasOwnProperty(u("on-"+e+"-change"))):return function(t,n){var r;return y.setState((r={},r[e+""]=t,r),n)}}}.call(y)})(["highlightedUid","open","search","value"]),s=a[0],l=a[1],c=a[2],p=a[3],f=function(){var e;switch(!1){case!(null!=(e=this.props)&&e.children):return m(function(e){var t,n,r;return null!=(t=null!=e?e.props:void 0)&&(n=t.value,r=t.children),{label:r,value:n}})("Array"===x.call(this.props.children).slice(8,-1)?this.props.children:[this.props.children]);default:return[]}}.call(this),d=this.props.hasOwnProperty("options")?null!=(a=this.props.options)?a:[]:f,h=this.props.filterOptions(d,n),v=function(){switch(!1){case"function"!=typeof this.props.createFromSearch:return this.props.createFromSearch(h,n);default:return null}}.call(this),g=(v?[(a=r({},v),a.newOption=!0,a)]:[]).concat(h),{highlightedUid:e,open:t,search:n,value:o,values:i,onHighlightedUidChange:s,onOpenChange:function(e,t){l(e,function(){if(t(),y.props.editable&&y.isOpen()&&o)return c(y.props.editable(o)+""+(1===n.length?n:""),function(){return y.highlightFirstSelectableOption(function(){})})})},onSearchChange:c,onValueChange:p,filteredOptions:h,options:g}},getInitialState:function(){var e;return{highlightedUid:void 0,open:!1,scrollLock:!1,search:"",value:null!=(e=this.props)?e.defaultValue:void 0}},firstOptionIndexToHighlight:function(e,t){var n,r,o;return n=t?f(function(e){return g(e,t)},e):void 0,r=function(){var t;switch(!1){case"undefined"==typeof n:return n;case 1!==e.length:return 0;case"undefined"!=typeof(null!=(t=e[0])?t.newOption:void 0):return 0;default:return i(function(e){return"boolean"==typeof e.selectable&&!e.selectable})(s(1)(e))?0:1}}(),o=this.props.hasOwnProperty("search")?this.props.search:this.state.search,this.props.firstOptionIndexToHighlight(r,e,t,o)},focus:function(){this.refs.select.focus()},blur:function(){this.refs.select.blur()},highlightFirstSelectableOption:function(e){var t,n,r;null==e&&(e=function(){}),this.state.open?(t=this.getComputedState(),n=t.options,r=t.value,this.refs.select.highlightAndScrollToSelectableOption(this.firstOptionIndexToHighlight(n,r),1,e)):e()},value:function(){return this.props.hasOwnProperty("value")?this.props.value:this.state.value},isOpen:function(){return this.props.hasOwnProperty("open")?this.props.open:this.state.open}})}).call(this)},function(e,t,n){(function(){var t,r,o,i,a;t=n(5),r=t.createClass,o=t.createFactory,i=t.DOM.path,a=o(n(85)),e.exports=r({getDefaultProps:function(){return{open:!1,flipped:!1}},render:function(){return a({className:"react-selectize-toggle-button",style:{width:10,height:8}},i({d:function(){switch(!1){case!(this.props.open&&!this.props.flipped||!this.props.open&&this.props.flipped):return"M0 6 L5 1 L10 6 Z";default:return"M0 1 L5 6 L10 1 Z"}}.call(this)}))}})}).call(this)},function(e,t,n){(function(){var t,r,o,i;t=n(5),r=t.createClass,o=t.DOM.div,i=n(16).isEqualToObject,e.exports=r({getDefaultProps:function(){return{}},render:function(){return o({className:"value-wrapper"},this.props.renderItem(this.props.item))},shouldComponentUpdate:function(e){var t;return!i(null!=e?e.uid:void 0,null!=(t=this.props)?t.uid:void 0)}})}).call(this)},function(e,t,n){(function(){var t,r,o,i;t=n(196),r=n(201),o=n(197),i=n(53),e.exports={HighlightedText:t,SimpleSelect:r,MultiSelect:o,ReactSelectize:i}}).call(this)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}t.__esModule=!0;var s=Object.assign||function(e){for(var t=1;t=0)&&r.push(o)}return r.push(e.ownerDocument.body),e.ownerDocument!==document&&r.push(e.ownerDocument.defaultView),r}function r(){w&&document.body.removeChild(w),w=null}function o(e){var n=void 0;e===document?(n=document,e=document.documentElement):n=e.ownerDocument;var r=n.documentElement,o=t(e),i=x();return o.top-=i.top,o.left-=i.left,"undefined"==typeof o.width&&(o.width=document.body.scrollWidth-o.left-o.right),"undefined"==typeof o.height&&(o.height=document.body.scrollHeight-o.top-o.bottom),o.top=o.top-r.clientTop,o.left=o.left-r.clientLeft, +o.right=n.body.clientWidth-o.width-o.left,o.bottom=n.body.clientHeight-o.height-o.top,o}function i(e){return e.offsetParent||document.documentElement}function a(){if(O)return O;var e=document.createElement("div");e.style.width="100%",e.style.height="200px";var t=document.createElement("div");s(t.style,{position:"absolute",top:0,left:0,pointerEvents:"none",visibility:"hidden",width:"200px",height:"150px",overflow:"hidden"}),t.appendChild(e),document.body.appendChild(t);var n=e.offsetWidth;t.style.overflow="scroll";var r=e.offsetWidth;n===r&&(r=t.clientWidth),document.body.removeChild(t);var o=n-r;return O={width:o,height:o}}function s(){var e=arguments.length<=0||void 0===arguments[0]?{}:arguments[0],t=[];return Array.prototype.push.apply(t,arguments),t.slice(1).forEach(function(t){if(t)for(var n in t)({}).hasOwnProperty.call(t,n)&&(e[n]=t[n])}),e}function u(e,t){if("undefined"!=typeof e.classList)t.split(" ").forEach(function(t){t.trim()&&e.classList.remove(t)});else{var n=new RegExp("(^| )"+t.split(" ").join("|")+"( |$)","gi"),r=p(e).replace(n," ");f(e,r)}}function l(e,t){if("undefined"!=typeof e.classList)t.split(" ").forEach(function(t){t.trim()&&e.classList.add(t)});else{u(e,t);var n=p(e)+(" "+t);f(e,n)}}function c(e,t){if("undefined"!=typeof e.classList)return e.classList.contains(t);var n=p(e);return new RegExp("(^| )"+t+"( |$)","gi").test(n)}function p(e){return e.className instanceof e.ownerDocument.defaultView.SVGAnimatedString?e.className.baseVal:e.className}function f(e,t){e.setAttribute("class",t)}function d(e,t,n){n.forEach(function(n){t.indexOf(n)===-1&&c(e,n)&&u(e,n)}),t.forEach(function(t){c(e,t)||l(e,t)})}function e(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function h(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function m(e,t){var n=arguments.length<=2||void 0===arguments[2]?1:arguments[2];return e+n>=t&&t>=e-n}function v(){return"object"==typeof performance&&"function"==typeof performance.now?performance.now():+new Date}function g(){for(var e={top:0,left:0},t=arguments.length,n=Array(t),r=0;r1?n-1:0),o=1;o16?(t=Math.min(t-16,250),void(n=setTimeout(r,250))):void("undefined"!=typeof e&&v()-e<10||(null!=n&&(clearTimeout(n),n=null),e=v(),L(),t=v()-e))};"undefined"!=typeof window&&"undefined"!=typeof window.addEventListener&&["resize","scroll","touchmove"].forEach(function(e){window.addEventListener(e,r)})}();var U={center:"center",left:"right",right:"left"},F={middle:"middle",top:"bottom",bottom:"top"},j={top:0,left:0,middle:"50%",center:"50%",bottom:"100%",right:"100%"},B=function(e,t){var n=e.left,r=e.top;return"auto"===n&&(n=U[t.left]),"auto"===r&&(r=F[t.top]),{left:n,top:r}},V=function(e){var t=e.left,n=e.top;return"undefined"!=typeof j[e.left]&&(t=j[e.left]),"undefined"!=typeof j[e.top]&&(n=j[e.top]),{left:t,top:n}},W=function(e){var t=e.split(" "),n=M(t,2),r=n[0],o=n[1];return{top:r,left:o}},H=W,q=function(t){function c(t){var n=this;e(this,c),A(Object.getPrototypeOf(c.prototype),"constructor",this).call(this),this.position=this.position.bind(this),R.push(this),this.history=[],this.setOptions(t,!1),_.modules.forEach(function(e){"undefined"!=typeof e.initialize&&e.initialize.call(n)}),this.position()}return h(c,t),C(c,[{key:"getClass",value:function(){var e=arguments.length<=0||void 0===arguments[0]?"":arguments[0],t=this.options.classes;return"undefined"!=typeof t&&t[e]?this.options.classes[e]:this.options.classPrefix?this.options.classPrefix+"-"+e:e}},{key:"setOptions",value:function(e){var t=this,r=arguments.length<=1||void 0===arguments[1]||arguments[1],o={offset:"0 0",targetOffset:"0 0",targetAttachment:"auto auto",classPrefix:"tether"};this.options=s(o,e);var i=this.options,a=i.element,u=i.target,c=i.targetModifier;if(this.element=a,this.target=u,this.targetModifier=c,"viewport"===this.target?(this.target=document.body,this.targetModifier="visible"):"scroll-handle"===this.target&&(this.target=document.body,this.targetModifier="scroll-handle"),["element","target"].forEach(function(e){if("undefined"==typeof t[e])throw new Error("Tether Error: Both element and target must be defined");"undefined"!=typeof t[e].jquery?t[e]=t[e][0]:"string"==typeof t[e]&&(t[e]=document.querySelector(t[e]))}),l(this.element,this.getClass("element")),this.options.addTargetClasses!==!1&&l(this.target,this.getClass("target")),!this.options.attachment)throw new Error("Tether Error: You must provide an attachment");this.targetAttachment=H(this.options.targetAttachment),this.attachment=H(this.options.attachment),this.offset=W(this.options.offset),this.targetOffset=W(this.options.targetOffset),"undefined"!=typeof this.scrollParents&&this.disable(),"scroll-handle"===this.targetModifier?this.scrollParents=[this.target]:this.scrollParents=n(this.target),this.options.enabled!==!1&&this.enable(r)}},{key:"getTargetBounds",value:function(){if("undefined"==typeof this.targetModifier)return o(this.target);if("visible"===this.targetModifier){if(this.target===document.body)return{top:pageYOffset,left:pageXOffset,height:innerHeight,width:innerWidth};var e=o(this.target),t={height:e.height,width:e.width,top:e.top,left:e.left};return t.height=Math.min(t.height,e.height-(pageYOffset-e.top)),t.height=Math.min(t.height,e.height-(e.top+e.height-(pageYOffset+innerHeight))),t.height=Math.min(innerHeight,t.height),t.height-=2,t.width=Math.min(t.width,e.width-(pageXOffset-e.left)),t.width=Math.min(t.width,e.width-(e.left+e.width-(pageXOffset+innerWidth))),t.width=Math.min(innerWidth,t.width),t.width-=2,t.topn.clientWidth||[r.overflow,r.overflowX].indexOf("scroll")>=0||this.target!==document.body,a=0;i&&(a=15);var s=e.height-parseFloat(r.borderTopWidth)-parseFloat(r.borderBottomWidth)-a,t={width:15,height:.975*s*(s/n.scrollHeight),left:e.left+e.width-parseFloat(r.borderLeftWidth)-15},u=0;s<408&&this.target===document.body&&(u=-11e-5*Math.pow(s,2)-.00727*s+22.58),this.target!==document.body&&(t.height=Math.max(t.height,24));var l=this.target.scrollTop/(n.scrollHeight-s);return t.top=l*(s-t.height-u)+e.top+parseFloat(r.borderTopWidth),this.target===document.body&&(t.height=Math.max(t.height,24)),t}}},{key:"clearCache",value:function(){this._cache={}}},{key:"cache",value:function(e,t){return"undefined"==typeof this._cache&&(this._cache={}),"undefined"==typeof this._cache[e]&&(this._cache[e]=t.call(this)),this._cache[e]}},{key:"enable",value:function(){var e=this,t=arguments.length<=0||void 0===arguments[0]||arguments[0];this.options.addTargetClasses!==!1&&l(this.target,this.getClass("enabled")),l(this.element,this.getClass("enabled")),this.enabled=!0,this.scrollParents.forEach(function(t){t!==e.target.ownerDocument&&t.addEventListener("scroll",e.position)}),t&&this.position()}},{key:"disable",value:function(){var e=this;u(this.target,this.getClass("enabled")),u(this.element,this.getClass("enabled")),this.enabled=!1,"undefined"!=typeof this.scrollParents&&this.scrollParents.forEach(function(t){t.removeEventListener("scroll",e.position)})}},{key:"destroy",value:function(){var e=this;this.disable(),R.forEach(function(t,n){t===e&&R.splice(n,1)}),0===R.length&&r()}},{key:"updateAttachClasses",value:function(e,t){var n=this;e=e||this.attachment,t=t||this.targetAttachment;var r=["left","top","bottom","right","middle","center"];"undefined"!=typeof this._addAttachClasses&&this._addAttachClasses.length&&this._addAttachClasses.splice(0,this._addAttachClasses.length),"undefined"==typeof this._addAttachClasses&&(this._addAttachClasses=[]);var o=this._addAttachClasses;e.top&&o.push(this.getClass("element-attached")+"-"+e.top),e.left&&o.push(this.getClass("element-attached")+"-"+e.left),t.top&&o.push(this.getClass("target-attached")+"-"+t.top),t.left&&o.push(this.getClass("target-attached")+"-"+t.left);var i=[];r.forEach(function(e){i.push(n.getClass("element-attached")+"-"+e),i.push(n.getClass("target-attached")+"-"+e)}),k(function(){"undefined"!=typeof n._addAttachClasses&&(d(n.element,n._addAttachClasses,i),n.options.addTargetClasses!==!1&&d(n.target,n._addAttachClasses,i),delete n._addAttachClasses)})}},{key:"position",value:function(){var e=this,t=arguments.length<=0||void 0===arguments[0]||arguments[0];if(this.enabled){this.clearCache();var n=B(this.targetAttachment,this.attachment);this.updateAttachClasses(this.attachment,n);var r=this.cache("element-bounds",function(){return o(e.element)}),s=r.width,u=r.height;if(0===s&&0===u&&"undefined"!=typeof this.lastSize){var l=this.lastSize;s=l.width,u=l.height}else this.lastSize={width:s,height:u};var c=this.cache("target-bounds",function(){return e.getTargetBounds()}),p=c,f=y(V(this.attachment),{width:s,height:u}),d=y(V(n),p),h=y(this.offset,{width:s,height:u}),m=y(this.targetOffset,p);f=g(f,h),d=g(d,m);for(var v=c.left+d.left-f.left,b=c.top+d.top-f.top,C=0;C<_.modules.length;++C){var w=_.modules[C],E=w.position.call(this,{left:v,top:b,targetAttachment:n,targetPos:c,elementPos:r,offset:f,targetOffset:d,manualOffset:h,manualTargetOffset:m,scrollbarSize:P,attachment:this.attachment});if(E===!1)return!1;"undefined"!=typeof E&&"object"==typeof E&&(b=E.top,v=E.left)}var T={page:{top:b,left:v},viewport:{top:b-pageYOffset,bottom:pageYOffset-b-u+innerHeight,left:v-pageXOffset,right:pageXOffset-v-s+innerWidth}},x=this.target.ownerDocument,O=x.defaultView,P=void 0;return O.innerHeight>x.documentElement.clientHeight&&(P=this.cache("scrollbar-size",a),T.viewport.bottom-=P.height),O.innerWidth>x.documentElement.clientWidth&&(P=this.cache("scrollbar-size",a),T.viewport.right-=P.width),["","static"].indexOf(x.body.style.position)!==-1&&["","static"].indexOf(x.body.parentElement.style.position)!==-1||(T.page.bottom=x.body.scrollHeight-b-u,T.page.right=x.body.scrollWidth-v-s),"undefined"!=typeof this.options.optimizations&&this.options.optimizations.moveElement!==!1&&"undefined"==typeof this.targetModifier&&!function(){var t=e.cache("target-offsetparent",function(){return i(e.target)}),n=e.cache("target-offsetparent-bounds",function(){return o(t)}),r=getComputedStyle(t),a=n,s={};if(["Top","Left","Bottom","Right"].forEach(function(e){s[e.toLowerCase()]=parseFloat(r["border"+e+"Width"])}),n.right=x.body.scrollWidth-n.left-a.width+s.right,n.bottom=x.body.scrollHeight-n.top-a.height+s.bottom,T.page.top>=n.top+s.top&&T.page.bottom>=n.bottom&&T.page.left>=n.left+s.left&&T.page.right>=n.right){var u=t.scrollTop,l=t.scrollLeft;T.offset={top:T.page.top-n.top+u-s.top,left:T.page.left-n.left+l-s.left}}}(),this.move(T),this.history.unshift(T),this.history.length>3&&this.history.pop(),t&&S(),!0}}},{key:"move",value:function(e){var t=this;if("undefined"!=typeof this.element.parentNode){var n={};for(var r in e){n[r]={};for(var o in e[r]){for(var a=!1,u=0;u=0){var d=a.split(" "),m=M(d,2);p=m[0],c=m[1]}else c=p=a;var C=b(t,o);"target"!==p&&"both"!==p||(nC[3]&&"bottom"===g.top&&(n-=f,g.top="top")),"together"===p&&("top"===g.top&&("bottom"===y.top&&nC[3]&&n-(u-f)>=C[1]&&(n-=u-f,g.top="bottom",y.top="bottom")),"bottom"===g.top&&("top"===y.top&&n+u>C[3]?(n-=f,g.top="top",n-=u,y.top="bottom"):"bottom"===y.top&&nC[3]&&"top"===y.top?(n-=u,y.top="bottom"):nC[2]&&"right"===g.left&&(r-=h,g.left="left")),"together"===c&&(rC[2]&&"right"===g.left?"left"===y.left?(r-=h,g.left="left",r-=l,y.left="right"):"right"===y.left&&(r-=h,g.left="left",r+=l,y.left="left"):"center"===g.left&&(r+l>C[2]&&"left"===y.left?(r-=l,y.left="right"):rC[3]&&"top"===y.top&&(n-=u,y.top="bottom")),"element"!==c&&"both"!==c||(rC[2]&&("left"===y.left?(r-=l,y.left="right"):"center"===y.left&&(r-=l/2,y.left="right"))),"string"==typeof s?s=s.split(",").map(function(e){return e.trim()}):s===!0&&(s=["top","left","right","bottom"]),s=s||[];var _=[],w=[];n=0?(n=C[1],_.push("top")):w.push("top")),n+u>C[3]&&(s.indexOf("bottom")>=0?(n=C[3]-u,_.push("bottom")):w.push("bottom")),r=0?(r=C[0],_.push("left")):w.push("left")),r+l>C[2]&&(s.indexOf("right")>=0?(r=C[2]-l,_.push("right")):w.push("right")),_.length&&!function(){var e=void 0;e="undefined"!=typeof t.options.pinnedClass?t.options.pinnedClass:t.getClass("pinned"),v.push(e),_.forEach(function(t){v.push(e+"-"+t)})}(),w.length&&!function(){var e=void 0;e="undefined"!=typeof t.options.outOfBoundsClass?t.options.outOfBoundsClass:t.getClass("out-of-bounds"),v.push(e),w.forEach(function(t){v.push(e+"-"+t)})}(),(_.indexOf("left")>=0||_.indexOf("right")>=0)&&(y.left=g.left=!1),(_.indexOf("top")>=0||_.indexOf("bottom")>=0)&&(y.top=g.top=!1),g.top===i.top&&g.left===i.left&&y.top===t.attachment.top&&y.left===t.attachment.left||(t.updateAttachClasses(y,g),t.trigger("update",{attachment:y,targetAttachment:g}))}),k(function(){t.options.addTargetClasses!==!1&&d(t.target,v,m),d(t.element,v,m)}),{top:n,left:r}}});var I=_.Utils,o=I.getBounds,d=I.updateClasses,k=I.defer;_.modules.push({position:function(e){var t=this,n=e.top,r=e.left,i=this.cache("element-bounds",function(){return o(t.element)}),a=i.height,s=i.width,u=this.getTargetBounds(),l=n+a,c=r+s,p=[];n<=u.bottom&&l>=u.top&&["left","right"].forEach(function(e){var t=u[e];t!==r&&t!==c||p.push(e)}),r<=u.right&&c>=u.left&&["top","bottom"].forEach(function(e){var t=u[e];t!==n&&t!==l||p.push(e)});var f=[],h=[],m=["left","top","right","bottom"];return f.push(this.getClass("abutted")),m.forEach(function(e){f.push(t.getClass("abutted")+"-"+e)}),p.length&&h.push(this.getClass("abutted")),p.forEach(function(e){h.push(t.getClass("abutted")+"-"+e)}),k(function(){t.options.addTargetClasses!==!1&&d(t.target,h,f),d(t.element,h,f)}),!0}});var M=function(){function e(e,t){var n=[],r=!0,o=!1,i=void 0;try{for(var a,s=e[Symbol.iterator]();!(r=(a=s.next()).done)&&(n.push(a.value),!t||n.length!==t);r=!0);}catch(e){o=!0,i=e}finally{try{!r&&s.return&&s.return()}finally{if(o)throw i}}return n}return function(t,n){if(Array.isArray(t))return t;if(Symbol.iterator in Object(t))return e(t,n);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}();return _.modules.push({position:function(e){var t=e.top,n=e.left;if(this.options.shift){var r=this.options.shift;"function"==typeof this.options.shift&&(r=this.options.shift.call(this,{top:t,left:n}));var o=void 0,i=void 0;if("string"==typeof r){r=r.split(" "),r[1]=r[1]||r[0];var a=r,s=M(a,2);o=s[0],i=s[1],o=parseFloat(o,10),i=parseFloat(i,10)}else o=r.top,i=r.left;return t+=o,n+=i,{top:t,left:n}}}}),z})},function(e,t,n){"use strict";var r=function(){};e.exports=r},function(e,t){!function(e){"use strict";function t(e){if("string"!=typeof e&&(e=String(e)),/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(e))throw new TypeError("Invalid character in header field name");return e.toLowerCase()}function n(e){return"string"!=typeof e&&(e=String(e)),e}function r(e){var t={next:function(){var t=e.shift();return{done:void 0===t,value:t}}};return g.iterable&&(t[Symbol.iterator]=function(){return t}),t}function o(e){this.map={},e instanceof o?e.forEach(function(e,t){this.append(t,e)},this):e&&Object.getOwnPropertyNames(e).forEach(function(t){this.append(t,e[t])},this)}function i(e){return e.bodyUsed?Promise.reject(new TypeError("Already read")):void(e.bodyUsed=!0)}function a(e){return new Promise(function(t,n){e.onload=function(){t(e.result)},e.onerror=function(){n(e.error)}})}function s(e){var t=new FileReader,n=a(t);return t.readAsArrayBuffer(e),n}function u(e){var t=new FileReader,n=a(t);return t.readAsText(e),n}function l(e){for(var t=new Uint8Array(e),n=new Array(t.length),r=0;r-1?t:e}function d(e,t){t=t||{};var n=t.body;if(e instanceof d){if(e.bodyUsed)throw new TypeError("Already read");this.url=e.url,this.credentials=e.credentials,t.headers||(this.headers=new o(e.headers)),this.method=e.method,this.mode=e.mode,n||null==e._bodyInit||(n=e._bodyInit,e.bodyUsed=!0)}else this.url=String(e);if(this.credentials=t.credentials||this.credentials||"omit",!t.headers&&this.headers||(this.headers=new o(t.headers)),this.method=f(t.method||this.method||"GET"),this.mode=t.mode||this.mode||null,this.referrer=null,("GET"===this.method||"HEAD"===this.method)&&n)throw new TypeError("Body not allowed for GET or HEAD requests");this._initBody(n)}function h(e){var t=new FormData;return e.trim().split("&").forEach(function(e){if(e){var n=e.split("="),r=n.shift().replace(/\+/g," "),o=n.join("=").replace(/\+/g," ");t.append(decodeURIComponent(r),decodeURIComponent(o))}}),t}function m(e){var t=new o;return e.split(/\r?\n/).forEach(function(e){var n=e.split(":"),r=n.shift().trim();if(r){var o=n.join(":").trim();t.append(r,o)}}),t}function v(e,t){t||(t={}),this.type="default",this.status="status"in t?t.status:200,this.ok=this.status>=200&&this.status<300,this.statusText="statusText"in t?t.statusText:"OK",this.headers=new o(t.headers),this.url=t.url||"",this._initBody(e)}if(!e.fetch){var g={searchParams:"URLSearchParams"in e,iterable:"Symbol"in e&&"iterator"in Symbol,blob:"FileReader"in e&&"Blob"in e&&function(){try{return new Blob,!0}catch(e){return!1}}(),formData:"FormData"in e,arrayBuffer:"ArrayBuffer"in e};if(g.arrayBuffer)var y=["[object Int8Array]","[object Uint8Array]","[object Uint8ClampedArray]","[object Int16Array]","[object Uint16Array]","[object Int32Array]","[object Uint32Array]","[object Float32Array]","[object Float64Array]"],b=function(e){return e&&DataView.prototype.isPrototypeOf(e)},C=ArrayBuffer.isView||function(e){return e&&y.indexOf(Object.prototype.toString.call(e))>-1};o.prototype.append=function(e,r){e=t(e),r=n(r);var o=this.map[e];this.map[e]=o?o+","+r:r},o.prototype.delete=function(e){delete this.map[t(e)]},o.prototype.get=function(e){return e=t(e),this.has(e)?this.map[e]:null},o.prototype.has=function(e){return this.map.hasOwnProperty(t(e))},o.prototype.set=function(e,r){this.map[t(e)]=n(r)},o.prototype.forEach=function(e,t){for(var n in this.map)this.map.hasOwnProperty(n)&&e.call(t,this.map[n],n,this)},o.prototype.keys=function(){var e=[];return this.forEach(function(t,n){e.push(n)}),r(e)},o.prototype.values=function(){var e=[];return this.forEach(function(t){e.push(t)}),r(e)},o.prototype.entries=function(){var e=[];return this.forEach(function(t,n){e.push([n,t])}),r(e)},g.iterable&&(o.prototype[Symbol.iterator]=o.prototype.entries);var _=["DELETE","GET","HEAD","OPTIONS","POST","PUT"];d.prototype.clone=function(){return new d(this,{body:this._bodyInit})},p.call(d.prototype),p.call(v.prototype),v.prototype.clone=function(){return new v(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new o(this.headers),url:this.url})},v.error=function(){var e=new v(null,{status:0,statusText:""});return e.type="error",e};var w=[301,302,303,307,308];v.redirect=function(e,t){if(w.indexOf(t)===-1)throw new RangeError("Invalid status code");return new v(null,{status:t,headers:{location:e}})},e.Headers=o,e.Request=d,e.Response=v,e.fetch=function(e,t){return new Promise(function(n,r){var o=new d(e,t),i=new XMLHttpRequest;i.onload=function(){var e={status:i.status,statusText:i.statusText,headers:m(i.getAllResponseHeaders()||"")};e.url="responseURL"in i?i.responseURL:e.headers.get("X-Request-URL");var t="response"in i?i.response:i.responseText;n(new v(t,e))},i.onerror=function(){r(new TypeError("Network request failed"))},i.ontimeout=function(){r(new TypeError("Network request failed"))},i.open(o.method,o.url,!0),"include"===o.credentials&&(i.withCredentials=!0),"responseType"in i&&g.blob&&(i.responseType="blob"),o.headers.forEach(function(e,t){i.setRequestHeader(t,e)}),i.send("undefined"==typeof o._bodyInit?null:o._bodyInit)})},e.fetch.polyfill=!0}}("undefined"!=typeof self?self:this)},function(e,t,n,r,o,i,a,s){function u(e,t){var n,r=function(o){return e.length>1?function(){var i=o?o.concat():[];return n=t?n||this:this,i.push.apply(i,arguments) Date: Fri, 19 Aug 2022 10:41:02 -0700 Subject: [PATCH 008/583] Add "does not begin with" and "does not end with" condition operators --- plexpy/notification_handler.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plexpy/notification_handler.py b/plexpy/notification_handler.py index 497973be..ed4e60b3 100644 --- a/plexpy/notification_handler.py +++ b/plexpy/notification_handler.py @@ -339,9 +339,15 @@ def notify_custom_conditions(notifier_id=None, parameters=None): elif operator == 'begins with': evaluated = parameter_value.startswith(tuple(values)) + elif operator == 'does not begin with': + evaluated = not parameter_value.startswith(tuple(values)) + elif operator == 'ends with': evaluated = parameter_value.endswith(tuple(values)) + elif operator == 'does not end with': + evaluated = not parameter_value.endswith(tuple(values)) + elif operator == 'is greater than': evaluated = any(parameter_value > c for c in values) From 70256dd0b977987609f07099f88d216562d3807c Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Tue, 23 Aug 2022 10:48:01 -0700 Subject: [PATCH 009/583] Update snap login in workflow --- .github/workflows/publish-snap.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish-snap.yml b/.github/workflows/publish-snap.yml index 26fb174c..b62f2e01 100644 --- a/.github/workflows/publish-snap.yml +++ b/.github/workflows/publish-snap.yml @@ -59,8 +59,9 @@ jobs: - name: Publish Snap Package uses: snapcore/action-publish@v1 if: startsWith(github.ref, 'refs/tags/') || github.ref == 'refs/heads/nightly' + env: + SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAP_LOGIN }} with: - store_login: ${{ secrets.SNAP_LOGIN }} snap: ${{ steps.build.outputs.snap }} release: ${{ steps.prepare.outputs.RELEASE }} From 806c8814b64af3cfa5b8ef93335199a6106ca038 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Tue, 23 Aug 2022 11:22:54 -0700 Subject: [PATCH 010/583] Unpin QEMU in snap workflow --- .github/workflows/publish-snap.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/publish-snap.yml b/.github/workflows/publish-snap.yml index b62f2e01..27682148 100644 --- a/.github/workflows/publish-snap.yml +++ b/.github/workflows/publish-snap.yml @@ -36,8 +36,6 @@ jobs: - name: Set Up QEMU uses: docker/setup-qemu-action@v2 - with: - image: tonistiigi/binfmt@sha256:df15403e06a03c2f461c1f7938b171fda34a5849eb63a70e2a2109ed5a778bde - name: Build Snap Package uses: diddlesnaps/snapcraft-multiarch-action@v1 From 925efe0db771b3953d62e8831bb8f1210e3a07f2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Aug 2022 09:17:13 -0700 Subject: [PATCH 011/583] Bump actions/cache from 3.0.6 to 3.0.8 (#1822) Bumps [actions/cache](https://github.com/actions/cache) from 3.0.6 to 3.0.8. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/v3.0.6...v3.0.8) --- updated-dependencies: - dependency-name: actions/cache dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> [skip ci] --- .github/workflows/publish-docker.yml | 2 +- .github/workflows/publish-installers.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml index 39533cb6..773f730f 100644 --- a/.github/workflows/publish-docker.yml +++ b/.github/workflows/publish-docker.yml @@ -47,7 +47,7 @@ jobs: version: latest - name: Cache Docker Layers - uses: actions/cache@v3.0.6 + uses: actions/cache@v3.0.8 with: path: /tmp/.buildx-cache key: ${{ runner.os }}-buildx-${{ github.sha }} diff --git a/.github/workflows/publish-installers.yml b/.github/workflows/publish-installers.yml index ffb1f2ee..6b9b9c8b 100644 --- a/.github/workflows/publish-installers.yml +++ b/.github/workflows/publish-installers.yml @@ -57,7 +57,7 @@ jobs: python-version: 3.9 - name: Cache Dependencies - uses: actions/cache@v3.0.6 + uses: actions/cache@v3.0.8 with: path: ~\AppData\Local\pip\Cache key: ${{ runner.os }}-pip-${{ hashFiles('package/requirements-package.txt') }} From 41b796e007764a95a18bc0779f3ef2e02e943bfa Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Mon, 5 Sep 2022 11:07:25 -0700 Subject: [PATCH 012/583] v2.10.4 --- CHANGELOG.md | 11 +++++++++++ plexpy/version.py | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb1af251..abd4cc2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## v2.10.4 (2022-09-05) + +* Activity: + * New: Added tooltip for quality profile on activity cards. +* Notifications: + * New: Added "does not begin with" and "does not end with" condition operators. +* UI: + * Fix: Album count showing 0 on library statistics. + * Fix: Library statistics not showing up for libraries without any history. + + ## v2.10.3 (2022-08-09) * Notifications: diff --git a/plexpy/version.py b/plexpy/version.py index 0b033d3d..a338dabd 100644 --- a/plexpy/version.py +++ b/plexpy/version.py @@ -18,4 +18,4 @@ from __future__ import unicode_literals PLEXPY_BRANCH = "master" -PLEXPY_RELEASE_VERSION = "v2.10.3" +PLEXPY_RELEASE_VERSION = "v2.10.4" From 0a6b12329cbeb21285bde0f62bdf2ab8ba393b29 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Fri, 9 Sep 2022 16:14:31 -0700 Subject: [PATCH 013/583] Link to MusicBrainz track for notifications --- plexpy/notification_handler.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plexpy/notification_handler.py b/plexpy/notification_handler.py index ed4e60b3..1fc6ff83 100644 --- a/plexpy/notification_handler.py +++ b/plexpy/notification_handler.py @@ -652,7 +652,7 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m # Check external guids if notify_params['media_type'] == 'episode': guids = notify_params['grandparent_guids'] - elif notify_params['media_type'] in ('season', 'track'): + elif notify_params['media_type'] == 'season': guids = notify_params['parent_guids'] else: guids = notify_params['guids'] @@ -704,8 +704,10 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m if 'mbid://' in notify_params['guid'] or notify_params['musicbrainz_id']: if notify_params['media_type'] == 'artist': notify_params['musicbrainz_url'] = 'https://musicbrainz.org/artist/' + notify_params['musicbrainz_id'] - else: + elif notify_params['media_type'] == 'album': notify_params['musicbrainz_url'] = 'https://musicbrainz.org/release/' + notify_params['musicbrainz_id'] + else: + notify_params['musicbrainz_url'] = 'https://musicbrainz.org/track/' + notify_params['musicbrainz_id'] # Get TheMovieDB info (for movies and tv only) if plexpy.CONFIG.THEMOVIEDB_LOOKUP and notify_params['media_type'] in ('movie', 'show', 'season', 'episode'): From 54af528f6c0ccd227a24e7c0ca43a7effded7256 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Sat, 10 Sep 2022 09:17:01 -0700 Subject: [PATCH 014/583] Add submit-winget.yml workflow --- .github/workflows/submit-winget.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/submit-winget.yml diff --git a/.github/workflows/submit-winget.yml b/.github/workflows/submit-winget.yml new file mode 100644 index 00000000..de943c29 --- /dev/null +++ b/.github/workflows/submit-winget.yml @@ -0,0 +1,24 @@ +name: Submit Tautulli package to Windows Package Manager Community Repository + +on: + workflow_dispatch: ~ + release: + types: [published] + +jobs: + winget: + name: Publish Winget Package + runs-on: windows-latest + steps: + - name: Submit package to Windows Package Manager Community Repository + run: | + $wingetPackage = "Tautulli.Tautulli" + $gitToken = "${{ secrets.GITHUB_TOKEN }}" + + $github = Invoke-RestMethod -uri "https://api.github.com/repos/Tautulli/Tautulli/releases/latest" + $installerUrl = $github | Select -ExpandProperty assets -First 1 | Where-Object -Property name -match "Tautulli-windows-.*-x64.exe" | Select -ExpandProperty browser_download_url + $version = "$($github.tag_name.Trim('v')).1" + + # getting latest wingetcreate file + iwr https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe + .\wingetcreate.exe update $wingetPackage -s -v $version -u $installerUrl -t $gitToken From 5ace9e163d04e49a8eb0c5555eab5e9dc31bb5a7 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Mon, 12 Sep 2022 16:47:06 -0700 Subject: [PATCH 015/583] Set default library count to 0 --- plexpy/pmsconnect.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py index 7149836d..30e82f18 100644 --- a/plexpy/pmsconnect.py +++ b/plexpy/pmsconnect.py @@ -2757,6 +2757,7 @@ class PmsConnect(object): return [] + library_count = '0' children_list = [] for a in xml_head: From 90cf863305f6f6c381e8381943a32fbb6eedd002 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Thu, 15 Sep 2022 10:05:57 -0700 Subject: [PATCH 016/583] Update pip cache in publish-installers workflow --- .github/workflows/publish-installers.yml | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/.github/workflows/publish-installers.yml b/.github/workflows/publish-installers.yml index 6b9b9c8b..ad590098 100644 --- a/.github/workflows/publish-installers.yml +++ b/.github/workflows/publish-installers.yml @@ -54,14 +54,9 @@ jobs: - name: Set Up Python uses: actions/setup-python@v4.2.0 with: - python-version: 3.9 - - - name: Cache Dependencies - uses: actions/cache@v3.0.8 - with: - path: ~\AppData\Local\pip\Cache - key: ${{ runner.os }}-pip-${{ hashFiles('package/requirements-package.txt') }} - restore-keys: ${{ runner.os }}-pip- + python-version: '3.9' + cache: pip + cache-dependency-path: '**/requirements*.txt' - name: Install Dependencies run: | From a8be53e0dcebad2329360a2d51dfb924ea472415 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Tue, 27 Sep 2022 21:18:52 +0000 Subject: [PATCH 017/583] Add edition_title to metadata details --- plexpy/pmsconnect.py | 11 +++++++++++ plexpy/webserve.py | 1 + 2 files changed, 12 insertions(+) diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py index 30e82f18..e83a0a2d 100644 --- a/plexpy/pmsconnect.py +++ b/plexpy/pmsconnect.py @@ -786,6 +786,7 @@ class PmsConnect(object): 'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'), 'original_title': helpers.get_xml_attr(metadata_main, 'originalTitle'), 'sort_title': helpers.get_xml_attr(metadata_main, 'titleSort'), + 'edition_title': helpers.get_xml_attr(metadata_main, 'editionTitle'), 'media_index': helpers.get_xml_attr(metadata_main, 'index'), 'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'), 'studio': helpers.get_xml_attr(metadata_main, 'studio'), @@ -844,6 +845,7 @@ class PmsConnect(object): 'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'), 'original_title': helpers.get_xml_attr(metadata_main, 'originalTitle'), 'sort_title': helpers.get_xml_attr(metadata_main, 'titleSort'), + 'edition_title': helpers.get_xml_attr(metadata_main, 'editionTitle'), 'media_index': helpers.get_xml_attr(metadata_main, 'index'), 'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'), 'studio': helpers.get_xml_attr(metadata_main, 'studio'), @@ -905,6 +907,7 @@ class PmsConnect(object): 'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'), 'original_title': helpers.get_xml_attr(metadata_main, 'originalTitle'), 'sort_title': helpers.get_xml_attr(metadata_main, 'titleSort'), + 'edition_title': helpers.get_xml_attr(metadata_main, 'editionTitle'), 'media_index': helpers.get_xml_attr(metadata_main, 'index'), 'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'), 'studio': show_details.get('studio', ''), @@ -983,6 +986,7 @@ class PmsConnect(object): 'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'), 'original_title': helpers.get_xml_attr(metadata_main, 'originalTitle'), 'sort_title': helpers.get_xml_attr(metadata_main, 'titleSort'), + 'edition_title': helpers.get_xml_attr(metadata_main, 'editionTitle'), 'media_index': helpers.get_xml_attr(metadata_main, 'index'), 'parent_media_index': parent_media_index, 'studio': show_details.get('studio', ''), @@ -1037,6 +1041,7 @@ class PmsConnect(object): 'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'), 'original_title': helpers.get_xml_attr(metadata_main, 'originalTitle'), 'sort_title': helpers.get_xml_attr(metadata_main, 'titleSort'), + 'edition_title': helpers.get_xml_attr(metadata_main, 'editionTitle'), 'media_index': helpers.get_xml_attr(metadata_main, 'index'), 'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'), 'studio': helpers.get_xml_attr(metadata_main, 'studio'), @@ -1092,6 +1097,7 @@ class PmsConnect(object): 'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'), 'original_title': helpers.get_xml_attr(metadata_main, 'originalTitle'), 'sort_title': helpers.get_xml_attr(metadata_main, 'titleSort'), + 'edition_title': helpers.get_xml_attr(metadata_main, 'editionTitle'), 'media_index': helpers.get_xml_attr(metadata_main, 'index'), 'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'), 'studio': helpers.get_xml_attr(metadata_main, 'studio'), @@ -1150,6 +1156,7 @@ class PmsConnect(object): 'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'), 'original_title': helpers.get_xml_attr(metadata_main, 'originalTitle'), 'sort_title': helpers.get_xml_attr(metadata_main, 'titleSort'), + 'edition_title': helpers.get_xml_attr(metadata_main, 'editionTitle'), 'media_index': helpers.get_xml_attr(metadata_main, 'index'), 'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'), 'studio': helpers.get_xml_attr(metadata_main, 'studio'), @@ -1204,6 +1211,7 @@ class PmsConnect(object): 'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'), 'original_title': helpers.get_xml_attr(metadata_main, 'originalTitle'), 'sort_title': helpers.get_xml_attr(metadata_main, 'titleSort'), + 'edition_title': helpers.get_xml_attr(metadata_main, 'editionTitle'), 'media_index': helpers.get_xml_attr(metadata_main, 'index'), 'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'), 'studio': helpers.get_xml_attr(metadata_main, 'studio'), @@ -1259,6 +1267,7 @@ class PmsConnect(object): 'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'), 'original_title': helpers.get_xml_attr(metadata_main, 'originalTitle'), 'sort_title': helpers.get_xml_attr(metadata_main, 'titleSort'), + 'edition_title': helpers.get_xml_attr(metadata_main, 'editionTitle'), 'media_index': helpers.get_xml_attr(metadata_main, 'index'), 'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'), 'studio': helpers.get_xml_attr(metadata_main, 'studio'), @@ -1314,6 +1323,7 @@ class PmsConnect(object): 'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'), 'original_title': helpers.get_xml_attr(metadata_main, 'originalTitle'), 'sort_title': helpers.get_xml_attr(metadata_main, 'titleSort'), + 'edition_title': helpers.get_xml_attr(metadata_main, 'editionTitle'), 'media_index': helpers.get_xml_attr(metadata_main, 'index'), 'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'), 'studio': helpers.get_xml_attr(metadata_main, 'studio'), @@ -1391,6 +1401,7 @@ class PmsConnect(object): 'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'), 'original_title': helpers.get_xml_attr(metadata_main, 'originalTitle'), 'sort_title': helpers.get_xml_attr(metadata_main, 'titleSort'), + 'edition_title': helpers.get_xml_attr(metadata_main, 'editionTitle'), 'media_index': helpers.get_xml_attr(metadata_main, 'index'), 'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'), 'studio': helpers.get_xml_attr(metadata_main, 'studio'), diff --git a/plexpy/webserve.py b/plexpy/webserve.py index e21b3139..3355d53e 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -5310,6 +5310,7 @@ class WebInterface(object): "Jeremy Podeswa" ], "duration": "2998290", + "edition_title": "", "full_title": "Game of Thrones - The Red Woman", "genres": [ "Action/Adventure", From 15afbe300131d38cb86751c16a29ecdb22bb51b5 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Tue, 27 Sep 2022 23:28:48 +0000 Subject: [PATCH 018/583] Add edition_title notification parameter --- plexpy/common.py | 3 ++- plexpy/notification_handler.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/plexpy/common.py b/plexpy/common.py index b89ef48a..039931f4 100644 --- a/plexpy/common.py +++ b/plexpy/common.py @@ -489,8 +489,9 @@ NOTIFICATION_PARAMETERS = [ 'category': 'Source Metadata Details', 'parameters': [ {'name': 'Media Type', 'type': 'str', 'value': 'media_type', 'description': 'The type of media.', 'example': 'movie, show, season, episode, artist, album, track, clip'}, - {'name': 'Title', 'type': 'str', 'value': 'title', 'description': 'The full title of the item.'}, {'name': 'Library Name', 'type': 'str', 'value': 'library_name', 'description': 'The library name of the item.'}, + {'name': 'Title', 'type': 'str', 'value': 'title', 'description': 'The full title of the item.'}, + {'name': 'Edition Title', 'type': 'str', 'value': 'edition_title', 'description': 'The edition title of the movie.'}, {'name': 'Show Name', 'type': 'str', 'value': 'show_name', 'description': 'The title of the TV show.'}, {'name': 'Season Name', 'type': 'str', 'value': 'season_name', 'description': 'The title of the TV season.'}, {'name': 'Episode Name', 'type': 'str', 'value': 'episode_name', 'description': 'The title of the TV episode.'}, diff --git a/plexpy/notification_handler.py b/plexpy/notification_handler.py index 1fc6ff83..ad774fa6 100644 --- a/plexpy/notification_handler.py +++ b/plexpy/notification_handler.py @@ -1066,8 +1066,9 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m 'machine_id': notify_params['machine_id'], # Source metadata parameters 'media_type': notify_params['media_type'], - 'title': notify_params['full_title'], 'library_name': notify_params['library_name'], + 'title': notify_params['full_title'], + 'edition_title': notify_params['edition_title'], 'show_name': show_name, 'season_name': season_name, 'episode_name': episode_name, From 0f872ab440c186a376d663c2e3394dd70c966f79 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Tue, 27 Sep 2022 23:41:27 +0000 Subject: [PATCH 019/583] Fix broken link on library stats cards --- plexpy/datafactory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plexpy/datafactory.py b/plexpy/datafactory.py index 09b26a45..7027ae81 100644 --- a/plexpy/datafactory.py +++ b/plexpy/datafactory.py @@ -1044,7 +1044,7 @@ class DataFactory(object): 'sh.id, shm.title, shm.grandparent_title, shm.full_title, shm.year, ' \ 'shm.media_index, shm.parent_media_index, ' \ 'sh.rating_key, shm.grandparent_rating_key, shm.thumb, shm.grandparent_thumb, ' \ - 'sh.user, sh.user_id, sh.player, sh.section_id, ' \ + 'sh.user, sh.user_id, sh.player, ' \ 'shm.art, sh.media_type, shm.content_rating, shm.labels, shm.live, shm.guid, ' \ 'MAX(sh.started) AS last_watch ' \ 'FROM library_sections AS ls ' \ From 5faeafedd53b4a9f83485d341fa9aed028e199ab Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Sat, 1 Oct 2022 19:24:57 +0000 Subject: [PATCH 020/583] Fix API 400 response code --- plexpy/api2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plexpy/api2.py b/plexpy/api2.py index add091b1..4d9efbdc 100644 --- a/plexpy/api2.py +++ b/plexpy/api2.py @@ -824,7 +824,7 @@ General optional parameters: if self._api_result_type == 'success' and not self._api_response_code: self._api_response_code = 200 - elif self._api_result_type == 'error' and not self._api_response_code: + elif self._api_result_type == 'error' and self._api_response_code != 500: self._api_response_code = 400 if not self._api_response_code: From a9949a07da6d8dc09d8914e06290a22a847a9146 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Mon, 10 Oct 2022 18:05:14 +0000 Subject: [PATCH 021/583] Update filterer * Clear condition operator if type changes --- data/interfaces/default/js/filterer.jquery.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/data/interfaces/default/js/filterer.jquery.js b/data/interfaces/default/js/filterer.jquery.js index 16458f13..c17646fe 100644 --- a/data/interfaces/default/js/filterer.jquery.js +++ b/data/interfaces/default/js/filterer.jquery.js @@ -1,10 +1,10 @@ !function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return e[r].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var n={};return t.m=e,t.c=n,t.p="/filterer/",t(0)}(function(e){for(var t in e)if(Object.prototype.hasOwnProperty.call(e,t))switch(typeof e[t]){case"function":break;case"object":e[t]=function(t){var n=t.slice(1),r=e[t[0]];return function(e,t,o){r.apply(this,[e,t,o].concat(n))}}(e[t]);break;default:e[t]=e[e[t]]}return e}([function(e,t,n){n(188),e.exports=n(94)},function(e,t,n){"use strict";function r(e,t,n,r,i,a,s,u){if(o(t),!e){var l;if(void 0===t)l=new Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.");else{var c=[n,r,i,a,s,u],p=0;l=new Error(t.replace(/%s/g,function(){return c[p++]})),l.name="Invariant Violation"}throw l.framesToPop=1,l}}var o=function(e){};e.exports=r},function(e,t,n){"use strict";var r=n(8),o=r;e.exports=o},function(e,t){"use strict";function n(e){for(var t=arguments.length-1,n="Minified React error #"+e+"; visit http://facebook.github.io/react/docs/error-decoder.html?invariant="+e,r=0;r1?function(){var i=o?o.concat():[];return n=t?n||this:this,i.push.apply(i,arguments)0?r({},e[n]):e[n],t[n])})(o),e))}),P=t(function(e,t,n){var r,o,i;return r=t[0],o=N.call(t,1),o.length>0?(e[r]=null!=(i=e[r])?i:{},P(e[r],o,n)):(e[r]=n,e)}),k=function(e){return d(function(t){return d(function(e){return e[t]})(e)})(f(e[0]))},S=t(function(e,n,r){var o;return(o=t(function(e,t,n,r,i){return s(function(i){var a,s;return a=i[0],s=i[1],n1){for(var v=Array(m),g=0;g1){for(var b=Array(y),C=0;C]/;e.exports=r},function(e,t,n){"use strict";var r,o=n(7),i=n(38),a=/^[ \r\n\t\f]/,s=/<(!--|link|noscript|meta|script|style)[ \r\n\t\f\/>]/,u=n(46),l=u(function(e,t){if(e.namespaceURI!==i.svg||"innerHTML"in e)e.innerHTML=t;else{r=r||document.createElement("div"),r.innerHTML=""+t+"";for(var n=r.firstChild;n.firstChild;)e.appendChild(n.firstChild)}});if(o.canUseDOM){var c=document.createElement("div");c.innerHTML=" ",""===c.innerHTML&&(l=function(e,t){if(e.parentNode&&e.parentNode.replaceChild(e,e),a.test(t)||"<"===t[0]&&s.test(t)){e.innerHTML=String.fromCharCode(65279)+t;var n=e.firstChild;1===n.data.length?e.removeChild(n):n.deleteData(0,1)}else e.innerHTML=t}),c=null}e.exports=l},function(e,t,n){e.exports=n(208)()},function(e,t){function n(e){return e&&e.__esModule?e:{default:e}}e.exports=n,e.exports.__esModule=!0,e.exports.default=e.exports},function(e,t){"use strict";function n(e,t){return e===t?0!==e||0!==t||1/e===1/t:e!==e&&t!==t}function r(e,t){if(n(e,t))return!0;if("object"!=typeof e||null===e||"object"!=typeof t||null===t)return!1;var r=Object.keys(e),i=Object.keys(t);if(r.length!==i.length)return!1;for(var a=0;a-1?void 0:a("96",e),!l.plugins[n]){t.extractEvents?void 0:a("97",e),l.plugins[n]=t;var r=t.eventTypes;for(var i in r)o(r[i],t,i)?void 0:a("98",i,e)}}}function o(e,t,n){l.eventNameDispatchConfigs.hasOwnProperty(n)?a("99",n):void 0,l.eventNameDispatchConfigs[n]=e;var r=e.phasedRegistrationNames;if(r){for(var o in r)if(r.hasOwnProperty(o)){var s=r[o];i(s,t,n)}return!0}return!!e.registrationName&&(i(e.registrationName,t,n),!0)}function i(e,t,n){l.registrationNameModules[e]?a("100",e):void 0,l.registrationNameModules[e]=t,l.registrationNameDependencies[e]=t.eventTypes[n].dependencies}var a=n(3),s=(n(1),null),u={},l={plugins:[],eventNameDispatchConfigs:{},registrationNameModules:{},registrationNameDependencies:{},possibleRegistrationNames:null,injectEventPluginOrder:function(e){s?a("101"):void 0,s=Array.prototype.slice.call(e),r()},injectEventPluginsByName:function(e){var t=!1;for(var n in e)if(e.hasOwnProperty(n)){var o=e[n];u.hasOwnProperty(n)&&u[n]===o||(u[n]?a("102",n):void 0,u[n]=o,t=!0)}t&&r()},getPluginModuleForEvent:function(e){var t=e.dispatchConfig;if(t.registrationName)return l.registrationNameModules[t.registrationName]||null;if(void 0!==t.phasedRegistrationNames){var n=t.phasedRegistrationNames;for(var r in n)if(n.hasOwnProperty(r)){var o=l.registrationNameModules[n[r]];if(o)return o}}return null},_resetEventPlugins:function(){s=null;for(var e in u)u.hasOwnProperty(e)&&delete u[e];l.plugins.length=0;var t=l.eventNameDispatchConfigs;for(var n in t)t.hasOwnProperty(n)&&delete t[n];var r=l.registrationNameModules;for(var o in r)r.hasOwnProperty(o)&&delete r[o]}};e.exports=l},function(e,t,n){"use strict";function r(e){return"topMouseUp"===e||"topTouchEnd"===e||"topTouchCancel"===e}function o(e){return"topMouseMove"===e||"topTouchMove"===e}function i(e){return"topMouseDown"===e||"topTouchStart"===e}function a(e,t,n,r){var o=e.type||"unknown-event";e.currentTarget=g.getNodeFromInstance(r),t?m.invokeGuardedCallbackWithCatch(o,n,e):m.invokeGuardedCallback(o,n,e),e.currentTarget=null}function s(e,t){var n=e._dispatchListeners,r=e._dispatchInstances;if(Array.isArray(n))for(var o=0;o0&&r.length<20?n+" (keys: "+r.join(", ")+")":n}function i(e,t){var n=s.get(e);if(!n){return null}return n}var a=n(3),s=(n(12),n(26)),u=(n(9),n(10)),l=(n(1),n(2),{isMounted:function(e){var t=s.get(e);return!!t&&!!t._renderedComponent},enqueueCallback:function(e,t,n){l.validateCallback(t,n);var o=i(e);return o?(o._pendingCallbacks?o._pendingCallbacks.push(t):o._pendingCallbacks=[t],void r(o)):null},enqueueCallbackInternal:function(e,t){e._pendingCallbacks?e._pendingCallbacks.push(t):e._pendingCallbacks=[t],r(e)},enqueueForceUpdate:function(e){var t=i(e,"forceUpdate");t&&(t._pendingForceUpdate=!0,r(t))},enqueueReplaceState:function(e,t,n){var o=i(e,"replaceState");o&&(o._pendingStateQueue=[t],o._pendingReplaceState=!0,void 0!==n&&null!==n&&(l.validateCallback(n,"replaceState"),o._pendingCallbacks?o._pendingCallbacks.push(n):o._pendingCallbacks=[n]),r(o))},enqueueSetState:function(e,t){var n=i(e,"setState");if(n){var o=n._pendingStateQueue||(n._pendingStateQueue=[]);o.push(t),r(n)}},enqueueElementInternal:function(e,t,n){e._pendingElement=t,e._context=n,r(e)},validateCallback:function(e,t){e&&"function"!=typeof e?a("122",t,o(e)):void 0}});e.exports=l},function(e,t){"use strict";var n=function(e){return"undefined"!=typeof MSApp&&MSApp.execUnsafeLocalFunction?function(t,n,r,o){MSApp.execUnsafeLocalFunction(function(){return e(t,n,r,o)})}:e};e.exports=n},function(e,t){"use strict";function n(e){var t,n=e.keyCode;return"charCode"in e?(t=e.charCode,0===t&&13===n&&(t=13)):t=n,t>=32||13===t?t:0}e.exports=n},function(e,t){"use strict";function n(e){var t=this,n=t.nativeEvent;if(n.getModifierState)return n.getModifierState(e);var r=o[e];return!!r&&!!n[r]}function r(e){return n}var o={Alt:"altKey",Control:"ctrlKey",Meta:"metaKey",Shift:"shiftKey"};e.exports=r},function(e,t){"use strict";function n(e){var t=e.target||e.srcElement||window;return t.correspondingUseElement&&(t=t.correspondingUseElement),3===t.nodeType?t.parentNode:t}e.exports=n},function(e,t,n){"use strict";function r(e,t){if(!i.canUseDOM||t&&!("addEventListener"in document))return!1;var n="on"+e,r=n in document;if(!r){var a=document.createElement("div");a.setAttribute(n,"return;"),r="function"==typeof a[n]}return!r&&o&&"wheel"===e&&(r=document.implementation.hasFeature("Events.wheel","3.0")),r}var o,i=n(7);i.canUseDOM&&(o=document.implementation&&document.implementation.hasFeature&&document.implementation.hasFeature("","")!==!0),e.exports=r},function(e,t){"use strict";function n(e,t){var n=null===e||e===!1,r=null===t||t===!1;if(n||r)return n===r;var o=typeof e,i=typeof t;return"string"===o||"number"===o?"string"===i||"number"===i:"object"===i&&e.type===t.type&&e.key===t.key}e.exports=n},function(e,t,n){"use strict";var r=(n(4),n(8)),o=(n(2),r);e.exports=o},function(e,t,n){(function(){function t(e,t){var n={}.hasOwnProperty;for(var r in t)n.call(t,r)&&(e[r]=t[r]);return e}function r(e,t){for(var n=-1,r=t.length>>>0;++n0&&!this.props.hideResetButton?T({className:"react-selectize-reset-button-container",onClick:function(e){return function(){return a.props.onValuesChange([],function(){return a.props.onSearchChange("",function(){return a.highlightAndFocus()})})}(),j(e)}},this.props.renderResetButton()):void 0,T({className:"react-selectize-toggle-button-container",onMouseDown:function(e){return a.props.open?a.onOpenChange(!1,function(){}):a.props.onAnchorChange(p(a.props.values),function(){return a.onOpenChange(!0,function(){})}),j(e)}},this.props.renderToggleButton({open:this.props.open,flipped:r}))),D((o=t({},this.props),o.ref="dropdownMenu",o.className=B((i={"react-selectize":1},i[this.props.className+""]=1,i)),o.theme=this.props.theme,o.scrollLock=this.props.scrollLock,o.onScrollChange=this.props.onScrollChange,o.bottomAnchor=function(){return M(a.refs.control)},o.tetherProps=(i=t({},this.props.tetherProps),i.target=function(){return M(a.refs.control)},i),o.highlightedUid=this.props.highlightedUid,o.onHighlightedUidChange=this.props.onHighlightedUidChange,o.onOptionClick=function(t){a.selectHighlightedUid(e,function(){})},o)))},handleKeydown:function(e,t){var n,o,i,a=this;switch(n=e.anchorIndex,t.persist(),t.which){case 8:if(this.props.search.length>0||n===-1)return;!function(){var e,t,r,o;return e=n,t=n-1<0?void 0:a.props.values[n-1],r=a.props.values[n],a.props.onValuesChange(null!=(o=m(function(e){return a.isEqualToObject(e,r)})(a.props.values))?o:[],function(){return function(){return function(e){return"undefined"==typeof s(function(e){return a.isEqualToObject(e,r)},a.props.values)?a.props.restoreOnBackspace?a.props.onSearchChange(a.props.restoreOnBackspace(r),function(){return e(!0)}):e(!0):e(!1)}}()(function(r){if(r&&(a.highlightAndScrollToSelectableOption(a.props.firstOptionIndexToHighlight(a.props.options),1),n===e&&("undefined"==typeof t||s(function(e){return a.isEqualToObject(e,t)})(a.props.values))))return a.props.onAnchorChange(t,function(){})})})}(),j(t);break;case 27:!function(){return a.props.open?function(e){return a.onOpenChange(!1,e)}:function(e){return a.props.onValuesChange([],e)}}()(function(){return a.props.onSearchChange("",function(){return a.focusOnInput()})})}if(this.props.open&&r(t.which,[13].concat(this.props.delimiters))&&!(null!=t&&t.metaKey||null!=t&&t.ctrlKey||null!=t&&t.shiftKey)&&(o=this.selectHighlightedUid(n,function(e){if("undefined"==typeof e)return a.props.onKeyboardSelectionFailed(t.which)}),o&&this.props.cancelKeyboardEventOnSelection))return j(t);if(0===this.props.search.length)switch(t.which){case 37:this.props.onAnchorChange(n-1<0||t.metaKey?void 0:this.props.values[_(n-1,0,this.props.values.length-1)],function(){});break;case 39:this.props.onAnchorChange(t.metaKey?p(this.props.values):this.props.values[_(n+1,0,this.props.values.length-1)],function(){})}switch(t.which){case 38:return this.props.onScrollLockChange(!0),i=function(){switch(!1){case"undefined"!=typeof this.props.highlightedUid:return 0;default:return-1+this.optionIndexFromUid(this.props.highlightedUid)}}.call(this),this.highlightAndScrollToSelectableOption(i,-1,function(e){if(!e)return a.highlightAndScrollToSelectableOption(a.props.options.length-1,-1)});case 40:return this.props.onScrollLockChange(!0),i=function(){switch(!1){case"undefined"!=typeof this.props.highlightedUid:return 0;default:return 1+this.optionIndexFromUid(this.props.highlightedUid)}}.call(this),this.highlightAndScrollToSelectableOption(i,1,function(e){if(!e)return a.highlightAndScrollToSelectableOption(0,1)})}},componentDidMount:function(){this.props.autofocus&&this.focus(),this.props.open&&this.highlightAndFocus()},componentDidUpdate:function(e){this.props.open&&!e.open&&void 0===this.props.highlightedUid&&this.highlightAndFocus(),!this.props.open&&e.open&&this.props.onHighlightedUidChange(void 0,function(){})},componentWillReceiveProps:function(e){"undefined"!=typeof this.props.disabled&&this.props.disabled!==!1||"undefined"==typeof e.disabled||e.disabled!==!0||this.onOpenChange(!1,function(){})},optionIndexFromUid:function(e){var t=this;return u(function(n){return w(e,t.props.uid(n))})(this.props.options)},closeDropdown:function(e){var t=this;this.onOpenChange(!1,function(){return t.props.onAnchorChange(p(t.props.values),e)})},blur:function(){this.refs.search.blur()},focus:function(){this.refs.search.focus()},focusOnInput:function(){var e;e=M(this.refs.search),e!==document.activeElement&&(this.focusLock=!0,e.focus(),e.value=e.value)},highlightAndFocus:function(){this.highlightAndScrollToSelectableOption(this.props.firstOptionIndexToHighlight(this.props.options),1),this.focusOnInput()},highlightAndScrollToOption:function(e,t){null==t&&(t=function(){}),this.refs.dropdownMenu.highlightAndScrollToOption(e,t)},highlightAndScrollToSelectableOption:function(e,t,n){var r=this;null==n&&(n=function(){}),function(){return r.props.open?function(e){return e()}:function(e){return r.onOpenChange(!0,e)}}()(function(){return r.refs.dropdownMenu.highlightAndScrollToSelectableOption(e,t,n)})},isEqualToObject:function(){return w(this.props.uid(arguments[0]),this.props.uid(arguments[1]))},onOpenChange:function(e,t){return this.props.onOpenChange(!this.props.disabled&&e,t)},selectHighlightedUid:function(e,t){var n,r,o=this;return void 0===this.props.highlightedUid?(t(),!1):(n=this.optionIndexFromUid(this.props.highlightedUid),"number"!=typeof n?(t(),!1):(r=this.props.options[n],function(){return o.props.onValuesChange(f(function(e){return o.props.values[e]},function(){var t,n,r=[];for(t=0,n=e;t<=n;++t)r.push(t);return r}()).concat([r],f(function(e){return o.props.values[e]},function(){var t,n,r=[];for(t=e+1,n=this.props.values.length;t1)for(var n=1;n.":"function"==typeof t?" Instead of passing a class like Foo, pass React.createElement(Foo) or .":null!=t&&void 0!==t.props?" This may be caused by unintentionally loading two independent copies of React.":"");var a,s=v.createElement(F,{child:t});if(e){var u=w.get(e);a=u._processChildContext(u._context)}else a=P;var c=f(n);if(c){var p=c._currentElement,h=p.props.child;if(N(h,t)){var m=c._renderedComponent.getPublicInstance(),g=r&&function(){r.call(m)};return j._updateRootComponent(c,s,a,n,g),m}j.unmountComponentAtNode(n)}var y=o(n),b=y&&!!i(y),C=l(n),_=b&&!c&&!C,E=j._renderNewRootComponent(s,n,_,a)._renderedComponent.getPublicInstance();return r&&r.call(E),E},render:function(e,t,n){return j._renderSubtreeIntoContainer(null,e,t,n)},unmountComponentAtNode:function(e){c(e)?void 0:d("40");var t=f(e);if(!t){l(e),1===e.nodeType&&e.hasAttribute(A);return!1}return delete L[t._instance.rootID],O.batchedUpdates(u,t,e,!1),!0},_mountImageIntoNode:function(e,t,n,i,a){if(c(t)?void 0:d("41"),i){var s=o(t);if(E.canReuseMarkup(e,s))return void y.precacheNode(n,s);var u=s.getAttribute(E.CHECKSUM_ATTR_NAME);s.removeAttribute(E.CHECKSUM_ATTR_NAME);var l=s.outerHTML;s.setAttribute(E.CHECKSUM_ATTR_NAME,u);var p=e,f=r(p,l),m=" (client) "+p.substring(f-20,f+20)+"\n (server) "+l.substring(f-20,f+20);t.nodeType===D?d("42",m):void 0}if(t.nodeType===D?d("43"):void 0,a.useCreateElement){for(;t.lastChild;)t.removeChild(t.lastChild);h.insertTreeBefore(t,e,null)}else S(t,e),y.precacheNode(n,t.firstChild)}};e.exports=j},function(e,t,n){"use strict";var r=n(3),o=n(20),i=(n(1),{HOST:0,COMPOSITE:1,EMPTY:2,getType:function(e){return null===e||e===!1?i.EMPTY:o.isValidElement(e)?"function"==typeof e.type?i.COMPOSITE:i.HOST:void r("26",e)}});e.exports=i},function(e,t){"use strict";var n={currentScrollLeft:0,currentScrollTop:0,refreshScrollValues:function(e){n.currentScrollLeft=e.x,n.currentScrollTop=e.y}};e.exports=n},function(e,t,n){"use strict";function r(e,t){return null==t?o("30"):void 0,null==e?t:Array.isArray(e)?Array.isArray(t)?(e.push.apply(e,t),e):(e.push(t),e):Array.isArray(t)?[e].concat(t):[e,t]}var o=n(3);n(1);e.exports=r},function(e,t){"use strict";function n(e,t,n){Array.isArray(e)?e.forEach(t,n):e&&t.call(n,e)}e.exports=n},function(e,t,n){"use strict";function r(e){for(var t;(t=e._renderedNodeType)===o.COMPOSITE;)e=e._renderedComponent;return t===o.HOST?e._renderedComponent:t===o.EMPTY?null:void 0}var o=n(74);e.exports=r},function(e,t,n){"use strict";function r(){return!i&&o.canUseDOM&&(i="textContent"in document.documentElement?"textContent":"innerText"),i}var o=n(7),i=null;e.exports=r},function(e,t,n){"use strict";function r(e){if(e){var t=e.getName();if(t)return" Check the render method of `"+t+"`."}return""}function o(e){return"function"==typeof e&&"undefined"!=typeof e.prototype&&"function"==typeof e.prototype.mountComponent&&"function"==typeof e.prototype.receiveComponent}function i(e,t){var n;if(null===e||e===!1)n=l.create(i);else if("object"==typeof e){var s=e,u=s.type;if("function"!=typeof u&&"string"!=typeof u){var f="";f+=r(s._owner),a("130",null==u?u:typeof u,f)}"string"==typeof s.type?n=c.createInternalComponent(s):o(s.type)?(n=new s.type(s),n.getHostNode||(n.getHostNode=n.getNativeNode)):n=new p(s)}else"string"==typeof e||"number"==typeof e?n=c.createInstanceForText(e):a("131",typeof e);return n._mountIndex=0,n._mountImage=null,n}var a=n(3),s=n(4),u=n(137),l=n(69),c=n(71),p=(n(221),n(1),n(2),function(e){this.construct(e)});s(p.prototype,u,{_instantiateReactComponent:i}),e.exports=i},function(e,t){"use strict";function n(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return"input"===t?!!r[e.type]:"textarea"===t}var r={color:!0,date:!0,datetime:!0,"datetime-local":!0,email:!0,month:!0,number:!0,password:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0};e.exports=n},function(e,t,n){"use strict";var r=n(7),o=n(32),i=n(33),a=function(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&3===n.nodeType)return void(n.nodeValue=t)}e.textContent=t};r.canUseDOM&&("textContent"in document.documentElement||(a=function(e,t){return 3===e.nodeType?void(e.nodeValue=t):void i(e,o(t))})),e.exports=a},function(e,t,n){"use strict";function r(e,t){return e&&"object"==typeof e&&null!=e.key?l.escape(e.key):t.toString(36)}function o(e,t,n,i){var f=typeof e;if("undefined"!==f&&"boolean"!==f||(e=null),null===e||"string"===f||"number"===f||"object"===f&&e.$$typeof===s)return n(i,e,""===t?c+r(e,0):t),1;var d,h,m=0,v=""===t?c:t+p;if(Array.isArray(e))for(var g=0;gc){for(var t=0,n=s.length-l;t-1}).map(function(e,t){return l.default.createElement("option",{key:t,value:e.name},e.name)})}},{key:"getValues",value:function(e){return e?e.map(function(e){return{label:e,value:e}}):[]}},{key:"render",value:function(){var e=this,t=this.props.parameters.find(function(t){return t.value===e.props.condition.parameter});return this.props.condition.type=t?t.type:null,l.default.createElement("div",{className:this.props.classes.filterLineRow},l.default.createElement("div",{className:this.props.classes.filterLineParameter},l.default.createElement("select",{className:this.props.classes.filterLineInput,name:"parameter",value:this.props.condition.parameter,onChange:this.handleInputChange},l.default.createElement("option",{value:""},"-- Parameter --"),this.getCoefficients(this.props.parameters))),l.default.createElement("div",{className:this.props.classes.filterLineOperator,style:{"padding-left":0,"padding-right":0}},l.default.createElement("select",{className:this.props.classes.filterLineInput,name:"operator",value:this.props.condition.operator,onChange:this.handleInputChange},l.default.createElement("option",{disabled:!0,value:""},"-- Operator --"),this.getOperators(this.props.operators,this.props.parameters.find(function(t){return t.value===e.props.condition.parameter})))),l.default.createElement("div",{className:this.props.classes.filterLineValue},l.default.createElement(c.MultiSelect,{style:{width:"100%"},placeholder:"-- Value --",theme:"bootstrap3",values:this.getValues(this.props.condition.value),onValuesChange:this.handleValueChange,uid:function(e){return e.value},restoreOnBackspace:function(e){return e.label.toString()},createFromSearch:function(t,n,r){return e.labels=n.map(function(e){return e.label}),0===r.trim().length||e.labels.indexOf(r.trim())!==-1?null:{label:r.trim(),value:r.trim()}},renderNoResultsFound:function(e,t){return l.default.createElement("div",{className:"no-results-found"},function(){return 0===t.trim().length?"Enter a new value":e.map(function(e){return e.label}).indexOf(t.trim())!==-1?"Value already exists":void 0}())}})))}}]),t}(u.Component);t.default=p},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var s=function(){function e(e,t){for(var n=0;n1){var t=this.state.conditions;t.splice(e,1),this.setState({conditions:t})}}},{key:"componentDidUpdate",value:function(e,t){t!==this.state&&this.props.config.updateConditions(this.state.conditions)}},{key:"render",value:function(){var e=this,t=this.state.conditions.map(function(t,n){return l.default.createElement("div",{key:n},l.default.createElement(d.default,{index:n,classes:e.props.config.classes,addCondition:e.addCondition,removeCondition:e.removeCondition}),l.default.createElement(p.default,{parameters:e.props.config.parameters,operators:e.props.config.operators,condition:t,index:n,classes:e.props.config.classes,onChange:e.updateCondition}))});return l.default.createElement("div",{className:"form-horizontal"},t)}}]),t}(u.Component);t.default=h},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}var o=n(5),i=r(o),a=n(13),s=r(a),u=n(93),l=r(u),c=window.$;if(c.fn.filterer=function(e){e.operators=[{name:"contains",types:["string","str"]},{name:"does not contain",types:["string","str"]},{name:"is",types:["string","str","number","int","float"]},{name:"is not",types:["string","str","number","int","float"]},{name:"begins with",types:["string","str"]},{name:"does not begin with",types:["string","str"]},{name:"ends with",types:["string","str"]},{name:"does not end with",types:["string","str"]},{name:"is greater than",types:["number","int","float"]},{name:"is less than",types:["number","int","float"]}],e.classes=Object.assign({plusIcon:"fa fa-fw fa-plus",minusIcon:"fa fa-fw fa-minus",filterLineRow:"form-group",filterLineParameter:"col-sm-4",filterLineOperator:"col-sm-3",filterLineValue:"col-sm-5",filterLineInput:"form-control",filterLineLabelRow:"row",filterLineLabelCondition:"col-sm-10",filterLineLabelControls:"col-sm-2 text-right"},e.classes),this.each(function(){s.default.render(i.default.createElement(l.default,{id:"filterer",config:e}),this)})},window.wcomartin_filterer_demo){var p={parameters:[{name:"Title",type:"string",value:"title"},{name:"Year",type:"number",value:"year"}],conditions:[{parameter:"year",operator:"is",value:[2017]}]};p.updateConditions=function(e){console.log(JSON.stringify(e))},c("#root").filterer(p)}},function(e,t){e.exports=function(){for(var e=arguments.length,t=[],n=0;n":a.innerHTML="<"+e+">",s[e]=!a.firstChild),s[e]?f[e]:null}var o=n(7),i=n(1),a=o.canUseDOM?document.createElement("div"):null,s={},u=[1,'"],l=[1,"","
    "],c=[3,"","
    "],p=[1,'',""],f={"*":[1,"?
    ","
    "],area:[1,"",""],col:[2,"","
    "],legend:[1,"
    ","
    "],param:[1,"",""],tr:[2,"","
    "],optgroup:u,option:u,caption:l,colgroup:l,tbody:l,tfoot:l,thead:l,td:c,th:c},d=["circle","clipPath","defs","ellipse","g","image","line","linearGradient","mask","path","pattern","polygon","polyline","radialGradient","rect","stop","text","tspan"];d.forEach(function(e){f[e]=p,s[e]=!0}),e.exports=r},function(e,t){"use strict";function n(e){return e.Window&&e instanceof e.Window?{x:e.pageXOffset||e.document.documentElement.scrollLeft,y:e.pageYOffset||e.document.documentElement.scrollTop}:{x:e.scrollLeft,y:e.scrollTop}}e.exports=n},function(e,t){"use strict";function n(e){return e.replace(r,"-$1").toLowerCase()}var r=/([A-Z])/g;e.exports=n},function(e,t,n){"use strict";function r(e){return o(e).replace(i,"-ms-")}var o=n(108),i=/^ms-/;e.exports=r},function(e,t){"use strict";function n(e){var t=e?e.ownerDocument||e:document,n=t.defaultView||window;return!(!e||!("function"==typeof n.Node?e instanceof n.Node:"object"==typeof e&&"number"==typeof e.nodeType&&"string"==typeof e.nodeName))}e.exports=n},function(e,t,n){"use strict";function r(e){return o(e)&&3==e.nodeType}var o=n(110);e.exports=r},function(e,t){"use strict";function n(e){var t={};return function(n){return t.hasOwnProperty(n)||(t[n]=e.call(this,n)),t[n]}}e.exports=n},function(e,t){function n(e,t){var n,r=function(o){return e.length>1?function(){var i=o?o.concat():[];return n=t?n||this:this,i.push.apply(i,arguments)1?function(){var i=o?o.concat():[];return n=t?n||this:this,i.push.apply(i,arguments)>>0;++n=0;--r)o=n[r],t=e(o,t);return t}),k=n(function(e,t){return P(e,t[t.length-1],t.slice(0,-1))}),S=n(function(e,t){var n,r,o;for(n=[],r=t;null!=(o=e(r));)n.push(o[0]),r=o[1];return n}),N=function(e){return[].concat.apply([],e)},M=n(function(e,t){var n;return[].concat.apply([],function(){ -var r,o,i,a=[];for(r=0,i=(o=t).length;rt?1:ee(n)?1:e(t)t&&(t=i);return t},Q=function(e){var t,n,r,o,i;for(t=e[0],n=0,o=(r=e.slice(1)).length;ne(n)&&(n=a);return n}),Z=n(function(e,t){var n,r,o,i,a;for(n=t[0],r=0,i=(o=t.slice(1)).length;r1?function(){var i=o?o.concat():[];return n=t?n||this:this,i.push.apply(i,arguments)t?e:t}),o=n(function(e,t){return e0?1:0},u=n(function(e,t){return~~(e/t)}),l=n(function(e,t){return e%t}),c=n(function(e,t){return Math.floor(e/t)}),p=n(function(e,t){var n;return(e%(n=t)+n)%n}),f=function(e){return 1/e},d=Math.PI,h=2*d,m=Math.exp,v=Math.sqrt,g=Math.log,y=n(function(e,t){return Math.pow(e,t)}),b=Math.sin,C=Math.tan,_=Math.cos,w=Math.asin,E=Math.acos,T=Math.atan,x=n(function(e,t){return Math.atan2(e,t)}),O=function(e){return~~e},P=Math.round,k=Math.ceil,S=Math.floor,N=function(e){return e!==e},M=function(e){return e%2===0},A=function(e){return e%2!==0},I=n(function(e,t){var n;for(e=Math.abs(e),t=Math.abs(t);0!==t;)n=e%t,e=t,t=n;return e}),D=n(function(e,t){return Math.abs(Math.floor(e/I(e,t)*t))}),e.exports={max:r,min:o,negate:i,abs:a,signum:s,quot:u,rem:l,div:c,mod:p,recip:f,pi:d,tau:h,exp:m,sqrt:v,ln:g,pow:y,sin:b,tan:C,cos:_,acos:E,asin:w,atan:T,atan2:x,truncate:O,round:P,ceiling:k,floor:S,isItNaN:N,even:M,odd:A,gcd:I,lcm:D}},function(e,t){function n(e,t){var n,r=function(o){return e.length>1?function(){var i=o?o.concat():[];return n=t?n||this:this,i.push.apply(i,arguments)1?function(){var i=o?o.concat():[];return n=t?n||this:this,i.push.apply(i,arguments)1?n:n.toLowerCase())}).replace(/^([A-Z]+)/,function(e,t){return t.length>1?t+"-":t.toLowerCase()})},e.exports={split:r,join:o,lines:i,unlines:a,words:s,unwords:u,chars:l,unchars:c,reverse:p,repeat:f,capitalize:d,camelize:h,dasherize:m}},[227,113,114,116,117,115],function(e,t,n){"use strict";function r(e){var t=new o(o._61);return t._81=1,t._65=e,t}var o=n(61);e.exports=o;var i=r(!0),a=r(!1),s=r(null),u=r(void 0),l=r(0),c=r("");o.resolve=function(e){if(e instanceof o)return e;if(null===e)return s;if(void 0===e)return u;if(e===!0)return i;if(e===!1)return a;if(0===e)return l;if(""===e)return c;if("object"==typeof e||"function"==typeof e)try{var t=e.then;if("function"==typeof t)return new o(t.bind(e))}catch(e){return new o(function(t,n){n(e)})}return r(e)},o.all=function(e){var t=Array.prototype.slice.call(e);return new o(function(e,n){function r(a,s){if(s&&("object"==typeof s||"function"==typeof s)){if(s instanceof o&&s.then===o.prototype.then){for(;3===s._81;)s=s._65;return 1===s._81?r(a,s._65):(2===s._81&&n(s._65),void s.then(function(e){r(a,e)},n))}var u=s.then;if("function"==typeof u){var l=new o(u.bind(s));return void l.then(function(e){r(a,e)},n)}}t[a]=s,0===--i&&e(t)}if(0===t.length)return e([]);for(var i=t.length,a=0;a8&&_<=11),T=32,x=String.fromCharCode(T),O={beforeInput:{phasedRegistrationNames:{bubbled:"onBeforeInput",captured:"onBeforeInputCapture"},dependencies:["topCompositionEnd","topKeyPress","topTextInput","topPaste"]},compositionEnd:{phasedRegistrationNames:{bubbled:"onCompositionEnd",captured:"onCompositionEndCapture"},dependencies:["topBlur","topCompositionEnd","topKeyDown","topKeyPress","topKeyUp","topMouseDown"]},compositionStart:{phasedRegistrationNames:{bubbled:"onCompositionStart",captured:"onCompositionStartCapture"},dependencies:["topBlur","topCompositionStart","topKeyDown","topKeyPress","topKeyUp","topMouseDown"]},compositionUpdate:{phasedRegistrationNames:{bubbled:"onCompositionUpdate",captured:"onCompositionUpdateCapture"},dependencies:["topBlur","topCompositionUpdate","topKeyDown","topKeyPress","topKeyUp","topMouseDown"]}},P=!1,k=null,S={eventTypes:O,extractEvents:function(e,t,n,r){return[l(e,t,n,r),f(e,t,n,r)]}};e.exports=S},function(e,t,n){"use strict";var r=n(64),o=n(7),i=(n(9),n(102),n(179)),a=n(109),s=n(112),u=(n(2),s(function(e){return a(e)})),l=!1,c="cssFloat";if(o.canUseDOM){var p=document.createElement("div").style;try{p.font=""}catch(e){l=!0}void 0===document.documentElement.style.cssFloat&&(c="styleFloat")}var f={createMarkupForStyles:function(e,t){var n="";for(var r in e)if(e.hasOwnProperty(r)){var o=e[r];null!=o&&(n+=u(r)+":",n+=i(r,o,t)+";")}return n||null},setValueForStyles:function(e,t,n){var o=e.style;for(var a in t)if(t.hasOwnProperty(a)){var s=i(a,t[a],n);if("float"!==a&&"cssFloat"!==a||(a=c),s)o[a]=s;else{var u=l&&r.shorthandPropertyExpansions[a];if(u)for(var p in u)o[p]="";else o[a]=""}}}};e.exports=f},function(e,t,n){"use strict";function r(e){var t=e.nodeName&&e.nodeName.toLowerCase();return"select"===t||"input"===t&&"file"===e.type}function o(e){var t=T.getPooled(k.change,N,e,x(e));C.accumulateTwoPhaseDispatches(t),E.batchedUpdates(i,t)}function i(e){b.enqueueEvents(e),b.processEventQueue(!1)}function a(e,t){S=e,N=t,S.attachEvent("onchange",o)}function s(){S&&(S.detachEvent("onchange",o),S=null,N=null)}function u(e,t){if("topChange"===e)return t}function l(e,t,n){"topFocus"===e?(s(),a(t,n)):"topBlur"===e&&s()}function c(e,t){S=e,N=t,M=e.value,A=Object.getOwnPropertyDescriptor(e.constructor.prototype,"value"),Object.defineProperty(S,"value",R),S.attachEvent?S.attachEvent("onpropertychange",f):S.addEventListener("propertychange",f,!1)}function p(){S&&(delete S.value,S.detachEvent?S.detachEvent("onpropertychange",f):S.removeEventListener("propertychange",f,!1),S=null,N=null,M=null,A=null)}function f(e){if("value"===e.propertyName){var t=e.srcElement.value;t!==M&&(M=t,o(e))}}function d(e,t){if("topInput"===e)return t}function h(e,t,n){"topFocus"===e?(p(),c(t,n)):"topBlur"===e&&p()}function m(e,t){if(("topSelectionChange"===e||"topKeyUp"===e||"topKeyDown"===e)&&S&&S.value!==M)return M=S.value,N}function v(e){return e.nodeName&&"input"===e.nodeName.toLowerCase()&&("checkbox"===e.type||"radio"===e.type)}function g(e,t){if("topClick"===e)return t}function y(e,t){if(null!=e){var n=e._wrapperState||t._wrapperState;if(n&&n.controlled&&"number"===t.type){var r=""+t.value;t.getAttribute("value")!==r&&t.setAttribute("value",r)}}}var b=n(24),C=n(25),_=n(7),w=n(6),E=n(10),T=n(11),x=n(49),O=n(50),P=n(81),k={change:{phasedRegistrationNames:{bubbled:"onChange",captured:"onChangeCapture"},dependencies:["topBlur","topChange","topClick","topFocus","topInput","topKeyDown","topKeyUp","topSelectionChange"]}},S=null,N=null,M=null,A=null,I=!1;_.canUseDOM&&(I=O("change")&&(!document.documentMode||document.documentMode>8));var D=!1;_.canUseDOM&&(D=O("input")&&(!document.documentMode||document.documentMode>11));var R={get:function(){return A.get.call(this)},set:function(e){M=""+e,A.set.call(this,e)}},L={eventTypes:k,extractEvents:function(e,t,n,o){var i,a,s=t?w.getNodeFromInstance(t):window;if(r(s)?I?i=u:a=l:P(s)?D?i=d:(i=m,a=h):v(s)&&(i=g),i){var c=i(e,t);if(c){var p=T.getPooled(k.change,c,n,o);return p.type="change",C.accumulateTwoPhaseDispatches(p),p}}a&&a(e,s,t),"topBlur"===e&&y(t,s)}};e.exports=L},function(e,t,n){"use strict";var r=n(3),o=n(17),i=n(7),a=n(105),s=n(8),u=(n(1),{dangerouslyReplaceNodeWithMarkup:function(e,t){if(i.canUseDOM?void 0:r("56"),t?void 0:r("57"),"HTML"===e.nodeName?r("58"):void 0,"string"==typeof t){var n=a(t,s)[0];e.parentNode.replaceChild(n,e)}else o.replaceChildWithTree(e,t)}});e.exports=u},function(e,t){"use strict";var n=["ResponderEventPlugin","SimpleEventPlugin","TapEventPlugin","EnterLeaveEventPlugin","ChangeEventPlugin","SelectEventPlugin","BeforeInputEventPlugin"];e.exports=n},function(e,t,n){"use strict";var r=n(25),o=n(6),i=n(30),a={mouseEnter:{registrationName:"onMouseEnter",dependencies:["topMouseOut","topMouseOver"]},mouseLeave:{registrationName:"onMouseLeave",dependencies:["topMouseOut","topMouseOver"]}},s={eventTypes:a,extractEvents:function(e,t,n,s){if("topMouseOver"===e&&(n.relatedTarget||n.fromElement))return null;if("topMouseOut"!==e&&"topMouseOver"!==e)return null;var u;if(s.window===s)u=s;else{var l=s.ownerDocument;u=l?l.defaultView||l.parentWindow:window}var c,p;if("topMouseOut"===e){c=t;var f=n.relatedTarget||n.toElement;p=f?o.getClosestInstanceFromNode(f):null}else c=null,p=t;if(c===p)return null;var d=null==c?u:o.getNodeFromInstance(c),h=null==p?u:o.getNodeFromInstance(p),m=i.getPooled(a.mouseLeave,c,n,s);m.type="mouseleave",m.target=d,m.relatedTarget=h;var v=i.getPooled(a.mouseEnter,p,n,s);return v.type="mouseenter",v.target=h,v.relatedTarget=d,r.accumulateEnterLeaveDispatches(m,v,c,p),[m,v]}};e.exports=s},function(e,t,n){"use strict";function r(e){this._root=e,this._startText=this.getText(),this._fallbackText=null}var o=n(4),i=n(14),a=n(79);o(r.prototype,{destructor:function(){this._root=null,this._startText=null,this._fallbackText=null},getText:function(){return"value"in this._root?this._root.value:this._root[a()]},getData:function(){if(this._fallbackText)return this._fallbackText;var e,t,n=this._startText,r=n.length,o=this.getText(),i=o.length;for(e=0;e1?1-t:void 0;return this._fallbackText=o.slice(e,s),this._fallbackText}}),i.addPoolingTo(r),e.exports=r},function(e,t,n){"use strict";var r=n(18),o=r.injection.MUST_USE_PROPERTY,i=r.injection.HAS_BOOLEAN_VALUE,a=r.injection.HAS_NUMERIC_VALUE,s=r.injection.HAS_POSITIVE_NUMERIC_VALUE,u=r.injection.HAS_OVERLOADED_BOOLEAN_VALUE,l={isCustomAttribute:RegExp.prototype.test.bind(new RegExp("^(data|aria)-["+r.ATTRIBUTE_NAME_CHAR+"]*$")),Properties:{accept:0,acceptCharset:0,accessKey:0,action:0,allowFullScreen:i,allowTransparency:0,alt:0,as:0,async:i,autoComplete:0,autoPlay:i,capture:i,cellPadding:0,cellSpacing:0,charSet:0,challenge:0,checked:o|i,cite:0,classID:0,className:0,cols:s,colSpan:0,content:0,contentEditable:0,contextMenu:0,controls:i,coords:0,crossOrigin:0,data:0,dateTime:0,default:i,defer:i,dir:0,disabled:i,download:u,draggable:0,encType:0,form:0,formAction:0,formEncType:0,formMethod:0,formNoValidate:i,formTarget:0,frameBorder:0,headers:0,height:0,hidden:i,high:0,href:0,hrefLang:0,htmlFor:0,httpEquiv:0,icon:0,id:0,inputMode:0,integrity:0,is:0,keyParams:0,keyType:0,kind:0,label:0,lang:0,list:0,loop:i,low:0,manifest:0,marginHeight:0,marginWidth:0,max:0,maxLength:0,media:0,mediaGroup:0,method:0,min:0,minLength:0,multiple:o|i,muted:o|i,name:0,nonce:0,noValidate:i,open:i,optimum:0,pattern:0,placeholder:0,playsInline:i,poster:0,preload:0,profile:0,radioGroup:0,readOnly:i,referrerPolicy:0,rel:0,required:i,reversed:i,role:0,rows:s,rowSpan:a,sandbox:0,scope:0,scoped:i,scrolling:0,seamless:i,selected:o|i,shape:0,size:s,sizes:0,span:s,spellCheck:0,src:0,srcDoc:0,srcLang:0,srcSet:0,start:a,step:0,style:0,summary:0,tabIndex:0,target:0,title:0,type:0,useMap:0,value:0,width:0,wmode:0,wrap:0,about:0,datatype:0,inlist:0,prefix:0,property:0,resource:0,typeof:0,vocab:0,autoCapitalize:0,autoCorrect:0,autoSave:0,color:0,itemProp:0,itemScope:i,itemType:0,itemID:0,itemRef:0,results:0,security:0,unselectable:0},DOMAttributeNames:{acceptCharset:"accept-charset",className:"class",htmlFor:"for",httpEquiv:"http-equiv"},DOMPropertyNames:{},DOMMutationMethods:{value:function(e,t){return null==t?e.removeAttribute("value"):void("number"!==e.type||e.hasAttribute("value")===!1?e.setAttribute("value",""+t):e.validity&&!e.validity.badInput&&e.ownerDocument.activeElement!==e&&e.setAttribute("value",""+t))}}};e.exports=l},function(e,t,n){(function(t){"use strict";function r(e,t,n,r){var o=void 0===e[n];null!=t&&o&&(e[n]=i(t,!0))}var o=n(19),i=n(80),a=(n(41),n(51)),s=n(83),u=(n(2),{instantiateChildren:function(e,t,n,o){if(null==e)return null;var i={};return s(e,r,i),i},updateChildren:function(e,t,n,r,s,u,l,c,p){if(t||e){var f,d;for(f in t)if(t.hasOwnProperty(f)){d=e&&e[f];var h=d&&d._currentElement,m=t[f];if(null!=d&&a(h,m))o.receiveComponent(d,m,s,c),t[f]=d;else{d&&(r[f]=o.getHostNode(d),o.unmountComponent(d,!1));var v=i(m,!0);t[f]=v;var g=o.mountComponent(v,s,u,l,c,p);n.push(g)}}for(f in e)!e.hasOwnProperty(f)||t&&t.hasOwnProperty(f)||(d=e[f],r[f]=o.getHostNode(d),o.unmountComponent(d,!1))}},unmountChildren:function(e,t){for(var n in e)if(e.hasOwnProperty(n)){var r=e[n];o.unmountComponent(r,t)}}});e.exports=u}).call(t,n(60))},function(e,t,n){"use strict";var r=n(37),o=n(143),i={processChildrenUpdates:o.dangerouslyProcessChildrenUpdates,replaceNodeWithMarkup:r.dangerouslyReplaceNodeWithMarkup};e.exports=i},function(e,t,n){"use strict";function r(e){}function o(e,t){}function i(e){return!(!e.prototype||!e.prototype.isReactComponent)}function a(e){return!(!e.prototype||!e.prototype.isPureReactComponent)}var s=n(3),u=n(4),l=n(20),c=n(43),p=n(12),f=n(44),d=n(26),h=(n(9),n(74)),m=n(19),v=n(23),g=(n(1),n(36)),y=n(51),b=(n(2),{ImpureClass:0,PureClass:1,StatelessFunctional:2});r.prototype.render=function(){var e=d.get(this)._currentElement.type,t=e(this.props,this.context,this.updater);return o(e,t),t};var C=1,_={construct:function(e){this._currentElement=e,this._rootNodeID=0,this._compositeType=null,this._instance=null,this._hostParent=null,this._hostContainerInfo=null,this._updateBatchNumber=null,this._pendingElement=null,this._pendingStateQueue=null,this._pendingReplaceState=!1,this._pendingForceUpdate=!1,this._renderedNodeType=null,this._renderedComponent=null,this._context=null,this._mountOrder=0,this._topLevelWrapper=null,this._pendingCallbacks=null,this._calledComponentWillUnmount=!1},mountComponent:function(e,t,n,u){this._context=u,this._mountOrder=C++,this._hostParent=t,this._hostContainerInfo=n;var c,p=this._currentElement.props,f=this._processContext(u),h=this._currentElement.type,m=e.getUpdateQueue(),g=i(h),y=this._constructComponent(g,p,f,m);g||null!=y&&null!=y.render?a(h)?this._compositeType=b.PureClass:this._compositeType=b.ImpureClass:(c=y,o(h,c),null===y||y===!1||l.isValidElement(y)?void 0:s("105",h.displayName||h.name||"Component"),y=new r(h),this._compositeType=b.StatelessFunctional);y.props=p,y.context=f,y.refs=v,y.updater=m,this._instance=y,d.set(y,this);var _=y.state;void 0===_&&(y.state=_=null),"object"!=typeof _||Array.isArray(_)?s("106",this.getName()||"ReactCompositeComponent"):void 0,this._pendingStateQueue=null,this._pendingReplaceState=!1,this._pendingForceUpdate=!1;var w;return w=y.unstable_handleError?this.performInitialMountWithErrorHandling(c,t,n,e,u):this.performInitialMount(c,t,n,e,u),y.componentDidMount&&e.getReactMountReady().enqueue(y.componentDidMount,y),w},_constructComponent:function(e,t,n,r){return this._constructComponentWithoutOwner(e,t,n,r)},_constructComponentWithoutOwner:function(e,t,n,r){var o=this._currentElement.type;return e?new o(t,n,r):o(t,n,r)},performInitialMountWithErrorHandling:function(e,t,n,r,o){var i,a=r.checkpoint();try{i=this.performInitialMount(e,t,n,r,o)}catch(s){r.rollback(a),this._instance.unstable_handleError(s),this._pendingStateQueue&&(this._instance.state=this._processPendingState(this._instance.props,this._instance.context)),a=r.checkpoint(),this._renderedComponent.unmountComponent(!0),r.rollback(a),i=this.performInitialMount(e,t,n,r,o)}return i},performInitialMount:function(e,t,n,r,o){var i=this._instance,a=0;i.componentWillMount&&(i.componentWillMount(),this._pendingStateQueue&&(i.state=this._processPendingState(i.props,i.context))),void 0===e&&(e=this._renderValidatedComponent());var s=h.getType(e);this._renderedNodeType=s; -var u=this._instantiateReactComponent(e,s!==h.EMPTY);this._renderedComponent=u;var l=m.mountComponent(u,r,t,n,this._processChildContext(o),a);return l},getHostNode:function(){return m.getHostNode(this._renderedComponent)},unmountComponent:function(e){if(this._renderedComponent){var t=this._instance;if(t.componentWillUnmount&&!t._calledComponentWillUnmount)if(t._calledComponentWillUnmount=!0,e){var n=this.getName()+".componentWillUnmount()";f.invokeGuardedCallback(n,t.componentWillUnmount.bind(t))}else t.componentWillUnmount();this._renderedComponent&&(m.unmountComponent(this._renderedComponent,e),this._renderedNodeType=null,this._renderedComponent=null,this._instance=null),this._pendingStateQueue=null,this._pendingReplaceState=!1,this._pendingForceUpdate=!1,this._pendingCallbacks=null,this._pendingElement=null,this._context=null,this._rootNodeID=0,this._topLevelWrapper=null,d.remove(t)}},_maskContext:function(e){var t=this._currentElement.type,n=t.contextTypes;if(!n)return v;var r={};for(var o in n)r[o]=e[o];return r},_processContext:function(e){var t=this._maskContext(e);return t},_processChildContext:function(e){var t,n=this._currentElement.type,r=this._instance;if(r.getChildContext&&(t=r.getChildContext()),t){"object"!=typeof n.childContextTypes?s("107",this.getName()||"ReactCompositeComponent"):void 0;for(var o in t)o in n.childContextTypes?void 0:s("108",this.getName()||"ReactCompositeComponent",o);return u({},e,t)}return e},_checkContextTypes:function(e,t,n){},receiveComponent:function(e,t,n){var r=this._currentElement,o=this._context;this._pendingElement=null,this.updateComponent(t,r,e,o,n)},performUpdateIfNecessary:function(e){null!=this._pendingElement?m.receiveComponent(this,this._pendingElement,e,this._context):null!==this._pendingStateQueue||this._pendingForceUpdate?this.updateComponent(e,this._currentElement,this._currentElement,this._context,this._context):this._updateBatchNumber=null},updateComponent:function(e,t,n,r,o){var i=this._instance;null==i?s("136",this.getName()||"ReactCompositeComponent"):void 0;var a,u=!1;this._context===o?a=i.context:(a=this._processContext(o),u=!0);var l=t.props,c=n.props;t!==n&&(u=!0),u&&i.componentWillReceiveProps&&i.componentWillReceiveProps(c,a);var p=this._processPendingState(c,a),f=!0;this._pendingForceUpdate||(i.shouldComponentUpdate?f=i.shouldComponentUpdate(c,p,a):this._compositeType===b.PureClass&&(f=!g(l,c)||!g(i.state,p))),this._updateBatchNumber=null,f?(this._pendingForceUpdate=!1,this._performComponentUpdate(n,c,p,a,e,o)):(this._currentElement=n,this._context=o,i.props=c,i.state=p,i.context=a)},_processPendingState:function(e,t){var n=this._instance,r=this._pendingStateQueue,o=this._pendingReplaceState;if(this._pendingReplaceState=!1,this._pendingStateQueue=null,!r)return n.state;if(o&&1===r.length)return r[0];for(var i=u({},o?r[0]:n.state),a=o?1:0;a=0||null!=t.is}function h(e){var t=e.type;f(t),this._currentElement=e,this._tag=t.toLowerCase(),this._namespaceURI=null,this._renderedChildren=null,this._previousStyle=null,this._previousStyleCopy=null,this._hostNode=null,this._hostParent=null,this._rootNodeID=0,this._domID=0,this._hostContainerInfo=null,this._wrapperState=null,this._topLevelWrapper=null,this._flags=0}var m=n(3),v=n(4),g=n(126),y=n(128),b=n(17),C=n(38),_=n(18),w=n(66),E=n(24),T=n(39),x=n(29),O=n(67),P=n(6),k=n(144),S=n(145),N=n(68),M=n(148),A=(n(9),n(157)),I=n(162),D=(n(8),n(32)),R=(n(1),n(50),n(36),n(52),n(2),O),L=E.deleteListener,U=P.getNodeFromInstance,F=x.listenTo,j=T.registrationNameModules,B={string:!0,number:!0},V="style",W="__html",H={children:null,dangerouslySetInnerHTML:null,suppressContentEditableWarning:null},q=11,z={topAbort:"abort",topCanPlay:"canplay",topCanPlayThrough:"canplaythrough",topDurationChange:"durationchange",topEmptied:"emptied",topEncrypted:"encrypted",topEnded:"ended",topError:"error",topLoadedData:"loadeddata",topLoadedMetadata:"loadedmetadata",topLoadStart:"loadstart",topPause:"pause",topPlay:"play",topPlaying:"playing",topProgress:"progress",topRateChange:"ratechange",topSeeked:"seeked",topSeeking:"seeking",topStalled:"stalled",topSuspend:"suspend",topTimeUpdate:"timeupdate",topVolumeChange:"volumechange",topWaiting:"waiting"},K={area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0},Y={listing:!0,pre:!0,textarea:!0},X=v({menuitem:!0},K),G=/^[a-zA-Z][a-zA-Z:_\.\-\d]*$/,Q={},$={}.hasOwnProperty,Z=1;h.displayName="ReactDOMComponent",h.Mixin={mountComponent:function(e,t,n,r){this._rootNodeID=Z++,this._domID=n._idCounter++,this._hostParent=t,this._hostContainerInfo=n;var i=this._currentElement.props;switch(this._tag){case"audio":case"form":case"iframe":case"img":case"link":case"object":case"source":case"video":this._wrapperState={listeners:null},e.getReactMountReady().enqueue(c,this);break;case"input":k.mountWrapper(this,i,t),i=k.getHostProps(this,i),e.getReactMountReady().enqueue(c,this);break;case"option":S.mountWrapper(this,i,t),i=S.getHostProps(this,i);break;case"select":N.mountWrapper(this,i,t),i=N.getHostProps(this,i),e.getReactMountReady().enqueue(c,this);break;case"textarea":M.mountWrapper(this,i,t),i=M.getHostProps(this,i),e.getReactMountReady().enqueue(c,this)}o(this,i);var a,p;null!=t?(a=t._namespaceURI,p=t._tag):n._tag&&(a=n._namespaceURI,p=n._tag),(null==a||a===C.svg&&"foreignobject"===p)&&(a=C.html),a===C.html&&("svg"===this._tag?a=C.svg:"math"===this._tag&&(a=C.mathml)),this._namespaceURI=a;var f;if(e.useCreateElement){var d,h=n._ownerDocument;if(a===C.html)if("script"===this._tag){var m=h.createElement("div"),v=this._currentElement.type;m.innerHTML="<"+v+">",d=m.removeChild(m.firstChild)}else d=i.is?h.createElement(this._currentElement.type,i.is):h.createElement(this._currentElement.type);else d=h.createElementNS(a,this._currentElement.type);P.precacheNode(this,d),this._flags|=R.hasCachedChildNodes,this._hostParent||w.setAttributeForRoot(d),this._updateDOMProperties(null,i,e);var y=b(d);this._createInitialChildren(e,i,r,y),f=y}else{var _=this._createOpenTagMarkupAndPutListeners(e,i),E=this._createContentMarkup(e,i,r);f=!E&&K[this._tag]?_+"/>":_+">"+E+""}switch(this._tag){case"input":e.getReactMountReady().enqueue(s,this),i.autoFocus&&e.getReactMountReady().enqueue(g.focusDOMComponent,this);break;case"textarea":e.getReactMountReady().enqueue(u,this),i.autoFocus&&e.getReactMountReady().enqueue(g.focusDOMComponent,this);break;case"select":i.autoFocus&&e.getReactMountReady().enqueue(g.focusDOMComponent,this);break;case"button":i.autoFocus&&e.getReactMountReady().enqueue(g.focusDOMComponent,this);break;case"option":e.getReactMountReady().enqueue(l,this)}return f},_createOpenTagMarkupAndPutListeners:function(e,t){var n="<"+this._currentElement.type;for(var r in t)if(t.hasOwnProperty(r)){var o=t[r];if(null!=o)if(j.hasOwnProperty(r))o&&i(this,r,o,e);else{r===V&&(o&&(o=this._previousStyleCopy=v({},t.style)),o=y.createMarkupForStyles(o,this));var a=null;null!=this._tag&&d(this._tag,t)?H.hasOwnProperty(r)||(a=w.createMarkupForCustomAttribute(r,o)):a=w.createMarkupForProperty(r,o),a&&(n+=" "+a)}}return e.renderToStaticMarkup?n:(this._hostParent||(n+=" "+w.createMarkupForRoot()),n+=" "+w.createMarkupForID(this._domID))},_createContentMarkup:function(e,t,n){var r="",o=t.dangerouslySetInnerHTML;if(null!=o)null!=o.__html&&(r=o.__html);else{var i=B[typeof t.children]?t.children:null,a=null!=i?null:t.children;if(null!=i)r=D(i);else if(null!=a){var s=this.mountChildren(a,e,n);r=s.join("")}}return Y[this._tag]&&"\n"===r.charAt(0)?"\n"+r:r},_createInitialChildren:function(e,t,n,r){var o=t.dangerouslySetInnerHTML;if(null!=o)null!=o.__html&&b.queueHTML(r,o.__html);else{var i=B[typeof t.children]?t.children:null,a=null!=i?null:t.children;if(null!=i)""!==i&&b.queueText(r,i);else if(null!=a)for(var s=this.mountChildren(a,e,n),u=0;u"},receiveComponent:function(){},getHostNode:function(){return i.getNodeFromInstance(this)},unmountComponent:function(){i.uncacheNode(this)}}),e.exports=a},function(e,t){"use strict";var n={useCreateElement:!0,useFiber:!1};e.exports=n},function(e,t,n){"use strict";var r=n(37),o=n(6),i={dangerouslyProcessChildrenUpdates:function(e,t){var n=o.getNodeFromInstance(e);r.processUpdates(n,t)}};e.exports=i},function(e,t,n){"use strict";function r(){this._rootNodeID&&f.updateWrapper(this)}function o(e){var t="checkbox"===e.type||"radio"===e.type;return t?null!=e.checked:null!=e.value}function i(e){var t=this._currentElement.props,n=l.executeOnChange(t,e);p.asap(r,this);var o=t.name;if("radio"===t.type&&null!=o){for(var i=c.getNodeFromInstance(this),s=i;s.parentNode;)s=s.parentNode;for(var u=s.querySelectorAll("input[name="+JSON.stringify(""+o)+'][type="radio"]'),f=0;ft.end?(n=t.end,r=t.start):(n=t.start,r=t.end),o.moveToElementText(e),o.moveStart("character",n),o.setEndPoint("EndToStart",o),o.moveEnd("character",r-n),o.select()}function s(e,t){if(window.getSelection){var n=window.getSelection(),r=e[c()].length,o=Math.min(t.start,r),i=void 0===t.end?o:Math.min(t.end,r);if(!n.extend&&o>i){var a=i;i=o,o=a}var s=l(e,o),u=l(e,i);if(s&&u){var p=document.createRange();p.setStart(s.node,s.offset),n.removeAllRanges(),o>i?(n.addRange(p),n.extend(u.node,u.offset)):(p.setEnd(u.node,u.offset),n.addRange(p))}}}var u=n(7),l=n(184),c=n(79),p=u.canUseDOM&&"selection"in document&&!("getSelection"in window),f={getOffsets:p?o:i,setOffsets:p?a:s};e.exports=f},function(e,t,n){"use strict";var r=n(3),o=n(4),i=n(37),a=n(17),s=n(6),u=n(32),l=(n(1),n(52),function(e){this._currentElement=e,this._stringText=""+e,this._hostNode=null,this._hostParent=null,this._domID=0,this._mountIndex=0,this._closingComment=null,this._commentNodes=null});o(l.prototype,{mountComponent:function(e,t,n,r){var o=n._idCounter++,i=" react-text: "+o+" ",l=" /react-text ";if(this._domID=o,this._hostParent=t,e.useCreateElement){var c=n._ownerDocument,p=c.createComment(i),f=c.createComment(l),d=a(c.createDocumentFragment());return a.queueChild(d,a(p)),this._stringText&&a.queueChild(d,a(c.createTextNode(this._stringText))),a.queueChild(d,a(f)),s.precacheNode(this,p),this._closingComment=f,d}var h=u(this._stringText);return e.renderToStaticMarkup?h:""+h+""},receiveComponent:function(e,t){if(e!==this._currentElement){this._currentElement=e;var n=""+e;if(n!==this._stringText){this._stringText=n;var r=this.getHostNode();i.replaceDelimitedText(r[0],r[1],n)}}},getHostNode:function(){var e=this._commentNodes;if(e)return e;if(!this._closingComment)for(var t=s.getNodeFromInstance(this),n=t.nextSibling;;){if(null==n?r("67",this._domID):void 0,8===n.nodeType&&" /react-text "===n.nodeValue){this._closingComment=n;break}n=n.nextSibling}return e=[this._hostNode,this._closingComment],this._commentNodes=e,e},unmountComponent:function(){this._closingComment=null,this._commentNodes=null,s.uncacheNode(this)}}),e.exports=l},function(e,t,n){"use strict";function r(){this._rootNodeID&&c.updateWrapper(this)}function o(e){var t=this._currentElement.props,n=s.executeOnChange(t,e);return l.asap(r,this),n}var i=n(3),a=n(4),s=n(42),u=n(6),l=n(10),c=(n(1),n(2),{getHostProps:function(e,t){null!=t.dangerouslySetInnerHTML?i("91"):void 0;var n=a({},t,{value:void 0,defaultValue:void 0,children:""+e._wrapperState.initialValue,onChange:e._wrapperState.onChange});return n},mountWrapper:function(e,t){var n=s.getValue(t),r=n;if(null==n){var a=t.defaultValue,u=t.children;null!=u&&(null!=a?i("92"):void 0,Array.isArray(u)&&(u.length<=1?void 0:i("93"),u=u[0]),a=""+u),null==a&&(a=""),r=a}e._wrapperState={initialValue:""+r,listeners:null,onChange:o.bind(e)}},updateWrapper:function(e){var t=e._currentElement.props,n=u.getNodeFromInstance(e),r=s.getValue(t);if(null!=r){var o=""+r;o!==n.value&&(n.value=o),null==t.defaultValue&&(n.defaultValue=o)}null!=t.defaultValue&&(n.defaultValue=t.defaultValue)},postMountWrapper:function(e){var t=u.getNodeFromInstance(e),n=t.textContent;n===e._wrapperState.initialValue&&(t.value=n)}});e.exports=c},function(e,t,n){"use strict";function r(e,t){"_hostNode"in e?void 0:u("33"),"_hostNode"in t?void 0:u("33");for(var n=0,r=e;r;r=r._hostParent)n++;for(var o=0,i=t;i;i=i._hostParent)o++;for(;n-o>0;)e=e._hostParent,n--;for(;o-n>0;)t=t._hostParent,o--;for(var a=n;a--;){if(e===t)return e;e=e._hostParent,t=t._hostParent}return null}function o(e,t){"_hostNode"in e?void 0:u("35"),"_hostNode"in t?void 0:u("35");for(;t;){if(t===e)return!0;t=t._hostParent}return!1}function i(e){return"_hostNode"in e?void 0:u("36"),e._hostParent}function a(e,t,n){for(var r=[];e;)r.push(e),e=e._hostParent;var o;for(o=r.length;o-- >0;)t(r[o],"captured",n);for(o=0;o0;)n(u[l],"captured",i)}var u=n(3);n(1);e.exports={isAncestor:o,getLowestCommonAncestor:r,getParentInstance:i,traverseTwoPhase:a,traverseEnterLeave:s}},function(e,t,n){"use strict";function r(){this.reinitializeTransaction()}var o=n(4),i=n(10),a=n(31),s=n(8),u={initialize:s,close:function(){f.isBatchingUpdates=!1}},l={initialize:s,close:i.flushBatchedUpdates.bind(i)},c=[l,u];o(r.prototype,a,{getTransactionWrappers:function(){return c}});var p=new r,f={isBatchingUpdates:!1,batchedUpdates:function(e,t,n,r,o,i){var a=f.isBatchingUpdates;return f.isBatchingUpdates=!0,a?e(t,n,r,o,i):p.perform(e,null,t,n,r,o,i)}};e.exports=f},function(e,t,n){"use strict";function r(){E||(E=!0,y.EventEmitter.injectReactEventListener(g),y.EventPluginHub.injectEventPluginOrder(s),y.EventPluginUtils.injectComponentTree(f),y.EventPluginUtils.injectTreeTraversal(h),y.EventPluginHub.injectEventPluginsByName({SimpleEventPlugin:w,EnterLeaveEventPlugin:u,ChangeEventPlugin:a,SelectEventPlugin:_,BeforeInputEventPlugin:i}),y.HostComponent.injectGenericComponentClass(p),y.HostComponent.injectTextComponentClass(m),y.DOMProperty.injectDOMPropertyConfig(o),y.DOMProperty.injectDOMPropertyConfig(l),y.DOMProperty.injectDOMPropertyConfig(C),y.EmptyComponent.injectEmptyComponentFactory(function(e){return new d(e)}),y.Updates.injectReconcileTransaction(b),y.Updates.injectBatchingStrategy(v),y.Component.injectEnvironment(c))}var o=n(125),i=n(127),a=n(129),s=n(131),u=n(132),l=n(134),c=n(136),p=n(139),f=n(6),d=n(141),h=n(149),m=n(147),v=n(150),g=n(154),y=n(155),b=n(160),C=n(165),_=n(166),w=n(167),E=!1;e.exports={inject:r}},88,function(e,t,n){"use strict";function r(e){o.enqueueEvents(e),o.processEventQueue(!1)}var o=n(24),i={handleTopLevel:function(e,t,n,i){var a=o.extractEvents(e,t,n,i);r(a)}};e.exports=i},function(e,t,n){"use strict";function r(e){for(;e._hostParent;)e=e._hostParent;var t=p.getNodeFromInstance(e),n=t.parentNode;return p.getClosestInstanceFromNode(n)}function o(e,t){this.topLevelType=e,this.nativeEvent=t,this.ancestors=[]}function i(e){var t=d(e.nativeEvent),n=p.getClosestInstanceFromNode(t),o=n;do e.ancestors.push(o),o=o&&r(o);while(o);for(var i=0;i/,i=/^<\!\-\-/,a={CHECKSUM_ATTR_NAME:"data-react-checksum",addChecksumToMarkup:function(e){var t=r(e);return i.test(e)?e:e.replace(o," "+a.CHECKSUM_ATTR_NAME+'="'+t+'"$&')},canReuseMarkup:function(e,t){var n=t.getAttribute(a.CHECKSUM_ATTR_NAME);n=n&&parseInt(n,10);var o=r(e);return o===n}};e.exports=a},function(e,t,n){"use strict";function r(e,t,n){return{type:"INSERT_MARKUP",content:e,fromIndex:null,fromNode:null,toIndex:n,afterNode:t}}function o(e,t,n){return{type:"MOVE_EXISTING",content:null,fromIndex:e._mountIndex,fromNode:f.getHostNode(e),toIndex:n,afterNode:t}}function i(e,t){return{type:"REMOVE_NODE",content:null,fromIndex:e._mountIndex,fromNode:t,toIndex:null,afterNode:null}}function a(e){return{type:"SET_MARKUP",content:e,fromIndex:null,fromNode:null,toIndex:null,afterNode:null}}function s(e){return{type:"TEXT_CONTENT",content:e,fromIndex:null,fromNode:null,toIndex:null,afterNode:null}}function u(e,t){return t&&(e=e||[],e.push(t)),e}function l(e,t){p.processChildrenUpdates(e,t)}var c=n(3),p=n(43),f=(n(26),n(9),n(12),n(19)),d=n(135),h=(n(8),n(181)),m=(n(1),{Mixin:{_reconcilerInstantiateChildren:function(e,t,n){return d.instantiateChildren(e,t,n)},_reconcilerUpdateChildren:function(e,t,n,r,o,i){var a,s=0;return a=h(t,s),d.updateChildren(e,a,n,r,o,this,this._hostContainerInfo,i,s),a},mountChildren:function(e,t,n){var r=this._reconcilerInstantiateChildren(e,t,n);this._renderedChildren=r;var o=[],i=0;for(var a in r)if(r.hasOwnProperty(a)){var s=r[a],u=0,l=f.mountComponent(s,t,this,this._hostContainerInfo,n,u);s._mountIndex=i++,o.push(l)}return o},updateTextContent:function(e){var t=this._renderedChildren;d.unmountChildren(t,!1);for(var n in t)t.hasOwnProperty(n)&&c("118");var r=[s(e)];l(this,r)},updateMarkup:function(e){var t=this._renderedChildren;d.unmountChildren(t,!1);for(var n in t)t.hasOwnProperty(n)&&c("118");var r=[a(e)];l(this,r)},updateChildren:function(e,t,n){this._updateChildren(e,t,n)},_updateChildren:function(e,t,n){var r=this._renderedChildren,o={},i=[],a=this._reconcilerUpdateChildren(r,e,i,o,t,n);if(a||r){var s,c=null,p=0,d=0,h=0,m=null;for(s in a)if(a.hasOwnProperty(s)){var v=r&&r[s],g=a[s];v===g?(c=u(c,this.moveChild(v,m,p,d)),d=Math.max(v._mountIndex,d),v._mountIndex=p):(v&&(d=Math.max(v._mountIndex,d)),c=u(c,this._mountChildAtIndex(g,i[h],m,p,t,n)),h++),p++,m=f.getHostNode(g)}for(s in o)o.hasOwnProperty(s)&&(c=u(c,this._unmountChild(r[s],o[s])));c&&l(this,c),this._renderedChildren=a}},unmountChildren:function(e){var t=this._renderedChildren;d.unmountChildren(t,e),this._renderedChildren=null},moveChild:function(e,t,n,r){if(e._mountIndex=t)return{node:o,offset:t-i};i=a}o=n(r(o))}}e.exports=o},function(e,t,n){"use strict";function r(e,t){var n={};return n[e.toLowerCase()]=t.toLowerCase(),n["Webkit"+e]="webkit"+t,n["Moz"+e]="moz"+t,n["ms"+e]="MS"+t,n["O"+e]="o"+t.toLowerCase(),n}function o(e){if(s[e])return s[e];if(!a[e])return e;var t=a[e];for(var n in t)if(t.hasOwnProperty(n)&&n in u)return s[e]=t[n];return""}var i=n(7),a={animationend:r("Animation","AnimationEnd"),animationiteration:r("Animation","AnimationIteration"),animationstart:r("Animation","AnimationStart"),transitionend:r("Transition","TransitionEnd")},s={},u={};i.canUseDOM&&(u=document.createElement("div").style,"AnimationEvent"in window||(delete a.animationend.animation,delete a.animationiteration.animation,delete a.animationstart.animation),"TransitionEvent"in window||delete a.transitionend.transition),e.exports=o},function(e,t,n){"use strict";function r(e){return'"'+o(e)+'"'}var o=n(32);e.exports=r},function(e,t,n){"use strict";var r=n(73);e.exports=r.renderSubtreeIntoContainer},function(e,t,n){"use strict";"undefined"==typeof Promise&&(n(120).enable(),window.Promise=n(119)),n(226),Object.assign=n(4)},113,114,115,116,117,function(e,t,n){(function(){var t,r,o;t=n(5),r=t.createClass,o=t.DOM.div,e.exports=r({getDefaultProps:function(){return{className:"",onHeightChange:function(){}}},render:function(){return o({className:this.props.className,ref:"dropdown"},this.props.children)},componentDidMount:function(){this.props.onHeightChange(this.refs.dropdown.offsetHeight)},componentDidUpdate:function(){this.props.onHeightChange(this.refs.dropdown.offsetHeight)},componentWillUnmount:function(){this.props.onHeightChange(0)}})}).call(this)},function(e,t,n){(function(){function t(e,t){var n={}.hasOwnProperty;for(var r in t)n.call(t,r)&&(e[r]=t[r]);return e}var r,o,i,a,s,u,l,c,p,f,d,h,m,v,g,y,b,C;r=n(15),o=r.filter,i=r.id,a=r.map,s=n(16).isEqualToObject,u=n(5),r=u.DOM,l=r.div,c=r.input,p=r.span,f=u.createClass,d=u.createFactory,h=n(13).findDOMNode,m=d(n(63)),v=d(n(198)),g=d(n(194)),y=d(n(84)),r=n(28),b=r.cancelEvent,C=r.classNameFromObject,e.exports=f({displayName:"DropdownMenu",getDefaultProps:function(){return{className:"",dropdownDirection:1,groupId:function(e){return e.groupId},groupsAsColumns:!1,highlightedUid:void 0,onHighlightedUidChange:function(e,t){},onOptionClick:function(e){},onScrollLockChange:function(e){},options:[],renderNoResultsFound:function(){return l({className:"no-results-found"},"No results found")},renderGroupTitle:function(e,t){var n,r;return null!=t&&(n=t.groupId,r=t.title),l({className:"simple-group-title",key:n},r)},renderOption:function(e){var t,n,r,o;return null!=e&&(t=e.label,n=e.newOption,r=e.selectable),o="undefined"==typeof r||r,l({className:"simple-option "+(o?"":"not-selectable")},p(null,n?"Add "+t+" ...":t))},scrollLock:!1,style:{},tether:!1,tetherProps:{},theme:"default",transitionEnter:!1,transitionLeave:!1,transitionEnterTimeout:200,transitionLeaveTimeout:200,uid:i}},render:function(){var e,n;return e=C((n={},n[this.props.theme+""]=1,n[this.props.className+""]=1,n.flipped=this.props.dropdownDirection===-1,n.tethered=this.props.tether,n)),this.props.tether?v((n=t({},this.props.tetherProps),n.options={attachment:"top left",targetAttachment:"bottom left",constraints:[{to:"scrollParent"}]},n),this.renderAnimatedDropdown({dynamicClassName:e})):this.renderAnimatedDropdown({dynamicClassName:e})},renderAnimatedDropdown:function(e){var t;return t=e.dynamicClassName,this.props.transitionEnter||this.props.transitionLeave?m({component:"div",transitionName:"custom",transitionEnter:this.props.transitionEnter,transitionLeave:this.props.transitionLeave,transitionEnterTimeout:this.props.transitionEnterTimeout,transitionLeaveTimeout:this.props.transitionLeaveTimeout,className:"dropdown-menu-wrapper "+t,ref:"dropdownMenuWrapper"},this.renderDropdown(e)):this.renderDropdown(e)},renderOptions:function(e){var n=this;return a(function(r){var o,i;return o=e[r],i=n.props.uid(o),y(t({uid:i,ref:"option-"+n.uidToString(i),key:n.uidToString(i),item:o,highlight:s(n.props.highlightedUid,i),selectable:null!=o?o.selectable:void 0,onMouseMove:function(e){var t;t=e.currentTarget,n.props.scrollLock&&n.props.onScrollLockChange(!1)},onMouseOut:function(){n.props.scrollLock||n.props.onHighlightedUidChange(void 0,function(){})},renderItem:n.props.renderOption},function(){switch(!1){case!("boolean"==typeof(null!=o?o.selectable:void 0)&&!o.selectable):return{onClick:b};default:return{onClick:function(){n.props.onOptionClick(n.props.highlightedUid)},onMouseOver:function(e){var t;t=e.currentTarget,n.props.scrollLock||n.props.onHighlightedUidChange(i,function(){})}}}}()))})(function(){var t,n,r=[];for(t=0,n=e.length;t0?(i=a(function(e){var t,n,r;return t=s.props.groups[e],n=t.groupId,r=o(function(e){return s.props.groupId(e)===n})(s.props.options),{index:e,group:t,options:r}})(function(){var e,t,n=[];for(e=0,t=this.props.groups.length;e0})(i)))):this.renderOptions(this.props.options)):null},componentDidUpdate:function(){var e,t,n;e=t=h(null!=(n=this.refs.dropdownMenuWrapper)?n:this.refs.dropdownMenu),null!=e&&(e.style.bottom=function(){switch(!1){case this.props.dropdownDirection!==-1:return this.props.bottomAnchor().offsetHeight+t.style.marginBottom+"px";default:return""}}.call(this))},highlightAndScrollToOption:function(e,t){var n,r=this;null==t&&(t=function(){}),n=this.props.uid(this.props.options[e]),this.props.onHighlightedUidChange(n,function(){var e,o,i,a,s;return null!=(e=h(null!=(o=r.refs)?o["option-"+r.uidToString(n)]:void 0))&&(i=e),i&&(a=h(r.refs.dropdownMenu),s=i.offsetHeight-1,i.offsetTop-a.scrollTop>=a.offsetHeight?a.scrollTop=i.offsetTop-a.offsetHeight+s:i.offsetTop-a.scrollTop+s<=0&&(a.scrollTop=i.offsetTop)),t()})},highlightAndScrollToSelectableOption:function(e,t,n){var r,o,i;null==n&&(n=function(){}),e<0||e>=this.props.options.length?this.props.onHighlightedUidChange(void 0,function(){return n(!1)}):(r=null!=(o=this.props)&&null!=(i=o.options)?i[e]:void 0,"boolean"!=typeof(null!=r?r.selectable:void 0)||r.selectable?this.highlightAndScrollToOption(e,function(){return n(!0)}):this.highlightAndScrollToSelectableOption(e+t,t,n))},uidToString:function(e){return("object"==typeof e?JSON.stringify:i)(e)}})}).call(this)},function(e,t,n){(function(){var t,r,o,i,a,s;t=n(5),r=t.createClass,o=t.DOM,i=o.div,a=o.span,s=n(15).map,e.exports=r({getDefaultProps:function(){return{partitions:[],text:"",style:{},highlightStyle:{}}},render:function(){var e=this;return i({className:"highlighted-text",style:this.props.style},s(function(t){var n,r,o;return n=t[0],r=t[1],o=t[2],a({key:e.props.text+""+n+r+o,className:o?"highlight":"",style:o?e.props.highlightStyle:{}},e.props.text.substring(n,r))})(this.props.partitions))}})}).call(this)},function(e,t,n){(function(){function t(e,t){for(var n=-1,r=t.length>>>0;++n1?function(){var i=o?o.concat():[];return n=t?n||this:this,i.push.apply(i,arguments)-1})(g(function(e){return t(e.label.trim(),v(function(e){return e.label.trim()},null!=n?n:[]))})(e))}),firstOptionIndexToHighlight:h,onBlur:function(e){},onFocus:function(e){},onPaste:function(e){},serialize:v(function(e){return null!=e?e.value:void 0}),tether:!1}},render:function(){var e,t,n,r,i,a,s,u,l,c,p,f,d,h,v,g,y,b,C,_,w,E,O,P,k,S,N,M,A,I,D,R,L,U,F,j,B,V,W=this;return e=this.getComputedState(),t=e.anchor,n=e.filteredOptions,r=e.highlightedUid,i=e.onAnchorChange,a=e.onOpenChange,s=e.onHighlightedUidChange,u=e.onSearchChange,l=e.onValuesChange,c=e.search,p=e.open,f=e.options,d=e.values,null!=(e=this.props)&&(h=e.autofocus,v=e.autosize,g=e.cancelKeyboardEventOnSelection,y=e.delimiters,b=e.disabled,C=e.dropdownDirection,_=e.groupId,w=e.groups,E=e.groupsAsColumns,O=e.hideResetButton,P=e.inputProps,k=e.name,S=e.onKeyboardSelectionFailed,N=e.renderToggleButton,M=e.renderGroupTitle,A=e.renderResetButton,I=e.serialize,D=e.tether,R=e.tetherProps,L=e.theme,U=e.transitionEnter,F=e.transitionLeave,j=e.transitionEnterTimeout,B=e.transitionLeaveTimeout,V=e.uid),T(o(o({autofocus:h,autosize:v,cancelKeyboardEventOnSelection:g,className:"multi-select "+this.props.className,delimiters:y,disabled:b,dropdownDirection:C,groupId:_,groups:w,groupsAsColumns:E,hideResetButton:O,highlightedUid:r,onHighlightedUidChange:s,inputProps:P,name:k,onKeyboardSelectionFailed:S,renderGroupTitle:M,renderResetButton:A,renderToggleButton:N,scrollLock:this.state.scrollLock,onScrollLockChange:function(e){return W.setState({scrollLock:e})},tether:D,tetherProps:R,theme:L,transitionEnter:U,transitionEnterTimeout:j,transitionLeave:F,transitionLeaveTimeout:B,uid:V,ref:"select",anchor:t,onAnchorChange:i,open:p,onOpenChange:a,options:f,renderOption:this.props.renderOption,firstOptionIndexToHighlight:function(){return W.firstOptionIndexToHighlight(f)},search:c,onSearchChange:function(e,t){return u(W.props.maxValues&&d.length>=W.props.maxValues?"":e,t)},values:d,onValuesChange:function(e,t){return l(e,function(){if(t(),W.props.closeOnSelect||W.props.maxValues&&W.values().length>=W.props.maxValues)return a(!1,function(){})})},renderValue:this.props.renderValue,serialize:I,onBlur:function(e){u("",function(){return W.props.onBlur({open:p,values:d,originalEvent:e})})},onFocus:function(e){W.props.onFocus({open:p,values:d,originalEvent:e})},onPaste:function(){var e;switch(!1){case"undefined"!=typeof(null!=(e=this.props)?e.valuesFromPaste:void 0):return this.props.onPaste;default:return function(e){var t;return t=e.clipboardData,function(){var e;return e=d.concat(W.props.valuesFromPaste(f,d,t.getData("text"))),l(e,function(){return i(m(e))})}(),x(e)}}}.call(this),placeholder:this.props.placeholder,style:this.props.style},function(){switch(!1){case"function"!=typeof this.props.restoreOnBackspace:return{restoreOnBackspace:this.props.restoreOnBackspace};default:return{}}}.call(this)),function(){switch(!1){case"function"!=typeof this.props.renderNoResultsFound:return{renderNoResultsFound:function(){return W.props.renderNoResultsFound(d,c)}};default:return{}}}.call(this)))},getComputedState:function(){var e,t,n,r,i,a,s,l,c,p,f,d,h,m,g,y,b=this;return e=this.props.hasOwnProperty("anchor")?this.props.anchor:this.state.anchor,t=this.props.hasOwnProperty("highlightedUid")?this.props.highlightedUid:this.state.highlightedUid,n=this.isOpen(),r=this.props.hasOwnProperty("search")?this.props.search:this.state.search,i=this.values(),a=v(function(e){switch(!1){case!(b.props.hasOwnProperty(e)&&b.props.hasOwnProperty(u("on-"+e+"-change"))):return function(t,n){return b.props[u("on-"+e+"-change")](t,function(){}),b.setState({},n)};case!(b.props.hasOwnProperty(e)&&!b.props.hasOwnProperty(u("on-"+e+"-change"))):return function(e,t){return t()};case!(!b.props.hasOwnProperty(e)&&b.props.hasOwnProperty(u("on-"+e+"-change"))):return function(t,n){var r;return b.setState((r={},r[e+""]=t,r),function(){return n(),b.props[u("on-"+e+"-change")](t,function(){})})};case!(!b.props.hasOwnProperty(e)&&!b.props.hasOwnProperty(u("on-"+e+"-change"))):return function(t,n){var r;return b.setState((r={},r[e+""]=t,r),n)}}})(["anchor","highlightedUid","open","search","values"]),s=a[0],l=a[1],c=a[2],p=a[3],f=a[4],d=function(){var e;switch(!1){case!(null!=(e=this.props)&&e.children):return v(function(e){var t,n,r;return null!=e&&(t=e.props),null!=t&&(n=t.value,r=t.children),{label:r,value:n}})("Array"===O.call(this.props.children).slice(8,-1)?this.props.children:[this.props.children]);default:return[]}}.call(this),h=this.props.hasOwnProperty("options")?null!=(a=this.props.options)?a:[]:d,m=this.props.filterOptions(h,i,r),g=function(){switch(!1){case"function"!=typeof this.props.createFromSearch:return this.props.createFromSearch(m,i,r);default:return null}}.call(this),y=(g?[(a=o({},g),a.newOption=!0,a)]:[]).concat(m),{anchor:e,highlightedUid:t,search:r,values:i,onAnchorChange:s,onHighlightedUidChange:l,open:n,onOpenChange:function(e,t){c(function(){switch(!1){case!("undefined"!=typeof this.props.maxValues&&this.values().length>=this.props.maxValues):return!1;default:return e}}.call(b),t)},onSearchChange:p,onValuesChange:f,filteredOptions:m,options:y}},getInitialState:function(){return{anchor:this.props.values?m(this.props.values):void 0,highlightedUid:void 0,open:!1,scrollLock:!1,search:"",values:this.props.defaultValues}},firstOptionIndexToHighlight:function(e){var t,n;return t=function(){var t;switch(!1){case 1!==e.length:return 0;case"undefined"!=typeof(null!=(t=e[0])?t.newOption:void 0):return 0;default:return a(function(e){return"boolean"==typeof e.selectable&&!e.selectable})(c(1)(e))?0:1}}(),n=this.props.hasOwnProperty("search")?this.props.search:this.state.search,this.props.firstOptionIndexToHighlight(t,e,this.values(),n)},focus:function(){this.refs.select.focus()},blur:function(){this.refs.select.blur()},highlightFirstSelectableOption:function(){this.state.open&&this.refs.select.highlightAndScrollToSelectableOption(this.firstOptionIndexToHighlight(this.getComputedState().options),1)},values:function(){return this.props.hasOwnProperty("values")?this.props.values:this.state.values},isOpen:function(){return this.props.hasOwnProperty("open")?this.props.open:this.state.open}})}).call(this)},function(e,t,n){(function(){function t(e,t){var n={}.hasOwnProperty;for(var r in t)n.call(t,r)&&(e[r]=t[r]);return e}var r,o,i,a,s,u;r=n(5).createClass,o=n(13),i=o.render,a=o.unmountComponentAtNode,s=n(124),u=n(224),e.exports=r({getDefaultProps:function(){return{parentElement:function(){return document.body}}},render:function(){ -return null},initTether:function(e){var n=this;this.node=document.createElement("div"),this.props.parentElement().appendChild(this.node),this.tether=new u(t({element:this.node,target:e.target()},e.options)),i(e.children,this.node,function(){return n.tether.position()})},destroyTether:function(){this.tether&&this.tether.destroy(),this.node&&(a(this.node),this.node.parentElement.removeChild(this.node)),this.node=this.tether=void 0},componentDidMount:function(){this.props.children&&this.initTether(this.props)},componentWillReceiveProps:function(e){var n=this;this.props.children&&!e.children?this.destroyTether():e.children&&!this.props.children?this.initTether(e):e.children&&(this.tether.setOptions(t({element:this.node,target:e.target()},e.options)),i(e.children,this.node,function(){return n.tether.position()}))},shouldComponentUpdate:function(e,t){return s(this,e,t)},componentWillUnmount:function(){this.destroyTether()}})}).call(this)},function(e,t,n){(function(){var t,r,o,i,a;t=n(5),r=t.createClass,o=t.createFactory,i=t.DOM.path,a=o(n(85)),e.exports=r({render:function(){return a({className:"react-selectize-reset-button",style:{width:8,height:8}},i({d:"M0 0 L8 8 M8 0 L 0 8"}))}})}).call(this)},function(e,t,n){(function(){function t(e,t){var n={}.hasOwnProperty;for(var r in t)n.call(t,r)&&(e[r]=t[r]);return e}var r,o,i,a,s,u,l,c;r=n(15),o=r.each,i=r.objToPairs,a=n(5),s=a.DOM.input,u=a.createClass,l=a.createFactory,c=n(13).findDOMNode,e.exports=u({displayName:"ResizableInput",render:function(){var e;return s((e=t({},this.props),e.type="input",e.className="resizable-input",e))},autosize:function(){var e,t,n,r,a;return e=t=c(this),e.style.width="0px",0===t.value.length?t.style.width=null!=t&&t.currentStyle?"4px":"2px":t.scrollWidth>0?t.style.width=2+t.scrollWidth+"px":(n=r=document.createElement("div"),n.innerHTML=t.value,function(){var e;return e=r.style,e.display="inline-block",e.width="",e}(o(function(e){var t,n;return t=e[0],n=e[1],r.style[t]=n})(i(t.currentStyle?t.currentStyle:null!=(a=document.defaultView)?a:window.getComputedStyle(t)))),document.body.appendChild(r),t.style.width=4+r.clientWidth+"px",document.body.removeChild(r))},componentDidMount:function(){this.autosize()},componentDidUpdate:function(){this.autosize()},blur:function(){return c(this).blur()},focus:function(){return c(this).focus()}})}).call(this)},function(e,t,n){(function(){function t(e,t){var n,r=function(o){return e.length>1?function(){var i=o?o.concat():[];return n=t?n||this:this,i.push.apply(i,arguments)-1})(e)}),firstOptionIndexToHighlight:d,onBlur:function(e){},onBlurResetsInput:!0,onFocus:function(e){},onKeyboardSelectionFailed:function(e){},onPaste:function(e){},placeholder:"",renderValue:function(e){var t;return t=e.label,C({className:"simple-value"},w(null,t))},serialize:function(e){return null!=e?e.value:void 0},style:{},tether:!1,uid:d}},render:function(){var e,t,n,o,i,a,s,u,l,c,p,f,d,m,v,y,b,C,_,w,x,O,P,k,S,N,M,A,I,D,R,L,U,F,j,B,V,W=this;return e=this.getComputedState(),t=e.filteredOptions,n=e.highlightedUid,o=e.onHighlightedUidChange,i=e.onOpenChange,a=e.onSearchChange,s=e.onValueChange,u=e.open,l=e.options,c=e.search,p=e.value,f=e.values,null!=(e=this.props)&&(d=e.autofocus,m=e.autosize,v=e.cancelKeyboardEventOnSelection,y=e.delimiters,b=e.disabled,C=e.dropdownDirection,_=e.groupId,w=e.groups,x=e.groupsAsColumns,O=e.hideResetButton,P=e.name,k=e.inputProps,S=e.onBlurResetsInput,N=e.renderToggleButton,M=e.renderGroupTitle,A=e.renderResetButton,I=e.serialize,D=e.tether,R=e.tetherProps,L=e.theme,U=e.transitionEnter,F=e.transitionLeave,j=e.transitionEnterTimeout,B=e.transitionLeaveTimeout,V=e.uid),E(r(r({autofocus:d,autosize:m,cancelKeyboardEventOnSelection:v,className:"simple-select"+(this.props.className?" "+this.props.className:""),delimiters:y,disabled:b,dropdownDirection:C,groupId:_,groups:w,groupsAsColumns:x,hideResetButton:O,highlightedUid:n,onHighlightedUidChange:o,inputProps:k,name:P,onBlurResetsInput:S,renderGroupTitle:M,renderResetButton:A,renderToggleButton:N,scrollLock:this.state.scrollLock,onScrollLockChange:function(e){return W.setState({scrollLock:e})},tether:D,tetherProps:R,theme:L,transitionEnter:U,transitionEnterTimeout:j,transitionLeave:F,transitionLeaveTimeout:B,ref:"select",anchor:h(f),onAnchorChange:function(e,t){return t()},open:u,onOpenChange:i,firstOptionIndexToHighlight:function(){return W.firstOptionIndexToHighlight(l,p)},options:l,renderOption:this.props.renderOption,renderNoResultsFound:this.props.renderNoResultsFound,search:c,onSearchChange:function(e,t){return a(e,t)},values:f,onValuesChange:function(e,t){var n,r;return 0===e.length?s(void 0,function(){return t()}):(n=h(e),r=!g(n,p),function(){return function(e){return r?s(n,e):e()}}()(function(){return t(),i(!1,function(){})}))},renderValue:function(e){return u&&(W.props.editable||c.length>0)?null:W.props.renderValue(e)},onKeyboardSelectionFailed:function(e){return a("",function(){return i(!1,function(){return W.props.onKeyboardSelectionFailed(e)})})},uid:function(e){return{uid:W.props.uid(e),open:u,search:c}},serialize:function(e){return I(e[0])},onBlur:function(e){var t;t=W.props.onBlurResetsInput,function(){return function(e){return c.length>0&&t?a("",e):e()}}()(function(){return W.props.onBlur({value:p,open:u,originalEvent:e})})},onFocus:function(e){W.props.onFocus({value:p,open:u,originalEvent:e})},onPaste:function(){var e;switch(!1){case"undefined"!=typeof(null!=(e=this.props)?e.valueFromPaste:void 0):return this.props.onPaste;default:return function(e){var t,n;if(t=e.clipboardData,n=W.props.valueFromPaste(l,p,t.getData("text")))return function(){return s(n,function(){return a("",function(){return i(!1)})})}(),T(e)}}}.call(this),placeholder:this.props.placeholder,style:this.props.style},function(){switch(!1){case"function"!=typeof this.props.restoreOnBackspace:return{restoreOnBackspace:this.props.restoreOnBackspace};default:return{}}}.call(this)),function(){switch(!1){case"function"!=typeof this.props.renderNoResultsFound:return{renderNoResultsFound:function(){return W.props.renderNoResultsFound(p,c)}};default:return{}}}.call(this)))},getComputedState:function(){var e,t,n,o,i,a,s,l,c,p,f,d,h,v,g,y=this;return e=this.props.hasOwnProperty("highlightedUid")?this.props.highlightedUid:this.state.highlightedUid,t=this.isOpen(),n=this.props.hasOwnProperty("search")?this.props.search:this.state.search,o=this.value(),i=o||0===o?[o]:[],a=m(function(e){var t;return t=function(){switch(!1){case!(this.props.hasOwnProperty(e)&&this.props.hasOwnProperty(u("on-"+e+"-change"))):return function(t,n){return y.props[u("on-"+e+"-change")](t,function(){}),y.setState({},n)};case!(this.props.hasOwnProperty(e)&&!this.props.hasOwnProperty(u("on-"+e+"-change"))):return function(e,t){return t()};case!(!this.props.hasOwnProperty(e)&&this.props.hasOwnProperty(u("on-"+e+"-change"))):return function(t,n){var r;return y.setState((r={},r[e+""]=t,r),function(){return n(),y.props[u("on-"+e+"-change")](t,function(){})})};case!(!this.props.hasOwnProperty(e)&&!this.props.hasOwnProperty(u("on-"+e+"-change"))):return function(t,n){var r;return y.setState((r={},r[e+""]=t,r),n)}}}.call(y)})(["highlightedUid","open","search","value"]),s=a[0],l=a[1],c=a[2],p=a[3],f=function(){var e;switch(!1){case!(null!=(e=this.props)&&e.children):return m(function(e){var t,n,r;return null!=(t=null!=e?e.props:void 0)&&(n=t.value,r=t.children),{label:r,value:n}})("Array"===x.call(this.props.children).slice(8,-1)?this.props.children:[this.props.children]);default:return[]}}.call(this),d=this.props.hasOwnProperty("options")?null!=(a=this.props.options)?a:[]:f,h=this.props.filterOptions(d,n),v=function(){switch(!1){case"function"!=typeof this.props.createFromSearch:return this.props.createFromSearch(h,n);default:return null}}.call(this),g=(v?[(a=r({},v),a.newOption=!0,a)]:[]).concat(h),{highlightedUid:e,open:t,search:n,value:o,values:i,onHighlightedUidChange:s,onOpenChange:function(e,t){l(e,function(){if(t(),y.props.editable&&y.isOpen()&&o)return c(y.props.editable(o)+""+(1===n.length?n:""),function(){return y.highlightFirstSelectableOption(function(){})})})},onSearchChange:c,onValueChange:p,filteredOptions:h,options:g}},getInitialState:function(){var e;return{highlightedUid:void 0,open:!1,scrollLock:!1,search:"",value:null!=(e=this.props)?e.defaultValue:void 0}},firstOptionIndexToHighlight:function(e,t){var n,r,o;return n=t?f(function(e){return g(e,t)},e):void 0,r=function(){var t;switch(!1){case"undefined"==typeof n:return n;case 1!==e.length:return 0;case"undefined"!=typeof(null!=(t=e[0])?t.newOption:void 0):return 0;default:return i(function(e){return"boolean"==typeof e.selectable&&!e.selectable})(s(1)(e))?0:1}}(),o=this.props.hasOwnProperty("search")?this.props.search:this.state.search,this.props.firstOptionIndexToHighlight(r,e,t,o)},focus:function(){this.refs.select.focus()},blur:function(){this.refs.select.blur()},highlightFirstSelectableOption:function(e){var t,n,r;null==e&&(e=function(){}),this.state.open?(t=this.getComputedState(),n=t.options,r=t.value,this.refs.select.highlightAndScrollToSelectableOption(this.firstOptionIndexToHighlight(n,r),1,e)):e()},value:function(){return this.props.hasOwnProperty("value")?this.props.value:this.state.value},isOpen:function(){return this.props.hasOwnProperty("open")?this.props.open:this.state.open}})}).call(this)},function(e,t,n){(function(){var t,r,o,i,a;t=n(5),r=t.createClass,o=t.createFactory,i=t.DOM.path,a=o(n(85)),e.exports=r({getDefaultProps:function(){return{open:!1,flipped:!1}},render:function(){return a({className:"react-selectize-toggle-button",style:{width:10,height:8}},i({d:function(){switch(!1){case!(this.props.open&&!this.props.flipped||!this.props.open&&this.props.flipped):return"M0 6 L5 1 L10 6 Z";default:return"M0 1 L5 6 L10 1 Z"}}.call(this)}))}})}).call(this)},function(e,t,n){(function(){var t,r,o,i;t=n(5),r=t.createClass,o=t.DOM.div,i=n(16).isEqualToObject,e.exports=r({getDefaultProps:function(){return{}},render:function(){return o({className:"value-wrapper"},this.props.renderItem(this.props.item))},shouldComponentUpdate:function(e){var t;return!i(null!=e?e.uid:void 0,null!=(t=this.props)?t.uid:void 0)}})}).call(this)},function(e,t,n){(function(){var t,r,o,i;t=n(196),r=n(201),o=n(197),i=n(53),e.exports={HighlightedText:t,SimpleSelect:r,MultiSelect:o,ReactSelectize:i}}).call(this)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}t.__esModule=!0;var s=Object.assign||function(e){for(var t=1;t=0)&&r.push(o)}return r.push(e.ownerDocument.body),e.ownerDocument!==document&&r.push(e.ownerDocument.defaultView),r}function r(){w&&document.body.removeChild(w),w=null}function o(e){var n=void 0;e===document?(n=document,e=document.documentElement):n=e.ownerDocument;var r=n.documentElement,o=t(e),i=x();return o.top-=i.top,o.left-=i.left,"undefined"==typeof o.width&&(o.width=document.body.scrollWidth-o.left-o.right),"undefined"==typeof o.height&&(o.height=document.body.scrollHeight-o.top-o.bottom),o.top=o.top-r.clientTop,o.left=o.left-r.clientLeft, -o.right=n.body.clientWidth-o.width-o.left,o.bottom=n.body.clientHeight-o.height-o.top,o}function i(e){return e.offsetParent||document.documentElement}function a(){if(O)return O;var e=document.createElement("div");e.style.width="100%",e.style.height="200px";var t=document.createElement("div");s(t.style,{position:"absolute",top:0,left:0,pointerEvents:"none",visibility:"hidden",width:"200px",height:"150px",overflow:"hidden"}),t.appendChild(e),document.body.appendChild(t);var n=e.offsetWidth;t.style.overflow="scroll";var r=e.offsetWidth;n===r&&(r=t.clientWidth),document.body.removeChild(t);var o=n-r;return O={width:o,height:o}}function s(){var e=arguments.length<=0||void 0===arguments[0]?{}:arguments[0],t=[];return Array.prototype.push.apply(t,arguments),t.slice(1).forEach(function(t){if(t)for(var n in t)({}).hasOwnProperty.call(t,n)&&(e[n]=t[n])}),e}function u(e,t){if("undefined"!=typeof e.classList)t.split(" ").forEach(function(t){t.trim()&&e.classList.remove(t)});else{var n=new RegExp("(^| )"+t.split(" ").join("|")+"( |$)","gi"),r=p(e).replace(n," ");f(e,r)}}function l(e,t){if("undefined"!=typeof e.classList)t.split(" ").forEach(function(t){t.trim()&&e.classList.add(t)});else{u(e,t);var n=p(e)+(" "+t);f(e,n)}}function c(e,t){if("undefined"!=typeof e.classList)return e.classList.contains(t);var n=p(e);return new RegExp("(^| )"+t+"( |$)","gi").test(n)}function p(e){return e.className instanceof e.ownerDocument.defaultView.SVGAnimatedString?e.className.baseVal:e.className}function f(e,t){e.setAttribute("class",t)}function d(e,t,n){n.forEach(function(n){t.indexOf(n)===-1&&c(e,n)&&u(e,n)}),t.forEach(function(t){c(e,t)||l(e,t)})}function e(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function h(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function m(e,t){var n=arguments.length<=2||void 0===arguments[2]?1:arguments[2];return e+n>=t&&t>=e-n}function v(){return"object"==typeof performance&&"function"==typeof performance.now?performance.now():+new Date}function g(){for(var e={top:0,left:0},t=arguments.length,n=Array(t),r=0;r1?n-1:0),o=1;o16?(t=Math.min(t-16,250),void(n=setTimeout(r,250))):void("undefined"!=typeof e&&v()-e<10||(null!=n&&(clearTimeout(n),n=null),e=v(),L(),t=v()-e))};"undefined"!=typeof window&&"undefined"!=typeof window.addEventListener&&["resize","scroll","touchmove"].forEach(function(e){window.addEventListener(e,r)})}();var U={center:"center",left:"right",right:"left"},F={middle:"middle",top:"bottom",bottom:"top"},j={top:0,left:0,middle:"50%",center:"50%",bottom:"100%",right:"100%"},B=function(e,t){var n=e.left,r=e.top;return"auto"===n&&(n=U[t.left]),"auto"===r&&(r=F[t.top]),{left:n,top:r}},V=function(e){var t=e.left,n=e.top;return"undefined"!=typeof j[e.left]&&(t=j[e.left]),"undefined"!=typeof j[e.top]&&(n=j[e.top]),{left:t,top:n}},W=function(e){var t=e.split(" "),n=M(t,2),r=n[0],o=n[1];return{top:r,left:o}},H=W,q=function(t){function c(t){var n=this;e(this,c),A(Object.getPrototypeOf(c.prototype),"constructor",this).call(this),this.position=this.position.bind(this),R.push(this),this.history=[],this.setOptions(t,!1),_.modules.forEach(function(e){"undefined"!=typeof e.initialize&&e.initialize.call(n)}),this.position()}return h(c,t),C(c,[{key:"getClass",value:function(){var e=arguments.length<=0||void 0===arguments[0]?"":arguments[0],t=this.options.classes;return"undefined"!=typeof t&&t[e]?this.options.classes[e]:this.options.classPrefix?this.options.classPrefix+"-"+e:e}},{key:"setOptions",value:function(e){var t=this,r=arguments.length<=1||void 0===arguments[1]||arguments[1],o={offset:"0 0",targetOffset:"0 0",targetAttachment:"auto auto",classPrefix:"tether"};this.options=s(o,e);var i=this.options,a=i.element,u=i.target,c=i.targetModifier;if(this.element=a,this.target=u,this.targetModifier=c,"viewport"===this.target?(this.target=document.body,this.targetModifier="visible"):"scroll-handle"===this.target&&(this.target=document.body,this.targetModifier="scroll-handle"),["element","target"].forEach(function(e){if("undefined"==typeof t[e])throw new Error("Tether Error: Both element and target must be defined");"undefined"!=typeof t[e].jquery?t[e]=t[e][0]:"string"==typeof t[e]&&(t[e]=document.querySelector(t[e]))}),l(this.element,this.getClass("element")),this.options.addTargetClasses!==!1&&l(this.target,this.getClass("target")),!this.options.attachment)throw new Error("Tether Error: You must provide an attachment");this.targetAttachment=H(this.options.targetAttachment),this.attachment=H(this.options.attachment),this.offset=W(this.options.offset),this.targetOffset=W(this.options.targetOffset),"undefined"!=typeof this.scrollParents&&this.disable(),"scroll-handle"===this.targetModifier?this.scrollParents=[this.target]:this.scrollParents=n(this.target),this.options.enabled!==!1&&this.enable(r)}},{key:"getTargetBounds",value:function(){if("undefined"==typeof this.targetModifier)return o(this.target);if("visible"===this.targetModifier){if(this.target===document.body)return{top:pageYOffset,left:pageXOffset,height:innerHeight,width:innerWidth};var e=o(this.target),t={height:e.height,width:e.width,top:e.top,left:e.left};return t.height=Math.min(t.height,e.height-(pageYOffset-e.top)),t.height=Math.min(t.height,e.height-(e.top+e.height-(pageYOffset+innerHeight))),t.height=Math.min(innerHeight,t.height),t.height-=2,t.width=Math.min(t.width,e.width-(pageXOffset-e.left)),t.width=Math.min(t.width,e.width-(e.left+e.width-(pageXOffset+innerWidth))),t.width=Math.min(innerWidth,t.width),t.width-=2,t.topn.clientWidth||[r.overflow,r.overflowX].indexOf("scroll")>=0||this.target!==document.body,a=0;i&&(a=15);var s=e.height-parseFloat(r.borderTopWidth)-parseFloat(r.borderBottomWidth)-a,t={width:15,height:.975*s*(s/n.scrollHeight),left:e.left+e.width-parseFloat(r.borderLeftWidth)-15},u=0;s<408&&this.target===document.body&&(u=-11e-5*Math.pow(s,2)-.00727*s+22.58),this.target!==document.body&&(t.height=Math.max(t.height,24));var l=this.target.scrollTop/(n.scrollHeight-s);return t.top=l*(s-t.height-u)+e.top+parseFloat(r.borderTopWidth),this.target===document.body&&(t.height=Math.max(t.height,24)),t}}},{key:"clearCache",value:function(){this._cache={}}},{key:"cache",value:function(e,t){return"undefined"==typeof this._cache&&(this._cache={}),"undefined"==typeof this._cache[e]&&(this._cache[e]=t.call(this)),this._cache[e]}},{key:"enable",value:function(){var e=this,t=arguments.length<=0||void 0===arguments[0]||arguments[0];this.options.addTargetClasses!==!1&&l(this.target,this.getClass("enabled")),l(this.element,this.getClass("enabled")),this.enabled=!0,this.scrollParents.forEach(function(t){t!==e.target.ownerDocument&&t.addEventListener("scroll",e.position)}),t&&this.position()}},{key:"disable",value:function(){var e=this;u(this.target,this.getClass("enabled")),u(this.element,this.getClass("enabled")),this.enabled=!1,"undefined"!=typeof this.scrollParents&&this.scrollParents.forEach(function(t){t.removeEventListener("scroll",e.position)})}},{key:"destroy",value:function(){var e=this;this.disable(),R.forEach(function(t,n){t===e&&R.splice(n,1)}),0===R.length&&r()}},{key:"updateAttachClasses",value:function(e,t){var n=this;e=e||this.attachment,t=t||this.targetAttachment;var r=["left","top","bottom","right","middle","center"];"undefined"!=typeof this._addAttachClasses&&this._addAttachClasses.length&&this._addAttachClasses.splice(0,this._addAttachClasses.length),"undefined"==typeof this._addAttachClasses&&(this._addAttachClasses=[]);var o=this._addAttachClasses;e.top&&o.push(this.getClass("element-attached")+"-"+e.top),e.left&&o.push(this.getClass("element-attached")+"-"+e.left),t.top&&o.push(this.getClass("target-attached")+"-"+t.top),t.left&&o.push(this.getClass("target-attached")+"-"+t.left);var i=[];r.forEach(function(e){i.push(n.getClass("element-attached")+"-"+e),i.push(n.getClass("target-attached")+"-"+e)}),k(function(){"undefined"!=typeof n._addAttachClasses&&(d(n.element,n._addAttachClasses,i),n.options.addTargetClasses!==!1&&d(n.target,n._addAttachClasses,i),delete n._addAttachClasses)})}},{key:"position",value:function(){var e=this,t=arguments.length<=0||void 0===arguments[0]||arguments[0];if(this.enabled){this.clearCache();var n=B(this.targetAttachment,this.attachment);this.updateAttachClasses(this.attachment,n);var r=this.cache("element-bounds",function(){return o(e.element)}),s=r.width,u=r.height;if(0===s&&0===u&&"undefined"!=typeof this.lastSize){var l=this.lastSize;s=l.width,u=l.height}else this.lastSize={width:s,height:u};var c=this.cache("target-bounds",function(){return e.getTargetBounds()}),p=c,f=y(V(this.attachment),{width:s,height:u}),d=y(V(n),p),h=y(this.offset,{width:s,height:u}),m=y(this.targetOffset,p);f=g(f,h),d=g(d,m);for(var v=c.left+d.left-f.left,b=c.top+d.top-f.top,C=0;C<_.modules.length;++C){var w=_.modules[C],E=w.position.call(this,{left:v,top:b,targetAttachment:n,targetPos:c,elementPos:r,offset:f,targetOffset:d,manualOffset:h,manualTargetOffset:m,scrollbarSize:P,attachment:this.attachment});if(E===!1)return!1;"undefined"!=typeof E&&"object"==typeof E&&(b=E.top,v=E.left)}var T={page:{top:b,left:v},viewport:{top:b-pageYOffset,bottom:pageYOffset-b-u+innerHeight,left:v-pageXOffset,right:pageXOffset-v-s+innerWidth}},x=this.target.ownerDocument,O=x.defaultView,P=void 0;return O.innerHeight>x.documentElement.clientHeight&&(P=this.cache("scrollbar-size",a),T.viewport.bottom-=P.height),O.innerWidth>x.documentElement.clientWidth&&(P=this.cache("scrollbar-size",a),T.viewport.right-=P.width),["","static"].indexOf(x.body.style.position)!==-1&&["","static"].indexOf(x.body.parentElement.style.position)!==-1||(T.page.bottom=x.body.scrollHeight-b-u,T.page.right=x.body.scrollWidth-v-s),"undefined"!=typeof this.options.optimizations&&this.options.optimizations.moveElement!==!1&&"undefined"==typeof this.targetModifier&&!function(){var t=e.cache("target-offsetparent",function(){return i(e.target)}),n=e.cache("target-offsetparent-bounds",function(){return o(t)}),r=getComputedStyle(t),a=n,s={};if(["Top","Left","Bottom","Right"].forEach(function(e){s[e.toLowerCase()]=parseFloat(r["border"+e+"Width"])}),n.right=x.body.scrollWidth-n.left-a.width+s.right,n.bottom=x.body.scrollHeight-n.top-a.height+s.bottom,T.page.top>=n.top+s.top&&T.page.bottom>=n.bottom&&T.page.left>=n.left+s.left&&T.page.right>=n.right){var u=t.scrollTop,l=t.scrollLeft;T.offset={top:T.page.top-n.top+u-s.top,left:T.page.left-n.left+l-s.left}}}(),this.move(T),this.history.unshift(T),this.history.length>3&&this.history.pop(),t&&S(),!0}}},{key:"move",value:function(e){var t=this;if("undefined"!=typeof this.element.parentNode){var n={};for(var r in e){n[r]={};for(var o in e[r]){for(var a=!1,u=0;u=0){var d=a.split(" "),m=M(d,2);p=m[0],c=m[1]}else c=p=a;var C=b(t,o);"target"!==p&&"both"!==p||(nC[3]&&"bottom"===g.top&&(n-=f,g.top="top")),"together"===p&&("top"===g.top&&("bottom"===y.top&&nC[3]&&n-(u-f)>=C[1]&&(n-=u-f,g.top="bottom",y.top="bottom")),"bottom"===g.top&&("top"===y.top&&n+u>C[3]?(n-=f,g.top="top",n-=u,y.top="bottom"):"bottom"===y.top&&nC[3]&&"top"===y.top?(n-=u,y.top="bottom"):nC[2]&&"right"===g.left&&(r-=h,g.left="left")),"together"===c&&(rC[2]&&"right"===g.left?"left"===y.left?(r-=h,g.left="left",r-=l,y.left="right"):"right"===y.left&&(r-=h,g.left="left",r+=l,y.left="left"):"center"===g.left&&(r+l>C[2]&&"left"===y.left?(r-=l,y.left="right"):rC[3]&&"top"===y.top&&(n-=u,y.top="bottom")),"element"!==c&&"both"!==c||(rC[2]&&("left"===y.left?(r-=l,y.left="right"):"center"===y.left&&(r-=l/2,y.left="right"))),"string"==typeof s?s=s.split(",").map(function(e){return e.trim()}):s===!0&&(s=["top","left","right","bottom"]),s=s||[];var _=[],w=[];n=0?(n=C[1],_.push("top")):w.push("top")),n+u>C[3]&&(s.indexOf("bottom")>=0?(n=C[3]-u,_.push("bottom")):w.push("bottom")),r=0?(r=C[0],_.push("left")):w.push("left")),r+l>C[2]&&(s.indexOf("right")>=0?(r=C[2]-l,_.push("right")):w.push("right")),_.length&&!function(){var e=void 0;e="undefined"!=typeof t.options.pinnedClass?t.options.pinnedClass:t.getClass("pinned"),v.push(e),_.forEach(function(t){v.push(e+"-"+t)})}(),w.length&&!function(){var e=void 0;e="undefined"!=typeof t.options.outOfBoundsClass?t.options.outOfBoundsClass:t.getClass("out-of-bounds"),v.push(e),w.forEach(function(t){v.push(e+"-"+t)})}(),(_.indexOf("left")>=0||_.indexOf("right")>=0)&&(y.left=g.left=!1),(_.indexOf("top")>=0||_.indexOf("bottom")>=0)&&(y.top=g.top=!1),g.top===i.top&&g.left===i.left&&y.top===t.attachment.top&&y.left===t.attachment.left||(t.updateAttachClasses(y,g),t.trigger("update",{attachment:y,targetAttachment:g}))}),k(function(){t.options.addTargetClasses!==!1&&d(t.target,v,m),d(t.element,v,m)}),{top:n,left:r}}});var I=_.Utils,o=I.getBounds,d=I.updateClasses,k=I.defer;_.modules.push({position:function(e){var t=this,n=e.top,r=e.left,i=this.cache("element-bounds",function(){return o(t.element)}),a=i.height,s=i.width,u=this.getTargetBounds(),l=n+a,c=r+s,p=[];n<=u.bottom&&l>=u.top&&["left","right"].forEach(function(e){var t=u[e];t!==r&&t!==c||p.push(e)}),r<=u.right&&c>=u.left&&["top","bottom"].forEach(function(e){var t=u[e];t!==n&&t!==l||p.push(e)});var f=[],h=[],m=["left","top","right","bottom"];return f.push(this.getClass("abutted")),m.forEach(function(e){f.push(t.getClass("abutted")+"-"+e)}),p.length&&h.push(this.getClass("abutted")),p.forEach(function(e){h.push(t.getClass("abutted")+"-"+e)}),k(function(){t.options.addTargetClasses!==!1&&d(t.target,h,f),d(t.element,h,f)}),!0}});var M=function(){function e(e,t){var n=[],r=!0,o=!1,i=void 0;try{for(var a,s=e[Symbol.iterator]();!(r=(a=s.next()).done)&&(n.push(a.value),!t||n.length!==t);r=!0);}catch(e){o=!0,i=e}finally{try{!r&&s.return&&s.return()}finally{if(o)throw i}}return n}return function(t,n){if(Array.isArray(t))return t;if(Symbol.iterator in Object(t))return e(t,n);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}();return _.modules.push({position:function(e){var t=e.top,n=e.left;if(this.options.shift){var r=this.options.shift;"function"==typeof this.options.shift&&(r=this.options.shift.call(this,{top:t,left:n}));var o=void 0,i=void 0;if("string"==typeof r){r=r.split(" "),r[1]=r[1]||r[0];var a=r,s=M(a,2);o=s[0],i=s[1],o=parseFloat(o,10),i=parseFloat(i,10)}else o=r.top,i=r.left;return t+=o,n+=i,{top:t,left:n}}}}),z})},function(e,t,n){"use strict";var r=function(){};e.exports=r},function(e,t){!function(e){"use strict";function t(e){if("string"!=typeof e&&(e=String(e)),/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(e))throw new TypeError("Invalid character in header field name");return e.toLowerCase()}function n(e){return"string"!=typeof e&&(e=String(e)),e}function r(e){var t={next:function(){var t=e.shift();return{done:void 0===t,value:t}}};return g.iterable&&(t[Symbol.iterator]=function(){return t}),t}function o(e){this.map={},e instanceof o?e.forEach(function(e,t){this.append(t,e)},this):e&&Object.getOwnPropertyNames(e).forEach(function(t){this.append(t,e[t])},this)}function i(e){return e.bodyUsed?Promise.reject(new TypeError("Already read")):void(e.bodyUsed=!0)}function a(e){return new Promise(function(t,n){e.onload=function(){t(e.result)},e.onerror=function(){n(e.error)}})}function s(e){var t=new FileReader,n=a(t);return t.readAsArrayBuffer(e),n}function u(e){var t=new FileReader,n=a(t);return t.readAsText(e),n}function l(e){for(var t=new Uint8Array(e),n=new Array(t.length),r=0;r-1?t:e}function d(e,t){t=t||{};var n=t.body;if(e instanceof d){if(e.bodyUsed)throw new TypeError("Already read");this.url=e.url,this.credentials=e.credentials,t.headers||(this.headers=new o(e.headers)),this.method=e.method,this.mode=e.mode,n||null==e._bodyInit||(n=e._bodyInit,e.bodyUsed=!0)}else this.url=String(e);if(this.credentials=t.credentials||this.credentials||"omit",!t.headers&&this.headers||(this.headers=new o(t.headers)),this.method=f(t.method||this.method||"GET"),this.mode=t.mode||this.mode||null,this.referrer=null,("GET"===this.method||"HEAD"===this.method)&&n)throw new TypeError("Body not allowed for GET or HEAD requests");this._initBody(n)}function h(e){var t=new FormData;return e.trim().split("&").forEach(function(e){if(e){var n=e.split("="),r=n.shift().replace(/\+/g," "),o=n.join("=").replace(/\+/g," ");t.append(decodeURIComponent(r),decodeURIComponent(o))}}),t}function m(e){var t=new o;return e.split(/\r?\n/).forEach(function(e){var n=e.split(":"),r=n.shift().trim();if(r){var o=n.join(":").trim();t.append(r,o)}}),t}function v(e,t){t||(t={}),this.type="default",this.status="status"in t?t.status:200,this.ok=this.status>=200&&this.status<300,this.statusText="statusText"in t?t.statusText:"OK",this.headers=new o(t.headers),this.url=t.url||"",this._initBody(e)}if(!e.fetch){var g={searchParams:"URLSearchParams"in e,iterable:"Symbol"in e&&"iterator"in Symbol,blob:"FileReader"in e&&"Blob"in e&&function(){try{return new Blob,!0}catch(e){return!1}}(),formData:"FormData"in e,arrayBuffer:"ArrayBuffer"in e};if(g.arrayBuffer)var y=["[object Int8Array]","[object Uint8Array]","[object Uint8ClampedArray]","[object Int16Array]","[object Uint16Array]","[object Int32Array]","[object Uint32Array]","[object Float32Array]","[object Float64Array]"],b=function(e){return e&&DataView.prototype.isPrototypeOf(e)},C=ArrayBuffer.isView||function(e){return e&&y.indexOf(Object.prototype.toString.call(e))>-1};o.prototype.append=function(e,r){e=t(e),r=n(r);var o=this.map[e];this.map[e]=o?o+","+r:r},o.prototype.delete=function(e){delete this.map[t(e)]},o.prototype.get=function(e){return e=t(e),this.has(e)?this.map[e]:null},o.prototype.has=function(e){return this.map.hasOwnProperty(t(e))},o.prototype.set=function(e,r){this.map[t(e)]=n(r)},o.prototype.forEach=function(e,t){for(var n in this.map)this.map.hasOwnProperty(n)&&e.call(t,this.map[n],n,this)},o.prototype.keys=function(){var e=[];return this.forEach(function(t,n){e.push(n)}),r(e)},o.prototype.values=function(){var e=[];return this.forEach(function(t){e.push(t)}),r(e)},o.prototype.entries=function(){var e=[];return this.forEach(function(t,n){e.push([n,t])}),r(e)},g.iterable&&(o.prototype[Symbol.iterator]=o.prototype.entries);var _=["DELETE","GET","HEAD","OPTIONS","POST","PUT"];d.prototype.clone=function(){return new d(this,{body:this._bodyInit})},p.call(d.prototype),p.call(v.prototype),v.prototype.clone=function(){return new v(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new o(this.headers),url:this.url})},v.error=function(){var e=new v(null,{status:0,statusText:""});return e.type="error",e};var w=[301,302,303,307,308];v.redirect=function(e,t){if(w.indexOf(t)===-1)throw new RangeError("Invalid status code");return new v(null,{status:t,headers:{location:e}})},e.Headers=o,e.Request=d,e.Response=v,e.fetch=function(e,t){return new Promise(function(n,r){var o=new d(e,t),i=new XMLHttpRequest;i.onload=function(){var e={status:i.status,statusText:i.statusText,headers:m(i.getAllResponseHeaders()||"")};e.url="responseURL"in i?i.responseURL:e.headers.get("X-Request-URL");var t="response"in i?i.response:i.responseText;n(new v(t,e))},i.onerror=function(){r(new TypeError("Network request failed"))},i.ontimeout=function(){r(new TypeError("Network request failed"))},i.open(o.method,o.url,!0),"include"===o.credentials&&(i.withCredentials=!0),"responseType"in i&&g.blob&&(i.responseType="blob"),o.headers.forEach(function(e,t){i.setRequestHeader(t,e)}),i.send("undefined"==typeof o._bodyInit?null:o._bodyInit)})},e.fetch.polyfill=!0}}("undefined"!=typeof self?self:this)},function(e,t,n,r,o,i,a,s){function u(e,t){var n,r=function(o){return e.length>1?function(){var i=o?o.concat():[];return n=t?n||this:this,i.push.apply(i,arguments).":"function"==typeof t?" Instead of passing a class like Foo, pass React.createElement(Foo) or .":null!=t&&void 0!==t.props?" This may be caused by unintentionally loading two independent copies of React.":"");var a,s=v.createElement(F,{child:t});if(e){var u=w.get(e);a=u._processChildContext(u._context)}else a=P;var c=f(n);if(c){var p=c._currentElement,h=p.props.child;if(N(h,t)){var m=c._renderedComponent.getPublicInstance(),g=r&&function(){r.call(m)};return j._updateRootComponent(c,s,a,n,g),m}j.unmountComponentAtNode(n)}var y=o(n),b=y&&!!i(y),C=l(n),_=b&&!c&&!C,E=j._renderNewRootComponent(s,n,_,a)._renderedComponent.getPublicInstance();return r&&r.call(E),E},render:function(e,t,n){return j._renderSubtreeIntoContainer(null,e,t,n)},unmountComponentAtNode:function(e){c(e)?void 0:d("40");var t=f(e);if(!t){l(e),1===e.nodeType&&e.hasAttribute(A);return!1}return delete L[t._instance.rootID],O.batchedUpdates(u,t,e,!1),!0},_mountImageIntoNode:function(e,t,n,i,a){if(c(t)?void 0:d("41"),i){var s=o(t);if(E.canReuseMarkup(e,s))return void y.precacheNode(n,s);var u=s.getAttribute(E.CHECKSUM_ATTR_NAME);s.removeAttribute(E.CHECKSUM_ATTR_NAME);var l=s.outerHTML;s.setAttribute(E.CHECKSUM_ATTR_NAME,u);var p=e,f=r(p,l),m=" (client) "+p.substring(f-20,f+20)+"\n (server) "+l.substring(f-20,f+20);t.nodeType===D?d("42",m):void 0}if(t.nodeType===D?d("43"):void 0,a.useCreateElement){for(;t.lastChild;)t.removeChild(t.lastChild);h.insertTreeBefore(t,e,null)}else S(t,e),y.precacheNode(n,t.firstChild)}};e.exports=j},function(e,t,n){"use strict";var r=n(3),o=n(20),i=(n(1),{HOST:0,COMPOSITE:1,EMPTY:2,getType:function(e){return null===e||e===!1?i.EMPTY:o.isValidElement(e)?"function"==typeof e.type?i.COMPOSITE:i.HOST:void r("26",e)}});e.exports=i},function(e,t){"use strict";var n={currentScrollLeft:0,currentScrollTop:0,refreshScrollValues:function(e){n.currentScrollLeft=e.x,n.currentScrollTop=e.y}};e.exports=n},function(e,t,n){"use strict";function r(e,t){return null==t?o("30"):void 0,null==e?t:Array.isArray(e)?Array.isArray(t)?(e.push.apply(e,t),e):(e.push(t),e):Array.isArray(t)?[e].concat(t):[e,t]}var o=n(3);n(1);e.exports=r},function(e,t){"use strict";function n(e,t,n){Array.isArray(e)?e.forEach(t,n):e&&t.call(n,e)}e.exports=n},function(e,t,n){"use strict";function r(e){for(var t;(t=e._renderedNodeType)===o.COMPOSITE;)e=e._renderedComponent;return t===o.HOST?e._renderedComponent:t===o.EMPTY?null:void 0}var o=n(74);e.exports=r},function(e,t,n){"use strict";function r(){return!i&&o.canUseDOM&&(i="textContent"in document.documentElement?"textContent":"innerText"),i}var o=n(7),i=null;e.exports=r},function(e,t,n){"use strict";function r(e){if(e){var t=e.getName();if(t)return" Check the render method of `"+t+"`."}return""}function o(e){return"function"==typeof e&&"undefined"!=typeof e.prototype&&"function"==typeof e.prototype.mountComponent&&"function"==typeof e.prototype.receiveComponent}function i(e,t){var n;if(null===e||e===!1)n=l.create(i);else if("object"==typeof e){var s=e,u=s.type;if("function"!=typeof u&&"string"!=typeof u){var f="";f+=r(s._owner),a("130",null==u?u:typeof u,f)}"string"==typeof s.type?n=c.createInternalComponent(s):o(s.type)?(n=new s.type(s),n.getHostNode||(n.getHostNode=n.getNativeNode)):n=new p(s)}else"string"==typeof e||"number"==typeof e?n=c.createInstanceForText(e):a("131",typeof e);return n._mountIndex=0,n._mountImage=null,n}var a=n(3),s=n(4),u=n(137),l=n(69),c=n(71),p=(n(221),n(1),n(2),function(e){this.construct(e)});s(p.prototype,u,{_instantiateReactComponent:i}),e.exports=i},function(e,t){"use strict";function n(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return"input"===t?!!r[e.type]:"textarea"===t}var r={color:!0,date:!0,datetime:!0,"datetime-local":!0,email:!0,month:!0,number:!0,password:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0};e.exports=n},function(e,t,n){"use strict";var r=n(7),o=n(32),i=n(33),a=function(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&3===n.nodeType)return void(n.nodeValue=t)}e.textContent=t};r.canUseDOM&&("textContent"in document.documentElement||(a=function(e,t){return 3===e.nodeType?void(e.nodeValue=t):void i(e,o(t))})),e.exports=a},function(e,t,n){"use strict";function r(e,t){return e&&"object"==typeof e&&null!=e.key?l.escape(e.key):t.toString(36)}function o(e,t,n,i){var f=typeof e;if("undefined"!==f&&"boolean"!==f||(e=null),null===e||"string"===f||"number"===f||"object"===f&&e.$$typeof===s)return n(i,e,""===t?c+r(e,0):t),1;var d,h,m=0,v=""===t?c:t+p;if(Array.isArray(e))for(var g=0;gc){for(var t=0,n=s.length-l;t-1}).map(function(e,t){return l.default.createElement("option",{key:t,value:e.name},e.name)})}},{key:"getValues",value:function(e){return e?e.map(function(e){return{label:e,value:e}}):[]}},{key:"render",value:function(){var e=this,t=this.props.parameters.find(function(t){return t.value===e.props.condition.parameter});return this.props.condition.type=t?t.type:null,l.default.createElement("div",{className:this.props.classes.filterLineRow},l.default.createElement("div",{className:this.props.classes.filterLineParameter},l.default.createElement("select",{className:this.props.classes.filterLineInput,name:"parameter",value:this.props.condition.parameter,onChange:this.handleInputChange},l.default.createElement("option",{value:""},"-- Parameter --"),this.getCoefficients(this.props.parameters))),l.default.createElement("div",{className:this.props.classes.filterLineOperator,style:{"padding-left":0,"padding-right":0}},l.default.createElement("select",{className:this.props.classes.filterLineInput,name:"operator",value:this.props.condition.operator,onChange:this.handleInputChange},l.default.createElement("option",{disabled:!0,value:""},"-- Operator --"),this.getOperators(this.props.operators,this.props.parameters.find(function(t){return t.value===e.props.condition.parameter})))),l.default.createElement("div",{className:this.props.classes.filterLineValue},l.default.createElement(c.MultiSelect,{style:{width:"100%"},placeholder:"-- Value --",theme:"bootstrap3",values:this.getValues(this.props.condition.value),onValuesChange:this.handleValueChange,uid:function(e){return e.value},restoreOnBackspace:function(e){return e.label.toString()},createFromSearch:function(t,n,r){return e.labels=n.map(function(e){return e.label}),0===r.trim().length||e.labels.indexOf(r.trim())!==-1?null:{label:r.trim(),value:r.trim()}},renderNoResultsFound:function(e,t){return l.default.createElement("div",{className:"no-results-found"},function(){return 0===t.trim().length?"Enter a new value":e.map(function(e){return e.label}).indexOf(t.trim())!==-1?"Value already exists":void 0}())}})))}}]),t}(u.Component);t.default=p},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var s=function(){function e(e,t){for(var n=0;n1){var t=this.state.conditions;t.splice(e,1),this.setState({conditions:t})}}},{key:"componentDidUpdate",value:function(e,t){t!==this.state&&this.props.config.updateConditions(this.state.conditions)}},{key:"render",value:function(){var e=this,t=this.state.conditions.map(function(t,n){return l.default.createElement("div",{key:n},l.default.createElement(d.default,{index:n,classes:e.props.config.classes,addCondition:e.addCondition,removeCondition:e.removeCondition}),l.default.createElement(p.default,{parameters:e.props.config.parameters,operators:e.props.config.operators,condition:t,index:n,classes:e.props.config.classes,onChange:e.updateCondition}))});return l.default.createElement("div",{className:"form-horizontal"},t)}}]),t}(u.Component);t.default=h},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}var o=n(5),i=r(o),a=n(13),s=r(a),u=n(93),l=r(u),c=window.$;if(c.fn.filterer=function(e){e.operators=[{name:"contains",types:["string","str"]},{name:"does not contain",types:["string","str"]},{name:"is",types:["string","str","number","int","float"]},{name:"is not",types:["string","str","number","int","float"]},{name:"begins with",types:["string","str"]},{name:"does not begin with",types:["string","str"]},{name:"ends with",types:["string","str"]},{name:"does not end with",types:["string","str"]},{name:"is greater than",types:["number","int","float"]},{name:"is less than",types:["number","int","float"]}],e.classes=Object.assign({plusIcon:"fa fa-fw fa-plus",minusIcon:"fa fa-fw fa-minus",filterLineRow:"form-group",filterLineParameter:"col-sm-4",filterLineOperator:"col-sm-3",filterLineValue:"col-sm-5",filterLineInput:"form-control",filterLineLabelRow:"row",filterLineLabelCondition:"col-sm-10",filterLineLabelControls:"col-sm-2 text-right"},e.classes),this.each(function(){s.default.render(i.default.createElement(l.default,{id:"filterer",config:e}),this)})},window.wcomartin_filterer_demo){var p={parameters:[{name:"Title",type:"string",value:"title"},{name:"Year",type:"number",value:"year"}],conditions:[{parameter:"year",operator:"is",value:[2017]}]};p.updateConditions=function(e){console.log(JSON.stringify(e))},c("#root").filterer(p)}},function(e,t){e.exports=function(){for(var e=arguments.length,t=[],n=0;n":a.innerHTML="<"+e+">",s[e]=!a.firstChild),s[e]?f[e]:null}var o=n(7),i=n(1),a=o.canUseDOM?document.createElement("div"):null,s={},u=[1,'"],l=[1,"","
    "],c=[3,"","
    "],p=[1,'',""],f={"*":[1,"?
    ","
    "],area:[1,"",""],col:[2,"","
    "],legend:[1,"
    ","
    "],param:[1,"",""],tr:[2,"","
    "],optgroup:u,option:u,caption:l,colgroup:l,tbody:l,tfoot:l,thead:l,td:c,th:c},d=["circle","clipPath","defs","ellipse","g","image","line","linearGradient","mask","path","pattern","polygon","polyline","radialGradient","rect","stop","text","tspan"];d.forEach(function(e){f[e]=p,s[e]=!0}),e.exports=r},function(e,t){"use strict";function n(e){return e.Window&&e instanceof e.Window?{x:e.pageXOffset||e.document.documentElement.scrollLeft,y:e.pageYOffset||e.document.documentElement.scrollTop}:{x:e.scrollLeft,y:e.scrollTop}}e.exports=n},function(e,t){"use strict";function n(e){return e.replace(r,"-$1").toLowerCase()}var r=/([A-Z])/g;e.exports=n},function(e,t,n){"use strict";function r(e){return o(e).replace(i,"-ms-")}var o=n(108),i=/^ms-/;e.exports=r},function(e,t){"use strict";function n(e){var t=e?e.ownerDocument||e:document,n=t.defaultView||window;return!(!e||!("function"==typeof n.Node?e instanceof n.Node:"object"==typeof e&&"number"==typeof e.nodeType&&"string"==typeof e.nodeName))}e.exports=n},function(e,t,n){"use strict";function r(e){return o(e)&&3==e.nodeType}var o=n(110);e.exports=r},function(e,t){"use strict";function n(e){var t={};return function(n){return t.hasOwnProperty(n)||(t[n]=e.call(this,n)),t[n]}}e.exports=n},function(e,t){function n(e,t){var n,r=function(o){return e.length>1?function(){var i=o?o.concat():[];return n=t?n||this:this,i.push.apply(i,arguments)1?function(){var i=o?o.concat():[];return n=t?n||this:this,i.push.apply(i,arguments)>>0;++n=0;--r)o=n[r],t=e(o,t);return t}),k=n(function(e,t){return P(e,t[t.length-1],t.slice(0,-1))}),S=n(function(e,t){var n,r,o;for(n=[],r=t;null!=(o=e(r));)n.push(o[0]),r=o[1];return n}),N=function(e){return[].concat.apply([],e)},M=n(function(e,t){var n; +return[].concat.apply([],function(){var r,o,i,a=[];for(r=0,i=(o=t).length;rt?1:ee(n)?1:e(t)t&&(t=i);return t},Q=function(e){var t,n,r,o,i;for(t=e[0],n=0,o=(r=e.slice(1)).length;ne(n)&&(n=a);return n}),Z=n(function(e,t){var n,r,o,i,a;for(n=t[0],r=0,i=(o=t.slice(1)).length;r1?function(){var i=o?o.concat():[];return n=t?n||this:this,i.push.apply(i,arguments)t?e:t}),o=n(function(e,t){return e0?1:0},u=n(function(e,t){return~~(e/t)}),l=n(function(e,t){return e%t}),c=n(function(e,t){return Math.floor(e/t)}),p=n(function(e,t){var n;return(e%(n=t)+n)%n}),f=function(e){return 1/e},d=Math.PI,h=2*d,m=Math.exp,v=Math.sqrt,g=Math.log,y=n(function(e,t){return Math.pow(e,t)}),b=Math.sin,C=Math.tan,_=Math.cos,w=Math.asin,E=Math.acos,T=Math.atan,x=n(function(e,t){return Math.atan2(e,t)}),O=function(e){return~~e},P=Math.round,k=Math.ceil,S=Math.floor,N=function(e){return e!==e},M=function(e){return e%2===0},A=function(e){return e%2!==0},I=n(function(e,t){var n;for(e=Math.abs(e),t=Math.abs(t);0!==t;)n=e%t,e=t,t=n;return e}),D=n(function(e,t){return Math.abs(Math.floor(e/I(e,t)*t))}),e.exports={max:r,min:o,negate:i,abs:a,signum:s,quot:u,rem:l,div:c,mod:p,recip:f,pi:d,tau:h,exp:m,sqrt:v,ln:g,pow:y,sin:b,tan:C,cos:_,acos:E,asin:w,atan:T,atan2:x,truncate:O,round:P,ceiling:k,floor:S,isItNaN:N,even:M,odd:A,gcd:I,lcm:D}},function(e,t){function n(e,t){var n,r=function(o){return e.length>1?function(){var i=o?o.concat():[];return n=t?n||this:this,i.push.apply(i,arguments)1?function(){var i=o?o.concat():[];return n=t?n||this:this,i.push.apply(i,arguments)1?n:n.toLowerCase())}).replace(/^([A-Z]+)/,function(e,t){return t.length>1?t+"-":t.toLowerCase()})},e.exports={split:r,join:o,lines:i,unlines:a,words:s,unwords:u,chars:l,unchars:c,reverse:p,repeat:f,capitalize:d,camelize:h,dasherize:m}},[227,113,114,116,117,115],function(e,t,n){"use strict";function r(e){var t=new o(o._61);return t._81=1,t._65=e,t}var o=n(61);e.exports=o;var i=r(!0),a=r(!1),s=r(null),u=r(void 0),l=r(0),c=r("");o.resolve=function(e){if(e instanceof o)return e;if(null===e)return s;if(void 0===e)return u;if(e===!0)return i;if(e===!1)return a;if(0===e)return l;if(""===e)return c;if("object"==typeof e||"function"==typeof e)try{var t=e.then;if("function"==typeof t)return new o(t.bind(e))}catch(e){return new o(function(t,n){n(e)})}return r(e)},o.all=function(e){var t=Array.prototype.slice.call(e);return new o(function(e,n){function r(a,s){if(s&&("object"==typeof s||"function"==typeof s)){if(s instanceof o&&s.then===o.prototype.then){for(;3===s._81;)s=s._65;return 1===s._81?r(a,s._65):(2===s._81&&n(s._65),void s.then(function(e){r(a,e)},n))}var u=s.then;if("function"==typeof u){var l=new o(u.bind(s));return void l.then(function(e){r(a,e)},n)}}t[a]=s,0===--i&&e(t)}if(0===t.length)return e([]);for(var i=t.length,a=0;a8&&_<=11),T=32,x=String.fromCharCode(T),O={beforeInput:{phasedRegistrationNames:{bubbled:"onBeforeInput",captured:"onBeforeInputCapture"},dependencies:["topCompositionEnd","topKeyPress","topTextInput","topPaste"]},compositionEnd:{phasedRegistrationNames:{bubbled:"onCompositionEnd",captured:"onCompositionEndCapture"},dependencies:["topBlur","topCompositionEnd","topKeyDown","topKeyPress","topKeyUp","topMouseDown"]},compositionStart:{phasedRegistrationNames:{bubbled:"onCompositionStart",captured:"onCompositionStartCapture"},dependencies:["topBlur","topCompositionStart","topKeyDown","topKeyPress","topKeyUp","topMouseDown"]},compositionUpdate:{phasedRegistrationNames:{bubbled:"onCompositionUpdate",captured:"onCompositionUpdateCapture"},dependencies:["topBlur","topCompositionUpdate","topKeyDown","topKeyPress","topKeyUp","topMouseDown"]}},P=!1,k=null,S={eventTypes:O,extractEvents:function(e,t,n,r){return[l(e,t,n,r),f(e,t,n,r)]}};e.exports=S},function(e,t,n){"use strict";var r=n(64),o=n(7),i=(n(9),n(102),n(179)),a=n(109),s=n(112),u=(n(2),s(function(e){return a(e)})),l=!1,c="cssFloat";if(o.canUseDOM){var p=document.createElement("div").style;try{p.font=""}catch(e){l=!0}void 0===document.documentElement.style.cssFloat&&(c="styleFloat")}var f={createMarkupForStyles:function(e,t){var n="";for(var r in e)if(e.hasOwnProperty(r)){var o=e[r];null!=o&&(n+=u(r)+":",n+=i(r,o,t)+";")}return n||null},setValueForStyles:function(e,t,n){var o=e.style;for(var a in t)if(t.hasOwnProperty(a)){var s=i(a,t[a],n);if("float"!==a&&"cssFloat"!==a||(a=c),s)o[a]=s;else{var u=l&&r.shorthandPropertyExpansions[a];if(u)for(var p in u)o[p]="";else o[a]=""}}}};e.exports=f},function(e,t,n){"use strict";function r(e){var t=e.nodeName&&e.nodeName.toLowerCase();return"select"===t||"input"===t&&"file"===e.type}function o(e){var t=T.getPooled(k.change,N,e,x(e));C.accumulateTwoPhaseDispatches(t),E.batchedUpdates(i,t)}function i(e){b.enqueueEvents(e),b.processEventQueue(!1)}function a(e,t){S=e,N=t,S.attachEvent("onchange",o)}function s(){S&&(S.detachEvent("onchange",o),S=null,N=null)}function u(e,t){if("topChange"===e)return t}function l(e,t,n){"topFocus"===e?(s(),a(t,n)):"topBlur"===e&&s()}function c(e,t){S=e,N=t,M=e.value,A=Object.getOwnPropertyDescriptor(e.constructor.prototype,"value"),Object.defineProperty(S,"value",R),S.attachEvent?S.attachEvent("onpropertychange",f):S.addEventListener("propertychange",f,!1)}function p(){S&&(delete S.value,S.detachEvent?S.detachEvent("onpropertychange",f):S.removeEventListener("propertychange",f,!1),S=null,N=null,M=null,A=null)}function f(e){if("value"===e.propertyName){var t=e.srcElement.value;t!==M&&(M=t,o(e))}}function d(e,t){if("topInput"===e)return t}function h(e,t,n){"topFocus"===e?(p(),c(t,n)):"topBlur"===e&&p()}function m(e,t){if(("topSelectionChange"===e||"topKeyUp"===e||"topKeyDown"===e)&&S&&S.value!==M)return M=S.value,N}function v(e){return e.nodeName&&"input"===e.nodeName.toLowerCase()&&("checkbox"===e.type||"radio"===e.type)}function g(e,t){if("topClick"===e)return t}function y(e,t){if(null!=e){var n=e._wrapperState||t._wrapperState;if(n&&n.controlled&&"number"===t.type){var r=""+t.value;t.getAttribute("value")!==r&&t.setAttribute("value",r)}}}var b=n(24),C=n(25),_=n(7),w=n(6),E=n(10),T=n(11),x=n(49),O=n(50),P=n(81),k={change:{phasedRegistrationNames:{bubbled:"onChange",captured:"onChangeCapture"},dependencies:["topBlur","topChange","topClick","topFocus","topInput","topKeyDown","topKeyUp","topSelectionChange"]}},S=null,N=null,M=null,A=null,I=!1;_.canUseDOM&&(I=O("change")&&(!document.documentMode||document.documentMode>8));var D=!1;_.canUseDOM&&(D=O("input")&&(!document.documentMode||document.documentMode>11));var R={get:function(){return A.get.call(this)},set:function(e){M=""+e,A.set.call(this,e)}},L={eventTypes:k,extractEvents:function(e,t,n,o){var i,a,s=t?w.getNodeFromInstance(t):window;if(r(s)?I?i=u:a=l:P(s)?D?i=d:(i=m,a=h):v(s)&&(i=g),i){var c=i(e,t);if(c){var p=T.getPooled(k.change,c,n,o);return p.type="change",C.accumulateTwoPhaseDispatches(p),p}}a&&a(e,s,t),"topBlur"===e&&y(t,s)}};e.exports=L},function(e,t,n){"use strict";var r=n(3),o=n(17),i=n(7),a=n(105),s=n(8),u=(n(1),{dangerouslyReplaceNodeWithMarkup:function(e,t){if(i.canUseDOM?void 0:r("56"),t?void 0:r("57"),"HTML"===e.nodeName?r("58"):void 0,"string"==typeof t){var n=a(t,s)[0];e.parentNode.replaceChild(n,e)}else o.replaceChildWithTree(e,t)}});e.exports=u},function(e,t){"use strict";var n=["ResponderEventPlugin","SimpleEventPlugin","TapEventPlugin","EnterLeaveEventPlugin","ChangeEventPlugin","SelectEventPlugin","BeforeInputEventPlugin"];e.exports=n},function(e,t,n){"use strict";var r=n(25),o=n(6),i=n(30),a={mouseEnter:{registrationName:"onMouseEnter",dependencies:["topMouseOut","topMouseOver"]},mouseLeave:{registrationName:"onMouseLeave",dependencies:["topMouseOut","topMouseOver"]}},s={eventTypes:a,extractEvents:function(e,t,n,s){if("topMouseOver"===e&&(n.relatedTarget||n.fromElement))return null;if("topMouseOut"!==e&&"topMouseOver"!==e)return null;var u;if(s.window===s)u=s;else{var l=s.ownerDocument;u=l?l.defaultView||l.parentWindow:window}var c,p;if("topMouseOut"===e){c=t;var f=n.relatedTarget||n.toElement;p=f?o.getClosestInstanceFromNode(f):null}else c=null,p=t;if(c===p)return null;var d=null==c?u:o.getNodeFromInstance(c),h=null==p?u:o.getNodeFromInstance(p),m=i.getPooled(a.mouseLeave,c,n,s);m.type="mouseleave",m.target=d,m.relatedTarget=h;var v=i.getPooled(a.mouseEnter,p,n,s);return v.type="mouseenter",v.target=h,v.relatedTarget=d,r.accumulateEnterLeaveDispatches(m,v,c,p),[m,v]}};e.exports=s},function(e,t,n){"use strict";function r(e){this._root=e,this._startText=this.getText(),this._fallbackText=null}var o=n(4),i=n(14),a=n(79);o(r.prototype,{destructor:function(){this._root=null,this._startText=null,this._fallbackText=null},getText:function(){return"value"in this._root?this._root.value:this._root[a()]},getData:function(){if(this._fallbackText)return this._fallbackText;var e,t,n=this._startText,r=n.length,o=this.getText(),i=o.length;for(e=0;e1?1-t:void 0;return this._fallbackText=o.slice(e,s),this._fallbackText}}),i.addPoolingTo(r),e.exports=r},function(e,t,n){"use strict";var r=n(18),o=r.injection.MUST_USE_PROPERTY,i=r.injection.HAS_BOOLEAN_VALUE,a=r.injection.HAS_NUMERIC_VALUE,s=r.injection.HAS_POSITIVE_NUMERIC_VALUE,u=r.injection.HAS_OVERLOADED_BOOLEAN_VALUE,l={isCustomAttribute:RegExp.prototype.test.bind(new RegExp("^(data|aria)-["+r.ATTRIBUTE_NAME_CHAR+"]*$")),Properties:{accept:0,acceptCharset:0,accessKey:0,action:0,allowFullScreen:i,allowTransparency:0,alt:0,as:0,async:i,autoComplete:0,autoPlay:i,capture:i,cellPadding:0,cellSpacing:0,charSet:0,challenge:0,checked:o|i,cite:0,classID:0,className:0,cols:s,colSpan:0,content:0,contentEditable:0,contextMenu:0,controls:i,coords:0,crossOrigin:0,data:0,dateTime:0,default:i,defer:i,dir:0,disabled:i,download:u,draggable:0,encType:0,form:0,formAction:0,formEncType:0,formMethod:0,formNoValidate:i,formTarget:0,frameBorder:0,headers:0,height:0,hidden:i,high:0,href:0,hrefLang:0,htmlFor:0,httpEquiv:0,icon:0,id:0,inputMode:0,integrity:0,is:0,keyParams:0,keyType:0,kind:0,label:0,lang:0,list:0,loop:i,low:0,manifest:0,marginHeight:0,marginWidth:0,max:0,maxLength:0,media:0,mediaGroup:0,method:0,min:0,minLength:0,multiple:o|i,muted:o|i,name:0,nonce:0,noValidate:i,open:i,optimum:0,pattern:0,placeholder:0,playsInline:i,poster:0,preload:0,profile:0,radioGroup:0,readOnly:i,referrerPolicy:0,rel:0,required:i,reversed:i,role:0,rows:s,rowSpan:a,sandbox:0,scope:0,scoped:i,scrolling:0,seamless:i,selected:o|i,shape:0,size:s,sizes:0,span:s,spellCheck:0,src:0,srcDoc:0,srcLang:0,srcSet:0,start:a,step:0,style:0,summary:0,tabIndex:0,target:0,title:0,type:0,useMap:0,value:0,width:0,wmode:0,wrap:0,about:0,datatype:0,inlist:0,prefix:0,property:0,resource:0,typeof:0,vocab:0,autoCapitalize:0,autoCorrect:0,autoSave:0,color:0,itemProp:0,itemScope:i,itemType:0,itemID:0,itemRef:0,results:0,security:0,unselectable:0},DOMAttributeNames:{acceptCharset:"accept-charset",className:"class",htmlFor:"for",httpEquiv:"http-equiv"},DOMPropertyNames:{},DOMMutationMethods:{value:function(e,t){return null==t?e.removeAttribute("value"):void("number"!==e.type||e.hasAttribute("value")===!1?e.setAttribute("value",""+t):e.validity&&!e.validity.badInput&&e.ownerDocument.activeElement!==e&&e.setAttribute("value",""+t))}}};e.exports=l},function(e,t,n){(function(t){"use strict";function r(e,t,n,r){var o=void 0===e[n];null!=t&&o&&(e[n]=i(t,!0))}var o=n(19),i=n(80),a=(n(41),n(51)),s=n(83),u=(n(2),{instantiateChildren:function(e,t,n,o){if(null==e)return null;var i={};return s(e,r,i),i},updateChildren:function(e,t,n,r,s,u,l,c,p){if(t||e){var f,d;for(f in t)if(t.hasOwnProperty(f)){d=e&&e[f];var h=d&&d._currentElement,m=t[f];if(null!=d&&a(h,m))o.receiveComponent(d,m,s,c),t[f]=d;else{d&&(r[f]=o.getHostNode(d),o.unmountComponent(d,!1));var v=i(m,!0);t[f]=v;var g=o.mountComponent(v,s,u,l,c,p);n.push(g)}}for(f in e)!e.hasOwnProperty(f)||t&&t.hasOwnProperty(f)||(d=e[f],r[f]=o.getHostNode(d),o.unmountComponent(d,!1))}},unmountChildren:function(e,t){for(var n in e)if(e.hasOwnProperty(n)){var r=e[n];o.unmountComponent(r,t)}}});e.exports=u}).call(t,n(60))},function(e,t,n){"use strict";var r=n(37),o=n(143),i={processChildrenUpdates:o.dangerouslyProcessChildrenUpdates,replaceNodeWithMarkup:r.dangerouslyReplaceNodeWithMarkup};e.exports=i},function(e,t,n){"use strict";function r(e){}function o(e,t){}function i(e){return!(!e.prototype||!e.prototype.isReactComponent)}function a(e){return!(!e.prototype||!e.prototype.isPureReactComponent)}var s=n(3),u=n(4),l=n(20),c=n(43),p=n(12),f=n(44),d=n(26),h=(n(9),n(74)),m=n(19),v=n(23),g=(n(1),n(36)),y=n(51),b=(n(2),{ImpureClass:0,PureClass:1,StatelessFunctional:2});r.prototype.render=function(){var e=d.get(this)._currentElement.type,t=e(this.props,this.context,this.updater);return o(e,t),t};var C=1,_={construct:function(e){this._currentElement=e,this._rootNodeID=0,this._compositeType=null,this._instance=null,this._hostParent=null,this._hostContainerInfo=null,this._updateBatchNumber=null,this._pendingElement=null,this._pendingStateQueue=null,this._pendingReplaceState=!1,this._pendingForceUpdate=!1,this._renderedNodeType=null,this._renderedComponent=null,this._context=null,this._mountOrder=0,this._topLevelWrapper=null,this._pendingCallbacks=null,this._calledComponentWillUnmount=!1},mountComponent:function(e,t,n,u){this._context=u,this._mountOrder=C++,this._hostParent=t,this._hostContainerInfo=n;var c,p=this._currentElement.props,f=this._processContext(u),h=this._currentElement.type,m=e.getUpdateQueue(),g=i(h),y=this._constructComponent(g,p,f,m);g||null!=y&&null!=y.render?a(h)?this._compositeType=b.PureClass:this._compositeType=b.ImpureClass:(c=y,o(h,c),null===y||y===!1||l.isValidElement(y)?void 0:s("105",h.displayName||h.name||"Component"),y=new r(h),this._compositeType=b.StatelessFunctional);y.props=p,y.context=f,y.refs=v,y.updater=m,this._instance=y,d.set(y,this);var _=y.state;void 0===_&&(y.state=_=null),"object"!=typeof _||Array.isArray(_)?s("106",this.getName()||"ReactCompositeComponent"):void 0,this._pendingStateQueue=null,this._pendingReplaceState=!1,this._pendingForceUpdate=!1;var w;return w=y.unstable_handleError?this.performInitialMountWithErrorHandling(c,t,n,e,u):this.performInitialMount(c,t,n,e,u),y.componentDidMount&&e.getReactMountReady().enqueue(y.componentDidMount,y),w},_constructComponent:function(e,t,n,r){return this._constructComponentWithoutOwner(e,t,n,r)},_constructComponentWithoutOwner:function(e,t,n,r){var o=this._currentElement.type;return e?new o(t,n,r):o(t,n,r)},performInitialMountWithErrorHandling:function(e,t,n,r,o){var i,a=r.checkpoint();try{i=this.performInitialMount(e,t,n,r,o)}catch(s){r.rollback(a),this._instance.unstable_handleError(s),this._pendingStateQueue&&(this._instance.state=this._processPendingState(this._instance.props,this._instance.context)),a=r.checkpoint(),this._renderedComponent.unmountComponent(!0),r.rollback(a),i=this.performInitialMount(e,t,n,r,o)}return i},performInitialMount:function(e,t,n,r,o){var i=this._instance,a=0;i.componentWillMount&&(i.componentWillMount(),this._pendingStateQueue&&(i.state=this._processPendingState(i.props,i.context))),void 0===e&&(e=this._renderValidatedComponent());var s=h.getType(e); +this._renderedNodeType=s;var u=this._instantiateReactComponent(e,s!==h.EMPTY);this._renderedComponent=u;var l=m.mountComponent(u,r,t,n,this._processChildContext(o),a);return l},getHostNode:function(){return m.getHostNode(this._renderedComponent)},unmountComponent:function(e){if(this._renderedComponent){var t=this._instance;if(t.componentWillUnmount&&!t._calledComponentWillUnmount)if(t._calledComponentWillUnmount=!0,e){var n=this.getName()+".componentWillUnmount()";f.invokeGuardedCallback(n,t.componentWillUnmount.bind(t))}else t.componentWillUnmount();this._renderedComponent&&(m.unmountComponent(this._renderedComponent,e),this._renderedNodeType=null,this._renderedComponent=null,this._instance=null),this._pendingStateQueue=null,this._pendingReplaceState=!1,this._pendingForceUpdate=!1,this._pendingCallbacks=null,this._pendingElement=null,this._context=null,this._rootNodeID=0,this._topLevelWrapper=null,d.remove(t)}},_maskContext:function(e){var t=this._currentElement.type,n=t.contextTypes;if(!n)return v;var r={};for(var o in n)r[o]=e[o];return r},_processContext:function(e){var t=this._maskContext(e);return t},_processChildContext:function(e){var t,n=this._currentElement.type,r=this._instance;if(r.getChildContext&&(t=r.getChildContext()),t){"object"!=typeof n.childContextTypes?s("107",this.getName()||"ReactCompositeComponent"):void 0;for(var o in t)o in n.childContextTypes?void 0:s("108",this.getName()||"ReactCompositeComponent",o);return u({},e,t)}return e},_checkContextTypes:function(e,t,n){},receiveComponent:function(e,t,n){var r=this._currentElement,o=this._context;this._pendingElement=null,this.updateComponent(t,r,e,o,n)},performUpdateIfNecessary:function(e){null!=this._pendingElement?m.receiveComponent(this,this._pendingElement,e,this._context):null!==this._pendingStateQueue||this._pendingForceUpdate?this.updateComponent(e,this._currentElement,this._currentElement,this._context,this._context):this._updateBatchNumber=null},updateComponent:function(e,t,n,r,o){var i=this._instance;null==i?s("136",this.getName()||"ReactCompositeComponent"):void 0;var a,u=!1;this._context===o?a=i.context:(a=this._processContext(o),u=!0);var l=t.props,c=n.props;t!==n&&(u=!0),u&&i.componentWillReceiveProps&&i.componentWillReceiveProps(c,a);var p=this._processPendingState(c,a),f=!0;this._pendingForceUpdate||(i.shouldComponentUpdate?f=i.shouldComponentUpdate(c,p,a):this._compositeType===b.PureClass&&(f=!g(l,c)||!g(i.state,p))),this._updateBatchNumber=null,f?(this._pendingForceUpdate=!1,this._performComponentUpdate(n,c,p,a,e,o)):(this._currentElement=n,this._context=o,i.props=c,i.state=p,i.context=a)},_processPendingState:function(e,t){var n=this._instance,r=this._pendingStateQueue,o=this._pendingReplaceState;if(this._pendingReplaceState=!1,this._pendingStateQueue=null,!r)return n.state;if(o&&1===r.length)return r[0];for(var i=u({},o?r[0]:n.state),a=o?1:0;a=0||null!=t.is}function h(e){var t=e.type;f(t),this._currentElement=e,this._tag=t.toLowerCase(),this._namespaceURI=null,this._renderedChildren=null,this._previousStyle=null,this._previousStyleCopy=null,this._hostNode=null,this._hostParent=null,this._rootNodeID=0,this._domID=0,this._hostContainerInfo=null,this._wrapperState=null,this._topLevelWrapper=null,this._flags=0}var m=n(3),v=n(4),g=n(126),y=n(128),b=n(17),C=n(38),_=n(18),w=n(66),E=n(24),T=n(39),x=n(29),O=n(67),P=n(6),k=n(144),S=n(145),N=n(68),M=n(148),A=(n(9),n(157)),I=n(162),D=(n(8),n(32)),R=(n(1),n(50),n(36),n(52),n(2),O),L=E.deleteListener,U=P.getNodeFromInstance,F=x.listenTo,j=T.registrationNameModules,B={string:!0,number:!0},V="style",W="__html",H={children:null,dangerouslySetInnerHTML:null,suppressContentEditableWarning:null},q=11,z={topAbort:"abort",topCanPlay:"canplay",topCanPlayThrough:"canplaythrough",topDurationChange:"durationchange",topEmptied:"emptied",topEncrypted:"encrypted",topEnded:"ended",topError:"error",topLoadedData:"loadeddata",topLoadedMetadata:"loadedmetadata",topLoadStart:"loadstart",topPause:"pause",topPlay:"play",topPlaying:"playing",topProgress:"progress",topRateChange:"ratechange",topSeeked:"seeked",topSeeking:"seeking",topStalled:"stalled",topSuspend:"suspend",topTimeUpdate:"timeupdate",topVolumeChange:"volumechange",topWaiting:"waiting"},K={area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0},Y={listing:!0,pre:!0,textarea:!0},X=v({menuitem:!0},K),G=/^[a-zA-Z][a-zA-Z:_\.\-\d]*$/,Q={},$={}.hasOwnProperty,Z=1;h.displayName="ReactDOMComponent",h.Mixin={mountComponent:function(e,t,n,r){this._rootNodeID=Z++,this._domID=n._idCounter++,this._hostParent=t,this._hostContainerInfo=n;var i=this._currentElement.props;switch(this._tag){case"audio":case"form":case"iframe":case"img":case"link":case"object":case"source":case"video":this._wrapperState={listeners:null},e.getReactMountReady().enqueue(c,this);break;case"input":k.mountWrapper(this,i,t),i=k.getHostProps(this,i),e.getReactMountReady().enqueue(c,this);break;case"option":S.mountWrapper(this,i,t),i=S.getHostProps(this,i);break;case"select":N.mountWrapper(this,i,t),i=N.getHostProps(this,i),e.getReactMountReady().enqueue(c,this);break;case"textarea":M.mountWrapper(this,i,t),i=M.getHostProps(this,i),e.getReactMountReady().enqueue(c,this)}o(this,i);var a,p;null!=t?(a=t._namespaceURI,p=t._tag):n._tag&&(a=n._namespaceURI,p=n._tag),(null==a||a===C.svg&&"foreignobject"===p)&&(a=C.html),a===C.html&&("svg"===this._tag?a=C.svg:"math"===this._tag&&(a=C.mathml)),this._namespaceURI=a;var f;if(e.useCreateElement){var d,h=n._ownerDocument;if(a===C.html)if("script"===this._tag){var m=h.createElement("div"),v=this._currentElement.type;m.innerHTML="<"+v+">",d=m.removeChild(m.firstChild)}else d=i.is?h.createElement(this._currentElement.type,i.is):h.createElement(this._currentElement.type);else d=h.createElementNS(a,this._currentElement.type);P.precacheNode(this,d),this._flags|=R.hasCachedChildNodes,this._hostParent||w.setAttributeForRoot(d),this._updateDOMProperties(null,i,e);var y=b(d);this._createInitialChildren(e,i,r,y),f=y}else{var _=this._createOpenTagMarkupAndPutListeners(e,i),E=this._createContentMarkup(e,i,r);f=!E&&K[this._tag]?_+"/>":_+">"+E+""}switch(this._tag){case"input":e.getReactMountReady().enqueue(s,this),i.autoFocus&&e.getReactMountReady().enqueue(g.focusDOMComponent,this);break;case"textarea":e.getReactMountReady().enqueue(u,this),i.autoFocus&&e.getReactMountReady().enqueue(g.focusDOMComponent,this);break;case"select":i.autoFocus&&e.getReactMountReady().enqueue(g.focusDOMComponent,this);break;case"button":i.autoFocus&&e.getReactMountReady().enqueue(g.focusDOMComponent,this);break;case"option":e.getReactMountReady().enqueue(l,this)}return f},_createOpenTagMarkupAndPutListeners:function(e,t){var n="<"+this._currentElement.type;for(var r in t)if(t.hasOwnProperty(r)){var o=t[r];if(null!=o)if(j.hasOwnProperty(r))o&&i(this,r,o,e);else{r===V&&(o&&(o=this._previousStyleCopy=v({},t.style)),o=y.createMarkupForStyles(o,this));var a=null;null!=this._tag&&d(this._tag,t)?H.hasOwnProperty(r)||(a=w.createMarkupForCustomAttribute(r,o)):a=w.createMarkupForProperty(r,o),a&&(n+=" "+a)}}return e.renderToStaticMarkup?n:(this._hostParent||(n+=" "+w.createMarkupForRoot()),n+=" "+w.createMarkupForID(this._domID))},_createContentMarkup:function(e,t,n){var r="",o=t.dangerouslySetInnerHTML;if(null!=o)null!=o.__html&&(r=o.__html);else{var i=B[typeof t.children]?t.children:null,a=null!=i?null:t.children;if(null!=i)r=D(i);else if(null!=a){var s=this.mountChildren(a,e,n);r=s.join("")}}return Y[this._tag]&&"\n"===r.charAt(0)?"\n"+r:r},_createInitialChildren:function(e,t,n,r){var o=t.dangerouslySetInnerHTML;if(null!=o)null!=o.__html&&b.queueHTML(r,o.__html);else{var i=B[typeof t.children]?t.children:null,a=null!=i?null:t.children;if(null!=i)""!==i&&b.queueText(r,i);else if(null!=a)for(var s=this.mountChildren(a,e,n),u=0;u"},receiveComponent:function(){},getHostNode:function(){return i.getNodeFromInstance(this)},unmountComponent:function(){i.uncacheNode(this)}}),e.exports=a},function(e,t){"use strict";var n={useCreateElement:!0,useFiber:!1};e.exports=n},function(e,t,n){"use strict";var r=n(37),o=n(6),i={dangerouslyProcessChildrenUpdates:function(e,t){var n=o.getNodeFromInstance(e);r.processUpdates(n,t)}};e.exports=i},function(e,t,n){"use strict";function r(){this._rootNodeID&&f.updateWrapper(this)}function o(e){var t="checkbox"===e.type||"radio"===e.type;return t?null!=e.checked:null!=e.value}function i(e){var t=this._currentElement.props,n=l.executeOnChange(t,e);p.asap(r,this);var o=t.name;if("radio"===t.type&&null!=o){for(var i=c.getNodeFromInstance(this),s=i;s.parentNode;)s=s.parentNode;for(var u=s.querySelectorAll("input[name="+JSON.stringify(""+o)+'][type="radio"]'),f=0;ft.end?(n=t.end,r=t.start):(n=t.start,r=t.end),o.moveToElementText(e),o.moveStart("character",n),o.setEndPoint("EndToStart",o),o.moveEnd("character",r-n),o.select()}function s(e,t){if(window.getSelection){var n=window.getSelection(),r=e[c()].length,o=Math.min(t.start,r),i=void 0===t.end?o:Math.min(t.end,r);if(!n.extend&&o>i){var a=i;i=o,o=a}var s=l(e,o),u=l(e,i);if(s&&u){var p=document.createRange();p.setStart(s.node,s.offset),n.removeAllRanges(),o>i?(n.addRange(p),n.extend(u.node,u.offset)):(p.setEnd(u.node,u.offset),n.addRange(p))}}}var u=n(7),l=n(184),c=n(79),p=u.canUseDOM&&"selection"in document&&!("getSelection"in window),f={getOffsets:p?o:i,setOffsets:p?a:s};e.exports=f},function(e,t,n){"use strict";var r=n(3),o=n(4),i=n(37),a=n(17),s=n(6),u=n(32),l=(n(1),n(52),function(e){this._currentElement=e,this._stringText=""+e,this._hostNode=null,this._hostParent=null,this._domID=0,this._mountIndex=0,this._closingComment=null,this._commentNodes=null});o(l.prototype,{mountComponent:function(e,t,n,r){var o=n._idCounter++,i=" react-text: "+o+" ",l=" /react-text ";if(this._domID=o,this._hostParent=t,e.useCreateElement){var c=n._ownerDocument,p=c.createComment(i),f=c.createComment(l),d=a(c.createDocumentFragment());return a.queueChild(d,a(p)),this._stringText&&a.queueChild(d,a(c.createTextNode(this._stringText))),a.queueChild(d,a(f)),s.precacheNode(this,p),this._closingComment=f,d}var h=u(this._stringText);return e.renderToStaticMarkup?h:""+h+""},receiveComponent:function(e,t){if(e!==this._currentElement){this._currentElement=e;var n=""+e;if(n!==this._stringText){this._stringText=n;var r=this.getHostNode();i.replaceDelimitedText(r[0],r[1],n)}}},getHostNode:function(){var e=this._commentNodes;if(e)return e;if(!this._closingComment)for(var t=s.getNodeFromInstance(this),n=t.nextSibling;;){if(null==n?r("67",this._domID):void 0,8===n.nodeType&&" /react-text "===n.nodeValue){this._closingComment=n;break}n=n.nextSibling}return e=[this._hostNode,this._closingComment],this._commentNodes=e,e},unmountComponent:function(){this._closingComment=null,this._commentNodes=null,s.uncacheNode(this)}}),e.exports=l},function(e,t,n){"use strict";function r(){this._rootNodeID&&c.updateWrapper(this)}function o(e){var t=this._currentElement.props,n=s.executeOnChange(t,e);return l.asap(r,this),n}var i=n(3),a=n(4),s=n(42),u=n(6),l=n(10),c=(n(1),n(2),{getHostProps:function(e,t){null!=t.dangerouslySetInnerHTML?i("91"):void 0;var n=a({},t,{value:void 0,defaultValue:void 0,children:""+e._wrapperState.initialValue,onChange:e._wrapperState.onChange});return n},mountWrapper:function(e,t){var n=s.getValue(t),r=n;if(null==n){var a=t.defaultValue,u=t.children;null!=u&&(null!=a?i("92"):void 0,Array.isArray(u)&&(u.length<=1?void 0:i("93"),u=u[0]),a=""+u),null==a&&(a=""),r=a}e._wrapperState={initialValue:""+r,listeners:null,onChange:o.bind(e)}},updateWrapper:function(e){var t=e._currentElement.props,n=u.getNodeFromInstance(e),r=s.getValue(t);if(null!=r){var o=""+r;o!==n.value&&(n.value=o),null==t.defaultValue&&(n.defaultValue=o)}null!=t.defaultValue&&(n.defaultValue=t.defaultValue)},postMountWrapper:function(e){var t=u.getNodeFromInstance(e),n=t.textContent;n===e._wrapperState.initialValue&&(t.value=n)}});e.exports=c},function(e,t,n){"use strict";function r(e,t){"_hostNode"in e?void 0:u("33"),"_hostNode"in t?void 0:u("33");for(var n=0,r=e;r;r=r._hostParent)n++;for(var o=0,i=t;i;i=i._hostParent)o++;for(;n-o>0;)e=e._hostParent,n--;for(;o-n>0;)t=t._hostParent,o--;for(var a=n;a--;){if(e===t)return e;e=e._hostParent,t=t._hostParent}return null}function o(e,t){"_hostNode"in e?void 0:u("35"),"_hostNode"in t?void 0:u("35");for(;t;){if(t===e)return!0;t=t._hostParent}return!1}function i(e){return"_hostNode"in e?void 0:u("36"),e._hostParent}function a(e,t,n){for(var r=[];e;)r.push(e),e=e._hostParent;var o;for(o=r.length;o-- >0;)t(r[o],"captured",n);for(o=0;o0;)n(u[l],"captured",i)}var u=n(3);n(1);e.exports={isAncestor:o,getLowestCommonAncestor:r,getParentInstance:i,traverseTwoPhase:a,traverseEnterLeave:s}},function(e,t,n){"use strict";function r(){this.reinitializeTransaction()}var o=n(4),i=n(10),a=n(31),s=n(8),u={initialize:s,close:function(){f.isBatchingUpdates=!1}},l={initialize:s,close:i.flushBatchedUpdates.bind(i)},c=[l,u];o(r.prototype,a,{getTransactionWrappers:function(){return c}});var p=new r,f={isBatchingUpdates:!1,batchedUpdates:function(e,t,n,r,o,i){var a=f.isBatchingUpdates;return f.isBatchingUpdates=!0,a?e(t,n,r,o,i):p.perform(e,null,t,n,r,o,i)}};e.exports=f},function(e,t,n){"use strict";function r(){E||(E=!0,y.EventEmitter.injectReactEventListener(g),y.EventPluginHub.injectEventPluginOrder(s),y.EventPluginUtils.injectComponentTree(f),y.EventPluginUtils.injectTreeTraversal(h),y.EventPluginHub.injectEventPluginsByName({SimpleEventPlugin:w,EnterLeaveEventPlugin:u,ChangeEventPlugin:a,SelectEventPlugin:_,BeforeInputEventPlugin:i}),y.HostComponent.injectGenericComponentClass(p),y.HostComponent.injectTextComponentClass(m),y.DOMProperty.injectDOMPropertyConfig(o),y.DOMProperty.injectDOMPropertyConfig(l),y.DOMProperty.injectDOMPropertyConfig(C),y.EmptyComponent.injectEmptyComponentFactory(function(e){return new d(e)}),y.Updates.injectReconcileTransaction(b),y.Updates.injectBatchingStrategy(v),y.Component.injectEnvironment(c))}var o=n(125),i=n(127),a=n(129),s=n(131),u=n(132),l=n(134),c=n(136),p=n(139),f=n(6),d=n(141),h=n(149),m=n(147),v=n(150),g=n(154),y=n(155),b=n(160),C=n(165),_=n(166),w=n(167),E=!1;e.exports={inject:r}},88,function(e,t,n){"use strict";function r(e){o.enqueueEvents(e),o.processEventQueue(!1)}var o=n(24),i={handleTopLevel:function(e,t,n,i){var a=o.extractEvents(e,t,n,i);r(a)}};e.exports=i},function(e,t,n){"use strict";function r(e){for(;e._hostParent;)e=e._hostParent;var t=p.getNodeFromInstance(e),n=t.parentNode;return p.getClosestInstanceFromNode(n)}function o(e,t){this.topLevelType=e,this.nativeEvent=t,this.ancestors=[]}function i(e){var t=d(e.nativeEvent),n=p.getClosestInstanceFromNode(t),o=n;do e.ancestors.push(o),o=o&&r(o);while(o);for(var i=0;i/,i=/^<\!\-\-/,a={CHECKSUM_ATTR_NAME:"data-react-checksum",addChecksumToMarkup:function(e){var t=r(e);return i.test(e)?e:e.replace(o," "+a.CHECKSUM_ATTR_NAME+'="'+t+'"$&')},canReuseMarkup:function(e,t){var n=t.getAttribute(a.CHECKSUM_ATTR_NAME);n=n&&parseInt(n,10);var o=r(e);return o===n}};e.exports=a},function(e,t,n){"use strict";function r(e,t,n){return{type:"INSERT_MARKUP",content:e,fromIndex:null,fromNode:null,toIndex:n,afterNode:t}}function o(e,t,n){return{type:"MOVE_EXISTING",content:null,fromIndex:e._mountIndex,fromNode:f.getHostNode(e),toIndex:n,afterNode:t}}function i(e,t){return{type:"REMOVE_NODE",content:null,fromIndex:e._mountIndex,fromNode:t,toIndex:null,afterNode:null}}function a(e){return{type:"SET_MARKUP",content:e,fromIndex:null,fromNode:null,toIndex:null,afterNode:null}}function s(e){return{type:"TEXT_CONTENT",content:e,fromIndex:null,fromNode:null,toIndex:null,afterNode:null}}function u(e,t){return t&&(e=e||[],e.push(t)),e}function l(e,t){p.processChildrenUpdates(e,t)}var c=n(3),p=n(43),f=(n(26),n(9),n(12),n(19)),d=n(135),h=(n(8),n(181)),m=(n(1),{Mixin:{_reconcilerInstantiateChildren:function(e,t,n){return d.instantiateChildren(e,t,n)},_reconcilerUpdateChildren:function(e,t,n,r,o,i){var a,s=0;return a=h(t,s),d.updateChildren(e,a,n,r,o,this,this._hostContainerInfo,i,s),a},mountChildren:function(e,t,n){var r=this._reconcilerInstantiateChildren(e,t,n);this._renderedChildren=r;var o=[],i=0;for(var a in r)if(r.hasOwnProperty(a)){var s=r[a],u=0,l=f.mountComponent(s,t,this,this._hostContainerInfo,n,u);s._mountIndex=i++,o.push(l)}return o},updateTextContent:function(e){var t=this._renderedChildren;d.unmountChildren(t,!1);for(var n in t)t.hasOwnProperty(n)&&c("118");var r=[s(e)];l(this,r)},updateMarkup:function(e){var t=this._renderedChildren;d.unmountChildren(t,!1);for(var n in t)t.hasOwnProperty(n)&&c("118");var r=[a(e)];l(this,r)},updateChildren:function(e,t,n){this._updateChildren(e,t,n)},_updateChildren:function(e,t,n){var r=this._renderedChildren,o={},i=[],a=this._reconcilerUpdateChildren(r,e,i,o,t,n);if(a||r){var s,c=null,p=0,d=0,h=0,m=null;for(s in a)if(a.hasOwnProperty(s)){var v=r&&r[s],g=a[s];v===g?(c=u(c,this.moveChild(v,m,p,d)),d=Math.max(v._mountIndex,d),v._mountIndex=p):(v&&(d=Math.max(v._mountIndex,d)),c=u(c,this._mountChildAtIndex(g,i[h],m,p,t,n)),h++),p++,m=f.getHostNode(g)}for(s in o)o.hasOwnProperty(s)&&(c=u(c,this._unmountChild(r[s],o[s])));c&&l(this,c),this._renderedChildren=a}},unmountChildren:function(e){var t=this._renderedChildren;d.unmountChildren(t,e),this._renderedChildren=null},moveChild:function(e,t,n,r){if(e._mountIndex=t)return{node:o,offset:t-i};i=a}o=n(r(o))}}e.exports=o},function(e,t,n){"use strict";function r(e,t){var n={};return n[e.toLowerCase()]=t.toLowerCase(),n["Webkit"+e]="webkit"+t,n["Moz"+e]="moz"+t,n["ms"+e]="MS"+t,n["O"+e]="o"+t.toLowerCase(),n}function o(e){if(s[e])return s[e];if(!a[e])return e;var t=a[e];for(var n in t)if(t.hasOwnProperty(n)&&n in u)return s[e]=t[n];return""}var i=n(7),a={animationend:r("Animation","AnimationEnd"),animationiteration:r("Animation","AnimationIteration"),animationstart:r("Animation","AnimationStart"),transitionend:r("Transition","TransitionEnd")},s={},u={};i.canUseDOM&&(u=document.createElement("div").style,"AnimationEvent"in window||(delete a.animationend.animation,delete a.animationiteration.animation,delete a.animationstart.animation),"TransitionEvent"in window||delete a.transitionend.transition),e.exports=o},function(e,t,n){"use strict";function r(e){return'"'+o(e)+'"'}var o=n(32);e.exports=r},function(e,t,n){"use strict";var r=n(73);e.exports=r.renderSubtreeIntoContainer},function(e,t,n){"use strict";"undefined"==typeof Promise&&(n(120).enable(),window.Promise=n(119)),n(226),Object.assign=n(4)},113,114,115,116,117,function(e,t,n){(function(){var t,r,o;t=n(5),r=t.createClass,o=t.DOM.div,e.exports=r({getDefaultProps:function(){return{className:"",onHeightChange:function(){}}},render:function(){return o({className:this.props.className,ref:"dropdown"},this.props.children)},componentDidMount:function(){this.props.onHeightChange(this.refs.dropdown.offsetHeight)},componentDidUpdate:function(){this.props.onHeightChange(this.refs.dropdown.offsetHeight)},componentWillUnmount:function(){this.props.onHeightChange(0)}})}).call(this)},function(e,t,n){(function(){function t(e,t){var n={}.hasOwnProperty;for(var r in t)n.call(t,r)&&(e[r]=t[r]);return e}var r,o,i,a,s,u,l,c,p,f,d,h,m,v,g,y,b,C;r=n(15),o=r.filter,i=r.id,a=r.map,s=n(16).isEqualToObject,u=n(5),r=u.DOM,l=r.div,c=r.input,p=r.span,f=u.createClass,d=u.createFactory,h=n(13).findDOMNode,m=d(n(63)),v=d(n(198)),g=d(n(194)),y=d(n(84)),r=n(28),b=r.cancelEvent,C=r.classNameFromObject,e.exports=f({displayName:"DropdownMenu",getDefaultProps:function(){return{className:"",dropdownDirection:1,groupId:function(e){return e.groupId},groupsAsColumns:!1,highlightedUid:void 0,onHighlightedUidChange:function(e,t){},onOptionClick:function(e){},onScrollLockChange:function(e){},options:[],renderNoResultsFound:function(){return l({className:"no-results-found"},"No results found")},renderGroupTitle:function(e,t){var n,r;return null!=t&&(n=t.groupId,r=t.title),l({className:"simple-group-title",key:n},r)},renderOption:function(e){var t,n,r,o;return null!=e&&(t=e.label,n=e.newOption,r=e.selectable),o="undefined"==typeof r||r,l({className:"simple-option "+(o?"":"not-selectable")},p(null,n?"Add "+t+" ...":t))},scrollLock:!1,style:{},tether:!1,tetherProps:{},theme:"default",transitionEnter:!1,transitionLeave:!1,transitionEnterTimeout:200,transitionLeaveTimeout:200,uid:i}},render:function(){var e,n;return e=C((n={},n[this.props.theme+""]=1,n[this.props.className+""]=1,n.flipped=this.props.dropdownDirection===-1,n.tethered=this.props.tether,n)),this.props.tether?v((n=t({},this.props.tetherProps),n.options={attachment:"top left",targetAttachment:"bottom left",constraints:[{to:"scrollParent"}]},n),this.renderAnimatedDropdown({dynamicClassName:e})):this.renderAnimatedDropdown({dynamicClassName:e})},renderAnimatedDropdown:function(e){var t;return t=e.dynamicClassName,this.props.transitionEnter||this.props.transitionLeave?m({component:"div",transitionName:"custom",transitionEnter:this.props.transitionEnter,transitionLeave:this.props.transitionLeave,transitionEnterTimeout:this.props.transitionEnterTimeout,transitionLeaveTimeout:this.props.transitionLeaveTimeout,className:"dropdown-menu-wrapper "+t,ref:"dropdownMenuWrapper"},this.renderDropdown(e)):this.renderDropdown(e)},renderOptions:function(e){var n=this;return a(function(r){var o,i;return o=e[r],i=n.props.uid(o),y(t({uid:i,ref:"option-"+n.uidToString(i),key:n.uidToString(i),item:o,highlight:s(n.props.highlightedUid,i),selectable:null!=o?o.selectable:void 0,onMouseMove:function(e){var t;t=e.currentTarget,n.props.scrollLock&&n.props.onScrollLockChange(!1)},onMouseOut:function(){n.props.scrollLock||n.props.onHighlightedUidChange(void 0,function(){})},renderItem:n.props.renderOption},function(){switch(!1){case!("boolean"==typeof(null!=o?o.selectable:void 0)&&!o.selectable):return{onClick:b};default:return{onClick:function(){n.props.onOptionClick(n.props.highlightedUid)},onMouseOver:function(e){var t;t=e.currentTarget,n.props.scrollLock||n.props.onHighlightedUidChange(i,function(){})}}}}()))})(function(){var t,n,r=[];for(t=0,n=e.length;t0?(i=a(function(e){var t,n,r;return t=s.props.groups[e],n=t.groupId,r=o(function(e){return s.props.groupId(e)===n})(s.props.options),{index:e,group:t,options:r}})(function(){var e,t,n=[];for(e=0,t=this.props.groups.length;e0})(i)))):this.renderOptions(this.props.options)):null},componentDidUpdate:function(){var e,t,n;e=t=h(null!=(n=this.refs.dropdownMenuWrapper)?n:this.refs.dropdownMenu),null!=e&&(e.style.bottom=function(){switch(!1){case this.props.dropdownDirection!==-1:return this.props.bottomAnchor().offsetHeight+t.style.marginBottom+"px";default:return""}}.call(this))},highlightAndScrollToOption:function(e,t){var n,r=this;null==t&&(t=function(){}),n=this.props.uid(this.props.options[e]),this.props.onHighlightedUidChange(n,function(){var e,o,i,a,s;return null!=(e=h(null!=(o=r.refs)?o["option-"+r.uidToString(n)]:void 0))&&(i=e),i&&(a=h(r.refs.dropdownMenu),s=i.offsetHeight-1,i.offsetTop-a.scrollTop>=a.offsetHeight?a.scrollTop=i.offsetTop-a.offsetHeight+s:i.offsetTop-a.scrollTop+s<=0&&(a.scrollTop=i.offsetTop)),t()})},highlightAndScrollToSelectableOption:function(e,t,n){var r,o,i;null==n&&(n=function(){}),e<0||e>=this.props.options.length?this.props.onHighlightedUidChange(void 0,function(){return n(!1)}):(r=null!=(o=this.props)&&null!=(i=o.options)?i[e]:void 0,"boolean"!=typeof(null!=r?r.selectable:void 0)||r.selectable?this.highlightAndScrollToOption(e,function(){return n(!0)}):this.highlightAndScrollToSelectableOption(e+t,t,n))},uidToString:function(e){return("object"==typeof e?JSON.stringify:i)(e)}})}).call(this)},function(e,t,n){(function(){var t,r,o,i,a,s;t=n(5),r=t.createClass,o=t.DOM,i=o.div,a=o.span,s=n(15).map,e.exports=r({getDefaultProps:function(){return{partitions:[],text:"",style:{},highlightStyle:{}}},render:function(){var e=this;return i({className:"highlighted-text",style:this.props.style},s(function(t){var n,r,o;return n=t[0],r=t[1],o=t[2],a({key:e.props.text+""+n+r+o,className:o?"highlight":"",style:o?e.props.highlightStyle:{}},e.props.text.substring(n,r))})(this.props.partitions))}})}).call(this)},function(e,t,n){(function(){function t(e,t){for(var n=-1,r=t.length>>>0;++n1?function(){var i=o?o.concat():[];return n=t?n||this:this,i.push.apply(i,arguments)-1})(g(function(e){return t(e.label.trim(),v(function(e){return e.label.trim()},null!=n?n:[]))})(e))}),firstOptionIndexToHighlight:h,onBlur:function(e){},onFocus:function(e){},onPaste:function(e){},serialize:v(function(e){return null!=e?e.value:void 0}),tether:!1}},render:function(){var e,t,n,r,i,a,s,u,l,c,p,f,d,h,v,g,y,b,C,_,w,E,O,P,k,S,N,M,A,I,D,R,L,U,F,j,B,V,W=this;return e=this.getComputedState(),t=e.anchor,n=e.filteredOptions,r=e.highlightedUid,i=e.onAnchorChange,a=e.onOpenChange,s=e.onHighlightedUidChange,u=e.onSearchChange,l=e.onValuesChange,c=e.search,p=e.open,f=e.options,d=e.values,null!=(e=this.props)&&(h=e.autofocus,v=e.autosize,g=e.cancelKeyboardEventOnSelection,y=e.delimiters,b=e.disabled,C=e.dropdownDirection,_=e.groupId,w=e.groups,E=e.groupsAsColumns,O=e.hideResetButton,P=e.inputProps,k=e.name,S=e.onKeyboardSelectionFailed,N=e.renderToggleButton,M=e.renderGroupTitle,A=e.renderResetButton,I=e.serialize,D=e.tether,R=e.tetherProps,L=e.theme,U=e.transitionEnter,F=e.transitionLeave,j=e.transitionEnterTimeout,B=e.transitionLeaveTimeout,V=e.uid),T(o(o({autofocus:h,autosize:v,cancelKeyboardEventOnSelection:g,className:"multi-select "+this.props.className,delimiters:y,disabled:b,dropdownDirection:C,groupId:_,groups:w,groupsAsColumns:E,hideResetButton:O,highlightedUid:r,onHighlightedUidChange:s,inputProps:P,name:k,onKeyboardSelectionFailed:S,renderGroupTitle:M,renderResetButton:A,renderToggleButton:N,scrollLock:this.state.scrollLock,onScrollLockChange:function(e){return W.setState({scrollLock:e})},tether:D,tetherProps:R,theme:L,transitionEnter:U,transitionEnterTimeout:j,transitionLeave:F,transitionLeaveTimeout:B,uid:V,ref:"select",anchor:t,onAnchorChange:i,open:p,onOpenChange:a,options:f,renderOption:this.props.renderOption,firstOptionIndexToHighlight:function(){return W.firstOptionIndexToHighlight(f)},search:c,onSearchChange:function(e,t){return u(W.props.maxValues&&d.length>=W.props.maxValues?"":e,t)},values:d,onValuesChange:function(e,t){return l(e,function(){if(t(),W.props.closeOnSelect||W.props.maxValues&&W.values().length>=W.props.maxValues)return a(!1,function(){})})},renderValue:this.props.renderValue,serialize:I,onBlur:function(e){u("",function(){return W.props.onBlur({open:p,values:d,originalEvent:e})})},onFocus:function(e){W.props.onFocus({open:p,values:d,originalEvent:e})},onPaste:function(){var e;switch(!1){case"undefined"!=typeof(null!=(e=this.props)?e.valuesFromPaste:void 0):return this.props.onPaste;default:return function(e){var t;return t=e.clipboardData,function(){var e;return e=d.concat(W.props.valuesFromPaste(f,d,t.getData("text"))),l(e,function(){return i(m(e))})}(),x(e)}}}.call(this),placeholder:this.props.placeholder,style:this.props.style},function(){switch(!1){case"function"!=typeof this.props.restoreOnBackspace:return{restoreOnBackspace:this.props.restoreOnBackspace};default:return{}}}.call(this)),function(){switch(!1){case"function"!=typeof this.props.renderNoResultsFound:return{renderNoResultsFound:function(){return W.props.renderNoResultsFound(d,c)}};default:return{}}}.call(this)))},getComputedState:function(){var e,t,n,r,i,a,s,l,c,p,f,d,h,m,g,y,b=this;return e=this.props.hasOwnProperty("anchor")?this.props.anchor:this.state.anchor,t=this.props.hasOwnProperty("highlightedUid")?this.props.highlightedUid:this.state.highlightedUid,n=this.isOpen(),r=this.props.hasOwnProperty("search")?this.props.search:this.state.search,i=this.values(),a=v(function(e){switch(!1){case!(b.props.hasOwnProperty(e)&&b.props.hasOwnProperty(u("on-"+e+"-change"))):return function(t,n){return b.props[u("on-"+e+"-change")](t,function(){}),b.setState({},n)};case!(b.props.hasOwnProperty(e)&&!b.props.hasOwnProperty(u("on-"+e+"-change"))):return function(e,t){return t()};case!(!b.props.hasOwnProperty(e)&&b.props.hasOwnProperty(u("on-"+e+"-change"))):return function(t,n){var r;return b.setState((r={},r[e+""]=t,r),function(){return n(),b.props[u("on-"+e+"-change")](t,function(){})})};case!(!b.props.hasOwnProperty(e)&&!b.props.hasOwnProperty(u("on-"+e+"-change"))):return function(t,n){var r;return b.setState((r={},r[e+""]=t,r),n)}}})(["anchor","highlightedUid","open","search","values"]),s=a[0],l=a[1],c=a[2],p=a[3],f=a[4],d=function(){var e;switch(!1){case!(null!=(e=this.props)&&e.children):return v(function(e){var t,n,r;return null!=e&&(t=e.props),null!=t&&(n=t.value,r=t.children),{label:r,value:n}})("Array"===O.call(this.props.children).slice(8,-1)?this.props.children:[this.props.children]);default:return[]}}.call(this),h=this.props.hasOwnProperty("options")?null!=(a=this.props.options)?a:[]:d,m=this.props.filterOptions(h,i,r),g=function(){switch(!1){case"function"!=typeof this.props.createFromSearch:return this.props.createFromSearch(m,i,r);default:return null}}.call(this),y=(g?[(a=o({},g),a.newOption=!0,a)]:[]).concat(m),{anchor:e,highlightedUid:t,search:r,values:i,onAnchorChange:s,onHighlightedUidChange:l,open:n,onOpenChange:function(e,t){c(function(){switch(!1){case!("undefined"!=typeof this.props.maxValues&&this.values().length>=this.props.maxValues):return!1;default:return e}}.call(b),t)},onSearchChange:p,onValuesChange:f,filteredOptions:m,options:y}},getInitialState:function(){return{anchor:this.props.values?m(this.props.values):void 0,highlightedUid:void 0,open:!1,scrollLock:!1,search:"",values:this.props.defaultValues}},firstOptionIndexToHighlight:function(e){var t,n;return t=function(){var t;switch(!1){case 1!==e.length:return 0;case"undefined"!=typeof(null!=(t=e[0])?t.newOption:void 0):return 0;default:return a(function(e){return"boolean"==typeof e.selectable&&!e.selectable})(c(1)(e))?0:1}}(),n=this.props.hasOwnProperty("search")?this.props.search:this.state.search,this.props.firstOptionIndexToHighlight(t,e,this.values(),n)},focus:function(){this.refs.select.focus()},blur:function(){this.refs.select.blur()},highlightFirstSelectableOption:function(){this.state.open&&this.refs.select.highlightAndScrollToSelectableOption(this.firstOptionIndexToHighlight(this.getComputedState().options),1)},values:function(){return this.props.hasOwnProperty("values")?this.props.values:this.state.values},isOpen:function(){return this.props.hasOwnProperty("open")?this.props.open:this.state.open}})}).call(this)},function(e,t,n){(function(){function t(e,t){var n={}.hasOwnProperty;for(var r in t)n.call(t,r)&&(e[r]=t[r]);return e}var r,o,i,a,s,u;r=n(5).createClass,o=n(13),i=o.render,a=o.unmountComponentAtNode,s=n(124),u=n(224),e.exports=r({getDefaultProps:function(){return{parentElement:function(){return document.body; +}}},render:function(){return null},initTether:function(e){var n=this;this.node=document.createElement("div"),this.props.parentElement().appendChild(this.node),this.tether=new u(t({element:this.node,target:e.target()},e.options)),i(e.children,this.node,function(){return n.tether.position()})},destroyTether:function(){this.tether&&this.tether.destroy(),this.node&&(a(this.node),this.node.parentElement.removeChild(this.node)),this.node=this.tether=void 0},componentDidMount:function(){this.props.children&&this.initTether(this.props)},componentWillReceiveProps:function(e){var n=this;this.props.children&&!e.children?this.destroyTether():e.children&&!this.props.children?this.initTether(e):e.children&&(this.tether.setOptions(t({element:this.node,target:e.target()},e.options)),i(e.children,this.node,function(){return n.tether.position()}))},shouldComponentUpdate:function(e,t){return s(this,e,t)},componentWillUnmount:function(){this.destroyTether()}})}).call(this)},function(e,t,n){(function(){var t,r,o,i,a;t=n(5),r=t.createClass,o=t.createFactory,i=t.DOM.path,a=o(n(85)),e.exports=r({render:function(){return a({className:"react-selectize-reset-button",style:{width:8,height:8}},i({d:"M0 0 L8 8 M8 0 L 0 8"}))}})}).call(this)},function(e,t,n){(function(){function t(e,t){var n={}.hasOwnProperty;for(var r in t)n.call(t,r)&&(e[r]=t[r]);return e}var r,o,i,a,s,u,l,c;r=n(15),o=r.each,i=r.objToPairs,a=n(5),s=a.DOM.input,u=a.createClass,l=a.createFactory,c=n(13).findDOMNode,e.exports=u({displayName:"ResizableInput",render:function(){var e;return s((e=t({},this.props),e.type="input",e.className="resizable-input",e))},autosize:function(){var e,t,n,r,a;return e=t=c(this),e.style.width="0px",0===t.value.length?t.style.width=null!=t&&t.currentStyle?"4px":"2px":t.scrollWidth>0?t.style.width=2+t.scrollWidth+"px":(n=r=document.createElement("div"),n.innerHTML=t.value,function(){var e;return e=r.style,e.display="inline-block",e.width="",e}(o(function(e){var t,n;return t=e[0],n=e[1],r.style[t]=n})(i(t.currentStyle?t.currentStyle:null!=(a=document.defaultView)?a:window.getComputedStyle(t)))),document.body.appendChild(r),t.style.width=4+r.clientWidth+"px",document.body.removeChild(r))},componentDidMount:function(){this.autosize()},componentDidUpdate:function(){this.autosize()},blur:function(){return c(this).blur()},focus:function(){return c(this).focus()}})}).call(this)},function(e,t,n){(function(){function t(e,t){var n,r=function(o){return e.length>1?function(){var i=o?o.concat():[];return n=t?n||this:this,i.push.apply(i,arguments)-1})(e)}),firstOptionIndexToHighlight:d,onBlur:function(e){},onBlurResetsInput:!0,onFocus:function(e){},onKeyboardSelectionFailed:function(e){},onPaste:function(e){},placeholder:"",renderValue:function(e){var t;return t=e.label,C({className:"simple-value"},w(null,t))},serialize:function(e){return null!=e?e.value:void 0},style:{},tether:!1,uid:d}},render:function(){var e,t,n,o,i,a,s,u,l,c,p,f,d,m,v,y,b,C,_,w,x,O,P,k,S,N,M,A,I,D,R,L,U,F,j,B,V,W=this;return e=this.getComputedState(),t=e.filteredOptions,n=e.highlightedUid,o=e.onHighlightedUidChange,i=e.onOpenChange,a=e.onSearchChange,s=e.onValueChange,u=e.open,l=e.options,c=e.search,p=e.value,f=e.values,null!=(e=this.props)&&(d=e.autofocus,m=e.autosize,v=e.cancelKeyboardEventOnSelection,y=e.delimiters,b=e.disabled,C=e.dropdownDirection,_=e.groupId,w=e.groups,x=e.groupsAsColumns,O=e.hideResetButton,P=e.name,k=e.inputProps,S=e.onBlurResetsInput,N=e.renderToggleButton,M=e.renderGroupTitle,A=e.renderResetButton,I=e.serialize,D=e.tether,R=e.tetherProps,L=e.theme,U=e.transitionEnter,F=e.transitionLeave,j=e.transitionEnterTimeout,B=e.transitionLeaveTimeout,V=e.uid),E(r(r({autofocus:d,autosize:m,cancelKeyboardEventOnSelection:v,className:"simple-select"+(this.props.className?" "+this.props.className:""),delimiters:y,disabled:b,dropdownDirection:C,groupId:_,groups:w,groupsAsColumns:x,hideResetButton:O,highlightedUid:n,onHighlightedUidChange:o,inputProps:k,name:P,onBlurResetsInput:S,renderGroupTitle:M,renderResetButton:A,renderToggleButton:N,scrollLock:this.state.scrollLock,onScrollLockChange:function(e){return W.setState({scrollLock:e})},tether:D,tetherProps:R,theme:L,transitionEnter:U,transitionEnterTimeout:j,transitionLeave:F,transitionLeaveTimeout:B,ref:"select",anchor:h(f),onAnchorChange:function(e,t){return t()},open:u,onOpenChange:i,firstOptionIndexToHighlight:function(){return W.firstOptionIndexToHighlight(l,p)},options:l,renderOption:this.props.renderOption,renderNoResultsFound:this.props.renderNoResultsFound,search:c,onSearchChange:function(e,t){return a(e,t)},values:f,onValuesChange:function(e,t){var n,r;return 0===e.length?s(void 0,function(){return t()}):(n=h(e),r=!g(n,p),function(){return function(e){return r?s(n,e):e()}}()(function(){return t(),i(!1,function(){})}))},renderValue:function(e){return u&&(W.props.editable||c.length>0)?null:W.props.renderValue(e)},onKeyboardSelectionFailed:function(e){return a("",function(){return i(!1,function(){return W.props.onKeyboardSelectionFailed(e)})})},uid:function(e){return{uid:W.props.uid(e),open:u,search:c}},serialize:function(e){return I(e[0])},onBlur:function(e){var t;t=W.props.onBlurResetsInput,function(){return function(e){return c.length>0&&t?a("",e):e()}}()(function(){return W.props.onBlur({value:p,open:u,originalEvent:e})})},onFocus:function(e){W.props.onFocus({value:p,open:u,originalEvent:e})},onPaste:function(){var e;switch(!1){case"undefined"!=typeof(null!=(e=this.props)?e.valueFromPaste:void 0):return this.props.onPaste;default:return function(e){var t,n;if(t=e.clipboardData,n=W.props.valueFromPaste(l,p,t.getData("text")))return function(){return s(n,function(){return a("",function(){return i(!1)})})}(),T(e)}}}.call(this),placeholder:this.props.placeholder,style:this.props.style},function(){switch(!1){case"function"!=typeof this.props.restoreOnBackspace:return{restoreOnBackspace:this.props.restoreOnBackspace};default:return{}}}.call(this)),function(){switch(!1){case"function"!=typeof this.props.renderNoResultsFound:return{renderNoResultsFound:function(){return W.props.renderNoResultsFound(p,c)}};default:return{}}}.call(this)))},getComputedState:function(){var e,t,n,o,i,a,s,l,c,p,f,d,h,v,g,y=this;return e=this.props.hasOwnProperty("highlightedUid")?this.props.highlightedUid:this.state.highlightedUid,t=this.isOpen(),n=this.props.hasOwnProperty("search")?this.props.search:this.state.search,o=this.value(),i=o||0===o?[o]:[],a=m(function(e){var t;return t=function(){switch(!1){case!(this.props.hasOwnProperty(e)&&this.props.hasOwnProperty(u("on-"+e+"-change"))):return function(t,n){return y.props[u("on-"+e+"-change")](t,function(){}),y.setState({},n)};case!(this.props.hasOwnProperty(e)&&!this.props.hasOwnProperty(u("on-"+e+"-change"))):return function(e,t){return t()};case!(!this.props.hasOwnProperty(e)&&this.props.hasOwnProperty(u("on-"+e+"-change"))):return function(t,n){var r;return y.setState((r={},r[e+""]=t,r),function(){return n(),y.props[u("on-"+e+"-change")](t,function(){})})};case!(!this.props.hasOwnProperty(e)&&!this.props.hasOwnProperty(u("on-"+e+"-change"))):return function(t,n){var r;return y.setState((r={},r[e+""]=t,r),n)}}}.call(y)})(["highlightedUid","open","search","value"]),s=a[0],l=a[1],c=a[2],p=a[3],f=function(){var e;switch(!1){case!(null!=(e=this.props)&&e.children):return m(function(e){var t,n,r;return null!=(t=null!=e?e.props:void 0)&&(n=t.value,r=t.children),{label:r,value:n}})("Array"===x.call(this.props.children).slice(8,-1)?this.props.children:[this.props.children]);default:return[]}}.call(this),d=this.props.hasOwnProperty("options")?null!=(a=this.props.options)?a:[]:f,h=this.props.filterOptions(d,n),v=function(){switch(!1){case"function"!=typeof this.props.createFromSearch:return this.props.createFromSearch(h,n);default:return null}}.call(this),g=(v?[(a=r({},v),a.newOption=!0,a)]:[]).concat(h),{highlightedUid:e,open:t,search:n,value:o,values:i,onHighlightedUidChange:s,onOpenChange:function(e,t){l(e,function(){if(t(),y.props.editable&&y.isOpen()&&o)return c(y.props.editable(o)+""+(1===n.length?n:""),function(){return y.highlightFirstSelectableOption(function(){})})})},onSearchChange:c,onValueChange:p,filteredOptions:h,options:g}},getInitialState:function(){var e;return{highlightedUid:void 0,open:!1,scrollLock:!1,search:"",value:null!=(e=this.props)?e.defaultValue:void 0}},firstOptionIndexToHighlight:function(e,t){var n,r,o;return n=t?f(function(e){return g(e,t)},e):void 0,r=function(){var t;switch(!1){case"undefined"==typeof n:return n;case 1!==e.length:return 0;case"undefined"!=typeof(null!=(t=e[0])?t.newOption:void 0):return 0;default:return i(function(e){return"boolean"==typeof e.selectable&&!e.selectable})(s(1)(e))?0:1}}(),o=this.props.hasOwnProperty("search")?this.props.search:this.state.search,this.props.firstOptionIndexToHighlight(r,e,t,o)},focus:function(){this.refs.select.focus()},blur:function(){this.refs.select.blur()},highlightFirstSelectableOption:function(e){var t,n,r;null==e&&(e=function(){}),this.state.open?(t=this.getComputedState(),n=t.options,r=t.value,this.refs.select.highlightAndScrollToSelectableOption(this.firstOptionIndexToHighlight(n,r),1,e)):e()},value:function(){return this.props.hasOwnProperty("value")?this.props.value:this.state.value},isOpen:function(){return this.props.hasOwnProperty("open")?this.props.open:this.state.open}})}).call(this)},function(e,t,n){(function(){var t,r,o,i,a;t=n(5),r=t.createClass,o=t.createFactory,i=t.DOM.path,a=o(n(85)),e.exports=r({getDefaultProps:function(){return{open:!1,flipped:!1}},render:function(){return a({className:"react-selectize-toggle-button",style:{width:10,height:8}},i({d:function(){switch(!1){case!(this.props.open&&!this.props.flipped||!this.props.open&&this.props.flipped):return"M0 6 L5 1 L10 6 Z";default:return"M0 1 L5 6 L10 1 Z"}}.call(this)}))}})}).call(this)},function(e,t,n){(function(){var t,r,o,i;t=n(5),r=t.createClass,o=t.DOM.div,i=n(16).isEqualToObject,e.exports=r({getDefaultProps:function(){return{}},render:function(){return o({className:"value-wrapper"},this.props.renderItem(this.props.item))},shouldComponentUpdate:function(e){var t;return!i(null!=e?e.uid:void 0,null!=(t=this.props)?t.uid:void 0)}})}).call(this)},function(e,t,n){(function(){var t,r,o,i;t=n(196),r=n(201),o=n(197),i=n(53),e.exports={HighlightedText:t,SimpleSelect:r,MultiSelect:o,ReactSelectize:i}}).call(this)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}t.__esModule=!0;var s=Object.assign||function(e){for(var t=1;t=0)&&r.push(o)}return r.push(e.ownerDocument.body),e.ownerDocument!==document&&r.push(e.ownerDocument.defaultView),r}function r(){w&&document.body.removeChild(w),w=null}function o(e){var n=void 0;e===document?(n=document,e=document.documentElement):n=e.ownerDocument;var r=n.documentElement,o=t(e),i=x();return o.top-=i.top,o.left-=i.left,"undefined"==typeof o.width&&(o.width=document.body.scrollWidth-o.left-o.right),"undefined"==typeof o.height&&(o.height=document.body.scrollHeight-o.top-o.bottom),o.top=o.top-r.clientTop, +o.left=o.left-r.clientLeft,o.right=n.body.clientWidth-o.width-o.left,o.bottom=n.body.clientHeight-o.height-o.top,o}function i(e){return e.offsetParent||document.documentElement}function a(){if(O)return O;var e=document.createElement("div");e.style.width="100%",e.style.height="200px";var t=document.createElement("div");s(t.style,{position:"absolute",top:0,left:0,pointerEvents:"none",visibility:"hidden",width:"200px",height:"150px",overflow:"hidden"}),t.appendChild(e),document.body.appendChild(t);var n=e.offsetWidth;t.style.overflow="scroll";var r=e.offsetWidth;n===r&&(r=t.clientWidth),document.body.removeChild(t);var o=n-r;return O={width:o,height:o}}function s(){var e=arguments.length<=0||void 0===arguments[0]?{}:arguments[0],t=[];return Array.prototype.push.apply(t,arguments),t.slice(1).forEach(function(t){if(t)for(var n in t)({}).hasOwnProperty.call(t,n)&&(e[n]=t[n])}),e}function u(e,t){if("undefined"!=typeof e.classList)t.split(" ").forEach(function(t){t.trim()&&e.classList.remove(t)});else{var n=new RegExp("(^| )"+t.split(" ").join("|")+"( |$)","gi"),r=p(e).replace(n," ");f(e,r)}}function l(e,t){if("undefined"!=typeof e.classList)t.split(" ").forEach(function(t){t.trim()&&e.classList.add(t)});else{u(e,t);var n=p(e)+(" "+t);f(e,n)}}function c(e,t){if("undefined"!=typeof e.classList)return e.classList.contains(t);var n=p(e);return new RegExp("(^| )"+t+"( |$)","gi").test(n)}function p(e){return e.className instanceof e.ownerDocument.defaultView.SVGAnimatedString?e.className.baseVal:e.className}function f(e,t){e.setAttribute("class",t)}function d(e,t,n){n.forEach(function(n){t.indexOf(n)===-1&&c(e,n)&&u(e,n)}),t.forEach(function(t){c(e,t)||l(e,t)})}function e(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function h(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function m(e,t){var n=arguments.length<=2||void 0===arguments[2]?1:arguments[2];return e+n>=t&&t>=e-n}function v(){return"object"==typeof performance&&"function"==typeof performance.now?performance.now():+new Date}function g(){for(var e={top:0,left:0},t=arguments.length,n=Array(t),r=0;r1?n-1:0),o=1;o16?(t=Math.min(t-16,250),void(n=setTimeout(r,250))):void("undefined"!=typeof e&&v()-e<10||(null!=n&&(clearTimeout(n),n=null),e=v(),L(),t=v()-e))};"undefined"!=typeof window&&"undefined"!=typeof window.addEventListener&&["resize","scroll","touchmove"].forEach(function(e){window.addEventListener(e,r)})}();var U={center:"center",left:"right",right:"left"},F={middle:"middle",top:"bottom",bottom:"top"},j={top:0,left:0,middle:"50%",center:"50%",bottom:"100%",right:"100%"},B=function(e,t){var n=e.left,r=e.top;return"auto"===n&&(n=U[t.left]),"auto"===r&&(r=F[t.top]),{left:n,top:r}},V=function(e){var t=e.left,n=e.top;return"undefined"!=typeof j[e.left]&&(t=j[e.left]),"undefined"!=typeof j[e.top]&&(n=j[e.top]),{left:t,top:n}},W=function(e){var t=e.split(" "),n=M(t,2),r=n[0],o=n[1];return{top:r,left:o}},H=W,q=function(t){function c(t){var n=this;e(this,c),A(Object.getPrototypeOf(c.prototype),"constructor",this).call(this),this.position=this.position.bind(this),R.push(this),this.history=[],this.setOptions(t,!1),_.modules.forEach(function(e){"undefined"!=typeof e.initialize&&e.initialize.call(n)}),this.position()}return h(c,t),C(c,[{key:"getClass",value:function(){var e=arguments.length<=0||void 0===arguments[0]?"":arguments[0],t=this.options.classes;return"undefined"!=typeof t&&t[e]?this.options.classes[e]:this.options.classPrefix?this.options.classPrefix+"-"+e:e}},{key:"setOptions",value:function(e){var t=this,r=arguments.length<=1||void 0===arguments[1]||arguments[1],o={offset:"0 0",targetOffset:"0 0",targetAttachment:"auto auto",classPrefix:"tether"};this.options=s(o,e);var i=this.options,a=i.element,u=i.target,c=i.targetModifier;if(this.element=a,this.target=u,this.targetModifier=c,"viewport"===this.target?(this.target=document.body,this.targetModifier="visible"):"scroll-handle"===this.target&&(this.target=document.body,this.targetModifier="scroll-handle"),["element","target"].forEach(function(e){if("undefined"==typeof t[e])throw new Error("Tether Error: Both element and target must be defined");"undefined"!=typeof t[e].jquery?t[e]=t[e][0]:"string"==typeof t[e]&&(t[e]=document.querySelector(t[e]))}),l(this.element,this.getClass("element")),this.options.addTargetClasses!==!1&&l(this.target,this.getClass("target")),!this.options.attachment)throw new Error("Tether Error: You must provide an attachment");this.targetAttachment=H(this.options.targetAttachment),this.attachment=H(this.options.attachment),this.offset=W(this.options.offset),this.targetOffset=W(this.options.targetOffset),"undefined"!=typeof this.scrollParents&&this.disable(),"scroll-handle"===this.targetModifier?this.scrollParents=[this.target]:this.scrollParents=n(this.target),this.options.enabled!==!1&&this.enable(r)}},{key:"getTargetBounds",value:function(){if("undefined"==typeof this.targetModifier)return o(this.target);if("visible"===this.targetModifier){if(this.target===document.body)return{top:pageYOffset,left:pageXOffset,height:innerHeight,width:innerWidth};var e=o(this.target),t={height:e.height,width:e.width,top:e.top,left:e.left};return t.height=Math.min(t.height,e.height-(pageYOffset-e.top)),t.height=Math.min(t.height,e.height-(e.top+e.height-(pageYOffset+innerHeight))),t.height=Math.min(innerHeight,t.height),t.height-=2,t.width=Math.min(t.width,e.width-(pageXOffset-e.left)),t.width=Math.min(t.width,e.width-(e.left+e.width-(pageXOffset+innerWidth))),t.width=Math.min(innerWidth,t.width),t.width-=2,t.topn.clientWidth||[r.overflow,r.overflowX].indexOf("scroll")>=0||this.target!==document.body,a=0;i&&(a=15);var s=e.height-parseFloat(r.borderTopWidth)-parseFloat(r.borderBottomWidth)-a,t={width:15,height:.975*s*(s/n.scrollHeight),left:e.left+e.width-parseFloat(r.borderLeftWidth)-15},u=0;s<408&&this.target===document.body&&(u=-11e-5*Math.pow(s,2)-.00727*s+22.58),this.target!==document.body&&(t.height=Math.max(t.height,24));var l=this.target.scrollTop/(n.scrollHeight-s);return t.top=l*(s-t.height-u)+e.top+parseFloat(r.borderTopWidth),this.target===document.body&&(t.height=Math.max(t.height,24)),t}}},{key:"clearCache",value:function(){this._cache={}}},{key:"cache",value:function(e,t){return"undefined"==typeof this._cache&&(this._cache={}),"undefined"==typeof this._cache[e]&&(this._cache[e]=t.call(this)),this._cache[e]}},{key:"enable",value:function(){var e=this,t=arguments.length<=0||void 0===arguments[0]||arguments[0];this.options.addTargetClasses!==!1&&l(this.target,this.getClass("enabled")),l(this.element,this.getClass("enabled")),this.enabled=!0,this.scrollParents.forEach(function(t){t!==e.target.ownerDocument&&t.addEventListener("scroll",e.position)}),t&&this.position()}},{key:"disable",value:function(){var e=this;u(this.target,this.getClass("enabled")),u(this.element,this.getClass("enabled")),this.enabled=!1,"undefined"!=typeof this.scrollParents&&this.scrollParents.forEach(function(t){t.removeEventListener("scroll",e.position)})}},{key:"destroy",value:function(){var e=this;this.disable(),R.forEach(function(t,n){t===e&&R.splice(n,1)}),0===R.length&&r()}},{key:"updateAttachClasses",value:function(e,t){var n=this;e=e||this.attachment,t=t||this.targetAttachment;var r=["left","top","bottom","right","middle","center"];"undefined"!=typeof this._addAttachClasses&&this._addAttachClasses.length&&this._addAttachClasses.splice(0,this._addAttachClasses.length),"undefined"==typeof this._addAttachClasses&&(this._addAttachClasses=[]);var o=this._addAttachClasses;e.top&&o.push(this.getClass("element-attached")+"-"+e.top),e.left&&o.push(this.getClass("element-attached")+"-"+e.left),t.top&&o.push(this.getClass("target-attached")+"-"+t.top),t.left&&o.push(this.getClass("target-attached")+"-"+t.left);var i=[];r.forEach(function(e){i.push(n.getClass("element-attached")+"-"+e),i.push(n.getClass("target-attached")+"-"+e)}),k(function(){"undefined"!=typeof n._addAttachClasses&&(d(n.element,n._addAttachClasses,i),n.options.addTargetClasses!==!1&&d(n.target,n._addAttachClasses,i),delete n._addAttachClasses)})}},{key:"position",value:function(){var e=this,t=arguments.length<=0||void 0===arguments[0]||arguments[0];if(this.enabled){this.clearCache();var n=B(this.targetAttachment,this.attachment);this.updateAttachClasses(this.attachment,n);var r=this.cache("element-bounds",function(){return o(e.element)}),s=r.width,u=r.height;if(0===s&&0===u&&"undefined"!=typeof this.lastSize){var l=this.lastSize;s=l.width,u=l.height}else this.lastSize={width:s,height:u};var c=this.cache("target-bounds",function(){return e.getTargetBounds()}),p=c,f=y(V(this.attachment),{width:s,height:u}),d=y(V(n),p),h=y(this.offset,{width:s,height:u}),m=y(this.targetOffset,p);f=g(f,h),d=g(d,m);for(var v=c.left+d.left-f.left,b=c.top+d.top-f.top,C=0;C<_.modules.length;++C){var w=_.modules[C],E=w.position.call(this,{left:v,top:b,targetAttachment:n,targetPos:c,elementPos:r,offset:f,targetOffset:d,manualOffset:h,manualTargetOffset:m,scrollbarSize:P,attachment:this.attachment});if(E===!1)return!1;"undefined"!=typeof E&&"object"==typeof E&&(b=E.top,v=E.left)}var T={page:{top:b,left:v},viewport:{top:b-pageYOffset,bottom:pageYOffset-b-u+innerHeight,left:v-pageXOffset,right:pageXOffset-v-s+innerWidth}},x=this.target.ownerDocument,O=x.defaultView,P=void 0;return O.innerHeight>x.documentElement.clientHeight&&(P=this.cache("scrollbar-size",a),T.viewport.bottom-=P.height),O.innerWidth>x.documentElement.clientWidth&&(P=this.cache("scrollbar-size",a),T.viewport.right-=P.width),["","static"].indexOf(x.body.style.position)!==-1&&["","static"].indexOf(x.body.parentElement.style.position)!==-1||(T.page.bottom=x.body.scrollHeight-b-u,T.page.right=x.body.scrollWidth-v-s),"undefined"!=typeof this.options.optimizations&&this.options.optimizations.moveElement!==!1&&"undefined"==typeof this.targetModifier&&!function(){var t=e.cache("target-offsetparent",function(){return i(e.target)}),n=e.cache("target-offsetparent-bounds",function(){return o(t)}),r=getComputedStyle(t),a=n,s={};if(["Top","Left","Bottom","Right"].forEach(function(e){s[e.toLowerCase()]=parseFloat(r["border"+e+"Width"])}),n.right=x.body.scrollWidth-n.left-a.width+s.right,n.bottom=x.body.scrollHeight-n.top-a.height+s.bottom,T.page.top>=n.top+s.top&&T.page.bottom>=n.bottom&&T.page.left>=n.left+s.left&&T.page.right>=n.right){var u=t.scrollTop,l=t.scrollLeft;T.offset={top:T.page.top-n.top+u-s.top,left:T.page.left-n.left+l-s.left}}}(),this.move(T),this.history.unshift(T),this.history.length>3&&this.history.pop(),t&&S(),!0}}},{key:"move",value:function(e){var t=this;if("undefined"!=typeof this.element.parentNode){var n={};for(var r in e){n[r]={};for(var o in e[r]){for(var a=!1,u=0;u=0){var d=a.split(" "),m=M(d,2);p=m[0],c=m[1]}else c=p=a;var C=b(t,o);"target"!==p&&"both"!==p||(nC[3]&&"bottom"===g.top&&(n-=f,g.top="top")),"together"===p&&("top"===g.top&&("bottom"===y.top&&nC[3]&&n-(u-f)>=C[1]&&(n-=u-f,g.top="bottom",y.top="bottom")),"bottom"===g.top&&("top"===y.top&&n+u>C[3]?(n-=f,g.top="top",n-=u,y.top="bottom"):"bottom"===y.top&&nC[3]&&"top"===y.top?(n-=u,y.top="bottom"):nC[2]&&"right"===g.left&&(r-=h,g.left="left")),"together"===c&&(rC[2]&&"right"===g.left?"left"===y.left?(r-=h,g.left="left",r-=l,y.left="right"):"right"===y.left&&(r-=h,g.left="left",r+=l,y.left="left"):"center"===g.left&&(r+l>C[2]&&"left"===y.left?(r-=l,y.left="right"):rC[3]&&"top"===y.top&&(n-=u,y.top="bottom")),"element"!==c&&"both"!==c||(rC[2]&&("left"===y.left?(r-=l,y.left="right"):"center"===y.left&&(r-=l/2,y.left="right"))),"string"==typeof s?s=s.split(",").map(function(e){return e.trim()}):s===!0&&(s=["top","left","right","bottom"]),s=s||[];var _=[],w=[];n=0?(n=C[1],_.push("top")):w.push("top")),n+u>C[3]&&(s.indexOf("bottom")>=0?(n=C[3]-u,_.push("bottom")):w.push("bottom")),r=0?(r=C[0],_.push("left")):w.push("left")),r+l>C[2]&&(s.indexOf("right")>=0?(r=C[2]-l,_.push("right")):w.push("right")),_.length&&!function(){var e=void 0;e="undefined"!=typeof t.options.pinnedClass?t.options.pinnedClass:t.getClass("pinned"),v.push(e),_.forEach(function(t){v.push(e+"-"+t)})}(),w.length&&!function(){var e=void 0;e="undefined"!=typeof t.options.outOfBoundsClass?t.options.outOfBoundsClass:t.getClass("out-of-bounds"),v.push(e),w.forEach(function(t){v.push(e+"-"+t)})}(),(_.indexOf("left")>=0||_.indexOf("right")>=0)&&(y.left=g.left=!1),(_.indexOf("top")>=0||_.indexOf("bottom")>=0)&&(y.top=g.top=!1),g.top===i.top&&g.left===i.left&&y.top===t.attachment.top&&y.left===t.attachment.left||(t.updateAttachClasses(y,g),t.trigger("update",{attachment:y,targetAttachment:g}))}),k(function(){t.options.addTargetClasses!==!1&&d(t.target,v,m),d(t.element,v,m)}),{top:n,left:r}}});var I=_.Utils,o=I.getBounds,d=I.updateClasses,k=I.defer;_.modules.push({position:function(e){var t=this,n=e.top,r=e.left,i=this.cache("element-bounds",function(){return o(t.element)}),a=i.height,s=i.width,u=this.getTargetBounds(),l=n+a,c=r+s,p=[];n<=u.bottom&&l>=u.top&&["left","right"].forEach(function(e){var t=u[e];t!==r&&t!==c||p.push(e)}),r<=u.right&&c>=u.left&&["top","bottom"].forEach(function(e){var t=u[e];t!==n&&t!==l||p.push(e)});var f=[],h=[],m=["left","top","right","bottom"];return f.push(this.getClass("abutted")),m.forEach(function(e){f.push(t.getClass("abutted")+"-"+e)}),p.length&&h.push(this.getClass("abutted")),p.forEach(function(e){h.push(t.getClass("abutted")+"-"+e)}),k(function(){t.options.addTargetClasses!==!1&&d(t.target,h,f),d(t.element,h,f)}),!0}});var M=function(){function e(e,t){var n=[],r=!0,o=!1,i=void 0;try{for(var a,s=e[Symbol.iterator]();!(r=(a=s.next()).done)&&(n.push(a.value),!t||n.length!==t);r=!0);}catch(e){o=!0,i=e}finally{try{!r&&s.return&&s.return()}finally{if(o)throw i}}return n}return function(t,n){if(Array.isArray(t))return t;if(Symbol.iterator in Object(t))return e(t,n);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}();return _.modules.push({position:function(e){var t=e.top,n=e.left;if(this.options.shift){var r=this.options.shift;"function"==typeof this.options.shift&&(r=this.options.shift.call(this,{top:t,left:n}));var o=void 0,i=void 0;if("string"==typeof r){r=r.split(" "),r[1]=r[1]||r[0];var a=r,s=M(a,2);o=s[0],i=s[1],o=parseFloat(o,10),i=parseFloat(i,10)}else o=r.top,i=r.left;return t+=o,n+=i,{top:t,left:n}}}}),z})},function(e,t,n){"use strict";var r=function(){};e.exports=r},function(e,t){!function(e){"use strict";function t(e){if("string"!=typeof e&&(e=String(e)),/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(e))throw new TypeError("Invalid character in header field name");return e.toLowerCase()}function n(e){return"string"!=typeof e&&(e=String(e)),e}function r(e){var t={next:function(){var t=e.shift();return{done:void 0===t,value:t}}};return g.iterable&&(t[Symbol.iterator]=function(){return t}),t}function o(e){this.map={},e instanceof o?e.forEach(function(e,t){this.append(t,e)},this):e&&Object.getOwnPropertyNames(e).forEach(function(t){this.append(t,e[t])},this)}function i(e){return e.bodyUsed?Promise.reject(new TypeError("Already read")):void(e.bodyUsed=!0)}function a(e){return new Promise(function(t,n){e.onload=function(){t(e.result)},e.onerror=function(){n(e.error)}})}function s(e){var t=new FileReader,n=a(t);return t.readAsArrayBuffer(e),n}function u(e){var t=new FileReader,n=a(t);return t.readAsText(e),n}function l(e){for(var t=new Uint8Array(e),n=new Array(t.length),r=0;r-1?t:e}function d(e,t){t=t||{};var n=t.body;if(e instanceof d){if(e.bodyUsed)throw new TypeError("Already read");this.url=e.url,this.credentials=e.credentials,t.headers||(this.headers=new o(e.headers)),this.method=e.method,this.mode=e.mode,n||null==e._bodyInit||(n=e._bodyInit,e.bodyUsed=!0)}else this.url=String(e);if(this.credentials=t.credentials||this.credentials||"omit",!t.headers&&this.headers||(this.headers=new o(t.headers)),this.method=f(t.method||this.method||"GET"),this.mode=t.mode||this.mode||null,this.referrer=null,("GET"===this.method||"HEAD"===this.method)&&n)throw new TypeError("Body not allowed for GET or HEAD requests");this._initBody(n)}function h(e){var t=new FormData;return e.trim().split("&").forEach(function(e){if(e){var n=e.split("="),r=n.shift().replace(/\+/g," "),o=n.join("=").replace(/\+/g," ");t.append(decodeURIComponent(r),decodeURIComponent(o))}}),t}function m(e){var t=new o;return e.split(/\r?\n/).forEach(function(e){var n=e.split(":"),r=n.shift().trim();if(r){var o=n.join(":").trim();t.append(r,o)}}),t}function v(e,t){t||(t={}),this.type="default",this.status="status"in t?t.status:200,this.ok=this.status>=200&&this.status<300,this.statusText="statusText"in t?t.statusText:"OK",this.headers=new o(t.headers),this.url=t.url||"",this._initBody(e)}if(!e.fetch){var g={searchParams:"URLSearchParams"in e,iterable:"Symbol"in e&&"iterator"in Symbol,blob:"FileReader"in e&&"Blob"in e&&function(){try{return new Blob,!0}catch(e){return!1}}(),formData:"FormData"in e,arrayBuffer:"ArrayBuffer"in e};if(g.arrayBuffer)var y=["[object Int8Array]","[object Uint8Array]","[object Uint8ClampedArray]","[object Int16Array]","[object Uint16Array]","[object Int32Array]","[object Uint32Array]","[object Float32Array]","[object Float64Array]"],b=function(e){return e&&DataView.prototype.isPrototypeOf(e)},C=ArrayBuffer.isView||function(e){return e&&y.indexOf(Object.prototype.toString.call(e))>-1};o.prototype.append=function(e,r){e=t(e),r=n(r);var o=this.map[e];this.map[e]=o?o+","+r:r},o.prototype.delete=function(e){delete this.map[t(e)]},o.prototype.get=function(e){return e=t(e),this.has(e)?this.map[e]:null},o.prototype.has=function(e){return this.map.hasOwnProperty(t(e))},o.prototype.set=function(e,r){this.map[t(e)]=n(r)},o.prototype.forEach=function(e,t){for(var n in this.map)this.map.hasOwnProperty(n)&&e.call(t,this.map[n],n,this)},o.prototype.keys=function(){var e=[];return this.forEach(function(t,n){e.push(n)}),r(e)},o.prototype.values=function(){var e=[];return this.forEach(function(t){e.push(t)}),r(e)},o.prototype.entries=function(){var e=[];return this.forEach(function(t,n){e.push([n,t])}),r(e)},g.iterable&&(o.prototype[Symbol.iterator]=o.prototype.entries);var _=["DELETE","GET","HEAD","OPTIONS","POST","PUT"];d.prototype.clone=function(){return new d(this,{body:this._bodyInit})},p.call(d.prototype),p.call(v.prototype),v.prototype.clone=function(){return new v(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new o(this.headers),url:this.url})},v.error=function(){var e=new v(null,{status:0,statusText:""});return e.type="error",e};var w=[301,302,303,307,308];v.redirect=function(e,t){if(w.indexOf(t)===-1)throw new RangeError("Invalid status code");return new v(null,{status:t,headers:{location:e}})},e.Headers=o,e.Request=d,e.Response=v,e.fetch=function(e,t){return new Promise(function(n,r){var o=new d(e,t),i=new XMLHttpRequest;i.onload=function(){var e={status:i.status,statusText:i.statusText,headers:m(i.getAllResponseHeaders()||"")};e.url="responseURL"in i?i.responseURL:e.headers.get("X-Request-URL");var t="response"in i?i.response:i.responseText;n(new v(t,e))},i.onerror=function(){r(new TypeError("Network request failed"))},i.ontimeout=function(){r(new TypeError("Network request failed"))},i.open(o.method,o.url,!0),"include"===o.credentials&&(i.withCredentials=!0),"responseType"in i&&g.blob&&(i.responseType="blob"),o.headers.forEach(function(e,t){i.setRequestHeader(t,e)}),i.send("undefined"==typeof o._bodyInit?null:o._bodyInit)})},e.fetch.polyfill=!0}}("undefined"!=typeof self?self:this)},function(e,t,n,r,o,i,a,s){function u(e,t){var n,r=function(o){return e.length>1?function(){var i=o?o.concat():[];return n=t?n||this:this,i.push.apply(i,arguments) Date: Mon, 10 Oct 2022 19:05:13 +0000 Subject: [PATCH 022/583] Validate notifier custom conditions before saving * Fixes #1846 --- plexpy/common.py | 5 +++ plexpy/notification_handler.py | 8 +++- plexpy/notifiers.py | 74 +++++++++++++++++++++++++++++++++- 3 files changed, 84 insertions(+), 3 deletions(-) diff --git a/plexpy/common.py b/plexpy/common.py index 039931f4..b6800d3c 100644 --- a/plexpy/common.py +++ b/plexpy/common.py @@ -679,3 +679,8 @@ NEWSLETTER_PARAMETERS = [ ] } ] + + +NOTIFICATION_PARAMETERS_TYPES = { + parameter['value']: parameter['type'] for category in NOTIFICATION_PARAMETERS for parameter in category['parameters'] +} diff --git a/plexpy/notification_handler.py b/plexpy/notification_handler.py index ad774fa6..315bcb52 100644 --- a/plexpy/notification_handler.py +++ b/plexpy/notification_handler.py @@ -288,7 +288,7 @@ def notify_custom_conditions(notifier_id=None, parameters=None): continue # Make sure the condition values is in a list - if isinstance(values, str): + if not isinstance(values, list): values = [values] # Cast the condition values to the correct type @@ -302,6 +302,9 @@ def notify_custom_conditions(notifier_id=None, parameters=None): elif parameter_type == 'float': values = [helpers.cast_to_float(v) for v in values] + else: + raise ValueError + except ValueError as e: logger.error("Tautulli NotificationHandler :: {%s} Unable to cast condition '%s', values '%s', to type '%s'." % (i+1, parameter, values, parameter_type)) @@ -318,6 +321,9 @@ def notify_custom_conditions(notifier_id=None, parameters=None): elif parameter_type == 'float': parameter_value = helpers.cast_to_float(parameter_value) + else: + raise ValueError + except ValueError as e: logger.error("Tautulli NotificationHandler :: {%s} Unable to cast parameter '%s', value '%s', to type '%s'." % (i+1, parameter, parameter_value, parameter_type)) diff --git a/plexpy/notifiers.py b/plexpy/notifiers.py index b7ee4f17..ffe3d40a 100644 --- a/plexpy/notifiers.py +++ b/plexpy/notifiers.py @@ -112,7 +112,12 @@ AGENT_IDS = {'growl': 0, 'gotify': 29 } -DEFAULT_CUSTOM_CONDITIONS = [{'parameter': '', 'operator': '', 'value': ''}] +DEFAULT_CUSTOM_CONDITIONS = [{'parameter': '', 'operator': '', 'value': [], 'type': None}] +CUSTOM_CONDITION_TYPE_OPERATORS = { + 'float': ['is', 'is not', 'is greater than', 'is less than'], + 'int': ['is', 'is not', 'is greater than', 'is less than'], + 'str': ['contains', 'does not contain', 'is', 'is not', 'begins with', 'does not begin with', 'ends with', 'does not end with'], +} def available_notification_agents(): @@ -642,13 +647,18 @@ def set_notifier_config(notifier_id=None, agent_id=None, **kwargs): agent_class = get_agent_class(agent_id=agent['id'], config=notifier_config) + custom_conditions = validate_conditions(kwargs.get('custom_conditions')) + if custom_conditions is False: + logger.error("Tautulli Notifiers :: Unable to update notification agent: Invalid custom conditions.") + return False + keys = {'id': notifier_id} values = {'agent_id': agent['id'], 'agent_name': agent['name'], 'agent_label': agent['label'], 'friendly_name': kwargs.get('friendly_name', ''), 'notifier_config': json.dumps(agent_class.config), - 'custom_conditions': kwargs.get('custom_conditions', json.dumps(DEFAULT_CUSTOM_CONDITIONS)), + 'custom_conditions': json.dumps(custom_conditions or DEFAULT_CUSTOM_CONDITIONS), 'custom_conditions_logic': kwargs.get('custom_conditions_logic', ''), } values.update(actions) @@ -685,6 +695,66 @@ def send_notification(notifier_id=None, subject='', body='', notify_action='', n logger.debug("Tautulli Notifiers :: Notification requested but no notifier_id received.") +def validate_conditions(custom_conditions): + if custom_conditions is None: + return DEFAULT_CUSTOM_CONDITIONS + + try: + conditions = json.loads(custom_conditions) + except ValueError: + logger.error("Tautulli Notifiers :: Unable to parse custom conditions json: %s" % custom_conditions) + return False + + if not isinstance(conditions, list): + logger.error("Tautulli Notifiers :: Invalid custom conditions: %s. Conditions must be a list." % conditions) + return False + + validated_conditions = [] + + for condition in conditions: + validated_condition = DEFAULT_CUSTOM_CONDITIONS[0].copy() + + if not isinstance(condition, dict): + logger.error("Tautulli Notifiers :: Invalid custom condition: %s. Condition must be a dict." % condition) + return False + + parameter = str(condition.get('parameter', '')).lower() + operator = str(condition.get('operator', '')).lower() + values = condition.get('value', []) + + if parameter: + parameter_type = common.NOTIFICATION_PARAMETERS_TYPES.get(parameter) + + if not parameter_type: + logger.error("Tautulli Notifiers :: Invalid parameter '%s' in custom condition: %s" % (parameter, condition)) + return False + + validated_condition['parameter'] = parameter.lower() + validated_condition['type'] = parameter_type + + if operator: + if operator not in CUSTOM_CONDITION_TYPE_OPERATORS.get(parameter_type, []): + logger.error("Tautulli Notifiers :: Invalid operator '%s' for parameter '%s' in custom condition: %s" % (operator, parameter, condition)) + return False + + validated_condition['operator'] = operator + + if values: + if not isinstance(values, list): + values = [values] + + for value in values: + if not isinstance(value, (str, int, float)): + logger.error("Tautulli Notifiers :: Invalid value '%s' for parameter '%s' in custom condition: %s" % (value, parameter, condition)) + return False + + validated_condition['value'] = values + + validated_conditions.append(validated_condition) + + return validated_conditions + + def blacklist_logger(): db = database.MonitorDatabase() notifiers = db.select('SELECT notifier_config FROM notifiers') From 08bc365a7c8dd7f91ab5c5344f8a19927ba9acd9 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Tue, 25 Oct 2022 16:11:37 +0000 Subject: [PATCH 023/583] Add collections to get_children_metadata API data --- plexpy/pmsconnect.py | 6 ++++++ plexpy/webserve.py | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py index e83a0a2d..9353b716 100644 --- a/plexpy/pmsconnect.py +++ b/plexpy/pmsconnect.py @@ -2442,6 +2442,7 @@ class PmsConnect(object): actors = [] genres = [] labels = [] + collections = [] if m.getElementsByTagName('Director'): for director in m.getElementsByTagName('Director'): @@ -2463,6 +2464,10 @@ class PmsConnect(object): for label in m.getElementsByTagName('Label'): labels.append(helpers.get_xml_attr(label, 'tag')) + if m.getElementsByTagName('Collection'): + for collection in m.getElementsByTagName('Collection'): + collections.append(helpers.get_xml_attr(collection, 'tag')) + media_type = helpers.get_xml_attr(m, 'type') if m.nodeName == 'Directory' and media_type == 'photo': media_type = 'photo_album' @@ -2506,6 +2511,7 @@ class PmsConnect(object): 'actors': actors, 'genres': genres, 'labels': labels, + 'collections': collections, 'full_title': helpers.get_xml_attr(m, 'title') } children_list.append(children_output) diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 3355d53e..040d61c7 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -4591,6 +4591,7 @@ class WebInterface(object): "audience_rating": "", "audience_rating_image": "", "banner": "", + "collections": [], "content_rating": "", "directors": [], "duration": "", @@ -5442,8 +5443,8 @@ class WebInterface(object): "tagline": "", "thumb": "/library/metadata/153037/thumb/1462175060", "title": "The Red Woman", - "user_rating": "9.0", "updated_at": "1462175060", + "user_rating": "9.0", "writers": [ "David Benioff", "D. B. Weiss" From 571bbb2db114fcc6868ef9287223ccc3dee39139 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Tue, 1 Nov 2022 16:37:36 +0000 Subject: [PATCH 024/583] Fallback season thumb to show thumb in get_metadata --- plexpy/pmsconnect.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py index 9353b716..11142873 100644 --- a/plexpy/pmsconnect.py +++ b/plexpy/pmsconnect.py @@ -924,7 +924,7 @@ class PmsConnect(object): 'parent_year': show_details.get('year', ''), 'grandparent_year': helpers.get_xml_attr(metadata_main, 'grandparentYear'), 'thumb': helpers.get_xml_attr(metadata_main, 'thumb'), - 'parent_thumb': helpers.get_xml_attr(metadata_main, 'parentThumb'), + 'parent_thumb': helpers.get_xml_attr(metadata_main, 'parentThumb') or show_details.get('thumb'), 'grandparent_thumb': helpers.get_xml_attr(metadata_main, 'grandparentThumb'), 'art': helpers.get_xml_attr(metadata_main, 'art'), 'banner': show_details.get('banner', ''), @@ -1003,7 +1003,7 @@ class PmsConnect(object): 'parent_year': season_details.get('year', ''), 'grandparent_year': show_details.get('year', ''), 'thumb': helpers.get_xml_attr(metadata_main, 'thumb'), - 'parent_thumb': parent_thumb, + 'parent_thumb': parent_thumb or show_details.get('thumb'), 'grandparent_thumb': helpers.get_xml_attr(metadata_main, 'grandparentThumb'), 'art': helpers.get_xml_attr(metadata_main, 'art'), 'banner': show_details.get('banner', ''), From 5975b59c93c8cc1526c35b167670fe5d23c0d9f6 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Sat, 5 Nov 2022 20:03:48 +0000 Subject: [PATCH 025/583] Add user_thumb to get_history response --- plexpy/datafactory.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/plexpy/datafactory.py b/plexpy/datafactory.py index 7027ae81..8a15ef7d 100644 --- a/plexpy/datafactory.py +++ b/plexpy/datafactory.py @@ -34,6 +34,7 @@ if plexpy.PYTHON2: import logger import pmsconnect import session + import users else: from plexpy import libraries from plexpy import common @@ -43,6 +44,7 @@ else: from plexpy import logger from plexpy import pmsconnect from plexpy import session + from plexpy import users # Temporarily store update_metadata row ids in memory to prevent rating_key collisions _UPDATE_METADATA_IDS = { @@ -103,6 +105,8 @@ class DataFactory(object): 'session_history.user', '(CASE WHEN users.friendly_name IS NULL OR TRIM(users.friendly_name) = "" \ THEN users.username ELSE users.friendly_name END) AS friendly_name', + 'users.thumb AS user_thumb', + 'users.custom_avatar_url AS custom_thumb', 'platform', 'product', 'player', @@ -161,6 +165,8 @@ class DataFactory(object): 'user', '(CASE WHEN friendly_name IS NULL OR TRIM(friendly_name) = "" \ THEN user ELSE friendly_name END) AS friendly_name', + 'NULL AS user_thumb', + 'NULL AS custom_thumb', 'platform', 'product', 'player', @@ -244,7 +250,18 @@ class DataFactory(object): } rows = [] + + users_lookup = {} + for item in history: + if item['state']: + # Get user thumb from database for current activity + if not users_lookup: + # Cache user lookup + users_lookup = {u['user_id']: u['thumb'] for u in users.Users().get_users()} + + item['user_thumb'] = users_lookup.get(item['user_id']) + filter_duration += int(item['duration']) if item['media_type'] == 'episode' and item['parent_thumb']: @@ -267,6 +284,13 @@ class DataFactory(object): # Rename Mystery platform names platform = common.PLATFORM_NAME_OVERRIDES.get(item['platform'], item['platform']) + if item['custom_thumb'] and item['custom_thumb'] != item['user_thumb']: + user_thumb = item['custom_thumb'] + elif item['user_thumb']: + user_thumb = item['user_thumb'] + else: + user_thumb = common.DEFAULT_USER_THUMB + row = {'reference_id': item['reference_id'], 'row_id': item['row_id'], 'id': item['row_id'], @@ -278,6 +302,7 @@ class DataFactory(object): 'user_id': item['user_id'], 'user': item['user'], 'friendly_name': item['friendly_name'], + 'user_thumb': user_thumb, 'platform': platform, 'product': item['product'], 'player': item['player'], From 4b97382b7c8a6611ef10741118fa475139c220e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Nov 2022 09:30:19 -0800 Subject: [PATCH 026/583] Bump actions/stale from 5 to 6 (#1844) Bumps [actions/stale](https://github.com/actions/stale) from 5 to 6. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/stale dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> [skip ci] --- .github/workflows/issues-stale.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/issues-stale.yml b/.github/workflows/issues-stale.yml index 4605bcec..75c1e08f 100644 --- a/.github/workflows/issues-stale.yml +++ b/.github/workflows/issues-stale.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Stale - uses: actions/stale@v5 + uses: actions/stale@v6 with: stale-issue-message: > This issue is stale because it has been open for 30 days with no activity. @@ -30,7 +30,7 @@ jobs: days-before-close: 5 - name: Invalid Template - uses: actions/stale@v5 + uses: actions/stale@v6 with: stale-issue-message: > Invalid issues template. From 1977ca7db2b48bcc24be13019e618ce74ed471b9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Nov 2022 09:30:30 -0800 Subject: [PATCH 027/583] Bump actions/checkout from 3.0.2 to 3.1.0 (#1858) Bumps [actions/checkout](https://github.com/actions/checkout) from 3.0.2 to 3.1.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3.0.2...v3.1.0) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> [skip ci] --- .github/workflows/publish-docker.yml | 2 +- .github/workflows/publish-installers.yml | 4 ++-- .github/workflows/publish-snap.yml | 2 +- .github/workflows/pull-requests.yml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml index 773f730f..313ac340 100644 --- a/.github/workflows/publish-docker.yml +++ b/.github/workflows/publish-docker.yml @@ -13,7 +13,7 @@ jobs: if: ${{ !contains(github.event.head_commit.message, '[skip ci]') }} steps: - name: Checkout Code - uses: actions/checkout@v3.0.2 + uses: actions/checkout@v3.1.0 - name: Prepare id: prepare diff --git a/.github/workflows/publish-installers.yml b/.github/workflows/publish-installers.yml index ad590098..372e2591 100644 --- a/.github/workflows/publish-installers.yml +++ b/.github/workflows/publish-installers.yml @@ -24,7 +24,7 @@ jobs: steps: - name: Checkout Code - uses: actions/checkout@v3.0.2 + uses: actions/checkout@v3.1.0 - name: Set Release Version id: get_version @@ -103,7 +103,7 @@ jobs: uses: technote-space/workflow-conclusion-action@v3.0 - name: Checkout Code - uses: actions/checkout@v3.0.2 + uses: actions/checkout@v3.1.0 - name: Set Release Version id: get_version diff --git a/.github/workflows/publish-snap.yml b/.github/workflows/publish-snap.yml index 27682148..81a4728f 100644 --- a/.github/workflows/publish-snap.yml +++ b/.github/workflows/publish-snap.yml @@ -20,7 +20,7 @@ jobs: - armhf steps: - name: Checkout Code - uses: actions/checkout@v3.0.2 + uses: actions/checkout@v3.1.0 - name: Prepare id: prepare diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml index 08f92507..ff9a0839 100644 --- a/.github/workflows/pull-requests.yml +++ b/.github/workflows/pull-requests.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Code - uses: actions/checkout@v3.0.2 + uses: actions/checkout@v3.1.0 - name: Comment on Pull Request uses: mshick/add-pr-comment@v1 From a1b6c35bd2630ce280515c8419e30a452cac7feb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Nov 2022 09:30:40 -0800 Subject: [PATCH 028/583] Bump actions/setup-python from 4.2.0 to 4.3.0 (#1859) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4.2.0 to 4.3.0. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v4.2.0...v4.3.0) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> [skip ci] --- .github/workflows/publish-installers.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-installers.yml b/.github/workflows/publish-installers.yml index 372e2591..00c5f722 100644 --- a/.github/workflows/publish-installers.yml +++ b/.github/workflows/publish-installers.yml @@ -52,7 +52,7 @@ jobs: echo $GITHUB_SHA > version.txt - name: Set Up Python - uses: actions/setup-python@v4.2.0 + uses: actions/setup-python@v4.3.0 with: python-version: '3.9' cache: pip From c53566225cc03068161f66d13c72f6c41e250776 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Nov 2022 09:30:51 -0800 Subject: [PATCH 029/583] Bump actions/cache from 3.0.8 to 3.0.11 (#1864) Bumps [actions/cache](https://github.com/actions/cache) from 3.0.8 to 3.0.11. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/v3.0.8...v3.0.11) --- updated-dependencies: - dependency-name: actions/cache dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> [skip ci] --- .github/workflows/publish-docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml index 313ac340..5f95cd9d 100644 --- a/.github/workflows/publish-docker.yml +++ b/.github/workflows/publish-docker.yml @@ -47,7 +47,7 @@ jobs: version: latest - name: Cache Docker Layers - uses: actions/cache@v3.0.8 + uses: actions/cache@v3.0.11 with: path: /tmp/.buildx-cache key: ${{ runner.os }}-buildx-${{ github.sha }} From ed53d66aa71ff4f712da48aa2e6aed01a4454449 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Sun, 18 Sep 2022 15:30:53 -0700 Subject: [PATCH 030/583] Launch browser with IPv6 http_host --- plexpy/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plexpy/__init__.py b/plexpy/__init__.py index 6b10446b..e4f64b45 100644 --- a/plexpy/__init__.py +++ b/plexpy/__init__.py @@ -429,7 +429,7 @@ def daemonize(): def launch_browser(host, port, root): if not no_browser: - if host == '0.0.0.0': + if host in ('0.0.0.0', '::'): host = 'localhost' if CONFIG.ENABLE_HTTPS: From 894eaf0365d36aa80c6e8ee6ac647c853a001d33 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Sun, 18 Sep 2022 15:34:11 -0700 Subject: [PATCH 031/583] Check IPv6 HTTP host when retrieving app URL --- data/interfaces/default/mobile_devices_table.html | 4 ++-- plexpy/helpers.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/data/interfaces/default/mobile_devices_table.html b/data/interfaces/default/mobile_devices_table.html index 35c873bf..328e40e4 100644 --- a/data/interfaces/default/mobile_devices_table.html +++ b/data/interfaces/default/mobile_devices_table.html @@ -58,7 +58,7 @@ DOCUMENTATION :: END getPlexPyURL = function () { var deferred = $.Deferred(); - if (location.hostname !== "localhost" && location.hostname !== "127.0.0.1") { + if (location.hostname !== "localhost" && location.hostname !== "127.0.0.1" && location.hostname !== "[::1]") { deferred.resolve(location.href.split('/settings')[0]); } else { $.get('get_plexpy_url').then(function (url) { @@ -74,7 +74,7 @@ DOCUMENTATION :: END var hostname = parser.hostname; var protocol = parser.protocol; - if (hostname === '127.0.0.1' || hostname === 'localhost') { + if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '[::1]') { $('#api_qr_localhost').toggle(true); $('#api_qr_private').toggle(false); } else { diff --git a/plexpy/helpers.py b/plexpy/helpers.py index d5ef887f..da5a5df5 100644 --- a/plexpy/helpers.py +++ b/plexpy/helpers.py @@ -1191,9 +1191,10 @@ def get_plexpy_url(hostname=None): else: scheme = 'http' - if hostname is None and plexpy.CONFIG.HTTP_HOST == '0.0.0.0': + if hostname is None and plexpy.CONFIG.HTTP_HOST in ('0.0.0.0', '::'): import socket try: + # Only returns IPv4 address s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) s.connect(('', 0)) @@ -1206,7 +1207,7 @@ def get_plexpy_url(hostname=None): if not hostname: hostname = 'localhost' - elif hostname == 'localhost' and plexpy.CONFIG.HTTP_HOST != '0.0.0.0': + elif hostname == 'localhost' and plexpy.CONFIG.HTTP_HOST not in ('0.0.0.0', '::'): hostname = plexpy.CONFIG.HTTP_HOST else: hostname = hostname or plexpy.CONFIG.HTTP_HOST From b74a1a3c327aa0e80f6c7b3c370e4295b1ba439d Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Mon, 7 Nov 2022 10:39:39 -0800 Subject: [PATCH 032/583] Separate stdout and stderr console logging * Closes #1874 --- plexpy/logger.py | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/plexpy/logger.py b/plexpy/logger.py index 953073c3..9f44df95 100644 --- a/plexpy/logger.py +++ b/plexpy/logger.py @@ -85,6 +85,16 @@ def filter_usernames(new_users=None): _FILTER_USERNAMES = sorted(_FILTER_USERNAMES, key=len, reverse=True) +class LogLevelFilter(logging.Filter): + def __init__(self, max_level): + super(LogLevelFilter, self).__init__() + + self.max_level = max_level + + def filter(self, record): + return record.levelno <= self.max_level + + class NoThreadFilter(logging.Filter): """ Log filter for the current thread @@ -330,12 +340,20 @@ def initLogger(console=False, log_dir=False, verbose=False): # Setup console logger if console: console_formatter = logging.Formatter('%(asctime)s - %(levelname)s :: %(threadName)s : %(message)s', '%Y-%m-%d %H:%M:%S') - console_handler = logging.StreamHandler() - console_handler.setFormatter(console_formatter) - console_handler.setLevel(logging.DEBUG) - logger.addHandler(console_handler) - cherrypy.log.error_log.addHandler(console_handler) + stdout_handler = logging.StreamHandler(sys.stdout) + stdout_handler.setFormatter(console_formatter) + stdout_handler.setLevel(logging.DEBUG) + stdout_handler.addFilter(LogLevelFilter(logging.INFO)) + + stderr_handler = logging.StreamHandler(sys.stderr) + stderr_handler.setFormatter(console_formatter) + stderr_handler.setLevel(logging.WARNING) + + logger.addHandler(stdout_handler) + logger.addHandler(stderr_handler) + cherrypy.log.error_log.addHandler(stdout_handler) + cherrypy.log.error_log.addHandler(stderr_handler) # Add filters to log handlers # Only add filters after the config file has been initialized From a3ad40122d0eee52cf0029ddb798d85bf3a6b326 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Mon, 7 Nov 2022 11:26:13 -0800 Subject: [PATCH 033/583] Add months timeframe for newsletters * Closes #1876 --- data/interfaces/default/newsletter_config.html | 3 ++- plexpy/newsletters.py | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/data/interfaces/default/newsletter_config.html b/data/interfaces/default/newsletter_config.html index 003594a7..dc6de294 100644 --- a/data/interfaces/default/newsletter_config.html +++ b/data/interfaces/default/newsletter_config.html @@ -56,11 +56,12 @@
    -
    +
    -% endif \ No newline at end of file +% endif diff --git a/data/interfaces/default/index.html b/data/interfaces/default/index.html index b96e67cf..adcefce6 100644 --- a/data/interfaces/default/index.html +++ b/data/interfaces/default/index.html @@ -526,11 +526,11 @@ var a_codec = (s.audio_codec === 'truehd') ? 'TrueHD' : s.audio_codec.toUpperCase(); var sa_codec = (s.stream_audio_codec === 'truehd') ? 'TrueHD' : s.stream_audio_codec.toUpperCase(); if (s.stream_audio_decision === 'transcode') { - audio_decision = 'Transcode (' + a_codec + ' ' + capitalizeFirstLetter(s.audio_channel_layout.split('(')[0]) + ' ' + sa_codec + ' ' + capitalizeFirstLetter(s.stream_audio_channel_layout.split('(')[0]) + ')'; + audio_decision = 'Transcode ('+ capitalizeFirstLetter(s.stream_audio_language)+ ' - ' + a_codec + ' ' + capitalizeFirstLetter(s.audio_channel_layout.split('(')[0]) + ' ' + sa_codec + ' ' + capitalizeFirstLetter(s.stream_audio_channel_layout.split('(')[0]) + ')'; } else if (s.stream_audio_decision === 'copy') { - audio_decision = 'Direct Stream (' + sa_codec + ' ' + capitalizeFirstLetter(s.stream_audio_channel_layout.split('(')[0]) + ')'; + audio_decision = 'Direct Stream ('+ capitalizeFirstLetter(s.stream_audio_language)+ ' - ' + sa_codec + ' ' + capitalizeFirstLetter(s.stream_audio_channel_layout.split('(')[0]) + ')'; } else { - audio_decision = 'Direct Play (' + sa_codec + ' ' + capitalizeFirstLetter(s.stream_audio_channel_layout.split('(')[0]) + ')'; + audio_decision = 'Direct Play ('+ capitalizeFirstLetter(s.stream_audio_language)+ ' - ' + sa_codec + ' ' + capitalizeFirstLetter(s.stream_audio_channel_layout.split('(')[0]) + ')'; } } $('#audio_decision-' + key).html(audio_decision); @@ -539,13 +539,13 @@ if (['movie', 'episode', 'clip'].indexOf(s.media_type) > -1 && s.subtitles === 1) { var subtitle_codec = (s.stream_subtitle_codec && s.stream_subtitle_transient) ? 'None' : s.subtitle_codec.toUpperCase(); if (s.stream_subtitle_decision === 'transcode') { - subtitle_decision = 'Transcode (' + subtitle_codec + ' ' + s.stream_subtitle_codec.toUpperCase() + ')'; + subtitle_decision = 'Transcode ('+ capitalizeFirstLetter(s.stream_subtitle_language)+ ' - ' + subtitle_codec + ' ' + s.stream_subtitle_codec.toUpperCase() + ')'; } else if (s.stream_subtitle_decision === 'copy') { - subtitle_decision = 'Direct Stream (' + subtitle_codec + ')'; + subtitle_decision = 'Direct Stream ('+ capitalizeFirstLetter(s.stream_subtitle_language)+ ' - ' + subtitle_codec + ')'; } else if (s.stream_subtitle_decision === 'burn') { - subtitle_decision = 'Burn (' + subtitle_codec + ')'; + subtitle_decision = 'Burn ('+ capitalizeFirstLetter(s.stream_subtitle_language)+ ' - ' + subtitle_codec + ')'; } else { - subtitle_decision = 'Direct Play (' + ((s.synced_version === '1') ? subtitle_codec : s.stream_subtitle_codec.toUpperCase()) + ')'; + subtitle_decision = 'Direct Play ('+ capitalizeFirstLetter(s.stream_subtitle_language)+ ' - ' + ((s.synced_version === '1') ? subtitle_codec : s.stream_subtitle_codec.toUpperCase()) + ')'; } } $('#subtitle_decision-' + key).html(subtitle_decision); @@ -1076,4 +1076,4 @@ } % endif - \ No newline at end of file + From 7c030ef362e102fc9d13f8a1e32c2cdb637c7207 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Wed, 21 Dec 2022 15:33:34 -0800 Subject: [PATCH 111/583] Link watch statistics to info page using history metadata * Closes #1882 --- data/interfaces/default/home_stats.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/interfaces/default/home_stats.html b/data/interfaces/default/home_stats.html index 9a0c49f4..df2b7b69 100644 --- a/data/interfaces/default/home_stats.html +++ b/data/interfaces/default/home_stats.html @@ -102,7 +102,7 @@ DOCUMENTATION :: END if row0['live']: href = page('info', row0['rating_key'], row0['guid'], history=True, live=row0['live']) else: - href = page('info', row0['rating_key']) + href = page('info', row0['rating_key'], history=True) %>
    @@ -157,7 +157,7 @@ DOCUMENTATION :: END if row['live']: href = page('info', row['rating_key'], row['guid'], history=True, live=row['live']) else: - href = page('info', row['rating_key']) + href = page('info', row['rating_key'], history=True) %>
    ${row['title']} From 023fde7a848eb381163b2de50c57ff28ec1acb2b Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Wed, 21 Dec 2022 16:34:07 -0800 Subject: [PATCH 112/583] Add subtitle_language to database --- plexpy/__init__.py | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/plexpy/__init__.py b/plexpy/__init__.py index e4f64b45..c2e9b4ae 100644 --- a/plexpy/__init__.py +++ b/plexpy/__init__.py @@ -638,14 +638,14 @@ def dbcheck(): 'video_framerate TEXT, video_scan_type TEXT, video_full_resolution TEXT, ' 'video_dynamic_range TEXT, aspect_ratio TEXT, ' 'audio_codec TEXT, audio_bitrate INTEGER, audio_channels INTEGER, audio_language TEXT, audio_language_code TEXT, ' - 'subtitle_codec TEXT, stream_bitrate INTEGER, stream_video_resolution TEXT, quality_profile TEXT, ' + 'subtitle_codec TEXT, subtitle_language TEXT, stream_bitrate INTEGER, stream_video_resolution TEXT, quality_profile TEXT, ' 'stream_container_decision TEXT, stream_container TEXT, ' 'stream_video_decision TEXT, stream_video_codec TEXT, stream_video_bitrate INTEGER, stream_video_width INTEGER, ' 'stream_video_height INTEGER, stream_video_framerate TEXT, stream_video_scan_type TEXT, stream_video_full_resolution TEXT, ' 'stream_video_dynamic_range TEXT, ' 'stream_audio_decision TEXT, stream_audio_codec TEXT, stream_audio_bitrate INTEGER, stream_audio_channels INTEGER, ' 'stream_audio_language TEXT, stream_audio_language_code TEXT, ' - 'subtitles INTEGER, stream_subtitle_decision TEXT, stream_subtitle_codec TEXT, ' + 'subtitles INTEGER, stream_subtitle_decision TEXT, stream_subtitle_codec TEXT, stream_subtitle_language TEXT, ' 'transcode_protocol TEXT, transcode_container TEXT, ' 'transcode_video_codec TEXT, transcode_audio_codec TEXT, transcode_audio_channels INTEGER,' 'transcode_width INTEGER, transcode_height INTEGER, ' @@ -684,6 +684,7 @@ def dbcheck(): 'video_codec TEXT, video_codec_level TEXT, video_width INTEGER, video_height INTEGER, video_resolution TEXT, ' 'video_framerate TEXT, video_scan_type TEXT, video_full_resolution TEXT, video_dynamic_range TEXT, aspect_ratio TEXT, ' 'audio_bitrate INTEGER, audio_codec TEXT, audio_channels INTEGER, audio_language TEXT, audio_language_code TEXT, ' + 'subtitles INTEGER, subtitle_codec TEXT, subtitle_language TEXT,' 'transcode_protocol TEXT, transcode_container TEXT, transcode_video_codec TEXT, transcode_audio_codec TEXT, ' 'transcode_audio_channels INTEGER, transcode_width INTEGER, transcode_height INTEGER, ' 'transcode_hw_requested INTEGER, transcode_hw_full_pipeline INTEGER, ' @@ -695,8 +696,9 @@ def dbcheck(): 'stream_video_framerate TEXT, stream_video_scan_type TEXT, stream_video_full_resolution TEXT, stream_video_dynamic_range TEXT, ' 'stream_audio_decision TEXT, stream_audio_codec TEXT, stream_audio_bitrate INTEGER, stream_audio_channels INTEGER, ' 'stream_audio_language TEXT, stream_audio_language_code TEXT, ' - 'stream_subtitle_decision TEXT, stream_subtitle_codec TEXT, stream_subtitle_container TEXT, stream_subtitle_forced INTEGER, ' - 'subtitles INTEGER, subtitle_codec TEXT, synced_version INTEGER, synced_version_profile TEXT, ' + 'stream_subtitle_decision TEXT, stream_subtitle_codec TEXT, ' + 'stream_subtitle_container TEXT, stream_subtitle_forced INTEGER, stream_subtitle_language TEXT, ' + 'synced_version INTEGER, synced_version_profile TEXT, ' 'optimized_version INTEGER, optimized_version_profile TEXT, optimized_version_title TEXT)' ) @@ -1373,6 +1375,18 @@ def dbcheck(): 'ALTER TABLE sessions ADD COLUMN stream_audio_language_code TEXT' ) + # Upgrade sessions table from earlier versions + try: + c_db.execute('SELECT subtitle_language FROM sessions') + except sqlite3.OperationalError: + logger.debug(u"Altering database. Updating database table sessions.") + c_db.execute( + 'ALTER TABLE sessions ADD COLUMN subtitle_language TEXT' + ) + c_db.execute( + 'ALTER TABLE sessions ADD COLUMN stream_subtitle_language TEXT' + ) + # Upgrade session_history table from earlier versions try: c_db.execute('SELECT reference_id FROM session_history') @@ -1770,6 +1784,18 @@ def dbcheck(): 'ALTER TABLE session_history_media_info ADD COLUMN stream_audio_language_code TEXT' ) + # Upgrade session_history_media_info table from earlier versions + try: + c_db.execute('SELECT subtitle_language FROM session_history_media_info') + except sqlite3.OperationalError: + logger.debug("Altering database. Updating database table session_history_media_info.") + c_db.execute( + 'ALTER TABLE session_history_media_info ADD COLUMN subtitle_language TEXT' + ) + c_db.execute( + 'ALTER TABLE session_history_media_info ADD COLUMN stream_subtitle_language TEXT' + ) + # Upgrade session_history table from earlier versions try: c_db.execute('SELECT section_id FROM session_history') From c00dfca2f082e3c99f46525a3d9fe815c702ebf8 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Wed, 21 Dec 2022 16:34:24 -0800 Subject: [PATCH 113/583] Log subtitle language --- plexpy/activity_processor.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plexpy/activity_processor.py b/plexpy/activity_processor.py index f4e58329..8f0cb977 100644 --- a/plexpy/activity_processor.py +++ b/plexpy/activity_processor.py @@ -102,6 +102,7 @@ class ActivityProcessor(object): 'audio_language': session.get('audio_language', ''), 'audio_language_code': session.get('audio_language_code', ''), 'subtitle_codec': session.get('subtitle_codec', ''), + 'subtitle_language': session.get('subtitle_language', ''), 'transcode_protocol': session.get('transcode_protocol', ''), 'transcode_container': session.get('transcode_container', ''), 'transcode_video_codec': session.get('transcode_video_codec', ''), @@ -138,6 +139,7 @@ class ActivityProcessor(object): 'stream_audio_language_code': session.get('stream_audio_language_code', ''), 'stream_subtitle_decision': session.get('stream_subtitle_decision', ''), 'stream_subtitle_codec': session.get('stream_subtitle_codec', ''), + 'stream_subitile_language': session.get('stream_subtitle_language', ''), 'subtitles': session.get('subtitles', 0), 'live': session.get('live', 0), 'live_uuid': session.get('live_uuid', ''), @@ -424,6 +426,7 @@ class ActivityProcessor(object): 'audio_language': session['audio_language'], 'audio_language_code': session['audio_language_code'], 'subtitle_codec': session['subtitle_codec'], + 'subtitle_language': session['subtitle_language'], 'transcode_protocol': session['transcode_protocol'], 'transcode_container': session['transcode_container'], 'transcode_video_codec': session['transcode_video_codec'], @@ -464,6 +467,7 @@ class ActivityProcessor(object): 'stream_subtitle_codec': session['stream_subtitle_codec'], 'stream_subtitle_container': session['stream_subtitle_container'], 'stream_subtitle_forced': session['stream_subtitle_forced'], + 'stream_subtitle_language': session['stream_subtitle_language'], 'subtitles': session['subtitles'], 'synced_version': session['synced_version'], 'synced_version_profile': session['synced_version_profile'], From 44419fb0bfb9a5b5e178dd9b2cf0ba6928e064d8 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Wed, 21 Dec 2022 16:34:52 -0800 Subject: [PATCH 114/583] Display subtitle language in stream data modal --- data/interfaces/default/stream_data.html | 11 +++++++++-- plexpy/datafactory.py | 5 +++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/data/interfaces/default/stream_data.html b/data/interfaces/default/stream_data.html index bd2ffb2a..d4c81741 100644 --- a/data/interfaces/default/stream_data.html +++ b/data/interfaces/default/stream_data.html @@ -218,10 +218,10 @@ DOCUMENTATION :: END ${data['stream_audio_channels']} ${data['audio_channels']} - % if data['stream_audio_language'] != '': + % if data['audio_language'] != '': Language - ${data['stream_audio_language']} + - ${data['audio_language']} % endif @@ -245,6 +245,13 @@ DOCUMENTATION :: END ${data['stream_subtitle_codec'].upper() or '-'} ${data['subtitle_codec'].upper()} + % if data['subtitle_language'] != '': + + Language + - + ${data['subtitle_language']} + + % endif % endif diff --git a/plexpy/datafactory.py b/plexpy/datafactory.py index 8a15ef7d..5a77ba38 100644 --- a/plexpy/datafactory.py +++ b/plexpy/datafactory.py @@ -1286,13 +1286,14 @@ class DataFactory(object): 'synced_version, synced_version_profile, ' \ 'container, video_codec, video_bitrate, video_width, video_height, video_framerate, ' \ 'video_dynamic_range, aspect_ratio, ' \ - 'audio_codec, audio_bitrate, audio_channels, audio_language, audio_language_code, subtitle_codec, ' \ + 'audio_codec, audio_bitrate, audio_channels, audio_language, audio_language_code, ' \ + 'subtitle_codec, subtitle_language, ' \ 'stream_bitrate, stream_video_full_resolution, quality_profile, stream_container_decision, stream_container, ' \ 'stream_video_decision, stream_video_codec, stream_video_bitrate, stream_video_width, stream_video_height, ' \ 'stream_video_framerate, stream_video_dynamic_range, ' \ 'stream_audio_decision, stream_audio_codec, stream_audio_bitrate, stream_audio_channels, ' \ 'stream_audio_language, stream_audio_language_code, ' \ - 'subtitles, stream_subtitle_decision, stream_subtitle_codec, ' \ + 'subtitles, stream_subtitle_decision, stream_subtitle_codec, stream_subtitle_language, ' \ 'transcode_hw_decoding, transcode_hw_encoding, ' \ 'video_decision, audio_decision, transcode_decision, width, height, container, ' \ 'transcode_container, transcode_video_codec, transcode_audio_codec, transcode_audio_channels, ' \ From 185a5cb0eafe3c080cde550ec85427646fdd6ab3 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Wed, 21 Dec 2022 17:01:24 -0800 Subject: [PATCH 115/583] Add subtitle_forced to database --- plexpy/__init__.py | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/plexpy/__init__.py b/plexpy/__init__.py index c2e9b4ae..d27cd396 100644 --- a/plexpy/__init__.py +++ b/plexpy/__init__.py @@ -638,14 +638,16 @@ def dbcheck(): 'video_framerate TEXT, video_scan_type TEXT, video_full_resolution TEXT, ' 'video_dynamic_range TEXT, aspect_ratio TEXT, ' 'audio_codec TEXT, audio_bitrate INTEGER, audio_channels INTEGER, audio_language TEXT, audio_language_code TEXT, ' - 'subtitle_codec TEXT, subtitle_language TEXT, stream_bitrate INTEGER, stream_video_resolution TEXT, quality_profile TEXT, ' + 'subtitle_codec TEXT, subtitle_forced INTEGER, subtitle_language TEXT, ' + 'stream_bitrate INTEGER, stream_video_resolution TEXT, quality_profile TEXT, ' 'stream_container_decision TEXT, stream_container TEXT, ' 'stream_video_decision TEXT, stream_video_codec TEXT, stream_video_bitrate INTEGER, stream_video_width INTEGER, ' 'stream_video_height INTEGER, stream_video_framerate TEXT, stream_video_scan_type TEXT, stream_video_full_resolution TEXT, ' 'stream_video_dynamic_range TEXT, ' 'stream_audio_decision TEXT, stream_audio_codec TEXT, stream_audio_bitrate INTEGER, stream_audio_channels INTEGER, ' 'stream_audio_language TEXT, stream_audio_language_code TEXT, ' - 'subtitles INTEGER, stream_subtitle_decision TEXT, stream_subtitle_codec TEXT, stream_subtitle_language TEXT, ' + 'subtitles INTEGER, stream_subtitle_decision TEXT, stream_subtitle_codec TEXT, ' + 'stream_subtitle_forced INTEGER, stream_subtitle_language TEXT, ' 'transcode_protocol TEXT, transcode_container TEXT, ' 'transcode_video_codec TEXT, transcode_audio_codec TEXT, transcode_audio_channels INTEGER,' 'transcode_width INTEGER, transcode_height INTEGER, ' @@ -684,7 +686,7 @@ def dbcheck(): 'video_codec TEXT, video_codec_level TEXT, video_width INTEGER, video_height INTEGER, video_resolution TEXT, ' 'video_framerate TEXT, video_scan_type TEXT, video_full_resolution TEXT, video_dynamic_range TEXT, aspect_ratio TEXT, ' 'audio_bitrate INTEGER, audio_codec TEXT, audio_channels INTEGER, audio_language TEXT, audio_language_code TEXT, ' - 'subtitles INTEGER, subtitle_codec TEXT, subtitle_language TEXT,' + 'subtitles INTEGER, subtitle_codec TEXT, subtitle_forced, subtitle_language TEXT,' 'transcode_protocol TEXT, transcode_container TEXT, transcode_video_codec TEXT, transcode_audio_codec TEXT, ' 'transcode_audio_channels INTEGER, transcode_width INTEGER, transcode_height INTEGER, ' 'transcode_hw_requested INTEGER, transcode_hw_full_pipeline INTEGER, ' @@ -1387,6 +1389,18 @@ def dbcheck(): 'ALTER TABLE sessions ADD COLUMN stream_subtitle_language TEXT' ) + # Upgrade sessions table from earlier versions + try: + c_db.execute('SELECT subtitle_forced FROM sessions') + except sqlite3.OperationalError: + logger.debug(u"Altering database. Updating database table sessions.") + c_db.execute( + 'ALTER TABLE sessions ADD COLUMN subtitle_forced INTEGER' + ) + c_db.execute( + 'ALTER TABLE sessions ADD COLUMN stream_subtitle_forced INTEGER' + ) + # Upgrade session_history table from earlier versions try: c_db.execute('SELECT reference_id FROM session_history') @@ -1796,6 +1810,15 @@ def dbcheck(): 'ALTER TABLE session_history_media_info ADD COLUMN stream_subtitle_language TEXT' ) + # Upgrade session_history_media_info table from earlier versions + try: + c_db.execute('SELECT subtitle_forced FROM session_history_media_info') + except sqlite3.OperationalError: + logger.debug("Altering database. Updating database table session_history_media_info.") + c_db.execute( + 'ALTER TABLE session_history_media_info ADD COLUMN subtitle_forced INTEGER' + ) + # Upgrade session_history table from earlier versions try: c_db.execute('SELECT section_id FROM session_history') From 5a9ace0b1924d841fcfd1b8e49579c5054c6b4ab Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Wed, 21 Dec 2022 17:01:50 -0800 Subject: [PATCH 116/583] Log subtitle forced --- plexpy/activity_processor.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plexpy/activity_processor.py b/plexpy/activity_processor.py index 8f0cb977..4608e4c8 100644 --- a/plexpy/activity_processor.py +++ b/plexpy/activity_processor.py @@ -102,6 +102,7 @@ class ActivityProcessor(object): 'audio_language': session.get('audio_language', ''), 'audio_language_code': session.get('audio_language_code', ''), 'subtitle_codec': session.get('subtitle_codec', ''), + 'subtitle_forced': session.get('subtitle_forced', ''), 'subtitle_language': session.get('subtitle_language', ''), 'transcode_protocol': session.get('transcode_protocol', ''), 'transcode_container': session.get('transcode_container', ''), @@ -139,7 +140,8 @@ class ActivityProcessor(object): 'stream_audio_language_code': session.get('stream_audio_language_code', ''), 'stream_subtitle_decision': session.get('stream_subtitle_decision', ''), 'stream_subtitle_codec': session.get('stream_subtitle_codec', ''), - 'stream_subitile_language': session.get('stream_subtitle_language', ''), + 'stream_subtitle_forced': session.get('stream_subtitle_forced', ''), + 'stream_subtitle_language': session.get('stream_subtitle_language', ''), 'subtitles': session.get('subtitles', 0), 'live': session.get('live', 0), 'live_uuid': session.get('live_uuid', ''), @@ -426,6 +428,7 @@ class ActivityProcessor(object): 'audio_language': session['audio_language'], 'audio_language_code': session['audio_language_code'], 'subtitle_codec': session['subtitle_codec'], + 'subtitle_forced': session['subtitle_forced'], 'subtitle_language': session['subtitle_language'], 'transcode_protocol': session['transcode_protocol'], 'transcode_container': session['transcode_container'], From bcb9a9d6c60cee4b033919d7b1b776735277c5c5 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Wed, 21 Dec 2022 17:02:10 -0800 Subject: [PATCH 117/583] Display subtitle forced in stream data modal --- data/interfaces/default/stream_data.html | 7 +++++++ plexpy/datafactory.py | 13 +++++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/data/interfaces/default/stream_data.html b/data/interfaces/default/stream_data.html index d4c81741..9ccdc061 100644 --- a/data/interfaces/default/stream_data.html +++ b/data/interfaces/default/stream_data.html @@ -245,6 +245,13 @@ DOCUMENTATION :: END ${data['stream_subtitle_codec'].upper() or '-'} ${data['subtitle_codec'].upper()} + % if data['subtitle_forced']: + + Forced + - + ${bool(data['subtitle_forced'])} + + % endif % if data['subtitle_language'] != '': Language diff --git a/plexpy/datafactory.py b/plexpy/datafactory.py index 5a77ba38..700c14c6 100644 --- a/plexpy/datafactory.py +++ b/plexpy/datafactory.py @@ -1287,13 +1287,13 @@ class DataFactory(object): 'container, video_codec, video_bitrate, video_width, video_height, video_framerate, ' \ 'video_dynamic_range, aspect_ratio, ' \ 'audio_codec, audio_bitrate, audio_channels, audio_language, audio_language_code, ' \ - 'subtitle_codec, subtitle_language, ' \ + 'subtitle_codec, subtitle_forced, subtitle_language, ' \ 'stream_bitrate, stream_video_full_resolution, quality_profile, stream_container_decision, stream_container, ' \ 'stream_video_decision, stream_video_codec, stream_video_bitrate, stream_video_width, stream_video_height, ' \ 'stream_video_framerate, stream_video_dynamic_range, ' \ 'stream_audio_decision, stream_audio_codec, stream_audio_bitrate, stream_audio_channels, ' \ 'stream_audio_language, stream_audio_language_code, ' \ - 'subtitles, stream_subtitle_decision, stream_subtitle_codec, stream_subtitle_language, ' \ + 'subtitles, stream_subtitle_decision, stream_subtitle_codec, stream_subtitle_forced, stream_subtitle_language, ' \ 'transcode_hw_decoding, transcode_hw_encoding, ' \ 'video_decision, audio_decision, transcode_decision, width, height, container, ' \ 'transcode_container, transcode_video_codec, transcode_audio_codec, transcode_audio_channels, ' \ @@ -1310,13 +1310,14 @@ class DataFactory(object): 'synced_version, synced_version_profile, ' \ 'container, video_codec, video_bitrate, video_width, video_height, video_framerate, ' \ 'video_dynamic_range, aspect_ratio, ' \ - 'audio_codec, audio_bitrate, audio_channels, audio_language, audio_language_code, subtitle_codec, ' \ + 'audio_codec, audio_bitrate, audio_channels, audio_language, audio_language_code, ' \ + 'subtitle_codec, subtitle_forced, subtitle_language, ' \ 'stream_bitrate, stream_video_full_resolution, quality_profile, stream_container_decision, stream_container, ' \ 'stream_video_decision, stream_video_codec, stream_video_bitrate, stream_video_width, stream_video_height, ' \ 'stream_video_framerate, stream_video_dynamic_range, ' \ 'stream_audio_decision, stream_audio_codec, stream_audio_bitrate, stream_audio_channels, ' \ 'stream_audio_language, stream_audio_language_code, ' \ - 'subtitles, stream_subtitle_decision, stream_subtitle_codec, ' \ + 'subtitles, stream_subtitle_decision, stream_subtitle_codec, stream_subtitle_forced, stream_subtitle_language, ' \ 'transcode_hw_decoding, transcode_hw_encoding, ' \ 'video_decision, audio_decision, transcode_decision, width, height, container, ' \ 'transcode_container, transcode_video_codec, transcode_audio_codec, transcode_audio_channels, ' \ @@ -1369,6 +1370,8 @@ class DataFactory(object): 'audio_language': item['audio_language'], 'audio_language_code': item['audio_language_code'], 'subtitle_codec': item['subtitle_codec'], + 'subtitle_forced': item['subtitle_forced'], + 'subtitle_language': item['subtitle_language'], 'stream_bitrate': item['stream_bitrate'], 'stream_video_full_resolution': item['stream_video_full_resolution'], 'quality_profile': item['quality_profile'], @@ -1390,6 +1393,8 @@ class DataFactory(object): 'subtitles': item['subtitles'], 'stream_subtitle_decision': item['stream_subtitle_decision'], 'stream_subtitle_codec': item['stream_subtitle_codec'], + 'stream_subtitle_forced': item['stream_subtitle_forced'], + 'stream_subtitle_language': item['stream_subtitle_language'], 'transcode_hw_decoding': item['transcode_hw_decoding'], 'transcode_hw_encoding': item['transcode_hw_encoding'], 'video_decision': item['video_decision'], From fe1d850e1c1feff12620c95841bdf1d3aa1c961e Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Wed, 21 Dec 2022 17:24:14 -0800 Subject: [PATCH 118/583] Display "Unknown" language in stream data modal --- data/interfaces/default/stream_data.html | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/data/interfaces/default/stream_data.html b/data/interfaces/default/stream_data.html index 9ccdc061..7731e3f7 100644 --- a/data/interfaces/default/stream_data.html +++ b/data/interfaces/default/stream_data.html @@ -218,13 +218,12 @@ DOCUMENTATION :: END ${data['stream_audio_channels']} ${data['audio_channels']} - % if data['audio_language'] != '': Language - - ${data['audio_language']} + ${data['audio_language'] or 'Unknown'} - % endif + % if data['subtitles'] == 1: @@ -245,6 +244,11 @@ DOCUMENTATION :: END ${data['stream_subtitle_codec'].upper() or '-'} ${data['subtitle_codec'].upper()} + + Language + - + ${data['subtitle_language'] or 'Unknown'} + % if data['subtitle_forced']: Forced @@ -252,13 +256,6 @@ DOCUMENTATION :: END ${bool(data['subtitle_forced'])} % endif - % if data['subtitle_language'] != '': - - Language - - - ${data['subtitle_language']} - - % endif % endif From 8eed162367e7e42b6b1b47820e3a8698a4a262d9 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Wed, 21 Dec 2022 17:24:41 -0800 Subject: [PATCH 119/583] Override direct play subtitle decision in stream data modal --- data/interfaces/default/stream_data.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/interfaces/default/stream_data.html b/data/interfaces/default/stream_data.html index 7731e3f7..9e42189b 100644 --- a/data/interfaces/default/stream_data.html +++ b/data/interfaces/default/stream_data.html @@ -234,7 +234,7 @@ DOCUMENTATION :: END Subtitles - ${data['stream_subtitle_decision']} + ${'direct play' if data['stream_subtitle_decision'] not in ('transcode', 'copy', 'burn') else data['stream_subtitle_decision']} From d426c3e12ec59845e120447fbb6d9d657c6f0eab Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Wed, 21 Dec 2022 17:25:18 -0800 Subject: [PATCH 120/583] Don't capitalize language and display "Unknown" on activity cards --- .../default/current_activity_instance.html | 14 +++++++------- data/interfaces/default/index.html | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/data/interfaces/default/current_activity_instance.html b/data/interfaces/default/current_activity_instance.html index 83660745..d0e95ac8 100644 --- a/data/interfaces/default/current_activity_instance.html +++ b/data/interfaces/default/current_activity_instance.html @@ -266,11 +266,11 @@ DOCUMENTATION :: END
    % if data['stream_audio_decision']: % if data['stream_audio_decision'] == 'transcode': - Transcode (${data['stream_audio_language'].capitalize()} - ${AUDIO_CODEC_OVERRIDES.get(data['audio_codec'], data['audio_codec'].upper())} ${data['audio_channel_layout'].split('(')[0].capitalize()} ${AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()}) + Transcode (${data['stream_audio_language'] or 'Unknown'} - ${AUDIO_CODEC_OVERRIDES.get(data['audio_codec'], data['audio_codec'].upper())} ${data['audio_channel_layout'].split('(')[0].capitalize()} ${AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()}) % elif data['stream_audio_decision'] == 'copy': - Direct Stream (${data['stream_audio_language'].capitalize()} - ${AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()}) + Direct Stream (${data['stream_audio_language'] or 'Unknown'} - ${AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()}) % else: - Direct Play (${data['stream_audio_language'].capitalize()} - ${AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()}) + Direct Play (${data['stream_audio_language'] or 'Unknown'} - ${AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()}) % endif % endif
    @@ -285,13 +285,13 @@ DOCUMENTATION :: END subtitle_codec = 'None' if data['stream_subtitle_codec'] and data['stream_subtitle_transient'] else data['subtitle_codec'].upper() %> % if data['stream_subtitle_decision'] == 'transcode': - Transcode (${data['stream_subtitle_language'].capitalize()} - ${subtitle_codec} ${data['stream_subtitle_codec'].upper()}) + Transcode (${data['stream_subtitle_language'] or 'Unknown'} - ${subtitle_codec} ${data['stream_subtitle_codec'].upper()}) % elif data['stream_subtitle_decision'] == 'copy': - Direct Stream (${data['stream_subtitle_language'].capitalize()} - ${subtitle_codec}) + Direct Stream (${data['stream_subtitle_language'] or 'Unknown'} - ${subtitle_codec}) % elif data['stream_subtitle_decision'] == 'burn': - Burn (${data['stream_subtitle_language'].capitalize()} - ${subtitle_codec}) + Burn (${data['stream_subtitle_language'] or 'Unknown'} - ${subtitle_codec}) % else: - Direct Play (${data['stream_subtitle_language'].capitalize()} - ${subtitle_codec if data['synced_version'] else data['stream_subtitle_codec'].upper()}) + Direct Play (${data['stream_subtitle_language'] or 'Unknown'} - ${subtitle_codec if data['synced_version'] else data['stream_subtitle_codec'].upper()}) % endif % else: None diff --git a/data/interfaces/default/index.html b/data/interfaces/default/index.html index adcefce6..e4cc7d51 100644 --- a/data/interfaces/default/index.html +++ b/data/interfaces/default/index.html @@ -526,11 +526,11 @@ var a_codec = (s.audio_codec === 'truehd') ? 'TrueHD' : s.audio_codec.toUpperCase(); var sa_codec = (s.stream_audio_codec === 'truehd') ? 'TrueHD' : s.stream_audio_codec.toUpperCase(); if (s.stream_audio_decision === 'transcode') { - audio_decision = 'Transcode ('+ capitalizeFirstLetter(s.stream_audio_language)+ ' - ' + a_codec + ' ' + capitalizeFirstLetter(s.audio_channel_layout.split('(')[0]) + ' ' + sa_codec + ' ' + capitalizeFirstLetter(s.stream_audio_channel_layout.split('(')[0]) + ')'; + audio_decision = 'Transcode ('+ (s.stream_audio_language || 'Unknown')+ ' - ' + a_codec + ' ' + capitalizeFirstLetter(s.audio_channel_layout.split('(')[0]) + ' ' + sa_codec + ' ' + capitalizeFirstLetter(s.stream_audio_channel_layout.split('(')[0]) + ')'; } else if (s.stream_audio_decision === 'copy') { - audio_decision = 'Direct Stream ('+ capitalizeFirstLetter(s.stream_audio_language)+ ' - ' + sa_codec + ' ' + capitalizeFirstLetter(s.stream_audio_channel_layout.split('(')[0]) + ')'; + audio_decision = 'Direct Stream ('+ (s.stream_audio_language || 'Unknown')+ ' - ' + sa_codec + ' ' + capitalizeFirstLetter(s.stream_audio_channel_layout.split('(')[0]) + ')'; } else { - audio_decision = 'Direct Play ('+ capitalizeFirstLetter(s.stream_audio_language)+ ' - ' + sa_codec + ' ' + capitalizeFirstLetter(s.stream_audio_channel_layout.split('(')[0]) + ')'; + audio_decision = 'Direct Play ('+ (s.stream_audio_language || 'Unknown')+ ' - ' + sa_codec + ' ' + capitalizeFirstLetter(s.stream_audio_channel_layout.split('(')[0]) + ')'; } } $('#audio_decision-' + key).html(audio_decision); @@ -539,13 +539,13 @@ if (['movie', 'episode', 'clip'].indexOf(s.media_type) > -1 && s.subtitles === 1) { var subtitle_codec = (s.stream_subtitle_codec && s.stream_subtitle_transient) ? 'None' : s.subtitle_codec.toUpperCase(); if (s.stream_subtitle_decision === 'transcode') { - subtitle_decision = 'Transcode ('+ capitalizeFirstLetter(s.stream_subtitle_language)+ ' - ' + subtitle_codec + ' ' + s.stream_subtitle_codec.toUpperCase() + ')'; + subtitle_decision = 'Transcode ('+ (s.stream_subtitle_language || 'Unknown')+ ' - ' + subtitle_codec + ' ' + s.stream_subtitle_codec.toUpperCase() + ')'; } else if (s.stream_subtitle_decision === 'copy') { - subtitle_decision = 'Direct Stream ('+ capitalizeFirstLetter(s.stream_subtitle_language)+ ' - ' + subtitle_codec + ')'; + subtitle_decision = 'Direct Stream ('+ (s.stream_subtitle_language || 'Unknown')+ ' - ' + subtitle_codec + ')'; } else if (s.stream_subtitle_decision === 'burn') { - subtitle_decision = 'Burn ('+ capitalizeFirstLetter(s.stream_subtitle_language)+ ' - ' + subtitle_codec + ')'; + subtitle_decision = 'Burn ('+ (s.stream_subtitle_language || 'Unknown')+ ' - ' + subtitle_codec + ')'; } else { - subtitle_decision = 'Direct Play ('+ capitalizeFirstLetter(s.stream_subtitle_language)+ ' - ' + ((s.synced_version === '1') ? subtitle_codec : s.stream_subtitle_codec.toUpperCase()) + ')'; + subtitle_decision = 'Direct Play ('+ (s.stream_subtitle_language || 'Unknown')+ ' - ' + ((s.synced_version === '1') ? subtitle_codec : s.stream_subtitle_codec.toUpperCase()) + ')'; } } $('#subtitle_decision-' + key).html(subtitle_decision); From 8cd5b0b77510d857a1d3c6aa74ce8d3df2fcaa1b Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Wed, 21 Dec 2022 17:25:40 -0800 Subject: [PATCH 121/583] Use system language for requests --- plexpy/http_handler.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plexpy/http_handler.py b/plexpy/http_handler.py index 974b9ce4..87c4cff5 100644 --- a/plexpy/http_handler.py +++ b/plexpy/http_handler.py @@ -61,7 +61,8 @@ class HTTPHandler(object): 'X-Plex-Device': '{} {}'.format(plexpy.common.PLATFORM, plexpy.common.PLATFORM_RELEASE), 'X-Plex-Device-Name': '{} ({})'.format(plexpy.common.PLATFORM_DEVICE_NAME, - plexpy.common.PRODUCT) + plexpy.common.PRODUCT), + 'Accept-Language': plexpy.SYS_LANGUAGE } self.token = token From 31f6b02149786acd370e3e43322c98c02e738cce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Dec 2022 10:44:51 -0800 Subject: [PATCH 122/583] Bump plexapi from 4.13.1 to 4.13.2 (#1939) * Bump plexapi from 4.13.1 to 4.13.2 Bumps [plexapi](https://github.com/pkkid/python-plexapi) from 4.13.1 to 4.13.2. - [Release notes](https://github.com/pkkid/python-plexapi/releases) - [Commits](https://github.com/pkkid/python-plexapi/compare/4.13.1...4.13.2) --- updated-dependencies: - dependency-name: plexapi dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Update plexapi==4.13.2 Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> [skip ci] --- lib/plexapi/base.py | 23 ++++++---- lib/plexapi/collection.py | 5 -- lib/plexapi/const.py | 2 +- lib/plexapi/library.py | 19 ++++---- lib/plexapi/media.py | 63 ++++++++++++++++++++----- lib/plexapi/mixins.py | 26 ++++++----- lib/plexapi/myplex.py | 37 ++++++++++----- lib/plexapi/playlist.py | 36 +++++++++++---- lib/plexapi/playqueue.py | 19 ++++---- lib/plexapi/server.py | 96 +++++++++++++++++++++++++++++---------- lib/plexapi/settings.py | 9 +++- lib/plexapi/utils.py | 12 +++++ lib/plexapi/video.py | 8 ++++ requirements.txt | 2 +- 14 files changed, 250 insertions(+), 107 deletions(-) diff --git a/lib/plexapi/base.py b/lib/plexapi/base.py index 2cb78c53..fe6f0be0 100644 --- a/lib/plexapi/base.py +++ b/lib/plexapi/base.py @@ -6,6 +6,7 @@ from xml.etree import ElementTree from plexapi import log, utils from plexapi.exceptions import BadRequest, NotFound, UnknownType, Unsupported +from plexapi.utils import cached_property USER_DONT_RELOAD_FOR_KEYS = set() _DONT_RELOAD_FOR_KEYS = {'key'} @@ -666,6 +667,13 @@ class PlexPartialObject(PlexObject): """ return self._getWebURL(base=base) + def playQueue(self, *args, **kwargs): + """ Returns a new :class:`~plexapi.playqueue.PlayQueue` from this media item. + See :func:`~plexapi.playqueue.PlayQueue.create` for available parameters. + """ + from plexapi.playqueue import PlayQueue + return PlayQueue.create(self._server, self, *args, **kwargs) + class Playable: """ This is a general place to store functions specific to media that is Playable. @@ -841,7 +849,6 @@ class PlexSession(object): user = data.find('User') self._username = user.attrib.get('title') self._userId = utils.cast(int, user.attrib.get('id')) - self._user = None # Cache for user object # For backwards compatibility self.players = [self.player] if self.player else [] @@ -849,18 +856,16 @@ class PlexSession(object): self.transcodeSessions = [self.transcodeSession] if self.transcodeSession else [] self.usernames = [self._username] if self._username else [] - @property + @cached_property def user(self): """ Returns the :class:`~plexapi.myplex.MyPlexAccount` object (for admin) or :class:`~plexapi.myplex.MyPlexUser` object (for users) for this session. """ - if self._user is None: - myPlexAccount = self._server.myPlexAccount() - if self._userId == 1: - self._user = myPlexAccount - else: - self._user = myPlexAccount.user(self._username) - return self._user + myPlexAccount = self._server.myPlexAccount() + if self._userId == 1: + return myPlexAccount + + return myPlexAccount.user(self._username) def reload(self): """ Reload the data for the session. diff --git a/lib/plexapi/collection.py b/lib/plexapi/collection.py index ff26cf32..4561c158 100644 --- a/lib/plexapi/collection.py +++ b/lib/plexapi/collection.py @@ -11,7 +11,6 @@ from plexapi.mixins import ( ContentRatingMixin, SortTitleMixin, SummaryMixin, TitleMixin, LabelMixin ) -from plexapi.playqueue import PlayQueue from plexapi.utils import deprecated @@ -427,10 +426,6 @@ class Collection( """ Delete the collection. """ super(Collection, self).delete() - def playQueue(self, *args, **kwargs): - """ Returns a new :class:`~plexapi.playqueue.PlayQueue` from the collection. """ - return PlayQueue.create(self._server, self.items(), *args, **kwargs) - @classmethod def _create(cls, server, title, section, items): """ Create a regular collection. """ diff --git a/lib/plexapi/const.py b/lib/plexapi/const.py index 8ac71b57..605ed78c 100644 --- a/lib/plexapi/const.py +++ b/lib/plexapi/const.py @@ -4,6 +4,6 @@ # Library version MAJOR_VERSION = 4 MINOR_VERSION = 13 -PATCH_VERSION = 1 +PATCH_VERSION = 2 __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" diff --git a/lib/plexapi/library.py b/lib/plexapi/library.py index 647a89f0..ce2df733 100644 --- a/lib/plexapi/library.py +++ b/lib/plexapi/library.py @@ -7,7 +7,7 @@ from plexapi import X_PLEX_CONTAINER_SIZE, log, media, utils from plexapi.base import OPERATORS, PlexObject from plexapi.exceptions import BadRequest, NotFound from plexapi.settings import Setting -from plexapi.utils import deprecated +from plexapi.utils import cached_property, deprecated class Library(PlexObject): @@ -418,7 +418,6 @@ class LibrarySection(PlexObject): self._filterTypes = None self._fieldTypes = None self._totalViewSize = None - self._totalSize = None self._totalDuration = None self._totalStorage = None @@ -456,12 +455,10 @@ class LibrarySection(PlexObject): item.librarySectionID = librarySectionID return items - @property + @cached_property def totalSize(self): """ Returns the total number of items in the library for the default library type. """ - if self._totalSize is None: - self._totalSize = self.totalViewSize(includeCollections=False) - return self._totalSize + return self.totalViewSize(includeCollections=False) @property def totalDuration(self): @@ -644,12 +641,12 @@ class LibrarySection(PlexObject): guidLookup = {} for item in library.all(): guidLookup[item.guid] = item - guidLookup.update({guid.id for guid in item.guids}} + guidLookup.update({guid.id: item for guid in item.guids}} result1 = guidLookup['plex://show/5d9c086c46115600200aa2fe'] result2 = guidLookup['imdb://tt0944947'] - result4 = guidLookup['tmdb://1399'] - result5 = guidLookup['tvdb://121361'] + result3 = guidLookup['tmdb://1399'] + result4 = guidLookup['tvdb://121361'] """ @@ -1671,13 +1668,13 @@ class LibrarySection(PlexObject): return self.search(libtype='collection', **kwargs) def createPlaylist(self, title, items=None, smart=False, limit=None, - sort=None, filters=None, **kwargs): + sort=None, filters=None, m3ufilepath=None, **kwargs): """ Alias for :func:`~plexapi.server.PlexServer.createPlaylist` using this :class:`~plexapi.library.LibrarySection`. """ return self._server.createPlaylist( title, section=self, items=items, smart=smart, limit=limit, - sort=sort, filters=filters, **kwargs) + sort=sort, filters=filters, m3ufilepath=m3ufilepath, **kwargs) def playlist(self, title): """ Returns the playlist with the specified title. diff --git a/lib/plexapi/media.py b/lib/plexapi/media.py index ccdf9fab..c8ea8c4e 100644 --- a/lib/plexapi/media.py +++ b/lib/plexapi/media.py @@ -672,6 +672,7 @@ class MediaTag(PlexObject): role (str): The name of the character role for :class:`~plexapi.media.Role` only. tag (str): Name of the tag. This will be Animation, SciFi etc for Genres. The name of person for Directors and Roles (ex: Animation, Stephen Graham, etc). + tagKey (str): Plex GUID for the actor/actress for :class:`~plexapi.media.Role` only. thumb (str): URL to thumbnail image for :class:`~plexapi.media.Role` only. """ @@ -687,6 +688,7 @@ class MediaTag(PlexObject): self.key = data.attrib.get('key') self.role = data.attrib.get('role') self.tag = data.attrib.get('tag') + self.tagKey = data.attrib.get('tagKey') self.thumb = data.attrib.get('thumb') parent = self._parent() @@ -879,12 +881,15 @@ class Writer(MediaTag): FILTER = 'writer' -class GuidTag(PlexObject): - """ Base class for guid tags used only for Guids, as they contain only a string identifier +@utils.registerPlexObject +class Guid(PlexObject): + """ Represents a single Guid media tag. Attributes: + TAG (str): 'Guid' id (id): The guid for external metadata sources (e.g. IMDB, TMDB, TVDB, MBID). """ + TAG = 'Guid' def _loadData(self, data): """ Load attribute values from Plex XML response. """ @@ -893,13 +898,25 @@ class GuidTag(PlexObject): @utils.registerPlexObject -class Guid(GuidTag): - """ Represents a single Guid media tag. +class Rating(PlexObject): + """ Represents a single Rating media tag. Attributes: - TAG (str): 'Guid' + TAG (str): 'Rating' + image (str): The uri for the rating image + (e.g. ``imdb://image.rating``, ``rottentomatoes://image.rating.ripe``, + ``rottentomatoes://image.rating.upright``, ``themoviedb://image.rating``). + type (str): The type of rating (e.g. audience or critic). + value (float): The rating value. """ - TAG = 'Guid' + TAG = 'Rating' + + def _loadData(self, data): + """ Load attribute values from Plex XML response. """ + self._data = data + self.image = data.attrib.get('image') + self.type = data.attrib.get('type') + self.value = utils.cast(float, data.attrib.get('value')) @utils.registerPlexObject @@ -908,7 +925,7 @@ class Review(PlexObject): Attributes: TAG (str): 'Review' - filter (str): filter for reviews? + filter (str): The library filter for the review. id (int): The ID of the review. image (str): The image uri for the review. link (str): The url to the online review. @@ -983,18 +1000,34 @@ class Chapter(PlexObject): Attributes: TAG (str): 'Chapter' + end (int): The end time of the chapter in milliseconds. + filter (str): The library filter for the chapter. + id (int): The ID of the chapter. + index (int): The index of the chapter. + tag (str): The name of the chapter. + title (str): The title of the chapter. + thumb (str): The URL to retrieve the chapter thumbnail. + start (int): The start time of the chapter in milliseconds. """ TAG = 'Chapter' + def __repr__(self): + name = self._clean(self.firstAttr('tag')) + start = utils.millisecondToHumanstr(self._clean(self.firstAttr('start'))) + end = utils.millisecondToHumanstr(self._clean(self.firstAttr('end'))) + offsets = f'{start}-{end}' + return f"<{':'.join([self.__class__.__name__, name, offsets])}>" + def _loadData(self, data): self._data = data + self.end = utils.cast(int, data.attrib.get('endTimeOffset')) + self.filter = data.attrib.get('filter') self.id = utils.cast(int, data.attrib.get('id', 0)) - self.filter = data.attrib.get('filter') # I couldn't filter on it anyways + self.index = utils.cast(int, data.attrib.get('index')) self.tag = data.attrib.get('tag') self.title = self.tag - self.index = utils.cast(int, data.attrib.get('index')) + self.thumb = data.attrib.get('thumb') self.start = utils.cast(int, data.attrib.get('startTimeOffset')) - self.end = utils.cast(int, data.attrib.get('endTimeOffset')) @utils.registerPlexObject @@ -1003,6 +1036,10 @@ class Marker(PlexObject): Attributes: TAG (str): 'Marker' + end (int): The end time of the marker in milliseconds. + id (int): The ID of the marker. + type (str): The type of marker. + start (int): The start time of the marker in milliseconds. """ TAG = 'Marker' @@ -1015,10 +1052,10 @@ class Marker(PlexObject): def _loadData(self, data): self._data = data + self.end = utils.cast(int, data.attrib.get('endTimeOffset')) self.id = utils.cast(int, data.attrib.get('id')) self.type = data.attrib.get('type') self.start = utils.cast(int, data.attrib.get('startTimeOffset')) - self.end = utils.cast(int, data.attrib.get('endTimeOffset')) @utils.registerPlexObject @@ -1027,13 +1064,15 @@ class Field(PlexObject): Attributes: TAG (str): 'Field' + locked (bool): True if the field is locked. + name (str): The name of the field. """ TAG = 'Field' def _loadData(self, data): self._data = data - self.name = data.attrib.get('name') self.locked = utils.cast(bool, data.attrib.get('locked')) + self.name = data.attrib.get('name') @utils.registerPlexObject diff --git a/lib/plexapi/mixins.py b/lib/plexapi/mixins.py index 16414cf5..91c9caaa 100644 --- a/lib/plexapi/mixins.py +++ b/lib/plexapi/mixins.py @@ -5,7 +5,7 @@ from urllib.parse import parse_qsl, quote_plus, unquote, urlencode, urlsplit from plexapi import media, settings, utils from plexapi.exceptions import BadRequest, NotFound -from plexapi.utils import deprecated +from plexapi.utils import deprecated, openOrRead class AdvancedSettingsMixin: @@ -341,14 +341,14 @@ class ArtMixin(ArtUrlMixin): Parameters: url (str): The full URL to the image to upload. - filepath (str): The full file path the the image to upload. + filepath (str): The full file path the the image to upload or file-like object. """ if url: key = f'/library/metadata/{self.ratingKey}/arts?url={quote_plus(url)}' self._server.query(key, method=self._server._session.post) elif filepath: key = f'/library/metadata/{self.ratingKey}/arts' - data = open(filepath, 'rb').read() + data = openOrRead(filepath) self._server.query(key, method=self._server._session.post, data=data) return self @@ -392,14 +392,14 @@ class BannerMixin(BannerUrlMixin): Parameters: url (str): The full URL to the image to upload. - filepath (str): The full file path the the image to upload. + filepath (str): The full file path the the image to upload or file-like object. """ if url: key = f'/library/metadata/{self.ratingKey}/banners?url={quote_plus(url)}' self._server.query(key, method=self._server._session.post) elif filepath: key = f'/library/metadata/{self.ratingKey}/banners' - data = open(filepath, 'rb').read() + data = openOrRead(filepath) self._server.query(key, method=self._server._session.post, data=data) return self @@ -448,14 +448,14 @@ class PosterMixin(PosterUrlMixin): Parameters: url (str): The full URL to the image to upload. - filepath (str): The full file path the the image to upload. + filepath (str): The full file path the the image to upload or file-like object. """ if url: key = f'/library/metadata/{self.ratingKey}/posters?url={quote_plus(url)}' self._server.query(key, method=self._server._session.post) elif filepath: key = f'/library/metadata/{self.ratingKey}/posters' - data = open(filepath, 'rb').read() + data = openOrRead(filepath) self._server.query(key, method=self._server._session.post, data=data) return self @@ -494,22 +494,24 @@ class ThemeMixin(ThemeUrlMixin): """ Returns list of available :class:`~plexapi.media.Theme` objects. """ return self.fetchItems(f'/library/metadata/{self.ratingKey}/themes', cls=media.Theme) - def uploadTheme(self, url=None, filepath=None): + def uploadTheme(self, url=None, filepath=None, timeout=None): """ Upload a theme from url or filepath. Warning: Themes cannot be deleted using PlexAPI! Parameters: url (str): The full URL to the theme to upload. - filepath (str): The full file path to the theme to upload. + filepath (str): The full file path to the theme to upload or file-like object. + timeout (int, optional): Timeout, in seconds, to use when uploading themes to the server. + (default config.TIMEOUT). """ if url: key = f'/library/metadata/{self.ratingKey}/themes?url={quote_plus(url)}' - self._server.query(key, method=self._server._session.post) + self._server.query(key, method=self._server._session.post, timeout=timeout) elif filepath: key = f'/library/metadata/{self.ratingKey}/themes' - data = open(filepath, 'rb').read() - self._server.query(key, method=self._server._session.post, data=data) + data = openOrRead(filepath) + self._server.query(key, method=self._server._session.post, data=data, timeout=timeout) return self def setTheme(self, theme): diff --git a/lib/plexapi/myplex.py b/lib/plexapi/myplex.py index 7bfbe7ab..ca3f026a 100644 --- a/lib/plexapi/myplex.py +++ b/lib/plexapi/myplex.py @@ -32,6 +32,7 @@ class MyPlexAccount(PlexObject): session (requests.Session, optional): Use your own session object if you want to cache the http responses from PMS timeout (int): timeout in seconds on initial connect to myplex (default config.TIMEOUT). + code (str): Two-factor authentication code to use when logging in. Attributes: SIGNIN (str): 'https://plex.tv/users/sign_in.xml' @@ -88,19 +89,21 @@ class MyPlexAccount(PlexObject): # https://plex.tv/api/v2/user?X-Plex-Token={token}&X-Plex-Client-Identifier={clientId} key = 'https://plex.tv/users/account' - def __init__(self, username=None, password=None, token=None, session=None, timeout=None): + def __init__(self, username=None, password=None, token=None, session=None, timeout=None, code=None): self._token = token or CONFIG.get('auth.server_token') self._session = session or requests.Session() self._sonos_cache = [] self._sonos_cache_timestamp = 0 - data, initpath = self._signin(username, password, timeout) + data, initpath = self._signin(username, password, code, timeout) super(MyPlexAccount, self).__init__(self, data, initpath) - def _signin(self, username, password, timeout): + def _signin(self, username, password, code, timeout): if self._token: return self.query(self.key), self.key username = username or CONFIG.get('auth.myplex_username') password = password or CONFIG.get('auth.myplex_password') + if code: + password += code data = self.query(self.SIGNIN, method=self._session.post, auth=(username, password), timeout=timeout) return data, self.SIGNIN @@ -390,12 +393,13 @@ class MyPlexAccount(PlexObject): url = self.HOMEUSER.format(userId=user.id) return self.query(url, self._session.delete) - def switchHomeUser(self, user): + def switchHomeUser(self, user, pin=None): """ Returns a new :class:`~plexapi.myplex.MyPlexAccount` object switched to the given home user. Parameters: user (:class:`~plexapi.myplex.MyPlexUser` or str): :class:`~plexapi.myplex.MyPlexUser`, username, or email of the home user to switch to. + pin (str): PIN for the home user (required if the home user has a PIN set). Example: @@ -410,9 +414,12 @@ class MyPlexAccount(PlexObject): """ user = user if isinstance(user, MyPlexUser) else self.user(user) url = f'{self.HOMEUSERS}/{user.id}/switch' - data = self.query(url, self._session.post) + params = {} + if pin: + params['pin'] = pin + data = self.query(url, self._session.post, params=params) userToken = data.attrib.get('authenticationToken') - return MyPlexAccount(token=userToken) + return MyPlexAccount(token=userToken, session=self._session) def setPin(self, newPin, currentPin=None): """ Set a new Plex Home PIN for the account. @@ -861,7 +868,12 @@ class MyPlexAccount(PlexObject): results += subresults[:maxresults - len(results)] params['X-Plex-Container-Start'] += params['X-Plex-Container-Size'] - return self._toOnlineMetadata(results) + # totalSize is available in first response, update maxresults from it + totalSize = utils.cast(int, data.attrib.get('totalSize')) + if maxresults > totalSize: + maxresults = totalSize + + return self._toOnlineMetadata(results, **kwargs) def onWatchlist(self, item): """ Returns True if the item is on the user's watchlist. @@ -941,7 +953,7 @@ class MyPlexAccount(PlexObject): } params = { 'query': query, - 'limit ': limit, + 'limit': limit, 'searchTypes': libtype, 'includeMetadata': 1 } @@ -1005,21 +1017,24 @@ class MyPlexAccount(PlexObject): data = {'code': pin} self.query(self.LINK, self._session.put, headers=headers, data=data) - def _toOnlineMetadata(self, objs): + def _toOnlineMetadata(self, objs, **kwargs): """ Convert a list of media objects to online metadata objects. """ # TODO: Add proper support for metadata.provider.plex.tv # Temporary workaround to allow reloading and browsing of online media objects - server = PlexServer(self.METADATA, self._token) + server = PlexServer(self.METADATA, self._token, session=self._session) + + includeUserState = int(bool(kwargs.pop('includeUserState', True))) if not isinstance(objs, list): objs = [objs] + for obj in objs: obj._server = server # Parse details key to modify query string url = urlsplit(obj._details_key) query = dict(parse_qsl(url.query)) - query['includeUserState'] = 1 + query['includeUserState'] = includeUserState query.pop('includeFields', None) obj._details_key = urlunsplit((url.scheme, url.netloc, url.path, urlencode(query), url.fragment)) diff --git a/lib/plexapi/playlist.py b/lib/plexapi/playlist.py index b4174c84..f5ece634 100644 --- a/lib/plexapi/playlist.py +++ b/lib/plexapi/playlist.py @@ -5,9 +5,8 @@ from urllib.parse import quote_plus, unquote from plexapi import media, utils from plexapi.base import Playable, PlexPartialObject from plexapi.exceptions import BadRequest, NotFound, Unsupported -from plexapi.library import LibrarySection +from plexapi.library import LibrarySection, MusicSection from plexapi.mixins import SmartFilterMixin, ArtMixin, PosterMixin -from plexapi.playqueue import PlayQueue from plexapi.utils import deprecated @@ -330,10 +329,6 @@ class Playlist( """ Delete the playlist. """ self._server.query(self.key, method=self._server._session.delete) - def playQueue(self, *args, **kwargs): - """ Returns a new :class:`~plexapi.playqueue.PlayQueue` from the playlist. """ - return PlayQueue.create(self._server, self, *args, **kwargs) - @classmethod def _create(cls, server, title, items): """ Create a regular playlist. """ @@ -375,15 +370,32 @@ class Playlist( data = server.query(key, method=server._session.post)[0] return cls(server, data, initpath=key) + @classmethod + def _createFromM3U(cls, server, title, section, m3ufilepath): + """ Create a playlist from uploading an m3u file. """ + if not isinstance(section, LibrarySection): + section = server.library.section(section) + + if not isinstance(section, MusicSection): + raise BadRequest('Can only create playlists from m3u files in a music library.') + + args = {'sectionID': section.key, 'path': m3ufilepath} + key = f"/playlists/upload{utils.joinArgs(args)}" + server.query(key, method=server._session.post) + try: + return server.playlists(sectionId=section.key, guid__endswith=m3ufilepath)[0].edit(title=title).reload() + except IndexError: + raise BadRequest('Failed to create playlist from m3u file.') from None + @classmethod def create(cls, server, title, section=None, items=None, smart=False, limit=None, - libtype=None, sort=None, filters=None, **kwargs): + libtype=None, sort=None, filters=None, m3ufilepath=None, **kwargs): """ Create a playlist. Parameters: server (:class:`~plexapi.server.PlexServer`): Server to create the playlist on. title (str): Title of the playlist. - section (:class:`~plexapi.library.LibrarySection`, str): Smart playlists only, + section (:class:`~plexapi.library.LibrarySection`, str): Smart playlists and m3u import only, the library section to create the playlist in. items (List): Regular playlists only, list of :class:`~plexapi.audio.Audio`, :class:`~plexapi.video.Video`, or :class:`~plexapi.photo.Photo` objects to be added to the playlist. @@ -396,17 +408,23 @@ class Playlist( See :func:`~plexapi.library.LibrarySection.search` for more info. filters (dict): Smart playlists only, a dictionary of advanced filters. See :func:`~plexapi.library.LibrarySection.search` for more info. + m3ufilepath (str): Music playlists only, the full file path to an m3u file to import. + Note: This will overwrite any playlist previously created from the same m3u file. **kwargs (dict): Smart playlists only, additional custom filters to apply to the search results. See :func:`~plexapi.library.LibrarySection.search` for more info. Raises: :class:`plexapi.exceptions.BadRequest`: When no items are included to create the playlist. :class:`plexapi.exceptions.BadRequest`: When mixing media types in the playlist. + :class:`plexapi.exceptions.BadRequest`: When attempting to import m3u file into non-music library. + :class:`plexapi.exceptions.BadRequest`: When failed to import m3u file. Returns: :class:`~plexapi.playlist.Playlist`: A new instance of the created Playlist. """ - if smart: + if m3ufilepath: + return cls._createFromM3U(server, title, section, m3ufilepath) + elif smart: return cls._createSmart(server, title, section, limit, libtype, sort, filters, **kwargs) else: return cls._create(server, title, items) diff --git a/lib/plexapi/playqueue.py b/lib/plexapi/playqueue.py index 6f4646dc..4c49f8d2 100644 --- a/lib/plexapi/playqueue.py +++ b/lib/plexapi/playqueue.py @@ -150,8 +150,8 @@ class PlayQueue(PlexObject): Parameters: server (:class:`~plexapi.server.PlexServer`): Server you are connected to. - items (:class:`~plexapi.base.Playable` or :class:`~plexapi.playlist.Playlist`): - A media item, list of media items, or Playlist. + items (:class:`~plexapi.base.PlexPartialObject`): + A media item or a list of media items. startItem (:class:`~plexapi.base.Playable`, optional): Media item in the PlayQueue where playback should begin. shuffle (int, optional): Start the playqueue shuffled. @@ -174,16 +174,13 @@ class PlayQueue(PlexObject): uri_args = quote_plus(f"/library/metadata/{item_keys}") args["uri"] = f"library:///directory/{uri_args}" args["type"] = items[0].listType - elif items.type == "playlist": - args["type"] = items.playlistType - if items.radio: - args["uri"] = f"server://{server.machineIdentifier}/{server.library.identifier}{items.key}" - else: - args["playlistID"] = items.ratingKey else: - uuid = items.section().uuid - args["type"] = items.listType - args["uri"] = f"library://{uuid}/item/{items.key}" + if items.type == "playlist": + args["type"] = items.playlistType + args["playlistID"] = items.ratingKey + else: + args["type"] = items.listType + args["uri"] = f"server://{server.machineIdentifier}/{server.library.identifier}{items.key}" if startItem: args["key"] = startItem.key diff --git a/lib/plexapi/server.py b/lib/plexapi/server.py index 168efd40..bec9fc08 100644 --- a/lib/plexapi/server.py +++ b/lib/plexapi/server.py @@ -17,7 +17,7 @@ from plexapi.media import Conversion, Optimized from plexapi.playlist import Playlist from plexapi.playqueue import PlayQueue from plexapi.settings import Settings -from plexapi.utils import deprecated +from plexapi.utils import cached_property, deprecated from requests.status_codes import _codes as codes # Need these imports to populate utils.PLEXOBJECTS @@ -109,8 +109,6 @@ class PlexServer(PlexObject): self._showSecrets = CONFIG.get('log.show_secrets', '').lower() == 'true' self._session = session or requests.Session() self._timeout = timeout - self._library = None # cached library - self._settings = None # cached settings self._myPlexAccount = None # cached myPlexAccount self._systemAccounts = None # cached list of SystemAccount self._systemDevices = None # cached list of SystemDevice @@ -173,27 +171,22 @@ class PlexServer(PlexObject): def _uriRoot(self): return f'server://{self.machineIdentifier}/com.plexapp.plugins.library' - @property + @cached_property def library(self): """ Library to browse or search your media. """ - if not self._library: - try: - data = self.query(Library.key) - self._library = Library(self, data) - except BadRequest: - data = self.query('/library/sections/') - # Only the owner has access to /library - # so just return the library without the data. - return Library(self, data) - return self._library + try: + data = self.query(Library.key) + except BadRequest: + # Only the owner has access to /library + # so just return the library without the data. + data = self.query('/library/sections/') + return Library(self, data) - @property + @cached_property def settings(self): """ Returns a list of all server settings. """ - if not self._settings: - data = self.query(Settings.key) - self._settings = Settings(self, data) - return self._settings + data = self.query(Settings.key) + return Settings(self, data) def account(self): """ Returns the :class:`~plexapi.server.Account` object this server belongs to. """ @@ -318,7 +311,7 @@ class PlexServer(PlexObject): """ if self._myPlexAccount is None: from plexapi.myplex import MyPlexAccount - self._myPlexAccount = MyPlexAccount(token=self._token) + self._myPlexAccount = MyPlexAccount(token=self._token, session=self._session) return self._myPlexAccount def _myPlexClientPorts(self): @@ -454,19 +447,42 @@ class PlexServer(PlexObject): Returns: :class:`~plexapi.collection.Collection`: A new instance of the created Collection. + + Example: + + .. code-block:: python + + # Create a regular collection + movies = plex.library.section("Movies") + movie1 = movies.get("Big Buck Bunny") + movie2 = movies.get("Sita Sings the Blues") + collection = plex.createCollection( + title="Favorite Movies", + section=movies, + items=[movie1, movie2] + ) + + # Create a smart collection + collection = plex.createCollection( + title="Recently Aired Comedy TV Shows", + section="TV Shows", + smart=True, + sort="episode.originallyAvailableAt:desc", + filters={"episode.originallyAvailableAt>>": "4w", "genre": "comedy"} + ) """ return Collection.create( self, title, section, items=items, smart=smart, limit=limit, libtype=libtype, sort=sort, filters=filters, **kwargs) def createPlaylist(self, title, section=None, items=None, smart=False, limit=None, - libtype=None, sort=None, filters=None, **kwargs): + libtype=None, sort=None, filters=None, m3ufilepath=None, **kwargs): """ Creates and returns a new :class:`~plexapi.playlist.Playlist`. Parameters: title (str): Title of the playlist. - section (:class:`~plexapi.library.LibrarySection`, str): Smart playlists only, - library section to create the playlist in. + section (:class:`~plexapi.library.LibrarySection`, str): Smart playlists and m3u import only, + the library section to create the playlist in. items (List): Regular playlists only, list of :class:`~plexapi.audio.Audio`, :class:`~plexapi.video.Video`, or :class:`~plexapi.photo.Photo` objects to be added to the playlist. smart (bool): True to create a smart playlist. Default False. @@ -478,19 +494,51 @@ class PlexServer(PlexObject): See :func:`~plexapi.library.LibrarySection.search` for more info. filters (dict): Smart playlists only, a dictionary of advanced filters. See :func:`~plexapi.library.LibrarySection.search` for more info. + m3ufilepath (str): Music playlists only, the full file path to an m3u file to import. + Note: This will overwrite any playlist previously created from the same m3u file. **kwargs (dict): Smart playlists only, additional custom filters to apply to the search results. See :func:`~plexapi.library.LibrarySection.search` for more info. Raises: :class:`plexapi.exceptions.BadRequest`: When no items are included to create the playlist. :class:`plexapi.exceptions.BadRequest`: When mixing media types in the playlist. + :class:`plexapi.exceptions.BadRequest`: When attempting to import m3u file into non-music library. + :class:`plexapi.exceptions.BadRequest`: When failed to import m3u file. Returns: :class:`~plexapi.playlist.Playlist`: A new instance of the created Playlist. + + Example: + + .. code-block:: python + + # Create a regular playlist + episodes = plex.library.section("TV Shows").get("Game of Thrones").episodes() + playlist = plex.createPlaylist( + title="GoT Episodes", + items=episodes + ) + + # Create a smart playlist + playlist = plex.createPlaylist( + title="Top 10 Unwatched Movies", + section="Movies", + smart=True, + limit=10, + sort="audienceRating:desc", + filters={"audienceRating>>": 8.0, "unwatched": True} + ) + + # Create a music playlist from an m3u file + playlist = plex.createPlaylist( + title="Favorite Tracks", + section="Music", + m3ufilepath="/path/to/playlist.m3u" + ) """ return Playlist.create( self, title, section=section, items=items, smart=smart, limit=limit, - libtype=libtype, sort=sort, filters=filters, **kwargs) + libtype=libtype, sort=sort, filters=filters, m3ufilepath=m3ufilepath, **kwargs) def createPlayQueue(self, item, **kwargs): """ Creates and returns a new :class:`~plexapi.playqueue.PlayQueue`. diff --git a/lib/plexapi/settings.py b/lib/plexapi/settings.py index b6b4d2e6..ef91391b 100644 --- a/lib/plexapi/settings.py +++ b/lib/plexapi/settings.py @@ -139,7 +139,14 @@ class Setting(PlexObject): if not enumstr: return None if ':' in enumstr: - return {self._cast(k): v for k, v in [kv.split(':') for kv in enumstr.split('|')]} + d = {} + for kv in enumstr.split('|'): + try: + k, v = kv.split(':') + d[self._cast(k)] = v + except ValueError: + d[self._cast(kv)] = kv + return d return enumstr.split('|') def set(self, value): diff --git a/lib/plexapi/utils.py b/lib/plexapi/utils.py index 36827311..f429147e 100644 --- a/lib/plexapi/utils.py +++ b/lib/plexapi/utils.py @@ -24,6 +24,11 @@ try: except ImportError: tqdm = None +try: + from functools import cached_property +except ImportError: + from backports.cached_property import cached_property # noqa: F401 + log = logging.getLogger('plexapi') # Search Types - Plex uses these to filter specific media types when searching. @@ -618,3 +623,10 @@ def toJson(obj, **kwargs): return obj.isoformat() return {k: v for k, v in obj.__dict__.items() if not k.startswith('_')} return json.dumps(obj, default=serialize, **kwargs) + + +def openOrRead(file): + if hasattr(file, 'read'): + return file.read() + with open(file, 'rb') as f: + return f.read() diff --git a/lib/plexapi/video.py b/lib/plexapi/video.py index 0d664c14..fe12ce67 100644 --- a/lib/plexapi/video.py +++ b/lib/plexapi/video.py @@ -323,6 +323,7 @@ class Movie( producers (List<:class:`~plexapi.media.Producer`>): List of producers objects. rating (float): Movie critic rating (7.9; 9.8; 8.1). ratingImage (str): Key to critic rating image (rottentomatoes://image.rating.rotten). + ratings (List<:class:`~plexapi.media.Rating`>): List of rating objects. roles (List<:class:`~plexapi.media.Role`>): List of role objects. similar (List<:class:`~plexapi.media.Similar`>): List of Similar objects. studio (str): Studio that created movie (Di Bonaventura Pictures; 21 Laps Entertainment). @@ -363,6 +364,7 @@ class Movie( self.producers = self.findItems(data, media.Producer) self.rating = utils.cast(float, data.attrib.get('rating')) self.ratingImage = data.attrib.get('ratingImage') + self.ratings = self.findItems(data, media.Rating) self.roles = self.findItems(data, media.Role) self.similar = self.findItems(data, media.Similar) self.studio = data.attrib.get('studio') @@ -459,6 +461,7 @@ class Show( originallyAvailableAt (datetime): Datetime the show was released. originalTitle (str): The original title of the show. rating (float): Show rating (7.9; 9.8; 8.1). + ratings (List<:class:`~plexapi.media.Rating`>): List of rating objects. roles (List<:class:`~plexapi.media.Role`>): List of role objects. showOrdering (str): Setting that indicates the episode ordering for the show (None = Library default). @@ -503,6 +506,7 @@ class Show( self.originallyAvailableAt = utils.toDatetime(data.attrib.get('originallyAvailableAt'), '%Y-%m-%d') self.originalTitle = data.attrib.get('originalTitle') self.rating = utils.cast(float, data.attrib.get('rating')) + self.ratings = self.findItems(data, media.Rating) self.roles = self.findItems(data, media.Role) self.showOrdering = data.attrib.get('showOrdering') self.similar = self.findItems(data, media.Similar) @@ -639,6 +643,7 @@ class Season( parentTheme (str): URL to show theme resource (/library/metadata//theme/). parentThumb (str): URL to show thumbnail image (/library/metadata//thumb/). parentTitle (str): Name of the show for the season. + ratings (List<:class:`~plexapi.media.Rating`>): List of rating objects. viewedLeafCount (int): Number of items marked as played in the season view. year (int): Year the season was released. """ @@ -663,6 +668,7 @@ class Season( self.parentTheme = data.attrib.get('parentTheme') self.parentThumb = data.attrib.get('parentThumb') self.parentTitle = data.attrib.get('parentTitle') + self.ratings = self.findItems(data, media.Rating) self.viewedLeafCount = utils.cast(int, data.attrib.get('viewedLeafCount')) self.year = utils.cast(int, data.attrib.get('year')) @@ -800,6 +806,7 @@ class Episode( parentYear (int): Year the season was released. producers (List<:class:`~plexapi.media.Producer`>): List of producers objects. rating (float): Episode rating (7.9; 9.8; 8.1). + ratings (List<:class:`~plexapi.media.Rating`>): List of rating objects. roles (List<:class:`~plexapi.media.Role`>): List of role objects. skipParent (bool): True if the show's seasons are set to hidden. viewOffset (int): View offset in milliseconds. @@ -845,6 +852,7 @@ class Episode( self.parentYear = utils.cast(int, data.attrib.get('parentYear')) self.producers = self.findItems(data, media.Producer) self.rating = utils.cast(float, data.attrib.get('rating')) + self.ratings = self.findItems(data, media.Rating) self.roles = self.findItems(data, media.Role) self.skipParent = utils.cast(bool, data.attrib.get('skipParent', '0')) self.viewOffset = utils.cast(int, data.attrib.get('viewOffset', 0)) diff --git a/requirements.txt b/requirements.txt index 70ca360f..b69f5fc2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -27,7 +27,7 @@ MarkupSafe==2.1.1 musicbrainzngs==0.7.1 packaging==22.0 paho-mqtt==1.6.1 -plexapi==4.13.1 +plexapi==4.13.2 portend==3.1.0 profilehooks==1.12.0 PyJWT==2.6.0 From 4064ac083e0f8c61cbddba883feb3d688b158232 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Dec 2022 10:45:05 -0800 Subject: [PATCH 123/583] Bump actions/setup-python from 4.3.1 to 4.4.0 (#1938) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4.3.1 to 4.4.0. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v4.3.1...v4.4.0) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> [skip ci] --- .github/workflows/publish-installers.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-installers.yml b/.github/workflows/publish-installers.yml index 7002834b..77530960 100644 --- a/.github/workflows/publish-installers.yml +++ b/.github/workflows/publish-installers.yml @@ -52,7 +52,7 @@ jobs: echo $GITHUB_SHA > version.txt - name: Set Up Python - uses: actions/setup-python@v4.3.1 + uses: actions/setup-python@v4.4.0 with: python-version: '3.9' cache: pip From f6dd38ad126917193d5ac3bf014815623e349dc9 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Thu, 22 Dec 2022 11:15:59 -0800 Subject: [PATCH 124/583] v2.11.0 --- CHANGELOG.md | 24 ++++++++++++++++++++++++ plexpy/version.py | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44da2582..89f15eb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ # Changelog +## v2.11.0 (2022-12-22) + +* Activity: + * New: Added audio and subtitle language to activity cards. (#1831, #1900) (Thanks @fscorrupt) +* History: + * New: Log subtitle language and subtitle forced to database. (#1826) +* Notifications: + * Fix: Validating condition operators would fail with a blank parameter. + * New: Added start time and stop time notification parameters. (#1931) + * New: Added session_key to LunaSea notification payload. (#1929) (Thanks @JagandeepBrar) +* Newsletters: + * Fix: Allow CSS to support light and dark themes. +* Exporter: + * New: Added editionTitle to movie exporter fields. + * Change: m3u8 export changed to .m3u file extension. File is still encoded using UTF-8. +* UI: + * Fix: Link watch statistics to media page using metadata from history. (#1882) + * New: Show subtitle language and subtitle forced flag in stream data modal. +* Other: + * Fix: Mask more user and metadata fields for guest access. (#1913) + * Change: Disable TLS 1.0 and 1.1 for the webserver. Minimum TLS version is 1.2. (#1870) + * Change: Use system language for requests to Plex Media Server. + + ## v2.10.5 (2022-11-07) * Notifications: diff --git a/plexpy/version.py b/plexpy/version.py index 6c3d1104..57d3f6ae 100644 --- a/plexpy/version.py +++ b/plexpy/version.py @@ -18,4 +18,4 @@ from __future__ import unicode_literals PLEXPY_BRANCH = "master" -PLEXPY_RELEASE_VERSION = "v2.10.5" +PLEXPY_RELEASE_VERSION = "v2.11.0" \ No newline at end of file From 808ca348e86f4541100b0fc6c2e9c39c31df3466 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Thu, 22 Dec 2022 11:24:41 -0800 Subject: [PATCH 125/583] Use source language instead of stream language on activity cards --- .../default/current_activity_instance.html | 14 +++++++------- data/interfaces/default/index.html | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/data/interfaces/default/current_activity_instance.html b/data/interfaces/default/current_activity_instance.html index d0e95ac8..b48a70f3 100644 --- a/data/interfaces/default/current_activity_instance.html +++ b/data/interfaces/default/current_activity_instance.html @@ -266,11 +266,11 @@ DOCUMENTATION :: END
    % if data['stream_audio_decision']: % if data['stream_audio_decision'] == 'transcode': - Transcode (${data['stream_audio_language'] or 'Unknown'} - ${AUDIO_CODEC_OVERRIDES.get(data['audio_codec'], data['audio_codec'].upper())} ${data['audio_channel_layout'].split('(')[0].capitalize()} ${AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()}) + Transcode (${data['audio_language'] or 'Unknown'} - ${AUDIO_CODEC_OVERRIDES.get(data['audio_codec'], data['audio_codec'].upper())} ${data['audio_channel_layout'].split('(')[0].capitalize()} ${AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()}) % elif data['stream_audio_decision'] == 'copy': - Direct Stream (${data['stream_audio_language'] or 'Unknown'} - ${AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()}) + Direct Stream (${data['audio_language'] or 'Unknown'} - ${AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()}) % else: - Direct Play (${data['stream_audio_language'] or 'Unknown'} - ${AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()}) + Direct Play (${data['audio_language'] or 'Unknown'} - ${AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()}) % endif % endif
    @@ -285,13 +285,13 @@ DOCUMENTATION :: END subtitle_codec = 'None' if data['stream_subtitle_codec'] and data['stream_subtitle_transient'] else data['subtitle_codec'].upper() %> % if data['stream_subtitle_decision'] == 'transcode': - Transcode (${data['stream_subtitle_language'] or 'Unknown'} - ${subtitle_codec} ${data['stream_subtitle_codec'].upper()}) + Transcode (${data['subtitle_language'] or 'Unknown'} - ${subtitle_codec} ${data['stream_subtitle_codec'].upper()}) % elif data['stream_subtitle_decision'] == 'copy': - Direct Stream (${data['stream_subtitle_language'] or 'Unknown'} - ${subtitle_codec}) + Direct Stream (${data['subtitle_language'] or 'Unknown'} - ${subtitle_codec}) % elif data['stream_subtitle_decision'] == 'burn': - Burn (${data['stream_subtitle_language'] or 'Unknown'} - ${subtitle_codec}) + Burn (${data['subtitle_language'] or 'Unknown'} - ${subtitle_codec}) % else: - Direct Play (${data['stream_subtitle_language'] or 'Unknown'} - ${subtitle_codec if data['synced_version'] else data['stream_subtitle_codec'].upper()}) + Direct Play (${data['subtitle_language'] or 'Unknown'} - ${subtitle_codec if data['synced_version'] else data['stream_subtitle_codec'].upper()}) % endif % else: None diff --git a/data/interfaces/default/index.html b/data/interfaces/default/index.html index e4cc7d51..e094d0e4 100644 --- a/data/interfaces/default/index.html +++ b/data/interfaces/default/index.html @@ -526,11 +526,11 @@ var a_codec = (s.audio_codec === 'truehd') ? 'TrueHD' : s.audio_codec.toUpperCase(); var sa_codec = (s.stream_audio_codec === 'truehd') ? 'TrueHD' : s.stream_audio_codec.toUpperCase(); if (s.stream_audio_decision === 'transcode') { - audio_decision = 'Transcode ('+ (s.stream_audio_language || 'Unknown')+ ' - ' + a_codec + ' ' + capitalizeFirstLetter(s.audio_channel_layout.split('(')[0]) + ' ' + sa_codec + ' ' + capitalizeFirstLetter(s.stream_audio_channel_layout.split('(')[0]) + ')'; + audio_decision = 'Transcode ('+ (s.audio_language || 'Unknown')+ ' - ' + a_codec + ' ' + capitalizeFirstLetter(s.audio_channel_layout.split('(')[0]) + ' ' + sa_codec + ' ' + capitalizeFirstLetter(s.stream_audio_channel_layout.split('(')[0]) + ')'; } else if (s.stream_audio_decision === 'copy') { - audio_decision = 'Direct Stream ('+ (s.stream_audio_language || 'Unknown')+ ' - ' + sa_codec + ' ' + capitalizeFirstLetter(s.stream_audio_channel_layout.split('(')[0]) + ')'; + audio_decision = 'Direct Stream ('+ (s.audio_language || 'Unknown')+ ' - ' + sa_codec + ' ' + capitalizeFirstLetter(s.stream_audio_channel_layout.split('(')[0]) + ')'; } else { - audio_decision = 'Direct Play ('+ (s.stream_audio_language || 'Unknown')+ ' - ' + sa_codec + ' ' + capitalizeFirstLetter(s.stream_audio_channel_layout.split('(')[0]) + ')'; + audio_decision = 'Direct Play ('+ (s.audio_language || 'Unknown')+ ' - ' + sa_codec + ' ' + capitalizeFirstLetter(s.stream_audio_channel_layout.split('(')[0]) + ')'; } } $('#audio_decision-' + key).html(audio_decision); @@ -539,13 +539,13 @@ if (['movie', 'episode', 'clip'].indexOf(s.media_type) > -1 && s.subtitles === 1) { var subtitle_codec = (s.stream_subtitle_codec && s.stream_subtitle_transient) ? 'None' : s.subtitle_codec.toUpperCase(); if (s.stream_subtitle_decision === 'transcode') { - subtitle_decision = 'Transcode ('+ (s.stream_subtitle_language || 'Unknown')+ ' - ' + subtitle_codec + ' ' + s.stream_subtitle_codec.toUpperCase() + ')'; + subtitle_decision = 'Transcode ('+ (s.subtitle_language || 'Unknown')+ ' - ' + subtitle_codec + ' ' + s.stream_subtitle_codec.toUpperCase() + ')'; } else if (s.stream_subtitle_decision === 'copy') { - subtitle_decision = 'Direct Stream ('+ (s.stream_subtitle_language || 'Unknown')+ ' - ' + subtitle_codec + ')'; + subtitle_decision = 'Direct Stream ('+ (s.subtitle_language || 'Unknown')+ ' - ' + subtitle_codec + ')'; } else if (s.stream_subtitle_decision === 'burn') { - subtitle_decision = 'Burn ('+ (s.stream_subtitle_language || 'Unknown')+ ' - ' + subtitle_codec + ')'; + subtitle_decision = 'Burn ('+ (s.subtitle_language || 'Unknown')+ ' - ' + subtitle_codec + ')'; } else { - subtitle_decision = 'Direct Play ('+ (s.stream_subtitle_language || 'Unknown')+ ' - ' + ((s.synced_version === '1') ? subtitle_codec : s.stream_subtitle_codec.toUpperCase()) + ')'; + subtitle_decision = 'Direct Play ('+ (s.subtitle_language || 'Unknown')+ ' - ' + ((s.synced_version === '1') ? subtitle_codec : s.stream_subtitle_codec.toUpperCase()) + ')'; } } $('#subtitle_decision-' + key).html(subtitle_decision); From e8af221e99a4a37038affe4459603bcdf8879931 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Thu, 22 Dec 2022 11:44:30 -0800 Subject: [PATCH 126/583] Rename submit winget job name --- .github/workflows/submit-winget.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/submit-winget.yml b/.github/workflows/submit-winget.yml index 33b6d9da..43c1ce24 100644 --- a/.github/workflows/submit-winget.yml +++ b/.github/workflows/submit-winget.yml @@ -7,7 +7,7 @@ on: jobs: winget: - name: Publish Winget Package + name: Submit Winget Package runs-on: windows-latest steps: - name: Submit package to Windows Package Manager Community Repository From 680c8f1ee3e31019065fd407bee0c2ed10f3e320 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Thu, 22 Dec 2022 11:44:48 -0800 Subject: [PATCH 127/583] Update publish installer workflow token --- .github/workflows/publish-installers.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish-installers.yml b/.github/workflows/publish-installers.yml index 77530960..3b0911ee 100644 --- a/.github/workflows/publish-installers.yml +++ b/.github/workflows/publish-installers.yml @@ -125,7 +125,7 @@ jobs: uses: actions/create-release@v1 id: create_release env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GHACTIONS_TOKEN }} with: tag_name: ${{ steps.get_version.outputs.RELEASE_VERSION }} release_name: Tautulli ${{ steps.get_version.outputs.RELEASE_VERSION }} @@ -140,7 +140,7 @@ jobs: uses: actions/upload-release-asset@v1 if: env.WORKFLOW_CONCLUSION == 'success' env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GHACTIONS_TOKEN }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} asset_path: Tautulli-windows-installer/Tautulli-windows-${{ steps.get_version.outputs.RELEASE_VERSION }}-x64.exe @@ -151,7 +151,7 @@ jobs: uses: actions/upload-release-asset@v1 if: env.WORKFLOW_CONCLUSION == 'success' env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GHACTIONS_TOKEN }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} asset_path: Tautulli-macos-installer/Tautulli-macos-${{ steps.get_version.outputs.RELEASE_VERSION }}-x64.pkg From 6181aa178ddf144cbbd4159626933106b70a74be Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Thu, 22 Dec 2022 12:01:52 -0800 Subject: [PATCH 128/583] Fix publish installer changelog sed command --- .github/workflows/publish-installers.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-installers.yml b/.github/workflows/publish-installers.yml index 3b0911ee..3b271a1b 100644 --- a/.github/workflows/publish-installers.yml +++ b/.github/workflows/publish-installers.yml @@ -118,7 +118,7 @@ jobs: id: get_changelog run: | CHANGELOG="$( sed -n '/^## /{p; :loop n; p; /^## /q; b loop}' CHANGELOG.md \ - | sed '$d' | sed '$d' | sed '$d' | sed ':a;N;$!ba;s/\n/%0A/g' )" + | sed '$d' | sed '$d' | sed '$d' )" echo "CHANGELOG=${CHANGELOG}" >> $GITHUB_OUTPUT - name: Create Release From b0ba73cf46d1ec80d5dd707b6265e0bb19680176 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Thu, 22 Dec 2022 13:52:11 -0800 Subject: [PATCH 129/583] Fix no started parameter for recently added notifications * Fixes #1940 --- plexpy/notification_handler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plexpy/notification_handler.py b/plexpy/notification_handler.py index 7c16de00..57f5ff83 100644 --- a/plexpy/notification_handler.py +++ b/plexpy/notification_handler.py @@ -985,8 +985,8 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m 'product': notify_params['product'], 'player': notify_params['player'], 'ip_address': notify_params.get('ip_address', 'N/A'), - 'started_datestamp': arrow.get(notify_params['started']).format(date_format), - 'started_timestamp': arrow.get(notify_params['started']).format(time_format), + 'started_datestamp': arrow.get(notify_params['started']).format(date_format) if notify_params['started'] else '', + 'started_timestamp': arrow.get(notify_params['started']).format(time_format) if notify_params['started'] else '', 'started_unixtime': notify_params['started'], 'stopped_datestamp': arrow.get(notify_params['stopped']).format(date_format) if notify_params['stopped'] else '', 'stopped_timestamp': arrow.get(notify_params['stopped']).format(time_format) if notify_params['stopped'] else '', From 1e58847bcd945a49fe01e9ebc95e57ea6a4642bb Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Thu, 22 Dec 2022 16:24:51 -0800 Subject: [PATCH 130/583] Update snap to core22 * Fixes #1941 --- snap/snapcraft.yaml | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 82171392..184f09d5 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -7,7 +7,7 @@ description: > The only thing missing is "why they watched it", but who am I to question your 42 plays of Frozen. All statistics are presented in a nice and clean interface with many tables and graphs, which makes it easy to brag about your server to everyone else. -base: core18 +base: core22 confinement: strict architectures: @@ -20,12 +20,18 @@ parts: plugin: dump source: . stage-packages: - - python3.8 + - python3 - python3-openssl - python3-pycryptodome - python3-setuptools + - python3-pkg-resources build-packages: - git + - python3 + - python3-openssl + - python3-pycryptodome + - python3-setuptools + - python3-pkg-resources override-pull: | snapcraftctl pull git config --global --add safe.directory /data/parts/tautulli/src @@ -52,15 +58,14 @@ parts: echo $COMMIT > version.txt snapcraftctl set-version "$VERSION" snapcraftctl set-grade "$GRADE" + override-prime: | + snapcraftctl prime + mkdir -p usr/bin + ln -s /usr/bin/python3 usr/bin/python3 apps: tautulli: - command: > - usr/bin/python3 $SNAP/Tautulli.py - --datadir $SNAP_USER_COMMON/Tautulli - --config $SNAP_USER_COMMON/Tautulli/config.ini - --quiet - --nolaunch + command: usr/bin/python3 $SNAP/Tautulli.py --datadir $SNAP_USER_COMMON/Tautulli --config $SNAP_USER_COMMON/Tautulli/config.ini --quiet --nolaunch daemon: simple restart-condition: on-abnormal restart-delay: 5s @@ -70,4 +75,5 @@ apps: - network-bind - removable-media environment: + PYTHONPATH: $SNAP/usr/lib/python3/dist-packages:$PYTHONPATH TAUTULLI_SNAP: "True" From 2a0e07aca518dcb97cbcbb5ee96076befeadf923 Mon Sep 17 00:00:00 2001 From: James Tufarelli Date: Thu, 22 Dec 2022 17:28:09 -0800 Subject: [PATCH 131/583] Fix update check failing when `git` is not found (#1943) * Update versioncheck to cast commit_hash to string After the most recent v2.11.0 update, Tautulli would not start until I made this change to the codebase. * Update plexpy/versioncheck.py Co-authored-by: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Co-authored-by: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> --- plexpy/versioncheck.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plexpy/versioncheck.py b/plexpy/versioncheck.py index 33f2b917..2f07d7ae 100644 --- a/plexpy/versioncheck.py +++ b/plexpy/versioncheck.py @@ -101,9 +101,9 @@ def get_version(): else: cur_commit_hash = str(output) - if not re.match('^[a-z0-9]+$', cur_commit_hash): - logger.error('Output does not look like a hash, not using it.') - cur_commit_hash = None + if not re.match('^[a-z0-9]+$', cur_commit_hash): + logger.error('Output does not look like a hash, not using it.') + cur_commit_hash = None if plexpy.CONFIG.DO_NOT_OVERRIDE_GIT_BRANCH and plexpy.CONFIG.GIT_BRANCH: remote_name = None From 7386ca1c429cd4444587a9f7d5fc6e4f1c0a4f2d Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Thu, 22 Dec 2022 17:05:23 -0800 Subject: [PATCH 132/583] Add backports.cached-property==1.0.2 --- lib/backports/cached_property/__init__.py | 94 ++++++++++++++++++++++ lib/backports/cached_property/__init__.pyi | 24 ++++++ lib/backports/cached_property/_version.py | 5 ++ lib/backports/cached_property/py.typed | 1 + 4 files changed, 124 insertions(+) create mode 100644 lib/backports/cached_property/__init__.py create mode 100644 lib/backports/cached_property/__init__.pyi create mode 100644 lib/backports/cached_property/_version.py create mode 100644 lib/backports/cached_property/py.typed diff --git a/lib/backports/cached_property/__init__.py b/lib/backports/cached_property/__init__.py new file mode 100644 index 00000000..80f1f0b7 --- /dev/null +++ b/lib/backports/cached_property/__init__.py @@ -0,0 +1,94 @@ +"""Backport of python 3.8 functools.cached_property. + +cached_property() - computed once per instance, cached as attribute +""" + +__all__ = ("cached_property",) + +# Standard Library +from sys import version_info +try: + # Local Implementation + from ._version import version as __version__ +except ImportError: + pass + +if version_info >= (3, 8): + # Standard Library + from functools import cached_property # pylint: disable=no-name-in-module +else: + # Standard Library + from threading import RLock + from typing import Any + from typing import Callable + from typing import Optional + from typing import Type + from typing import TypeVar + + _NOT_FOUND = object() + _T = TypeVar("_T") + _S = TypeVar("_S") + + # noinspection PyPep8Naming + class cached_property: # NOSONAR # pylint: disable=invalid-name # noqa: N801 + """Cached property implementation. + + Transform a method of a class into a property whose value is computed once + and then cached as a normal attribute for the life of the instance. + Similar to property(), with the addition of caching. + Useful for expensive computed properties of instances + that are otherwise effectively immutable. + """ + + def __init__(self, func: Callable[[Any], _T]) -> None: + """Cached property implementation.""" + self.func = func + self.attrname: Optional[str] = None + self.__doc__ = func.__doc__ + self.lock = RLock() + + def __set_name__(self, owner: Type[Any], name: str) -> None: + """Assign attribute name and owner.""" + if self.attrname is None: + self.attrname = name + elif name != self.attrname: + raise TypeError( + "Cannot assign the same cached_property to two different names " + f"({self.attrname!r} and {name!r})." + ) + + def __get__(self, instance: Optional[_S], owner: Optional[Type[Any]] = None) -> Any: + """Property-like getter implementation. + + :return: property instance if requested on class or value/cached value if requested on instance. + :rtype: Union[cached_property[_T], _T] + :raises TypeError: call without calling __set_name__ or no '__dict__' attribute + """ + if instance is None: + return self + if self.attrname is None: + raise TypeError("Cannot use cached_property instance without calling __set_name__ on it.") + try: + cache = instance.__dict__ + except AttributeError: # not all objects have __dict__ (e.g. class defines slots) + msg = ( + f"No '__dict__' attribute on {type(instance).__name__!r} " + f"instance to cache {self.attrname!r} property." + ) + raise TypeError(msg) from None + val = cache.get(self.attrname, _NOT_FOUND) + if val is _NOT_FOUND: + with self.lock: + # check if another thread filled cache while we awaited lock + val = cache.get(self.attrname, _NOT_FOUND) + if val is _NOT_FOUND: + val = self.func(instance) + try: + cache[self.attrname] = val + except TypeError: + msg = ( + f"The '__dict__' attribute on {type(instance).__name__!r} instance " + f"does not support item assignment for caching {self.attrname!r} property." + ) + raise TypeError(msg) from None + return val diff --git a/lib/backports/cached_property/__init__.pyi b/lib/backports/cached_property/__init__.pyi new file mode 100644 index 00000000..13e619ba --- /dev/null +++ b/lib/backports/cached_property/__init__.pyi @@ -0,0 +1,24 @@ +# Standard Library +from threading import RLock +from typing import Any +from typing import Callable +from typing import Generic +from typing import Optional +from typing import Type +from typing import TypeVar +from typing import overload + +_T = TypeVar("_T") +_S = TypeVar("_S") + +# noinspection PyPep8Naming +class cached_property(Generic[_T]): + func: Callable[[Any], _T] + attrname: Optional[str] + lock: RLock + def __init__(self, func: Callable[[Any], _T]) -> None: ... + @overload + def __get__(self, instance: None, owner: Optional[Type[Any]] = ...) -> cached_property[_T]: ... + @overload + def __get__(self, instance: _S, owner: Optional[Type[Any]] = ...) -> _T: ... + def __set_name__(self, owner: Type[Any], name: str) -> None: ... diff --git a/lib/backports/cached_property/_version.py b/lib/backports/cached_property/_version.py new file mode 100644 index 00000000..87c708f5 --- /dev/null +++ b/lib/backports/cached_property/_version.py @@ -0,0 +1,5 @@ +# coding: utf-8 +# file generated by setuptools_scm +# don't change, don't track in version control +version = '1.0.2' +version_tuple = (1, 0, 2) diff --git a/lib/backports/cached_property/py.typed b/lib/backports/cached_property/py.typed new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/lib/backports/cached_property/py.typed @@ -0,0 +1 @@ + From d9343730da97192e1099d5443440eb2125fb81b0 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Thu, 22 Dec 2022 17:50:24 -0800 Subject: [PATCH 133/583] Downgrade snap to core20 --- snap/snapcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 184f09d5..1220971e 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -7,7 +7,7 @@ description: > The only thing missing is "why they watched it", but who am I to question your 42 plays of Frozen. All statistics are presented in a nice and clean interface with many tables and graphs, which makes it easy to brag about your server to everyone else. -base: core22 +base: core20 confinement: strict architectures: From 3c996f01a9fe15466978781022fdbb00d1e189a9 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Thu, 22 Dec 2022 17:58:38 -0800 Subject: [PATCH 134/583] v2.11.1 --- CHANGELOG.md | 12 ++++++++++++ plexpy/version.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89f15eb6..79b3546a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## v2.11.1 (2022-12-22) + +* Activity: + * Fix: Use source language instead of stream language on activity cards. +* Notifications: + * Fix: Blank start time notification parameters causing recently added notifications to fail. (#1940) +* Other: + * Fix: Tautulli failing to start when using python 3.7. + * Fix: Snap install failing to start. (#1941) + * Fix: Update check crashing when git is missing. (#1943) (Thanks @Minituff) + + ## v2.11.0 (2022-12-22) * Activity: diff --git a/plexpy/version.py b/plexpy/version.py index 57d3f6ae..ec5d5f61 100644 --- a/plexpy/version.py +++ b/plexpy/version.py @@ -18,4 +18,4 @@ from __future__ import unicode_literals PLEXPY_BRANCH = "master" -PLEXPY_RELEASE_VERSION = "v2.11.0" \ No newline at end of file +PLEXPY_RELEASE_VERSION = "v2.11.1" \ No newline at end of file From 2f6869ed2ad55f50b364e2fb9d63ca5b791ed6cd Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Fri, 23 Dec 2022 11:10:39 -0800 Subject: [PATCH 135/583] Update CI badges in README * Ref: badges/shields#8671 [skip ci] --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index fa35aab8..d38f69e1 100644 --- a/README.md +++ b/README.md @@ -57,24 +57,24 @@ Read the [Installation Guides][Installation] for instructions on how to install [badge-release-nightly-last-commit]: https://img.shields.io/github/last-commit/Tautulli/Tautulli/nightly?style=flat-square&color=blue [badge-release-nightly-commits]: https://img.shields.io/github/commits-since/Tautulli/Tautulli/latest/nightly?style=flat-square&color=blue [badge-docker-master]: https://img.shields.io/badge/docker-latest-blue?style=flat-square -[badge-docker-master-ci]: https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Docker/master?style=flat-square +[badge-docker-master-ci]: https://img.shields.io/github/actions/workflow/status/Tautulli/Tautulli/.github/workflows/publish-docker.yml?style=flat-square&branch=master [badge-docker-beta]: https://img.shields.io/badge/docker-beta-blue?style=flat-square -[badge-docker-beta-ci]: https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Docker/beta?style=flat-square +[badge-docker-beta-ci]: https://img.shields.io/github/actions/workflow/status/Tautulli/Tautulli/.github/workflows/publish-docker.yml?style=flat-square&branch=beta [badge-docker-nightly]: https://img.shields.io/badge/docker-nightly-blue?style=flat-square -[badge-docker-nightly-ci]: https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Docker/nightly?style=flat-square +[badge-docker-nightly-ci]: https://img.shields.io/github/actions/workflow/status/Tautulli/Tautulli/.github/workflows/publish-docker.yml?style=flat-square&branch=nightly [badge-snap-master]: https://img.shields.io/badge/snap-stable-blue?style=flat-square -[badge-snap-master-ci]: https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Snap/master?style=flat-square +[badge-snap-master-ci]: https://img.shields.io/github/actions/workflow/status/Tautulli/Tautulli/.github/workflows/publish-snap.yml?style=flat-square&branch=master [badge-snap-beta]: https://img.shields.io/badge/snap-beta-blue?style=flat-square -[badge-snap-beta-ci]: https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Snap/beta?style=flat-square +[badge-snap-beta-ci]: https://img.shields.io/github/actions/workflow/status/Tautulli/Tautulli/.github/workflows/publish-snap.yml?style=flat-square&branch=beta [badge-snap-nightly]: https://img.shields.io/badge/snap-edge-blue?style=flat-square -[badge-snap-nightly-ci]: https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Snap/nightly?style=flat-square +[badge-snap-nightly-ci]: https://img.shields.io/github/actions/workflow/status/Tautulli/Tautulli/.github/workflows/publish-snap.yml?style=flat-square&branch=nightly [badge-installer-master-win]: https://img.shields.io/github/v/release/Tautulli/Tautulli?label=windows&style=flat-square [badge-installer-master-macos]: https://img.shields.io/github/v/release/Tautulli/Tautulli?label=macos&style=flat-square -[badge-installer-master-ci]: https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Installers/master?style=flat-square +[badge-installer-master-ci]: https://img.shields.io/github/actions/workflow/status/Tautulli/Tautulli/.github/workflows/publish-installers.yml?style=flat-square&branch=master [badge-installer-beta-win]: https://img.shields.io/github/v/release/Tautulli/Tautulli?label=windows&include_prereleases&style=flat-square [badge-installer-beta-macos]: https://img.shields.io/github/v/release/Tautulli/Tautulli?label=macos&include_prereleases&style=flat-square -[badge-installer-beta-ci]: https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Installers/beta?style=flat-square -[badge-installer-nightly-ci]: https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Installers/nightly?style=flat-square +[badge-installer-beta-ci]: https://img.shields.io/github/actions/workflow/status/Tautulli/Tautulli/.github/workflows/publish-installers.yml?style=flat-square&branch=beta +[badge-installer-nightly-ci]: https://img.shields.io/github/actions/workflow/status/Tautulli/Tautulli/.github/workflows/publish-installers.yml?style=flat-square&branch=nightly ## Support From 460a463be11386e2777f488f60e6d437672f283c Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Thu, 19 Jan 2023 13:54:55 -0800 Subject: [PATCH 136/583] Update notification parameter description for Plex API image paths --- plexpy/common.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plexpy/common.py b/plexpy/common.py index 33b4cc00..3e56a4c9 100644 --- a/plexpy/common.py +++ b/plexpy/common.py @@ -600,11 +600,11 @@ NOTIFICATION_PARAMETERS = [ {'name': 'Rating Key', 'type': 'int', 'value': 'rating_key', 'description': 'The unique identifier for the movie, episode, or track.'}, {'name': 'Parent Rating Key', 'type': 'int', 'value': 'parent_rating_key', 'description': 'The unique identifier for the season or album.'}, {'name': 'Grandparent Rating Key', 'type': 'int', 'value': 'grandparent_rating_key', 'description': 'The unique identifier for the TV show or artist.'}, - {'name': 'Art', 'type': 'str', 'value': 'art', 'description': 'The Plex background art for the media.'}, - {'name': 'Thumb', 'type': 'str', 'value': 'thumb', 'description': 'The Plex thumbnail for the movie or episode.'}, - {'name': 'Parent Thumb', 'type': 'str', 'value': 'parent_thumb', 'description': 'The Plex thumbnail for the season or album.'}, - {'name': 'Grandparent Thumb', 'type': 'str', 'value': 'grandparent_thumb', 'description': 'The Plex thumbnail for the TV show or artist.'}, - {'name': 'Poster Thumb', 'type': 'str', 'value': 'poster_thumb', 'description': 'The Plex thumbnail for the poster image.'}, + {'name': 'Art', 'type': 'str', 'value': 'art', 'description': 'The Plex API path to the background art for the media.'}, + {'name': 'Thumb', 'type': 'str', 'value': 'thumb', 'description': 'The Plex API path to the thumbnail for the movie or episode.'}, + {'name': 'Parent Thumb', 'type': 'str', 'value': 'parent_thumb', 'description': 'The Plex API path to the thumbnail for the season or album.'}, + {'name': 'Grandparent Thumb', 'type': 'str', 'value': 'grandparent_thumb', 'description': 'The Plex API path to the thumbnail for the TV show or artist.'}, + {'name': 'Poster Thumb', 'type': 'str', 'value': 'poster_thumb', 'description': 'The Plex API path to the thumbnail for the poster image.'}, {'name': 'Poster Title', 'type': 'str', 'value': 'poster_title', 'description': 'The title for the poster image.'}, {'name': 'Indexes', 'type': 'int', 'value': 'indexes', 'description': 'If the media has video preview thumbnails.', 'example': '0 or 1'}, ] From 548264d51a905bd80d418e0bdb34266b6da32a8c Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Fri, 20 Jan 2023 17:05:30 -0800 Subject: [PATCH 137/583] Add prvenance: false to docker/build-push-action Ref: docker/buildx#1533 --- .github/workflows/publish-docker.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml index 125cae51..37885c2f 100644 --- a/.github/workflows/publish-docker.yml +++ b/.github/workflows/publish-docker.yml @@ -87,6 +87,7 @@ jobs: ghcr.io/${{ steps.prepare.outputs.docker_image }}:${{ steps.prepare.outputs.tag }} cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,dest=/tmp/.buildx-cache + provenance: false discord: name: Discord Notification From 1e02c26a9ad1fa3c36040e9a6c3cd58179739d0c Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Fri, 20 Jan 2023 17:09:03 -0800 Subject: [PATCH 138/583] Relax workflow action versions --- .github/workflows/publish-docker.yml | 4 ++-- .github/workflows/publish-installers.yml | 4 ++-- .github/workflows/publish-snap.yml | 2 +- .github/workflows/pull-requests.yml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml index 37885c2f..85ce6266 100644 --- a/.github/workflows/publish-docker.yml +++ b/.github/workflows/publish-docker.yml @@ -13,7 +13,7 @@ jobs: if: ${{ !contains(github.event.head_commit.message, '[skip ci]') }} steps: - name: Checkout Code - uses: actions/checkout@v3.2.0 + uses: actions/checkout@v3 - name: Prepare id: prepare @@ -47,7 +47,7 @@ jobs: version: latest - name: Cache Docker Layers - uses: actions/cache@v3.2.0 + uses: actions/cache@v3 with: path: /tmp/.buildx-cache key: ${{ runner.os }}-buildx-${{ github.sha }} diff --git a/.github/workflows/publish-installers.yml b/.github/workflows/publish-installers.yml index 3b271a1b..31ad0f81 100644 --- a/.github/workflows/publish-installers.yml +++ b/.github/workflows/publish-installers.yml @@ -24,7 +24,7 @@ jobs: steps: - name: Checkout Code - uses: actions/checkout@v3.2.0 + uses: actions/checkout@v3 - name: Set Release Version id: get_version @@ -52,7 +52,7 @@ jobs: echo $GITHUB_SHA > version.txt - name: Set Up Python - uses: actions/setup-python@v4.4.0 + uses: actions/setup-python@v4 with: python-version: '3.9' cache: pip diff --git a/.github/workflows/publish-snap.yml b/.github/workflows/publish-snap.yml index 7ad8fe95..9df4d2fd 100644 --- a/.github/workflows/publish-snap.yml +++ b/.github/workflows/publish-snap.yml @@ -20,7 +20,7 @@ jobs: - armhf steps: - name: Checkout Code - uses: actions/checkout@v3.2.0 + uses: actions/checkout@v3 - name: Prepare id: prepare diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml index d7c8e45d..58cb4ee4 100644 --- a/.github/workflows/pull-requests.yml +++ b/.github/workflows/pull-requests.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Code - uses: actions/checkout@v3.2.0 + uses: actions/checkout@v3 - name: Comment on Pull Request uses: mshick/add-pr-comment@v2 From 0959f28e957ef119e5abbd083796c650862501c4 Mon Sep 17 00:00:00 2001 From: herby2212 <12448284+herby2212@users.noreply.github.com> Date: Sat, 28 Jan 2023 23:09:11 +0100 Subject: [PATCH 139/583] Add edition detail field for movie info (#1957) * edition addition for movie info * swap position to match plex order --- data/interfaces/default/info.html | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/data/interfaces/default/info.html b/data/interfaces/default/info.html index 6d8b3aaf..a7acb11b 100644 --- a/data/interfaces/default/info.html +++ b/data/interfaces/default/info.html @@ -14,6 +14,7 @@ rating_key Returns the unique identifier for the media item. media_type Returns the type of media. Either 'movie', 'show', 'season', 'episode', 'artist', 'album', or 'track'. art Returns the location of the item's artwork title Returns the name of the movie, show, episode, artist, album, or track. +edition_title Returns the edition title of a movie. duration Returns the standard runtime of the media. content_rating Returns the age rating for the media. summary Returns a brief description of the media plot. @@ -390,6 +391,11 @@ DOCUMENTATION :: END Runtime ${data['duration']} % endif
    + % if data['edition_title']: +
    + Edition ${data['edition_title']} +
    + % endif
    % if data['content_rating']: Rated ${data['content_rating']} From c51ee673e83e4ed2fe143f3ef8466575070e31ff Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Sat, 28 Jan 2023 13:29:12 -0800 Subject: [PATCH 140/583] Add support for Telegram group topics * Closes #1980 --- plexpy/notifiers.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/plexpy/notifiers.py b/plexpy/notifiers.py index fbd48c4b..75f810d8 100644 --- a/plexpy/notifiers.py +++ b/plexpy/notifiers.py @@ -3962,7 +3962,10 @@ class TELEGRAM(Notifier): } def agent_notify(self, subject='', body='', action='', **kwargs): - data = {'chat_id': self.config['chat_id']} + chat_id, *message_thread_id = self.config['chat_id'].split('/') + data = {'chat_id': chat_id} + if message_thread_id: + data['message_thread_id'] = message_thread_id[0] if self.config['incl_subject']: text = subject + '\r\n' + body @@ -4032,7 +4035,8 @@ class TELEGRAM(Notifier): 'description': 'Your Telegram Chat ID, Group ID, Channel ID or @channelusername. ' 'Contact @myidbot' - ' on Telegram to get an ID.', + ' on Telegram to get an ID. ' + 'For a group topic, append /topicID to the group ID.', 'input_type': 'text' }, {'label': 'Include Subject Line', From b0a55df8620e37795e6a5e202fdd8b10e4d5338a Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Sat, 28 Jan 2023 13:43:42 -0800 Subject: [PATCH 141/583] Add anidb_id and anidb_url notification parameters * Closes #1973 --- plexpy/common.py | 2 ++ plexpy/notification_handler.py | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/plexpy/common.py b/plexpy/common.py index 3e56a4c9..ad91ee87 100644 --- a/plexpy/common.py +++ b/plexpy/common.py @@ -553,6 +553,8 @@ NOTIFICATION_PARAMETERS = [ {'name': 'TVmaze URL', 'type': 'str', 'value': 'tvmaze_url', 'description': 'The TVmaze URL for the TV show.'}, {'name': 'MusicBrainz ID', 'type': 'str', 'value': 'musicbrainz_id', 'description': 'The MusicBrainz ID for the artist, album, or track.', 'example': 'e.g. b670dfcf-9824-4309-a57e-03595aaba286'}, {'name': 'MusicBrainz URL', 'type': 'str', 'value': 'musicbrainz_url', 'description': 'The MusicBrainz URL for the artist, album, or track.'}, + {'name': 'AniDB ID', 'type': 'str', 'value': 'anidb_id', 'description': 'The AniDB ID for the Anime', 'example': 'e.g. 69', 'help_text': 'TV show library agent must be HAMA'}, + {'name': 'AniDB URL', 'type': 'str', 'value': 'anidb_url', 'description': 'The AniDB URL for the Anime', 'help_text': 'TV show library agent must be HAMA'}, {'name': 'Last.fm URL', 'type': 'str', 'value': 'lastfm_url', 'description': 'The Last.fm URL for the album.', 'help_text': 'Music library agent must be Last.fm'}, {'name': 'Trakt.tv URL', 'type': 'str', 'value': 'trakt_url', 'description': 'The trakt.tv URL for the movie or TV show.'}, {'name': 'Container', 'type': 'str', 'value': 'container', 'description': 'The media container of the original media.'}, diff --git a/plexpy/notification_handler.py b/plexpy/notification_handler.py index 57f5ff83..6b55c5b7 100644 --- a/plexpy/notification_handler.py +++ b/plexpy/notification_handler.py @@ -715,6 +715,10 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m else: notify_params['musicbrainz_url'] = 'https://musicbrainz.org/track/' + notify_params['musicbrainz_id'] + if 'hama://' in notify_params['guid']: + notify_params['anidb_id'] = notify_params['guid'].split('hama://')[1].split('/')[0].split('?')[0].split('-')[1] + notify_params['anidb_url'] = 'https://anidb.net/anime/' + notify_params['anidb_id'] + # Get TheMovieDB info (for movies and tv only) if plexpy.CONFIG.THEMOVIEDB_LOOKUP and notify_params['media_type'] in ('movie', 'show', 'season', 'episode'): if notify_params.get('themoviedb_id'): @@ -1142,6 +1146,8 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m 'tvmaze_url': notify_params['tvmaze_url'], 'musicbrainz_id': notify_params['musicbrainz_id'], 'musicbrainz_url': notify_params['musicbrainz_url'], + 'anidb_id': notify_params['anidb_id'], + 'anidb_url': notify_params['anidb_url'], 'lastfm_url': notify_params['lastfm_url'], 'trakt_url': notify_params['trakt_url'], 'container': notify_params['container'], From b6ff45138f29763b92fa59646c7499cfd3d28c41 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Sat, 28 Jan 2023 14:04:45 -0800 Subject: [PATCH 142/583] Add section_id and user_id parameters to get_home_stats API command * Closes #1944 --- plexpy/datafactory.py | 49 ++++++++++++++++++++++++------------------- plexpy/webserve.py | 9 ++++++-- 2 files changed, 35 insertions(+), 23 deletions(-) diff --git a/plexpy/datafactory.py b/plexpy/datafactory.py index 700c14c6..cf55a2c0 100644 --- a/plexpy/datafactory.py +++ b/plexpy/datafactory.py @@ -349,7 +349,8 @@ class DataFactory(object): return dict def get_home_stats(self, grouping=None, time_range=30, stats_type='plays', - stats_start=0, stats_count=10, stat_id='', stats_cards=None): + stats_start=0, stats_count=10, stat_id='', stats_cards=None, + section_id=None, user_id=None): monitor_db = database.MonitorDatabase() time_range = helpers.cast_to_int(time_range) @@ -364,6 +365,12 @@ class DataFactory(object): if stats_cards is None: stats_cards = plexpy.CONFIG.HOME_STATS_CARDS + where_id = '' + if section_id: + where_id += 'AND session_history.section_id = %s ' % section_id + if user_id: + where_id += 'AND session_history.user_id = %s ' % user_id + movie_watched_percent = plexpy.CONFIG.MOVIE_WATCHED_PERCENT tv_watched_percent = plexpy.CONFIG.TV_WATCHED_PERCENT music_watched_percent = plexpy.CONFIG.MUSIC_WATCHED_PERCENT @@ -385,12 +392,12 @@ class DataFactory(object): ' AS d ' \ ' FROM session_history ' \ ' WHERE session_history.stopped >= %s ' \ - ' AND session_history.media_type = "movie" ' \ + ' AND session_history.media_type = "movie" %s ' \ ' GROUP BY %s) AS sh ' \ 'JOIN session_history_metadata AS shm ON shm.id = sh.id ' \ 'GROUP BY shm.full_title, shm.year ' \ 'ORDER BY %s DESC, sh.started DESC ' \ - 'LIMIT %s OFFSET %s ' % (timestamp, group_by, sort_type, stats_count, stats_start) + 'LIMIT %s OFFSET %s ' % (timestamp, where_id, group_by, sort_type, stats_count, stats_start) result = monitor_db.select(query) except Exception as e: logger.warn("Tautulli DataFactory :: Unable to execute database query for get_home_stats: top_movies: %s." % e) @@ -438,12 +445,12 @@ class DataFactory(object): ' AS d ' \ ' FROM session_history ' \ ' WHERE session_history.stopped >= %s ' \ - ' AND session_history.media_type = "movie" ' \ + ' AND session_history.media_type = "movie" %s ' \ ' GROUP BY %s) AS sh ' \ 'JOIN session_history_metadata AS shm ON shm.id = sh.id ' \ 'GROUP BY shm.full_title, shm.year ' \ 'ORDER BY users_watched DESC, %s DESC, sh.started DESC ' \ - 'LIMIT %s OFFSET %s ' % (timestamp, group_by, sort_type, stats_count, stats_start) + 'LIMIT %s OFFSET %s ' % (timestamp, where_id, group_by, sort_type, stats_count, stats_start) result = monitor_db.select(query) except Exception as e: logger.warn("Tautulli DataFactory :: Unable to execute database query for get_home_stats: popular_movies: %s." % e) @@ -490,12 +497,12 @@ class DataFactory(object): ' AS d ' \ ' FROM session_history ' \ ' WHERE session_history.stopped >= %s ' \ - ' AND session_history.media_type = "episode" ' \ + ' AND session_history.media_type = "episode" %s ' \ ' GROUP BY %s) AS sh ' \ 'JOIN session_history_metadata AS shm ON shm.id = sh.id ' \ 'GROUP BY shm.grandparent_title ' \ 'ORDER BY %s DESC, sh.started DESC ' \ - 'LIMIT %s OFFSET %s ' % (timestamp, group_by, sort_type, stats_count, stats_start) + 'LIMIT %s OFFSET %s ' % (timestamp, where_id, group_by, sort_type, stats_count, stats_start) result = monitor_db.select(query) except Exception as e: logger.warn("Tautulli DataFactory :: Unable to execute database query for get_home_stats: top_tv: %s." % e) @@ -545,12 +552,12 @@ class DataFactory(object): ' AS d ' \ ' FROM session_history ' \ ' WHERE session_history.stopped >= %s ' \ - ' AND session_history.media_type = "episode" ' \ + ' AND session_history.media_type = "episode" %s ' \ ' GROUP BY %s) AS sh ' \ 'JOIN session_history_metadata AS shm ON shm.id = sh.id ' \ 'GROUP BY shm.grandparent_title ' \ 'ORDER BY users_watched DESC, %s DESC, sh.started DESC ' \ - 'LIMIT %s OFFSET %s ' % (timestamp, group_by, sort_type, stats_count, stats_start) + 'LIMIT %s OFFSET %s ' % (timestamp, where_id, group_by, sort_type, stats_count, stats_start) result = monitor_db.select(query) except Exception as e: logger.warn("Tautulli DataFactory :: Unable to execute database query for get_home_stats: popular_tv: %s." % e) @@ -596,12 +603,12 @@ class DataFactory(object): ' AS d ' \ ' FROM session_history ' \ ' WHERE session_history.stopped >= %s ' \ - ' AND session_history.media_type = "track" ' \ + ' AND session_history.media_type = "track" %s ' \ ' GROUP BY %s) AS sh ' \ 'JOIN session_history_metadata AS shm ON shm.id = sh.id ' \ 'GROUP BY shm.original_title, shm.grandparent_title ' \ 'ORDER BY %s DESC, sh.started DESC ' \ - 'LIMIT %s OFFSET %s ' % (timestamp, group_by, sort_type, stats_count, stats_start) + 'LIMIT %s OFFSET %s ' % (timestamp, where_id, group_by, sort_type, stats_count, stats_start) result = monitor_db.select(query) except Exception as e: logger.warn("Tautulli DataFactory :: Unable to execute database query for get_home_stats: top_music: %s." % e) @@ -650,12 +657,12 @@ class DataFactory(object): ' AS d ' \ ' FROM session_history ' \ ' WHERE session_history.stopped >= %s ' \ - ' AND session_history.media_type = "track" ' \ + ' AND session_history.media_type = "track" %s ' \ ' GROUP BY %s) AS sh ' \ 'JOIN session_history_metadata AS shm ON shm.id = sh.id ' \ 'GROUP BY shm.original_title, shm.grandparent_title ' \ 'ORDER BY users_watched DESC, %s DESC, sh.started DESC ' \ - 'LIMIT %s OFFSET %s ' % (timestamp, group_by, sort_type, stats_count, stats_start) + 'LIMIT %s OFFSET %s ' % (timestamp, where_id, group_by, sort_type, stats_count, stats_start) result = monitor_db.select(query) except Exception as e: logger.warn("Tautulli DataFactory :: Unable to execute database query for get_home_stats: popular_music: %s." % e) @@ -706,14 +713,14 @@ class DataFactory(object): ' (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) ' \ ' AS d ' \ ' FROM session_history ' \ - ' WHERE session_history.stopped >= %s ' \ + ' WHERE session_history.stopped >= %s %s ' \ ' GROUP BY %s) AS sh ' \ 'JOIN session_history_metadata AS shm ON shm.id = sh.id ' \ 'LEFT OUTER JOIN (SELECT * FROM library_sections WHERE deleted_section = 0) ' \ ' AS ls ON sh.section_id = ls.section_id ' \ 'GROUP BY sh.section_id ' \ 'ORDER BY %s DESC, sh.started DESC ' \ - 'LIMIT %s OFFSET %s ' % (timestamp, group_by, sort_type, stats_count, stats_start) + 'LIMIT %s OFFSET %s ' % (timestamp, where_id, group_by, sort_type, stats_count, stats_start) result = monitor_db.select(query) except Exception as e: logger.warn("Tautulli DataFactory :: Unable to execute database query for get_home_stats: top_libraries: %s." % e) @@ -793,13 +800,13 @@ class DataFactory(object): ' (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) ' \ ' AS d ' \ ' FROM session_history ' \ - ' WHERE session_history.stopped >= %s ' \ + ' WHERE session_history.stopped >= %s %s ' \ ' GROUP BY %s) AS sh ' \ 'JOIN session_history_metadata AS shm ON shm.id = sh.id ' \ 'LEFT OUTER JOIN users AS u ON sh.user_id = u.user_id ' \ 'GROUP BY sh.user_id ' \ 'ORDER BY %s DESC, sh.started DESC ' \ - 'LIMIT %s OFFSET %s ' % (timestamp, group_by, sort_type, stats_count, stats_start) + 'LIMIT %s OFFSET %s ' % (timestamp, where_id, group_by, sort_type, stats_count, stats_start) result = monitor_db.select(query) except Exception as e: logger.warn("Tautulli DataFactory :: Unable to execute database query for get_home_stats: top_users: %s." % e) @@ -862,11 +869,11 @@ class DataFactory(object): ' (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) ' \ ' AS d ' \ ' FROM session_history ' \ - ' WHERE session_history.stopped >= %s ' \ + ' WHERE session_history.stopped >= %s %s ' \ ' GROUP BY %s) AS sh ' \ 'GROUP BY sh.platform ' \ 'ORDER BY %s DESC, sh.started DESC ' \ - 'LIMIT %s OFFSET %s ' % (timestamp, group_by, sort_type, stats_count, stats_start) + 'LIMIT %s OFFSET %s ' % (timestamp, where_id, group_by, sort_type, stats_count, stats_start) result = monitor_db.select(query) except Exception as e: logger.warn("Tautulli DataFactory :: Unable to execute database query for get_home_stats: top_platforms: %s." % e) @@ -918,7 +925,7 @@ class DataFactory(object): 'FROM (SELECT *, MAX(id) FROM session_history ' \ ' WHERE session_history.stopped >= %s ' \ ' AND (session_history.media_type = "movie" ' \ - ' OR session_history.media_type = "episode") ' \ + ' OR session_history.media_type = "episode") %s ' \ ' GROUP BY %s) AS sh ' \ 'JOIN session_history_metadata AS shm ON shm.id = sh.id ' \ 'LEFT OUTER JOIN users AS u ON sh.user_id = u.user_id ' \ @@ -926,7 +933,7 @@ class DataFactory(object): ' OR sh.media_type == "episode" AND percent_complete >= %s ' \ 'GROUP BY sh.id ' \ 'ORDER BY last_watch DESC ' \ - 'LIMIT %s OFFSET %s' % (timestamp, group_by, movie_watched_percent, tv_watched_percent, + 'LIMIT %s OFFSET %s' % (timestamp, where_id, group_by, movie_watched_percent, tv_watched_percent, stats_count, stats_start) result = monitor_db.select(query) except Exception as e: diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 76c44243..8ad3e664 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -6185,7 +6185,8 @@ class WebInterface(object): @requireAuth(member_of("admin")) @addtoapi() def get_home_stats(self, grouping=None, time_range=30, stats_type='plays', - stats_start=0, stats_count=10, stat_id='', **kwargs): + stats_start=0, stats_count=10, stat_id='', + section_id=None, user_id=None, **kwargs): """ Get the homepage watch statistics. ``` @@ -6201,6 +6202,8 @@ class WebInterface(object): stat_id (str): A single stat to return, 'top_movies', 'popular_movies', 'top_tv', 'popular_tv', 'top_music', 'popular_music', 'top_libraries', 'top_users', 'top_platforms', 'last_watched', 'most_concurrent' + section_id (int): The id of the Plex library section + user_id (int): The id of the Plex user Returns: json: @@ -6282,7 +6285,9 @@ class WebInterface(object): stats_type=stats_type, stats_start=stats_start, stats_count=stats_count, - stat_id=stat_id) + stat_id=stat_id, + section_id=section_id, + user_id=user_id) if result: return result From 5ab9315f1601850c04bff5858429799da0148ee8 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Tue, 14 Feb 2023 18:19:35 -0800 Subject: [PATCH 143/583] Upload notification images directly to Discord --- plexpy/notifiers.py | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/plexpy/notifiers.py b/plexpy/notifiers.py index 75f810d8..3e721515 100644 --- a/plexpy/notifiers.py +++ b/plexpy/notifiers.py @@ -892,6 +892,15 @@ class PrettyMetadata(object): parameters[''] = '' return parameters + def get_image(self): + result = pmsconnect.PmsConnect().get_image(img=self.parameters.get('poster_thumb', '')) + if result and result[0]: + poster_content = result[0] + poster_filename = 'poster_{}.png'.format(self.parameters['rating_key']) + return (poster_filename, poster_content, 'image/png') + + logger.error("Tautulli Notifiers :: Unable to retrieve image for notification.") + class Notifier(object): NAME = '' @@ -1117,10 +1126,16 @@ class DISCORD(Notifier): if self.config['tts']: data['tts'] = True + files = {} + if self.config['incl_card'] and kwargs.get('parameters', {}).get('media_type'): # Grab formatted metadata pretty_metadata = PrettyMetadata(kwargs['parameters']) + image = pretty_metadata.get_image() + if image: + files = {'files[0]': image} + if pretty_metadata.media_type == 'movie': provider = self.config['movie_provider'] elif pretty_metadata.media_type in ('show', 'season', 'episode'): @@ -1150,9 +1165,9 @@ class DISCORD(Notifier): attachment['color'] = helpers.hex_to_int(hex) if self.config['incl_thumbnail']: - attachment['thumbnail'] = {'url': poster_url} + attachment['thumbnail'] = {'url': 'attachment://{}'.format(image[0]) if image else poster_url} else: - attachment['image'] = {'url': poster_url} + attachment['image'] = {'url': 'attachment://{}'.format(image[0]) if image else poster_url} if self.config['incl_description']: attachment['description'] = description[:2045] + (description[2045:] and '...') @@ -1172,10 +1187,13 @@ class DISCORD(Notifier): data['embeds'] = [attachment] - headers = {'Content-type': 'application/json'} params = {'wait': True} - return self.make_request(self.config['hook'], params=params, headers=headers, json=data) + if files: + files['payload_json'] = (None, json.dumps(data), 'application/json') + return self.make_request(self.config['hook'], params=params, files=files) + else: + return self.make_request(self.config['hook'], params=params, json=data) def _return_config_options(self): config_option = [{'label': 'Discord Webhook URL', @@ -1217,10 +1235,7 @@ class DISCORD(Notifier): {'label': 'Include Rich Metadata Info', 'value': self.config['incl_card'], 'name': 'discord_incl_card', - 'description': 'Include an info card with a poster and metadata with the notifications.
    ' - 'Note: Image Hosting ' - 'must be enabled under the 3rd Party APIs settings tab.', + 'description': 'Include an info card with a poster and metadata with the notifications.', 'input_type': 'checkbox' }, {'label': 'Include Summary', From e263f0b8a3a40f92820a6a230f5a377eef817a5c Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Tue, 14 Feb 2023 18:21:11 -0800 Subject: [PATCH 144/583] Refactor notification image upload code --- plexpy/notifiers.py | 57 +++++++++++---------------------------------- 1 file changed, 13 insertions(+), 44 deletions(-) diff --git a/plexpy/notifiers.py b/plexpy/notifiers.py index 3e721515..e3ef6862 100644 --- a/plexpy/notifiers.py +++ b/plexpy/notifiers.py @@ -1807,19 +1807,12 @@ class GROUPME(Notifier): if self.config['incl_poster'] and kwargs.get('parameters'): pretty_metadata = PrettyMetadata(kwargs.get('parameters')) - # Retrieve the poster from Plex - result = pmsconnect.PmsConnect().get_image(img=pretty_metadata.parameters.get('poster_thumb','')) - if result and result[0]: - poster_content = result[0] - else: - poster_content = '' - logger.error("Tautulli Notifiers :: Unable to retrieve image for {name}.".format(name=self.NAME)) - - if poster_content: + image = pretty_metadata.get_image() + if image: headers = {'X-Access-Token': self.config['access_token'], 'Content-Type': 'image/png'} - r = requests.post('https://image.groupme.com/pictures', headers=headers, data=poster_content) + r = requests.post('https://image.groupme.com/pictures', headers=headers, data=image[1]) if r.status_code == 200: logger.info("Tautulli Notifiers :: {name} poster sent.".format(name=self.NAME)) @@ -3042,18 +3035,10 @@ class PUSHBULLET(Notifier): # Grab formatted metadata pretty_metadata = PrettyMetadata(kwargs['parameters']) - # Retrieve the poster from Plex - result = pmsconnect.PmsConnect().get_image(img=pretty_metadata.parameters.get('poster_thumb', '')) - if result and result[0]: - poster_content = result[0] - else: - poster_content = '' - logger.error("Tautulli Notifiers :: Unable to retrieve image for {name}.".format(name=self.NAME)) - - if poster_content: - poster_filename = 'poster_{}.png'.format(pretty_metadata.parameters['rating_key']) - file_json = {'file_name': poster_filename, 'file_type': 'image/png'} - files = {'file': (poster_filename, poster_content, 'image/png')} + image = pretty_metadata.get_image() + if image: + file_json = {'file_name': image[0], 'file_type': image[2]} + files = {'file': image} r = requests.post('https://api.pushbullet.com/v2/upload-request', headers=headers, json=file_json) @@ -3199,17 +3184,9 @@ class PUSHOVER(Notifier): # Grab formatted metadata pretty_metadata = PrettyMetadata(kwargs['parameters']) - # Retrieve the poster from Plex - result = pmsconnect.PmsConnect().get_image(img=pretty_metadata.parameters.get('poster_thumb', '')) - if result and result[0]: - poster_content = result[0] - else: - poster_content = '' - logger.error("Tautulli Notifiers :: Unable to retrieve image for {name}.".format(name=self.NAME)) - - if poster_content: - poster_filename = 'poster_{}.png'.format(pretty_metadata.parameters['rating_key']) - files = {'attachment': (poster_filename, poster_content, 'image/png')} + image = pretty_metadata.get_image() + if image: + files = {'attachment': image} headers = {} return self.make_request('https://api.pushover.net/1/messages.json', headers=headers, data=data, files=files) @@ -3994,17 +3971,9 @@ class TELEGRAM(Notifier): # Grab formatted metadata pretty_metadata = PrettyMetadata(kwargs['parameters']) - # Retrieve the poster from Plex - result = pmsconnect.PmsConnect().get_image(img=pretty_metadata.parameters.get('poster_thumb', '')) - if result and result[0]: - poster_content = result[0] - else: - poster_content = '' - logger.error("Tautulli Notifiers :: Unable to retrieve image for {name}.".format(name=self.NAME)) - - if poster_content: - poster_filename = 'poster_{}.png'.format(pretty_metadata.parameters['rating_key']) - files = {'photo': (poster_filename, poster_content, 'image/png')} + image = pretty_metadata.get_image() + if image: + files = {'photo': image} if len(text) > 1024: data['disable_notification'] = True From 0db9548995303d5669e06d5111ed074a486583dd Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Fri, 20 Jan 2023 17:05:30 -0800 Subject: [PATCH 145/583] Revert "Add prvenance: false to docker/build-push-action" This reverts commit 548264d51a905bd80d418e0bdb34266b6da32a8c. --- .github/workflows/publish-docker.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml index 85ce6266..8cb8267f 100644 --- a/.github/workflows/publish-docker.yml +++ b/.github/workflows/publish-docker.yml @@ -87,7 +87,6 @@ jobs: ghcr.io/${{ steps.prepare.outputs.docker_image }}:${{ steps.prepare.outputs.tag }} cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,dest=/tmp/.buildx-cache - provenance: false discord: name: Discord Notification From a8539b29272995e0cf6ade177923c26119838dc3 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Thu, 16 Feb 2023 11:29:18 -0800 Subject: [PATCH 146/583] Retrieve intro/credits markers for metadata details --- plexpy/pmsconnect.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py index 11142873..3a7a2450 100644 --- a/plexpy/pmsconnect.py +++ b/plexpy/pmsconnect.py @@ -140,7 +140,7 @@ class PmsConnect(object): Output: array """ - uri = '/library/metadata/' + rating_key + uri = '/library/metadata/' + rating_key + '?includeMarkers=1' request = self.request_handler.make_request(uri=uri, request_type='GET', output_format=output_format) @@ -745,6 +745,7 @@ class PmsConnect(object): labels = [] collections = [] guids = [] + markers = [] if metadata_main.getElementsByTagName('Director'): for director in metadata_main.getElementsByTagName('Director'): @@ -774,6 +775,16 @@ class PmsConnect(object): for guid in metadata_main.getElementsByTagName('Guid'): guids.append(helpers.get_xml_attr(guid, 'id')) + if metadata_main.getElementsByTagName('Marker'): + for marker in metadata_main.getElementsByTagName('Marker'): + markers.append({ + 'id': helpers.get_xml_attr(marker, 'id'), + 'type': helpers.get_xml_attr(marker, 'type'), + 'start_time_offset': helpers.cast_to_int(helpers.get_xml_attr(marker, 'startTimeOffset')), + 'end_time_offset': helpers.cast_to_int(helpers.get_xml_attr(marker, 'endTimeOffset')), + 'final': helpers.bool_true(helpers.get_xml_attr(marker, 'final')) + }) + if metadata_type == 'movie': metadata = {'media_type': metadata_type, 'section_id': section_id, @@ -821,6 +832,7 @@ class PmsConnect(object): 'labels': labels, 'collections': collections, 'guids': guids, + 'markers': markers, 'parent_guids': [], 'grandparent_guids': [], 'full_title': helpers.get_xml_attr(metadata_main, 'title'), @@ -880,6 +892,7 @@ class PmsConnect(object): 'labels': labels, 'collections': collections, 'guids': guids, + 'markers': markers, 'parent_guids': [], 'grandparent_guids': [], 'full_title': helpers.get_xml_attr(metadata_main, 'title'), @@ -942,6 +955,7 @@ class PmsConnect(object): 'labels': show_details.get('labels', []), 'collections': show_details.get('collections', []), 'guids': guids, + 'markers': markers, 'parent_guids': show_details.get('guids', []), 'grandparent_guids': [], 'full_title': '{} - {}'.format(helpers.get_xml_attr(metadata_main, 'parentTitle'), @@ -1021,6 +1035,7 @@ class PmsConnect(object): 'labels': show_details.get('labels', []), 'collections': show_details.get('collections', []), 'guids': guids, + 'markers': markers, 'parent_guids': season_details.get('guids', []), 'grandparent_guids': show_details.get('guids', []), 'full_title': '{} - {}'.format(helpers.get_xml_attr(metadata_main, 'grandparentTitle'), @@ -1076,6 +1091,7 @@ class PmsConnect(object): 'labels': labels, 'collections': collections, 'guids': guids, + 'markers': markers, 'parent_guids': [], 'grandparent_guids': [], 'full_title': helpers.get_xml_attr(metadata_main, 'title'), @@ -1132,6 +1148,7 @@ class PmsConnect(object): 'labels': labels, 'collections': collections, 'guids': guids, + 'markers': markers, 'parent_guids': artist_details.get('guids', []), 'grandparent_guids': [], 'full_title': '{} - {}'.format(helpers.get_xml_attr(metadata_main, 'parentTitle'), @@ -1191,6 +1208,7 @@ class PmsConnect(object): 'labels': album_details.get('labels', []), 'collections': album_details.get('collections', []), 'guids': guids, + 'markers': markers, 'parent_guids': album_details.get('guids', []), 'grandparent_guids': album_details.get('parent_guids', []), 'full_title': '{} - {}'.format(helpers.get_xml_attr(metadata_main, 'title'), @@ -1246,6 +1264,7 @@ class PmsConnect(object): 'labels': labels, 'collections': collections, 'guids': guids, + 'markers': markers, 'parent_guids': [], 'grandparent_guids': [], 'full_title': helpers.get_xml_attr(metadata_main, 'title'), @@ -1302,6 +1321,7 @@ class PmsConnect(object): 'labels': photo_album_details.get('labels', []), 'collections': photo_album_details.get('collections', []), 'guids': [], + 'markers': markers, 'parent_guids': photo_album_details.get('guids', []), 'grandparent_guids': [], 'full_title': '{} - {}'.format(helpers.get_xml_attr(metadata_main, 'parentTitle') or library_name, @@ -1361,6 +1381,7 @@ class PmsConnect(object): 'labels': labels, 'collections': collections, 'guids': guids, + 'markers': markers, 'parent_guids': [], 'grandparent_guids': [], 'full_title': helpers.get_xml_attr(metadata_main, 'title'), @@ -1435,6 +1456,7 @@ class PmsConnect(object): 'labels': labels, 'collections': collections, 'guids': guids, + 'markers': markers, 'parent_guids': [], 'grandparent_guids': [], 'full_title': helpers.get_xml_attr(metadata_main, 'title'), From 9a152932ee8c81341123749d61de8b1987390a0d Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Thu, 16 Feb 2023 11:29:47 -0800 Subject: [PATCH 147/583] Monitor stream intro/credits marker activity --- plexpy/__init__.py | 15 +- plexpy/activity_handler.py | 380 ++++++++++++++++++----------------- plexpy/activity_processor.py | 12 +- 3 files changed, 226 insertions(+), 181 deletions(-) diff --git a/plexpy/__init__.py b/plexpy/__init__.py index d27cd396..003259bc 100644 --- a/plexpy/__init__.py +++ b/plexpy/__init__.py @@ -656,7 +656,8 @@ def dbcheck(): 'synced_version INTEGER, synced_version_profile TEXT, ' 'live INTEGER, live_uuid TEXT, channel_call_sign TEXT, channel_identifier TEXT, channel_thumb TEXT, ' 'secure INTEGER, relayed INTEGER, ' - 'buffer_count INTEGER DEFAULT 0, buffer_last_triggered INTEGER, last_paused INTEGER, watched INTEGER DEFAULT 0, ' + 'buffer_count INTEGER DEFAULT 0, buffer_last_triggered INTEGER, last_paused INTEGER, ' + 'watched INTEGER DEFAULT 0, intro INTEGER DEFAULT 0, credits INTEGER DEFAULT 0, ' 'initial_stream INTEGER DEFAULT 1, write_attempts INTEGER DEFAULT 0, raw_stream_info TEXT, ' 'rating_key_websocket TEXT)' ) @@ -1401,6 +1402,18 @@ def dbcheck(): 'ALTER TABLE sessions ADD COLUMN stream_subtitle_forced INTEGER' ) + # Upgrade sessions table from earlier versions + try: + c_db.execute('SELECT intro FROM sessions') + except sqlite3.OperationalError: + logger.debug(u"Altering database. Updating database table sessions.") + c_db.execute( + 'ALTER TABLE sessions ADD COLUMN intro INTEGER DEFAULT 0' + ) + c_db.execute( + 'ALTER TABLE sessions ADD COLUMN credits INTEGER DEFAULT 0' + ) + # Upgrade session_history table from earlier versions try: c_db.execute('SELECT reference_id FROM session_history') diff --git a/plexpy/activity_handler.py b/plexpy/activity_handler.py index 07d0f8e3..a89ccb98 100644 --- a/plexpy/activity_handler.py +++ b/plexpy/activity_handler.py @@ -51,7 +51,11 @@ RECENTLY_ADDED_QUEUE = {} class ActivityHandler(object): def __init__(self, timeline): + self.ap = activity_processor.ActivityProcessor() self.timeline = timeline + self.db_session = None + self.session = None + self.metadata = None def is_valid_session(self): if 'sessionKey' in self.timeline: @@ -72,15 +76,18 @@ class ActivityHandler(object): return None + def get_db_session(self): + # Retrieve the session data from our temp table + self.db_session = self.ap.get_session_by_key(session_key=self.get_session_key()) + def get_metadata(self, skip_cache=False): - cache_key = None if skip_cache else self.get_session_key() - pms_connect = pmsconnect.PmsConnect() - metadata = pms_connect.get_metadata_details(rating_key=self.get_rating_key(), cache_key=cache_key) + if self.metadata is None: + cache_key = None if skip_cache else self.get_session_key() + pms_connect = pmsconnect.PmsConnect() + metadata = pms_connect.get_metadata_details(rating_key=self.get_rating_key(), cache_key=cache_key) - if metadata: - return metadata - - return None + if metadata: + self.metadata = metadata def get_live_session(self, skip_cache=False): pms_connect = pmsconnect.PmsConnect() @@ -94,196 +101,179 @@ class ActivityHandler(object): if not session['rating_key']: session['rating_key'] = self.get_rating_key() session['rating_key_websocket'] = self.get_rating_key() + self.session = session return session - return None + def update_db_session(self, notify=False): + if self.session is None: + self.get_live_session() - def update_db_session(self, session=None, notify=False): - if session is None: - session = self.get_live_session() - - if session: + if self.session: # Update our session temp table values - ap = activity_processor.ActivityProcessor() - ap.write_session(session=session, notify=notify) + self.ap.write_session(session=self.session, notify=notify) self.set_session_state() + self.get_db_session() def set_session_state(self): - ap = activity_processor.ActivityProcessor() - ap.set_session_state(session_key=self.get_session_key(), + self.ap.set_session_state(session_key=self.get_session_key(), state=self.timeline['state'], view_offset=self.timeline['viewOffset'], stopped=helpers.timestamp()) + + def put_notification(self, notify_action, **kwargs): + notification = {'stream_data': self.db_session.copy(), 'notify_action': notify_action} + notification.update(kwargs) + plexpy.NOTIFY_QUEUE.put(notification) def on_start(self): - if self.is_valid_session(): - session = self.get_live_session(skip_cache=True) + self.get_live_session(skip_cache=True) - if not session: + if not self.session: + return + + # Some DLNA clients create a new session temporarily when browsing the library + # Wait and get session again to make sure it is an actual session + if self.session['platform'] == 'DLNA': + time.sleep(1) + self.get_live_session() + if not self.session: return - # Some DLNA clients create a new session temporarily when browsing the library - # Wait and get session again to make sure it is an actual session - if session['platform'] == 'DLNA': - time.sleep(1) - session = self.get_live_session() - if not session: - return + logger.debug("Tautulli ActivityHandler :: Session %s started by user %s (%s) with ratingKey %s (%s)%s." + % (str(self.session['session_key']), str(self.session['user_id']), self.session['username'], + str(self.session['rating_key']), self.session['full_title'], '[Live TV]' if self.session['live'] else '')) - logger.debug("Tautulli ActivityHandler :: Session %s started by user %s (%s) with ratingKey %s (%s)%s." - % (str(session['session_key']), str(session['user_id']), session['username'], - str(session['rating_key']), session['full_title'], '[Live TV]' if session['live'] else '')) + # Write the new session to our temp session table + self.update_db_session(notify=True) - # Send notification after updating db - #plexpy.NOTIFY_QUEUE.put({'stream_data': session.copy(), 'notify_action': 'on_play'}) - - # Write the new session to our temp session table - self.update_db_session(session=session, notify=True) - - # Schedule a callback to force stop a stale stream 5 minutes later - schedule_callback('session_key-{}'.format(self.get_session_key()), - func=force_stop_stream, - args=[self.get_session_key(), session['full_title'], session['username']], - minutes=5) + # Schedule a callback to force stop a stale stream 5 minutes later + schedule_callback('session_key-{}'.format(self.get_session_key()), + func=force_stop_stream, + args=[self.get_session_key(), self.session['full_title'], self.session['username']], + minutes=5) + + self.check_markers() def on_stop(self, force_stop=False): - if self.is_valid_session(): - logger.debug("Tautulli ActivityHandler :: Session %s %sstopped." - % (str(self.get_session_key()), 'force ' if force_stop else '')) + logger.debug("Tautulli ActivityHandler :: Session %s %sstopped." + % (str(self.get_session_key()), 'force ' if force_stop else '')) - # Set the session last_paused timestamp - ap = activity_processor.ActivityProcessor() - ap.set_session_last_paused(session_key=self.get_session_key(), timestamp=None) + # Set the session last_paused timestamp + self.ap.set_session_last_paused(session_key=self.get_session_key(), timestamp=None) - # Update the session state and viewOffset - # Set force_stop to true to disable the state set - if not force_stop: - self.set_session_state() + # Update the session state and viewOffset + # Set force_stop to true to disable the state set + if not force_stop: + self.set_session_state() - # Retrieve the session data from our temp table - db_session = ap.get_session_by_key(session_key=self.get_session_key()) + # Write it to the history table + row_id = self.ap.write_session_history(session=self.db_session) - # Write it to the history table - monitor_proc = activity_processor.ActivityProcessor() - row_id = monitor_proc.write_session_history(session=db_session) + if row_id: + self.put_notification('on_stop') - if row_id: - plexpy.NOTIFY_QUEUE.put({'stream_data': db_session.copy(), 'notify_action': 'on_stop'}) + schedule_callback('session_key-{}'.format(self.get_session_key()), remove_job=True) - schedule_callback('session_key-{}'.format(self.get_session_key()), remove_job=True) - - # Remove the session from our temp session table - logger.debug("Tautulli ActivityHandler :: Removing sessionKey %s ratingKey %s from session queue" - % (str(self.get_session_key()), str(self.get_rating_key()))) - ap.delete_session(row_id=row_id) - delete_metadata_cache(self.get_session_key()) - else: - schedule_callback('session_key-{}'.format(self.get_session_key()), - func=force_stop_stream, - args=[self.get_session_key(), db_session['full_title'], db_session['user']], - seconds=30) + # Remove the session from our temp session table + logger.debug("Tautulli ActivityHandler :: Removing sessionKey %s ratingKey %s from session queue" + % (str(self.get_session_key()), str(self.get_rating_key()))) + self.ap.delete_session(row_id=row_id) + delete_metadata_cache(self.get_session_key()) + else: + schedule_callback('session_key-{}'.format(self.get_session_key()), + func=force_stop_stream, + args=[self.get_session_key(), self.db_session['full_title'], self.db_session['user']], + seconds=30) def on_pause(self, still_paused=False): - if self.is_valid_session(): - if not still_paused: - logger.debug("Tautulli ActivityHandler :: Session %s paused." % str(self.get_session_key())) + if not still_paused: + logger.debug("Tautulli ActivityHandler :: Session %s paused." % str(self.get_session_key())) - # Set the session last_paused timestamp - ap = activity_processor.ActivityProcessor() - ap.set_session_last_paused(session_key=self.get_session_key(), timestamp=helpers.timestamp()) + # Set the session last_paused timestamp + self.ap.set_session_last_paused(session_key=self.get_session_key(), timestamp=helpers.timestamp()) - # Update the session state and viewOffset - self.update_db_session() + self.update_db_session() - # Retrieve the session data from our temp table - db_session = ap.get_session_by_key(session_key=self.get_session_key()) - - if not still_paused: - plexpy.NOTIFY_QUEUE.put({'stream_data': db_session.copy(), 'notify_action': 'on_pause'}) + if not still_paused: + self.put_notification('on_pause') def on_resume(self): - if self.is_valid_session(): - logger.debug("Tautulli ActivityHandler :: Session %s resumed." % str(self.get_session_key())) + logger.debug("Tautulli ActivityHandler :: Session %s resumed." % str(self.get_session_key())) - # Set the session last_paused timestamp - ap = activity_processor.ActivityProcessor() - ap.set_session_last_paused(session_key=self.get_session_key(), timestamp=None) + # Set the session last_paused timestamp + self.ap.set_session_last_paused(session_key=self.get_session_key(), timestamp=None) - # Update the session state and viewOffset - self.update_db_session() + self.update_db_session() - # Retrieve the session data from our temp table - db_session = ap.get_session_by_key(session_key=self.get_session_key()) - - plexpy.NOTIFY_QUEUE.put({'stream_data': db_session.copy(), 'notify_action': 'on_resume'}) - - def on_change(self): - if self.is_valid_session(): - logger.debug("Tautulli ActivityHandler :: Session %s has changed transcode decision." % str(self.get_session_key())) - - # Update the session state and viewOffset - self.update_db_session() - - # Retrieve the session data from our temp table - ap = activity_processor.ActivityProcessor() - db_session = ap.get_session_by_key(session_key=self.get_session_key()) - - plexpy.NOTIFY_QUEUE.put({'stream_data': db_session.copy(), 'notify_action': 'on_change'}) + self.put_notification('on_resume') def on_buffer(self): - if self.is_valid_session(): - logger.debug("Tautulli ActivityHandler :: Session %s is buffering." % self.get_session_key()) - ap = activity_processor.ActivityProcessor() - db_stream = ap.get_session_by_key(session_key=self.get_session_key()) + logger.debug("Tautulli ActivityHandler :: Session %s is buffering." % self.get_session_key()) - # Increment our buffer count - ap.increment_session_buffer_count(session_key=self.get_session_key()) + # Increment our buffer count + self.ap.increment_session_buffer_count(session_key=self.get_session_key()) - # Get our current buffer count - current_buffer_count = ap.get_session_buffer_count(self.get_session_key()) - logger.debug("Tautulli ActivityHandler :: Session %s buffer count is %s." % - (self.get_session_key(), current_buffer_count)) + # Get our current buffer count + current_buffer_count = self.ap.get_session_buffer_count(self.get_session_key()) + logger.debug("Tautulli ActivityHandler :: Session %s buffer count is %s." % + (self.get_session_key(), current_buffer_count)) - # Get our last triggered time - buffer_last_triggered = ap.get_session_buffer_trigger_time(self.get_session_key()) + # Get our last triggered time + buffer_last_triggered = self.ap.get_session_buffer_trigger_time(self.get_session_key()) - # Update the session state and viewOffset - self.update_db_session() + self.update_db_session() - time_since_last_trigger = 0 - if buffer_last_triggered: - logger.debug("Tautulli ActivityHandler :: Session %s buffer last triggered at %s." % - (self.get_session_key(), buffer_last_triggered)) - time_since_last_trigger = helpers.timestamp() - int(buffer_last_triggered) + time_since_last_trigger = 0 + if buffer_last_triggered: + logger.debug("Tautulli ActivityHandler :: Session %s buffer last triggered at %s." % + (self.get_session_key(), buffer_last_triggered)) + time_since_last_trigger = helpers.timestamp() - int(buffer_last_triggered) - if current_buffer_count >= plexpy.CONFIG.BUFFER_THRESHOLD and time_since_last_trigger == 0 or \ - time_since_last_trigger >= plexpy.CONFIG.BUFFER_WAIT: - ap.set_session_buffer_trigger_time(session_key=self.get_session_key()) + if current_buffer_count >= plexpy.CONFIG.BUFFER_THRESHOLD and time_since_last_trigger == 0 or \ + time_since_last_trigger >= plexpy.CONFIG.BUFFER_WAIT: + self.ap.set_session_buffer_trigger_time(session_key=self.get_session_key()) - # Retrieve the session data from our temp table - db_session = ap.get_session_by_key(session_key=self.get_session_key()) - - plexpy.NOTIFY_QUEUE.put({'stream_data': db_session.copy(), 'notify_action': 'on_buffer'}) + self.put_notification('on_buffer') def on_error(self): - if self.is_valid_session(): - logger.debug("Tautulli ActivityHandler :: Session %s encountered an error." % str(self.get_session_key())) + logger.debug("Tautulli ActivityHandler :: Session %s encountered an error." % str(self.get_session_key())) - # Update the session state and viewOffset - self.update_db_session() + self.update_db_session() - # Retrieve the session data from our temp table - ap = activity_processor.ActivityProcessor() - db_session = ap.get_session_by_key(session_key=self.get_session_key()) + self.put_notification('on_error') - plexpy.NOTIFY_QUEUE.put({'stream_data': db_session.copy(), 'notify_action': 'on_error'}) + def on_change(self): + logger.debug("Tautulli ActivityHandler :: Session %s has changed transcode decision." % str(self.get_session_key())) + + self.update_db_session() + + self.put_notification('on_change') + + def on_intro(self): + if self.get_live_session(): + logger.debug("Tautulli ActivityHandler :: Session %s intro marker reached." % str(self.get_session_key())) + + self.put_notification('on_intro') + + def on_credits(self): + if self.get_live_session(): + logger.debug("Tautulli ActivityHandler :: Session %s credits marker reached." % str(self.get_session_key())) + self.put_notification('on_credits') + + def on_watched(self): + logger.debug("Tautulli ActivityHandler :: Session %s watched." % str(self.get_session_key())) + + watched_notifiers = notification_handler.get_notify_state_enabled( + session=self.db_session, notify_action='on_watched', notified=False) + + for d in watched_notifiers: + self.put_notification('on_watched', notifier_id=d['notifier_id']) # This function receives events from our websocket connection def process(self): if self.is_valid_session(): - ap = activity_processor.ActivityProcessor() - db_session = ap.get_session_by_key(session_key=self.get_session_key()) + self.get_db_session() this_state = self.timeline['state'] this_rating_key = str(self.timeline['ratingKey']) @@ -294,27 +284,27 @@ class ActivityHandler(object): this_live_uuid = this_key.split('/')[-1] if this_key.startswith('/livetv/sessions') else None # If we already have this session in the temp table, check for state changes - if db_session: + if self.db_session: # Re-schedule the callback to reset the 5 minutes timer schedule_callback('session_key-{}'.format(self.get_session_key()), func=force_stop_stream, - args=[self.get_session_key(), db_session['full_title'], db_session['user']], + args=[self.get_session_key(), self.db_session['full_title'], self.db_session['user']], minutes=5) - last_state = db_session['state'] - last_rating_key = str(db_session['rating_key']) - last_live_uuid = db_session['live_uuid'] - last_transcode_key = db_session['transcode_key'].split('/')[-1] - last_paused = db_session['last_paused'] - last_rating_key_websocket = db_session['rating_key_websocket'] - last_guid = db_session['guid'] + last_state = self.db_session['state'] + last_rating_key = str(self.db_session['rating_key']) + last_live_uuid = self.db_session['live_uuid'] + last_transcode_key = self.db_session['transcode_key'].split('/')[-1] + last_paused = self.db_session['last_paused'] + last_rating_key_websocket = self.db_session['rating_key_websocket'] + last_guid = self.db_session['guid'] this_guid = last_guid # Check guid for live TV metadata every 60 seconds - if db_session['live'] and helpers.timestamp() - db_session['stopped'] > 60: - metadata = self.get_metadata(skip_cache=True) - if metadata: - this_guid = metadata['guid'] + if self.db_session['live'] and helpers.timestamp() - self.db_session['stopped'] > 60: + self.get_metadata(skip_cache=True) + if self.metadata: + this_guid = self.metadata['guid'] # Make sure the same item is being played if (this_rating_key == last_rating_key @@ -325,7 +315,7 @@ class ActivityHandler(object): if this_state == 'playing': # Update the session in our temp session table # if the last set temporary stopped time exceeds 60 seconds - if helpers.timestamp() - db_session['stopped'] > 60: + if helpers.timestamp() - self.db_session['stopped'] > 60: self.update_db_session() # Start our state checks @@ -356,33 +346,65 @@ class ActivityHandler(object): self.on_stop(force_stop=True) self.on_start() - # Monitor if the stream has reached the watch percentage for notifications - # The only purpose of this is for notifications - if not db_session['watched'] and this_state != 'buffering': - progress_percent = helpers.get_percent(self.timeline['viewOffset'], db_session['duration']) - watched_percent = {'movie': plexpy.CONFIG.MOVIE_WATCHED_PERCENT, - 'episode': plexpy.CONFIG.TV_WATCHED_PERCENT, - 'track': plexpy.CONFIG.MUSIC_WATCHED_PERCENT, - 'clip': plexpy.CONFIG.TV_WATCHED_PERCENT - } - - if progress_percent >= watched_percent.get(db_session['media_type'], 101): - logger.debug("Tautulli ActivityHandler :: Session %s watched." - % str(self.get_session_key())) - ap.set_watched(session_key=self.get_session_key()) - - watched_notifiers = notification_handler.get_notify_state_enabled( - session=db_session, notify_action='on_watched', notified=False) - - for d in watched_notifiers: - plexpy.NOTIFY_QUEUE.put({'stream_data': db_session.copy(), - 'notifier_id': d['notifier_id'], - 'notify_action': 'on_watched'}) + # Check for stream offset notifications + self.check_markers() + self.check_watched() else: # We don't have this session in our table yet, start a new one. if this_state != 'buffering': self.on_start() + + def check_markers(self): + # Monitor if the stream has reached the intro or credit marker offsets + self.get_metadata() + + intro_markers, credits_markers = [], [] + for marker in self.metadata['markers']: + if marker['type'] == 'intro': + intro_markers.append(marker) + elif marker['type'] == 'credits': + credits_markers.append(marker) + + self._check_marker('intro', intro_markers) + self._check_marker('credits', credits_markers) + + def _check_marker(self, marker_type, markers): + if self.db_session[marker_type] < len(markers): + marker = markers[self.db_session[marker_type]] + + # Websocket events only fire every 10 seconds + # Check if the marker is within 10 seconds of the current viewOffset + if marker['start_time_offset'] - 10000 <= self.timeline['viewOffset'] <= marker['end_time_offset']: + set_func = getattr(self.ap, 'set_{}'.format(marker_type)) + callback_func = getattr(self, 'on_{}'.format(marker_type)) + + set_func(session_key=self.get_session_key()) + + if self.timeline['viewOffset'] < marker['start_time_offset']: + # Schedule a callback for the exact offset of the marker + schedule_callback( + 'session_key-{}-{}-{}'.format(self.get_session_key(), marker_type, self.db_session[marker_type]), + func=callback_func, + milliseconds=marker['start_time_offset'] - self.timeline['viewOffset'] + ) + else: + callback_func() + + def check_watched(self): + # Monitor if the stream has reached the watch percentage for notifications + if not self.db_session['watched'] and self.timeline['state'] != 'buffering': + progress_percent = helpers.get_percent(self.timeline['viewOffset'], self.db_session['duration']) + watched_percent = { + 'movie': plexpy.CONFIG.MOVIE_WATCHED_PERCENT, + 'episode': plexpy.CONFIG.TV_WATCHED_PERCENT, + 'track': plexpy.CONFIG.MUSIC_WATCHED_PERCENT, + 'clip': plexpy.CONFIG.TV_WATCHED_PERCENT + } + + if progress_percent >= watched_percent.get(self.db_session['media_type'], 101): + self.ap.set_watched(session_key=self.get_session_key()) + self.on_watched() class TimelineHandler(object): diff --git a/plexpy/activity_processor.py b/plexpy/activity_processor.py index 4608e4c8..e110ea64 100644 --- a/plexpy/activity_processor.py +++ b/plexpy/activity_processor.py @@ -660,8 +660,18 @@ class ActivityProcessor(object): self.db.action('UPDATE sessions SET write_attempts = ? WHERE session_key = ?', [session['write_attempts'] + 1, session_key]) + def set_intro(self, session_key=None): + self.db.action('UPDATE sessions SET intro = intro + 1 ' + 'WHERE session_key = ?', + [session_key]) + + def set_credits(self, session_key=None): + self.db.action('UPDATE sessions SET credits = credits + 1 ' + 'WHERE session_key = ?', + [session_key]) + def set_watched(self, session_key=None): - self.db.action('UPDATE sessions SET watched = ?' + self.db.action('UPDATE sessions SET watched = ? ' 'WHERE session_key = ?', [1, session_key]) From 71bc0631559e6630610afd570afc2ec7551c12bc Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Thu, 16 Feb 2023 11:31:35 -0800 Subject: [PATCH 148/583] Add notification triggers for intro/credit markers --- plexpy/__init__.py | 29 ++++++++++++++++++++++++++++- plexpy/common.py | 8 ++++++++ plexpy/notification_handler.py | 24 ++++++++++++++++++++++++ plexpy/notifiers.py | 16 ++++++++++++++++ 4 files changed, 76 insertions(+), 1 deletion(-) diff --git a/plexpy/__init__.py b/plexpy/__init__.py index 003259bc..873b46f0 100644 --- a/plexpy/__init__.py +++ b/plexpy/__init__.py @@ -754,19 +754,22 @@ def dbcheck(): 'agent_id INTEGER, agent_name TEXT, agent_label TEXT, friendly_name TEXT, notifier_config TEXT, ' 'on_play INTEGER DEFAULT 0, on_stop INTEGER DEFAULT 0, on_pause INTEGER DEFAULT 0, ' 'on_resume INTEGER DEFAULT 0, on_change INTEGER DEFAULT 0, on_buffer INTEGER DEFAULT 0, ' - 'on_error INTEGER DEFAULT 0, on_watched INTEGER DEFAULT 0, on_created INTEGER DEFAULT 0, ' + 'on_error INTEGER DEFAULT 0, on_intro INTEGER DEFAULT 0, on_credits INTEGER DEFAULT 0, ' + 'on_watched INTEGER DEFAULT 0, on_created INTEGER DEFAULT 0, ' 'on_extdown INTEGER DEFAULT 0, on_intdown INTEGER DEFAULT 0, ' 'on_extup INTEGER DEFAULT 0, on_intup INTEGER DEFAULT 0, on_pmsupdate INTEGER DEFAULT 0, ' 'on_concurrent INTEGER DEFAULT 0, on_newdevice INTEGER DEFAULT 0, on_plexpyupdate INTEGER DEFAULT 0, ' 'on_plexpydbcorrupt INTEGER DEFAULT 0, ' 'on_play_subject TEXT, on_stop_subject TEXT, on_pause_subject TEXT, ' 'on_resume_subject TEXT, on_change_subject TEXT, on_buffer_subject TEXT, on_error_subject TEXT, ' + 'on_intro_subject TEXT, on_credits_subject TEXT, ' 'on_watched_subject TEXT, on_created_subject TEXT, on_extdown_subject TEXT, on_intdown_subject TEXT, ' 'on_extup_subject TEXT, on_intup_subject TEXT, on_pmsupdate_subject TEXT, ' 'on_concurrent_subject TEXT, on_newdevice_subject TEXT, on_plexpyupdate_subject TEXT, ' 'on_plexpydbcorrupt_subject TEXT, ' 'on_play_body TEXT, on_stop_body TEXT, on_pause_body TEXT, ' 'on_resume_body TEXT, on_change_body TEXT, on_buffer_body TEXT, on_error_body TEXT, ' + 'on_intro_body TEXT, on_credits_body TEXT, ' 'on_watched_body TEXT, on_created_body TEXT, on_extdown_body TEXT, on_intdown_body TEXT, ' 'on_extup_body TEXT, on_intup_body TEXT, on_pmsupdate_body TEXT, ' 'on_concurrent_body TEXT, on_newdevice_body TEXT, on_plexpyupdate_body TEXT, ' @@ -2384,6 +2387,30 @@ def dbcheck(): 'ALTER TABLE notifiers ADD COLUMN on_error_body TEXT' ) + # Upgrade notifiers table from earlier versions + try: + c_db.execute('SELECT on_intro FROM notifiers') + except sqlite3.OperationalError: + logger.debug("Altering database. Updating database table notifiers.") + c_db.execute( + 'ALTER TABLE notifiers ADD COLUMN on_intro INTEGER DEFAULT 0' + ) + c_db.execute( + 'ALTER TABLE notifiers ADD COLUMN on_intro_subject TEXT' + ) + c_db.execute( + 'ALTER TABLE notifiers ADD COLUMN on_intro_body TEXT' + ) + c_db.execute( + 'ALTER TABLE notifiers ADD COLUMN on_credits INTEGER DEFAULT 0' + ) + c_db.execute( + 'ALTER TABLE notifiers ADD COLUMN on_credits_subject TEXT' + ) + c_db.execute( + 'ALTER TABLE notifiers ADD COLUMN on_credits_body TEXT' + ) + # Upgrade tvmaze_lookup table from earlier versions try: c_db.execute('SELECT rating_key FROM tvmaze_lookup') diff --git a/plexpy/common.py b/plexpy/common.py index ad91ee87..65de4810 100644 --- a/plexpy/common.py +++ b/plexpy/common.py @@ -416,6 +416,7 @@ NOTIFICATION_PARAMETERS = [ {'name': 'Progress Duration (sec)', 'type': 'int', 'value': 'progress_duration_sec', 'description': 'The last reported offset (in seconds) of the stream.'}, {'name': 'Progress Time', 'type': 'str', 'value': 'progress_time', 'description': 'The last reported offset (in time format) of the stream.'}, {'name': 'Progress Percent', 'type': 'int', 'value': 'progress_percent', 'description': 'The last reported progress percent of the stream.'}, + {'name': 'View Offset (ms)', 'type': 'int', 'value': 'view_offset', 'description': 'The current view offset (in milliseconds) for the stream.'}, {'name': 'Transcode Decision', 'type': 'str', 'value': 'transcode_decision', 'description': 'The transcode decision of the stream.'}, {'name': 'Container Decision', 'type': 'str', 'value': 'container_decision', 'description': 'The container transcode decision of the stream.'}, {'name': 'Video Decision', 'type': 'str', 'value': 'video_decision', 'description': 'The video transcode decision of the stream.'}, @@ -426,6 +427,12 @@ NOTIFICATION_PARAMETERS = [ {'name': 'Optimized Version Profile', 'type': 'str', 'value': 'optimized_version_profile', 'description': 'The optimized version profile of the stream.'}, {'name': 'Synced Version', 'type': 'int', 'value': 'synced_version', 'description': 'If the stream is an synced version.', 'example': '0 or 1'}, {'name': 'Live', 'type': 'int', 'value': 'live', 'description': 'If the stream is live TV.', 'example': '0 or 1'}, + {'name': 'Intro Marker Start Time', 'type': 'int', 'value': 'intro_marker_start', 'description': 'The intro marker start time offset in milliseconds.'}, + {'name': 'Intro Marker End Time', 'type': 'int', 'value': 'intro_marker_end', 'description': 'The intro marker end time offset in milliseconds.'}, + {'name': 'Credits Marker First', 'type': 'int', 'value': 'credits_marker_first', 'description': 'If the credits marker is the first marker.', 'example': '0 or 1'}, + {'name': 'Credits Marker Final', 'type': 'int', 'value': 'credits_marker_final', 'description': 'If the credits marker is the final marker.', 'example': '0 or 1'}, + {'name': 'Credits Marker Start Time', 'type': 'int', 'value': 'credits_marker_start', 'description': 'The credits marker start time offset in milliseconds.'}, + {'name': 'Credits Marker End Time', 'type': 'int', 'value': 'credits_marker_end', 'description': 'The credits marker end time offset in milliseconds.'}, {'name': 'Channel Call Sign', 'type': 'str', 'value': 'channel_call_sign', 'description': 'The Live TV channel call sign.'}, {'name': 'Channel Identifier', 'type': 'str', 'value': 'channel_identifier', 'description': 'The Live TV channel number.'}, {'name': 'Channel Thumb', 'type': 'str', 'value': 'channel_thumb', 'description': 'The URL for the Live TV channel logo.'}, @@ -540,6 +547,7 @@ NOTIFICATION_PARAMETERS = [ {'name': 'User Rating', 'type': 'float', 'value': 'user_rating', 'description': 'The user (star) rating (out of 10) for the item.'}, {'name': 'Duration', 'type': 'int', 'value': 'duration', 'description': 'The duration (in minutes) for the item.'}, {'name': 'Duration (sec)', 'type': 'int', 'value': 'duration_sec', 'description': 'The duration (in seconds) for the item.'}, + {'name': 'Duration (ms)', 'type': 'int', 'value': 'duration_ms', 'description': 'The duration (in milliseconds) for the item.'}, {'name': 'Poster URL', 'type': 'str', 'value': 'poster_url', 'description': 'A URL for the movie, TV show, or album poster.'}, {'name': 'Plex ID', 'type': 'str', 'value': 'plex_id', 'description': 'The Plex ID for the item.', 'example': 'e.g. 5d7769a9594b2b001e6a6b7e'}, {'name': 'Plex URL', 'type': 'str', 'value': 'plex_url', 'description': 'The Plex URL to your server for the item.'}, diff --git a/plexpy/notification_handler.py b/plexpy/notification_handler.py index 6b55c5b7..b9940b70 100644 --- a/plexpy/notification_handler.py +++ b/plexpy/notification_handler.py @@ -583,6 +583,8 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m notify_params.update(media_info) notify_params.update(media_part_info) + metadata = pmsconnect.PmsConnect().get_metadata_details(rating_key=rating_key) + child_metadata = grandchild_metadata = [] for key in kwargs.pop('child_keys', []): child = pmsconnect.PmsConnect().get_metadata_details(rating_key=key) @@ -938,6 +940,20 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m and audience_rating: audience_rating = helpers.get_percent(notify_params['audience_rating'], 10) + intro_markers, credits_markers = [], [] + for marker in metadata['markers']: + if marker['type'] == 'intro': + intro_markers.append(marker) + elif marker['type'] == 'credits': + credits_markers.append(marker) + + intro_marker = defaultdict(int) + credits_marker = defaultdict(int) + if notify_action == 'on_intro' and intro_markers and notify_params['intro'] < len(intro_markers): + intro_marker = intro_markers[notify_params['intro']] + if notify_action == 'on_credits' and credits_markers and notify_params['credits'] < len(credits_markers): + credits_marker = credits_markers[notify_params['credits']] + now = arrow.now() now_iso = now.isocalendar() @@ -1005,6 +1021,7 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m 'progress_duration_sec': view_offset_sec, 'progress_time': arrow.get(view_offset_sec).format(duration_format), 'progress_percent': helpers.get_percent(view_offset_sec, duration_sec), + 'view_offset': session.get('view_offset', 0), 'initial_stream': notify_params['initial_stream'], 'transcode_decision': transcode_decision, 'container_decision': notify_params['container_decision'], @@ -1016,6 +1033,12 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m 'optimized_version_profile': notify_params['optimized_version_profile'], 'synced_version': notify_params['synced_version'], 'live': notify_params['live'], + 'intro_marker_start': intro_marker['start_time_offset'], + 'intro_marker_end': intro_marker['end_time_offset'], + 'credits_marker_first': int(bool(credits_marker and notify_params['credits'] == 0)), + 'credits_marker_final': int(credits_marker['final']), + 'credits_marker_start': credits_marker['start_time_offset'], + 'credits_marker_end': credits_marker['end_time_offset'], 'channel_call_sign': notify_params['channel_call_sign'], 'channel_identifier': notify_params['channel_identifier'], 'channel_thumb': notify_params['channel_thumb'], @@ -1132,6 +1155,7 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m 'user_rating': notify_params['user_rating'], 'duration': duration, 'duration_sec': duration_sec, + 'duration_ms': notify_params['duration'], 'poster_title': notify_params['poster_title'], 'poster_url': notify_params['poster_url'], 'plex_id': notify_params['plex_id'], diff --git a/plexpy/notifiers.py b/plexpy/notifiers.py index e3ef6862..383f4908 100644 --- a/plexpy/notifiers.py +++ b/plexpy/notifiers.py @@ -340,6 +340,22 @@ def available_notification_actions(agent_id=None): 'icon': 'fa-exchange-alt', 'media_types': ('movie', 'episode', 'track') }, + {'label': 'Intro Marker', + 'name': 'on_intro', + 'description': 'Trigger a notification when a video stream reaches any intro marker.', + 'subject': 'Tautulli ({server_name})', + 'body': '{user} ({player}) has reached an intro marker for {title}.', + 'icon': 'fa-bookmark', + 'media_types': ('episode',) + }, + {'label': 'Credits Marker', + 'name': 'on_credits', + 'description': 'Trigger a notification when a video stream reaches any credits marker.', + 'subject': 'Tautulli ({server_name})', + 'body': '{user} ({player}) has reached a credits marker for {title}.', + 'icon': 'fa-bookmark', + 'media_types': ('movie', 'episode') + }, {'label': 'Watched', 'name': 'on_watched', 'description': 'Trigger a notification when a video stream reaches the specified watch percentage.', From 97af214ac1bcff2de971a3c2bdeb9ff1b4a4edb1 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Thu, 16 Feb 2023 16:19:29 -0800 Subject: [PATCH 149/583] Handle seeking through intro/credits markers --- plexpy/__init__.py | 11 +++++++- plexpy/activity_handler.py | 54 +++++++++++++++++------------------- plexpy/activity_processor.py | 17 ++++++------ 3 files changed, 44 insertions(+), 38 deletions(-) diff --git a/plexpy/__init__.py b/plexpy/__init__.py index 873b46f0..09c16586 100644 --- a/plexpy/__init__.py +++ b/plexpy/__init__.py @@ -657,7 +657,7 @@ def dbcheck(): 'live INTEGER, live_uuid TEXT, channel_call_sign TEXT, channel_identifier TEXT, channel_thumb TEXT, ' 'secure INTEGER, relayed INTEGER, ' 'buffer_count INTEGER DEFAULT 0, buffer_last_triggered INTEGER, last_paused INTEGER, ' - 'watched INTEGER DEFAULT 0, intro INTEGER DEFAULT 0, credits INTEGER DEFAULT 0, ' + 'watched INTEGER DEFAULT 0, intro INTEGER DEFAULT 0, credits INTEGER DEFAULT 0, marker INTEGER DEFAULT 0, ' 'initial_stream INTEGER DEFAULT 1, write_attempts INTEGER DEFAULT 0, raw_stream_info TEXT, ' 'rating_key_websocket TEXT)' ) @@ -1417,6 +1417,15 @@ def dbcheck(): 'ALTER TABLE sessions ADD COLUMN credits INTEGER DEFAULT 0' ) + # Upgrade sessions table from earlier versions + try: + c_db.execute('SELECT marker FROM sessions') + except sqlite3.OperationalError: + logger.debug(u"Altering database. Updating database table sessions.") + c_db.execute( + 'ALTER TABLE sessions ADD COLUMN marker INTEGER DEFAULT 0' + ) + # Upgrade session_history table from earlier versions try: c_db.execute('SELECT reference_id FROM session_history') diff --git a/plexpy/activity_handler.py b/plexpy/activity_handler.py index a89ccb98..3b5a283f 100644 --- a/plexpy/activity_handler.py +++ b/plexpy/activity_handler.py @@ -250,16 +250,16 @@ class ActivityHandler(object): self.put_notification('on_change') - def on_intro(self): + def on_intro(self, marker): if self.get_live_session(): logger.debug("Tautulli ActivityHandler :: Session %s intro marker reached." % str(self.get_session_key())) - self.put_notification('on_intro') + self.put_notification('on_intro', marker=marker) - def on_credits(self): + def on_credits(self, marker): if self.get_live_session(): logger.debug("Tautulli ActivityHandler :: Session %s credits marker reached." % str(self.get_session_key())) - self.put_notification('on_credits') + self.put_notification('on_credits', marker=marker) def on_watched(self): logger.debug("Tautulli ActivityHandler :: Session %s watched." % str(self.get_session_key())) @@ -359,37 +359,33 @@ class ActivityHandler(object): # Monitor if the stream has reached the intro or credit marker offsets self.get_metadata() - intro_markers, credits_markers = [], [] - for marker in self.metadata['markers']: - if marker['type'] == 'intro': - intro_markers.append(marker) - elif marker['type'] == 'credits': - credits_markers.append(marker) - - self._check_marker('intro', intro_markers) - self._check_marker('credits', credits_markers) - - def _check_marker(self, marker_type, markers): - if self.db_session[marker_type] < len(markers): - marker = markers[self.db_session[marker_type]] + marker_flag = False + for marker_idx, marker in enumerate(self.metadata['markers'], start=1): # Websocket events only fire every 10 seconds # Check if the marker is within 10 seconds of the current viewOffset if marker['start_time_offset'] - 10000 <= self.timeline['viewOffset'] <= marker['end_time_offset']: - set_func = getattr(self.ap, 'set_{}'.format(marker_type)) - callback_func = getattr(self, 'on_{}'.format(marker_type)) + marker_flag = True - set_func(session_key=self.get_session_key()) + if self.db_session['marker'] != marker_idx: + self.ap.set_marker(session_key=self.get_session_key(), marker_idx=marker_idx, marker_type=marker['type']) + callback_func = getattr(self, 'on_{}'.format(marker['type'])) - if self.timeline['viewOffset'] < marker['start_time_offset']: - # Schedule a callback for the exact offset of the marker - schedule_callback( - 'session_key-{}-{}-{}'.format(self.get_session_key(), marker_type, self.db_session[marker_type]), - func=callback_func, - milliseconds=marker['start_time_offset'] - self.timeline['viewOffset'] - ) - else: - callback_func() + if self.timeline['viewOffset'] < marker['start_time_offset']: + # Schedule a callback for the exact offset of the marker + schedule_callback( + 'session_key-{}-marker-{}'.format(self.get_session_key(), marker_idx), + func=callback_func, + args=[marker], + milliseconds=marker['start_time_offset'] - self.timeline['viewOffset'] + ) + else: + callback_func(marker) + + break + + if not marker_flag: + self.ap.set_marker(session_key=self.get_session_key(), marker_idx=0) def check_watched(self): # Monitor if the stream has reached the watch percentage for notifications diff --git a/plexpy/activity_processor.py b/plexpy/activity_processor.py index e110ea64..c821d23d 100644 --- a/plexpy/activity_processor.py +++ b/plexpy/activity_processor.py @@ -660,15 +660,16 @@ class ActivityProcessor(object): self.db.action('UPDATE sessions SET write_attempts = ? WHERE session_key = ?', [session['write_attempts'] + 1, session_key]) - def set_intro(self, session_key=None): - self.db.action('UPDATE sessions SET intro = intro + 1 ' + def set_marker(self, session_key=None, marker_idx=None, marker_type=None): + if marker_type == 'intro': + args = [1, 0] + elif marker_type == 'credits': + args = [0, 1] + else: + args = [0, 0] + self.db.action('UPDATE sessions SET intro = ?, credits = ?, marker = ? ' 'WHERE session_key = ?', - [session_key]) - - def set_credits(self, session_key=None): - self.db.action('UPDATE sessions SET credits = credits + 1 ' - 'WHERE session_key = ?', - [session_key]) + args + [marker_idx, session_key]) def set_watched(self, session_key=None): self.db.action('UPDATE sessions SET watched = ? ' From 9be3bbbf0f85eb33a7b6d09d17af52355cbcdadb Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Thu, 16 Feb 2023 16:20:25 -0800 Subject: [PATCH 150/583] Update marker notification parameters --- plexpy/common.py | 10 ++++------ plexpy/notification_handler.py | 24 +++++------------------- plexpy/pmsconnect.py | 10 +++++++++- 3 files changed, 18 insertions(+), 26 deletions(-) diff --git a/plexpy/common.py b/plexpy/common.py index 65de4810..cf1180dc 100644 --- a/plexpy/common.py +++ b/plexpy/common.py @@ -427,12 +427,10 @@ NOTIFICATION_PARAMETERS = [ {'name': 'Optimized Version Profile', 'type': 'str', 'value': 'optimized_version_profile', 'description': 'The optimized version profile of the stream.'}, {'name': 'Synced Version', 'type': 'int', 'value': 'synced_version', 'description': 'If the stream is an synced version.', 'example': '0 or 1'}, {'name': 'Live', 'type': 'int', 'value': 'live', 'description': 'If the stream is live TV.', 'example': '0 or 1'}, - {'name': 'Intro Marker Start Time', 'type': 'int', 'value': 'intro_marker_start', 'description': 'The intro marker start time offset in milliseconds.'}, - {'name': 'Intro Marker End Time', 'type': 'int', 'value': 'intro_marker_end', 'description': 'The intro marker end time offset in milliseconds.'}, - {'name': 'Credits Marker First', 'type': 'int', 'value': 'credits_marker_first', 'description': 'If the credits marker is the first marker.', 'example': '0 or 1'}, - {'name': 'Credits Marker Final', 'type': 'int', 'value': 'credits_marker_final', 'description': 'If the credits marker is the final marker.', 'example': '0 or 1'}, - {'name': 'Credits Marker Start Time', 'type': 'int', 'value': 'credits_marker_start', 'description': 'The credits marker start time offset in milliseconds.'}, - {'name': 'Credits Marker End Time', 'type': 'int', 'value': 'credits_marker_end', 'description': 'The credits marker end time offset in milliseconds.'}, + {'name': 'Marker Start Time', 'type': 'int', 'value': 'marker_start', 'description': 'The intro or credits marker start time offset in milliseconds.'}, + {'name': 'Marker End Time', 'type': 'int', 'value': 'marker_end', 'description': 'The intro or credits marker end time offset in milliseconds.'}, + {'name': 'Credits Marker First', 'type': 'int', 'value': 'credits_marker_first', 'description': 'If the marker is the first credits marker.', 'example': '0 or 1'}, + {'name': 'Credits Marker Final', 'type': 'int', 'value': 'credits_marker_final', 'description': 'If the marker is the final credits marker.', 'example': '0 or 1'}, {'name': 'Channel Call Sign', 'type': 'str', 'value': 'channel_call_sign', 'description': 'The Live TV channel call sign.'}, {'name': 'Channel Identifier', 'type': 'str', 'value': 'channel_identifier', 'description': 'The Live TV channel number.'}, {'name': 'Channel Thumb', 'type': 'str', 'value': 'channel_thumb', 'description': 'The URL for the Live TV channel logo.'}, diff --git a/plexpy/notification_handler.py b/plexpy/notification_handler.py index b9940b70..c92415e5 100644 --- a/plexpy/notification_handler.py +++ b/plexpy/notification_handler.py @@ -940,19 +940,7 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m and audience_rating: audience_rating = helpers.get_percent(notify_params['audience_rating'], 10) - intro_markers, credits_markers = [], [] - for marker in metadata['markers']: - if marker['type'] == 'intro': - intro_markers.append(marker) - elif marker['type'] == 'credits': - credits_markers.append(marker) - - intro_marker = defaultdict(int) - credits_marker = defaultdict(int) - if notify_action == 'on_intro' and intro_markers and notify_params['intro'] < len(intro_markers): - intro_marker = intro_markers[notify_params['intro']] - if notify_action == 'on_credits' and credits_markers and notify_params['credits'] < len(credits_markers): - credits_marker = credits_markers[notify_params['credits']] + marker = kwargs.pop('marker', defaultdict(int)) now = arrow.now() now_iso = now.isocalendar() @@ -1033,12 +1021,10 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m 'optimized_version_profile': notify_params['optimized_version_profile'], 'synced_version': notify_params['synced_version'], 'live': notify_params['live'], - 'intro_marker_start': intro_marker['start_time_offset'], - 'intro_marker_end': intro_marker['end_time_offset'], - 'credits_marker_first': int(bool(credits_marker and notify_params['credits'] == 0)), - 'credits_marker_final': int(credits_marker['final']), - 'credits_marker_start': credits_marker['start_time_offset'], - 'credits_marker_end': credits_marker['end_time_offset'], + 'marker_start': marker['start_time_offset'], + 'marker_end': marker['end_time_offset'], + 'credits_marker_first': int(marker['first']), + 'credits_marker_final': int(marker['final']), 'channel_call_sign': notify_params['channel_call_sign'], 'channel_identifier': notify_params['channel_identifier'], 'channel_thumb': notify_params['channel_thumb'], diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py index 3a7a2450..cc433027 100644 --- a/plexpy/pmsconnect.py +++ b/plexpy/pmsconnect.py @@ -776,12 +776,20 @@ class PmsConnect(object): guids.append(helpers.get_xml_attr(guid, 'id')) if metadata_main.getElementsByTagName('Marker'): + first = None for marker in metadata_main.getElementsByTagName('Marker'): + marker_type = helpers.get_xml_attr(marker, 'type') + if marker_type == 'credits': + if first is None: + first = True + elif first is True: + first = False markers.append({ - 'id': helpers.get_xml_attr(marker, 'id'), + 'id': helpers.cast_to_int(helpers.get_xml_attr(marker, 'id')), 'type': helpers.get_xml_attr(marker, 'type'), 'start_time_offset': helpers.cast_to_int(helpers.get_xml_attr(marker, 'startTimeOffset')), 'end_time_offset': helpers.cast_to_int(helpers.get_xml_attr(marker, 'endTimeOffset')), + 'first': first if marker_type == 'credits' else False, 'final': helpers.bool_true(helpers.get_xml_attr(marker, 'final')) }) From 599c54c9e10ce7293319f172fab4def78702a86b Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Thu, 16 Feb 2023 17:02:30 -0800 Subject: [PATCH 151/583] Refactor activity handler --- plexpy/activity_handler.py | 482 ++++++++++++++++++------------------- 1 file changed, 229 insertions(+), 253 deletions(-) diff --git a/plexpy/activity_handler.py b/plexpy/activity_handler.py index 3b5a283f..5ed69e10 100644 --- a/plexpy/activity_handler.py +++ b/plexpy/activity_handler.py @@ -53,38 +53,33 @@ class ActivityHandler(object): def __init__(self, timeline): self.ap = activity_processor.ActivityProcessor() self.timeline = timeline + + self.session_key = None + self.rating_key = None + + self.is_valid_session = ('sessionKey' in self.timeline and str(self.timeline['sessionKey']).isdigit()) + if self.is_valid_session: + self.session_key = int(self.timeline['sessionKey']) + self.rating_key = str(self.timeline['ratingKey']) + + self.key = self.timeline.get('key') + self.state = self.timeline.get('state') + self.view_offset = self.timeline.get('viewOffset') + self.transcode_key = self.timeline.get('transcodeSession', '') + self.db_session = None self.session = None self.metadata = None - def is_valid_session(self): - if 'sessionKey' in self.timeline: - if str(self.timeline['sessionKey']).isdigit(): - return True - - return False - - def get_session_key(self): - if self.is_valid_session(): - return int(self.timeline['sessionKey']) - - return None - - def get_rating_key(self): - if self.is_valid_session(): - return self.timeline['ratingKey'] - - return None - def get_db_session(self): # Retrieve the session data from our temp table - self.db_session = self.ap.get_session_by_key(session_key=self.get_session_key()) + self.db_session = self.ap.get_session_by_key(session_key=self.session_key) def get_metadata(self, skip_cache=False): if self.metadata is None: - cache_key = None if skip_cache else self.get_session_key() + cache_key = None if skip_cache else self.session_key pms_connect = pmsconnect.PmsConnect() - metadata = pms_connect.get_metadata_details(rating_key=self.get_rating_key(), cache_key=cache_key) + metadata = pms_connect.get_metadata_details(rating_key=self.rating_key, cache_key=cache_key) if metadata: self.metadata = metadata @@ -95,12 +90,12 @@ class ActivityHandler(object): if session_list: for session in session_list['sessions']: - if int(session['session_key']) == self.get_session_key(): + if int(session['session_key']) == self.session_key: # Live sessions don't have rating keys in sessions # Get it from the websocket data if not session['rating_key']: - session['rating_key'] = self.get_rating_key() - session['rating_key_websocket'] = self.get_rating_key() + session['rating_key'] = self.rating_key + session['rating_key_websocket'] = self.rating_key self.session = session return session @@ -116,9 +111,9 @@ class ActivityHandler(object): self.get_db_session() def set_session_state(self): - self.ap.set_session_state(session_key=self.get_session_key(), - state=self.timeline['state'], - view_offset=self.timeline['viewOffset'], + self.ap.set_session_state(session_key=self.session_key, + state=self.state, + view_offset=self.view_offset, stopped=helpers.timestamp()) def put_notification(self, notify_action, **kwargs): @@ -148,19 +143,19 @@ class ActivityHandler(object): self.update_db_session(notify=True) # Schedule a callback to force stop a stale stream 5 minutes later - schedule_callback('session_key-{}'.format(self.get_session_key()), + schedule_callback('session_key-{}'.format(self.session_key), func=force_stop_stream, - args=[self.get_session_key(), self.session['full_title'], self.session['username']], + args=[self.session_key, self.session['full_title'], self.session['username']], minutes=5) self.check_markers() def on_stop(self, force_stop=False): logger.debug("Tautulli ActivityHandler :: Session %s %sstopped." - % (str(self.get_session_key()), 'force ' if force_stop else '')) + % (str(self.session_key), 'force ' if force_stop else '')) # Set the session last_paused timestamp - self.ap.set_session_last_paused(session_key=self.get_session_key(), timestamp=None) + self.ap.set_session_last_paused(session_key=self.session_key, timestamp=None) # Update the session state and viewOffset # Set force_stop to true to disable the state set @@ -173,25 +168,25 @@ class ActivityHandler(object): if row_id: self.put_notification('on_stop') - schedule_callback('session_key-{}'.format(self.get_session_key()), remove_job=True) + schedule_callback('session_key-{}'.format(self.session_key), remove_job=True) # Remove the session from our temp session table logger.debug("Tautulli ActivityHandler :: Removing sessionKey %s ratingKey %s from session queue" - % (str(self.get_session_key()), str(self.get_rating_key()))) + % (str(self.session_key), str(self.rating_key))) self.ap.delete_session(row_id=row_id) - delete_metadata_cache(self.get_session_key()) + delete_metadata_cache(self.session_key) else: - schedule_callback('session_key-{}'.format(self.get_session_key()), + schedule_callback('session_key-{}'.format(self.session_key), func=force_stop_stream, - args=[self.get_session_key(), self.db_session['full_title'], self.db_session['user']], + args=[self.session_key, self.db_session['full_title'], self.db_session['user']], seconds=30) def on_pause(self, still_paused=False): if not still_paused: - logger.debug("Tautulli ActivityHandler :: Session %s paused." % str(self.get_session_key())) + logger.debug("Tautulli ActivityHandler :: Session %s paused." % str(self.session_key)) # Set the session last_paused timestamp - self.ap.set_session_last_paused(session_key=self.get_session_key(), timestamp=helpers.timestamp()) + self.ap.set_session_last_paused(session_key=self.session_key, timestamp=helpers.timestamp()) self.update_db_session() @@ -199,52 +194,52 @@ class ActivityHandler(object): self.put_notification('on_pause') def on_resume(self): - logger.debug("Tautulli ActivityHandler :: Session %s resumed." % str(self.get_session_key())) + logger.debug("Tautulli ActivityHandler :: Session %s resumed." % str(self.session_key)) # Set the session last_paused timestamp - self.ap.set_session_last_paused(session_key=self.get_session_key(), timestamp=None) + self.ap.set_session_last_paused(session_key=self.session_key, timestamp=None) self.update_db_session() self.put_notification('on_resume') def on_buffer(self): - logger.debug("Tautulli ActivityHandler :: Session %s is buffering." % self.get_session_key()) + logger.debug("Tautulli ActivityHandler :: Session %s is buffering." % self.session_key) # Increment our buffer count - self.ap.increment_session_buffer_count(session_key=self.get_session_key()) + self.ap.increment_session_buffer_count(session_key=self.session_key) # Get our current buffer count - current_buffer_count = self.ap.get_session_buffer_count(self.get_session_key()) + current_buffer_count = self.ap.get_session_buffer_count(self.session_key) logger.debug("Tautulli ActivityHandler :: Session %s buffer count is %s." % - (self.get_session_key(), current_buffer_count)) + (self.session_key, current_buffer_count)) # Get our last triggered time - buffer_last_triggered = self.ap.get_session_buffer_trigger_time(self.get_session_key()) + buffer_last_triggered = self.ap.get_session_buffer_trigger_time(self.session_key) self.update_db_session() time_since_last_trigger = 0 if buffer_last_triggered: logger.debug("Tautulli ActivityHandler :: Session %s buffer last triggered at %s." % - (self.get_session_key(), buffer_last_triggered)) + (self.session_key, buffer_last_triggered)) time_since_last_trigger = helpers.timestamp() - int(buffer_last_triggered) if current_buffer_count >= plexpy.CONFIG.BUFFER_THRESHOLD and time_since_last_trigger == 0 or \ time_since_last_trigger >= plexpy.CONFIG.BUFFER_WAIT: - self.ap.set_session_buffer_trigger_time(session_key=self.get_session_key()) + self.ap.set_session_buffer_trigger_time(session_key=self.session_key) self.put_notification('on_buffer') def on_error(self): - logger.debug("Tautulli ActivityHandler :: Session %s encountered an error." % str(self.get_session_key())) + logger.debug("Tautulli ActivityHandler :: Session %s encountered an error." % str(self.session_key)) self.update_db_session() self.put_notification('on_error') def on_change(self): - logger.debug("Tautulli ActivityHandler :: Session %s has changed transcode decision." % str(self.get_session_key())) + logger.debug("Tautulli ActivityHandler :: Session %s has changed transcode decision." % str(self.session_key)) self.update_db_session() @@ -252,17 +247,17 @@ class ActivityHandler(object): def on_intro(self, marker): if self.get_live_session(): - logger.debug("Tautulli ActivityHandler :: Session %s intro marker reached." % str(self.get_session_key())) + logger.debug("Tautulli ActivityHandler :: Session %s intro marker reached." % str(self.session_key)) self.put_notification('on_intro', marker=marker) def on_credits(self, marker): if self.get_live_session(): - logger.debug("Tautulli ActivityHandler :: Session %s credits marker reached." % str(self.get_session_key())) + logger.debug("Tautulli ActivityHandler :: Session %s credits marker reached." % str(self.session_key)) self.put_notification('on_credits', marker=marker) def on_watched(self): - logger.debug("Tautulli ActivityHandler :: Session %s watched." % str(self.get_session_key())) + logger.debug("Tautulli ActivityHandler :: Session %s watched." % str(self.session_key)) watched_notifiers = notification_handler.get_notify_state_enabled( session=self.db_session, notify_action='on_watched', notified=False) @@ -272,88 +267,85 @@ class ActivityHandler(object): # This function receives events from our websocket connection def process(self): - if self.is_valid_session(): - self.get_db_session() + if not self.is_valid_session: + return + + self.get_db_session() - this_state = self.timeline['state'] - this_rating_key = str(self.timeline['ratingKey']) - this_key = self.timeline['key'] - this_transcode_key = self.timeline.get('transcodeSession', '') + if not self.db_session: + # We don't have this session in our table yet, start a new one. + if self.state != 'buffering': + self.on_start() + return - # Get the live tv session uuid - this_live_uuid = this_key.split('/')[-1] if this_key.startswith('/livetv/sessions') else None + # If we already have this session in the temp table, check for state changes + # Re-schedule the callback to reset the 5 minutes timer + schedule_callback('session_key-{}'.format(self.session_key), + func=force_stop_stream, + args=[self.session_key, self.db_session['full_title'], self.db_session['user']], + minutes=5) - # If we already have this session in the temp table, check for state changes - if self.db_session: - # Re-schedule the callback to reset the 5 minutes timer - schedule_callback('session_key-{}'.format(self.get_session_key()), - func=force_stop_stream, - args=[self.get_session_key(), self.db_session['full_title'], self.db_session['user']], - minutes=5) + last_state = self.db_session['state'] + last_rating_key = str(self.db_session['rating_key']) + last_live_uuid = self.db_session['live_uuid'] + last_transcode_key = self.db_session['transcode_key'].split('/')[-1] + last_paused = self.db_session['last_paused'] + last_rating_key_websocket = self.db_session['rating_key_websocket'] + last_guid = self.db_session['guid'] - last_state = self.db_session['state'] - last_rating_key = str(self.db_session['rating_key']) - last_live_uuid = self.db_session['live_uuid'] - last_transcode_key = self.db_session['transcode_key'].split('/')[-1] - last_paused = self.db_session['last_paused'] - last_rating_key_websocket = self.db_session['rating_key_websocket'] - last_guid = self.db_session['guid'] + # Get the live tv session uuid + this_live_uuid = self.key.split('/')[-1] if self.key.startswith('/livetv/sessions') else None - this_guid = last_guid - # Check guid for live TV metadata every 60 seconds - if self.db_session['live'] and helpers.timestamp() - self.db_session['stopped'] > 60: - self.get_metadata(skip_cache=True) - if self.metadata: - this_guid = self.metadata['guid'] + this_guid = last_guid + # Check guid for live TV metadata every 60 seconds + if self.db_session['live'] and helpers.timestamp() - self.db_session['stopped'] > 60: + self.get_metadata(skip_cache=True) + if self.metadata: + this_guid = self.metadata['guid'] - # Make sure the same item is being played - if (this_rating_key == last_rating_key - or this_rating_key == last_rating_key_websocket - or this_live_uuid == last_live_uuid) \ - and this_guid == last_guid: - # Update the session state and viewOffset - if this_state == 'playing': - # Update the session in our temp session table - # if the last set temporary stopped time exceeds 60 seconds - if helpers.timestamp() - self.db_session['stopped'] > 60: - self.update_db_session() + # Make sure the same item is being played + if (self.rating_key == last_rating_key + or self.rating_key == last_rating_key_websocket + or this_live_uuid == last_live_uuid) \ + and this_guid == last_guid: + # Update the session state and viewOffset + if self.state == 'playing': + # Update the session in our temp session table + # if the last set temporary stopped time exceeds 60 seconds + if helpers.timestamp() - self.db_session['stopped'] > 60: + self.update_db_session() - # Start our state checks - if this_state != last_state: - if this_state == 'paused': - self.on_pause() - elif last_paused and this_state == 'playing': - self.on_resume() - elif this_state == 'stopped': - self.on_stop() - elif this_state == 'error': - self.on_error() + # Start our state checks + if self.state != last_state: + if self.state == 'paused': + self.on_pause() + elif last_paused and self.state == 'playing': + self.on_resume() + elif self.state == 'stopped': + self.on_stop() + elif self.state == 'error': + self.on_error() - elif this_state == 'paused': - # Update the session last_paused timestamp - self.on_pause(still_paused=True) + elif self.state == 'paused': + # Update the session last_paused timestamp + self.on_pause(still_paused=True) - if this_state == 'buffering': - self.on_buffer() + if self.state == 'buffering': + self.on_buffer() - if this_transcode_key != last_transcode_key and this_state != 'stopped': - self.on_change() + if self.transcode_key != last_transcode_key and self.state != 'stopped': + self.on_change() - # If a client doesn't register stop events (I'm looking at you PHT!) check if the ratingKey has changed - else: - # Manually stop and start - # Set force_stop so that we don't overwrite our last viewOffset - self.on_stop(force_stop=True) - self.on_start() + # If a client doesn't register stop events (I'm looking at you PHT!) check if the ratingKey has changed + else: + # Manually stop and start + # Set force_stop so that we don't overwrite our last viewOffset + self.on_stop(force_stop=True) + self.on_start() - # Check for stream offset notifications - self.check_markers() - self.check_watched() - - else: - # We don't have this session in our table yet, start a new one. - if this_state != 'buffering': - self.on_start() + # Check for stream offset notifications + self.check_markers() + self.check_watched() def check_markers(self): # Monitor if the stream has reached the intro or credit marker offsets @@ -364,20 +356,20 @@ class ActivityHandler(object): for marker_idx, marker in enumerate(self.metadata['markers'], start=1): # Websocket events only fire every 10 seconds # Check if the marker is within 10 seconds of the current viewOffset - if marker['start_time_offset'] - 10000 <= self.timeline['viewOffset'] <= marker['end_time_offset']: + if marker['start_time_offset'] - 10000 <= self.view_offset <= marker['end_time_offset']: marker_flag = True if self.db_session['marker'] != marker_idx: - self.ap.set_marker(session_key=self.get_session_key(), marker_idx=marker_idx, marker_type=marker['type']) + self.ap.set_marker(session_key=self.session_key, marker_idx=marker_idx, marker_type=marker['type']) callback_func = getattr(self, 'on_{}'.format(marker['type'])) - if self.timeline['viewOffset'] < marker['start_time_offset']: + if self.view_offset < marker['start_time_offset']: # Schedule a callback for the exact offset of the marker schedule_callback( - 'session_key-{}-marker-{}'.format(self.get_session_key(), marker_idx), + 'session_key-{}-marker-{}'.format(self.session_key, marker_idx), func=callback_func, args=[marker], - milliseconds=marker['start_time_offset'] - self.timeline['viewOffset'] + milliseconds=marker['start_time_offset'] - self.view_offset ) else: callback_func(marker) @@ -385,7 +377,7 @@ class ActivityHandler(object): break if not marker_flag: - self.ap.set_marker(session_key=self.get_session_key(), marker_idx=0) + self.ap.set_marker(session_key=self.session_key, marker_idx=0) def check_watched(self): # Monitor if the stream has reached the watch percentage for notifications @@ -399,7 +391,7 @@ class ActivityHandler(object): } if progress_percent >= watched_percent.get(self.db_session['media_type'], 101): - self.ap.set_watched(session_key=self.get_session_key()) + self.ap.set_watched(session_key=self.session_key) self.on_watched() @@ -408,121 +400,106 @@ class TimelineHandler(object): def __init__(self, timeline): self.timeline = timeline - def is_item(self): - if 'itemID' in self.timeline: - return True + self.rating_key = None - return False + self.is_item = ('itemID' in self.timeline) + if self.is_item: + self.rating_key = int(self.timeline['itemID']) - def get_rating_key(self): - if self.is_item(): - return int(self.timeline['itemID']) - - return None - - def get_metadata(self): - pms_connect = pmsconnect.PmsConnect() - metadata = pms_connect.get_metadata_details(self.get_rating_key()) - - if metadata: - return metadata - - return None + self.parent_rating_key = helpers.cast_to_int(self.timeline.get('parentItemID')) or None + self.grandparent_rating_key = helpers.cast_to_int(self.timeline.get('rootItemID')) or None + self.identifier = self.timeline.get('identifier') + self.state_type = self.timeline.get('state') + self.media_type = common.MEDIA_TYPE_VALUES.get(self.timeline.get('type')) + self.section_id = helpers.cast_to_int(self.timeline.get('sectionID', 0)) + self.title = self.timeline.get('title', 'Unknown') + self.metadata_state = self.timeline.get('metadataState') + self.media_state = self.timeline.get('mediaState') + self.queue_size = self.timeline.get('queueSize') # This function receives events from our websocket connection def process(self): - if self.is_item(): - global RECENTLY_ADDED_QUEUE + if not self.is_item: + return + + # Return if it is not a library event (i.e. DVR EPG event) + if self.identifier != 'com.plexapp.plugins.library': + return - rating_key = self.get_rating_key() - parent_rating_key = helpers.cast_to_int(self.timeline.get('parentItemID')) or None - grandparent_rating_key = helpers.cast_to_int(self.timeline.get('rootItemID')) or None + global RECENTLY_ADDED_QUEUE - identifier = self.timeline.get('identifier') - state_type = self.timeline.get('state') - media_type = common.MEDIA_TYPE_VALUES.get(self.timeline.get('type')) - section_id = helpers.cast_to_int(self.timeline.get('sectionID', 0)) - title = self.timeline.get('title', 'Unknown') - metadata_state = self.timeline.get('metadataState') - media_state = self.timeline.get('mediaState') - queue_size = self.timeline.get('queueSize') + # Add a new media item to the recently added queue + if self.media_type and self.section_id > 0 and self.state_type == 0 and self.metadata_state == 'created': - # Return if it is not a library event (i.e. DVR EPG event) - if identifier != 'com.plexapp.plugins.library': - return + if self.media_type in ('episode', 'track'): + grandparent_set = RECENTLY_ADDED_QUEUE.get(self.grandparent_rating_key, set()) + grandparent_set.add(self.parent_rating_key) + RECENTLY_ADDED_QUEUE[self.grandparent_rating_key] = grandparent_set - # Add a new media item to the recently added queue - if media_type and section_id > 0 and state_type == 0 and metadata_state == 'created': + parent_set = RECENTLY_ADDED_QUEUE.get(self.parent_rating_key, set()) + parent_set.add(self.rating_key) + RECENTLY_ADDED_QUEUE[self.parent_rating_key] = parent_set - if media_type in ('episode', 'track'): - grandparent_set = RECENTLY_ADDED_QUEUE.get(grandparent_rating_key, set()) - grandparent_set.add(parent_rating_key) - RECENTLY_ADDED_QUEUE[grandparent_rating_key] = grandparent_set + RECENTLY_ADDED_QUEUE[self.rating_key] = {self.grandparent_rating_key} - parent_set = RECENTLY_ADDED_QUEUE.get(parent_rating_key, set()) - parent_set.add(rating_key) - RECENTLY_ADDED_QUEUE[parent_rating_key] = parent_set + logger.debug("Tautulli TimelineHandler :: Library item '%s' (%s, grandparent %s) " + "added to recently added queue." + % (self.title, str(self.rating_key), str(self.grandparent_rating_key))) - RECENTLY_ADDED_QUEUE[rating_key] = {grandparent_rating_key} + # Schedule a callback to clear the recently added queue + schedule_callback('rating_key-{}'.format(self.grandparent_rating_key), + func=clear_recently_added_queue, + args=[self.grandparent_rating_key, self.title], + seconds=plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_DELAY) - logger.debug("Tautulli TimelineHandler :: Library item '%s' (%s, grandparent %s) " - "added to recently added queue." - % (title, str(rating_key), str(grandparent_rating_key))) + elif self.media_type in ('season', 'album'): + parent_set = RECENTLY_ADDED_QUEUE.get(self.parent_rating_key, set()) + parent_set.add(self.rating_key) + RECENTLY_ADDED_QUEUE[self.parent_rating_key] = parent_set - # Schedule a callback to clear the recently added queue - schedule_callback('rating_key-{}'.format(grandparent_rating_key), - func=clear_recently_added_queue, - args=[grandparent_rating_key, title], - seconds=plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_DELAY) + logger.debug("Tautulli TimelineHandler :: Library item '%s' (%s , parent %s) " + "added to recently added queue." + % (self.title, str(self.rating_key), str(self.parent_rating_key))) - elif media_type in ('season', 'album'): - parent_set = RECENTLY_ADDED_QUEUE.get(parent_rating_key, set()) - parent_set.add(rating_key) - RECENTLY_ADDED_QUEUE[parent_rating_key] = parent_set + # Schedule a callback to clear the recently added queue + schedule_callback('rating_key-{}'.format(self.parent_rating_key), + func=clear_recently_added_queue, + args=[self.parent_rating_key, self.title], + seconds=plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_DELAY) - logger.debug("Tautulli TimelineHandler :: Library item '%s' (%s , parent %s) " - "added to recently added queue." - % (title, str(rating_key), str(parent_rating_key))) - - # Schedule a callback to clear the recently added queue - schedule_callback('rating_key-{}'.format(parent_rating_key), - func=clear_recently_added_queue, - args=[parent_rating_key, title], - seconds=plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_DELAY) - - elif media_type in ('movie', 'show', 'artist'): - queue_set = RECENTLY_ADDED_QUEUE.get(rating_key, set()) - RECENTLY_ADDED_QUEUE[rating_key] = queue_set - - logger.debug("Tautulli TimelineHandler :: Library item '%s' (%s) " - "added to recently added queue." - % (title, str(rating_key))) - - # Schedule a callback to clear the recently added queue - schedule_callback('rating_key-{}'.format(rating_key), - func=clear_recently_added_queue, - args=[rating_key, title], - seconds=plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_DELAY) - - # A movie, show, or artist is done processing - elif media_type in ('movie', 'show', 'artist') and section_id > 0 and \ - state_type == 5 and metadata_state is None and queue_size is None and \ - rating_key in RECENTLY_ADDED_QUEUE: + elif self.media_type in ('movie', 'show', 'artist'): + queue_set = RECENTLY_ADDED_QUEUE.get(self.rating_key, set()) + RECENTLY_ADDED_QUEUE[self.rating_key] = queue_set logger.debug("Tautulli TimelineHandler :: Library item '%s' (%s) " - "done processing metadata." - % (title, str(rating_key))) + "added to recently added queue." + % (self.title, str(self.rating_key))) - # An item was deleted, make sure it is removed from the queue - elif state_type == 9 and metadata_state == 'deleted': - if rating_key in RECENTLY_ADDED_QUEUE and not RECENTLY_ADDED_QUEUE[rating_key]: - logger.debug("Tautulli TimelineHandler :: Library item %s " - "removed from recently added queue." - % str(rating_key)) - del_keys(rating_key) + # Schedule a callback to clear the recently added queue + schedule_callback('rating_key-{}'.format(self.rating_key), + func=clear_recently_added_queue, + args=[self.rating_key, self.title], + seconds=plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_DELAY) - # Remove the callback if the item is removed - schedule_callback('rating_key-{}'.format(rating_key), remove_job=True) + # A movie, show, or artist is done processing + elif self.media_type in ('movie', 'show', 'artist') and self.section_id > 0 and \ + self.state_type == 5 and self.metadata_state is None and self.queue_size is None and \ + self.rating_key in RECENTLY_ADDED_QUEUE: + + logger.debug("Tautulli TimelineHandler :: Library item '%s' (%s) " + "done processing metadata." + % (self.title, str(self.rating_key))) + + # An item was deleted, make sure it is removed from the queue + elif self.state_type == 9 and self.metadata_state == 'deleted': + if self.rating_key in RECENTLY_ADDED_QUEUE and not RECENTLY_ADDED_QUEUE[self.rating_key]: + logger.debug("Tautulli TimelineHandler :: Library item %s " + "removed from recently added queue." + % str(self.rating_key)) + del_keys(self.rating_key) + + # Remove the callback if the item is removed + schedule_callback('rating_key-{}'.format(self.rating_key), remove_job=True) class ReachabilityHandler(object): @@ -530,10 +507,7 @@ class ReachabilityHandler(object): def __init__(self, data): self.data = data - def is_reachable(self): - if 'reachability' in self.data: - return self.data['reachability'] - return False + self.is_reachable = self.data.get('reachable', False) def remote_access_enabled(self): pms_connect = pmsconnect.PmsConnect() @@ -552,42 +526,44 @@ class ReachabilityHandler(object): return # Do nothing if remote access is still up and hasn't changed - if self.is_reachable() and plexpy.PLEX_REMOTE_ACCESS_UP: + if self.is_reachable and plexpy.PLEX_REMOTE_ACCESS_UP: return pms_connect = pmsconnect.PmsConnect() server_response = pms_connect.get_server_response() - if server_response: - # Waiting for port mapping - if server_response['mapping_state'] == 'waiting': - logger.warn("Tautulli ReachabilityHandler :: Remote access waiting for port mapping.") + if not server_response: + return - elif plexpy.PLEX_REMOTE_ACCESS_UP is not False and server_response['reason']: - logger.warn("Tautulli ReachabilityHandler :: Remote access failed: %s" % server_response['reason']) - logger.info("Tautulli ReachabilityHandler :: Plex remote access is down.") + # Waiting for port mapping + if server_response['mapping_state'] == 'waiting': + logger.warn("Tautulli ReachabilityHandler :: Remote access waiting for port mapping.") - plexpy.PLEX_REMOTE_ACCESS_UP = False + elif plexpy.PLEX_REMOTE_ACCESS_UP is not False and server_response['reason']: + logger.warn("Tautulli ReachabilityHandler :: Remote access failed: %s" % server_response['reason']) + logger.info("Tautulli ReachabilityHandler :: Plex remote access is down.") - if not ACTIVITY_SCHED.get_job('on_extdown'): - logger.debug("Tautulli ReachabilityHandler :: Scheduling remote access down callback in %d seconds.", - plexpy.CONFIG.NOTIFY_REMOTE_ACCESS_THRESHOLD) - schedule_callback('on_extdown', func=self.on_extdown, args=[server_response], - seconds=plexpy.CONFIG.NOTIFY_REMOTE_ACCESS_THRESHOLD) + plexpy.PLEX_REMOTE_ACCESS_UP = False - elif plexpy.PLEX_REMOTE_ACCESS_UP is False and not server_response['reason']: - logger.info("Tautulli ReachabilityHandler :: Plex remote access is back up.") + if not ACTIVITY_SCHED.get_job('on_extdown'): + logger.debug("Tautulli ReachabilityHandler :: Scheduling remote access down callback in %d seconds.", + plexpy.CONFIG.NOTIFY_REMOTE_ACCESS_THRESHOLD) + schedule_callback('on_extdown', func=self.on_extdown, args=[server_response], + seconds=plexpy.CONFIG.NOTIFY_REMOTE_ACCESS_THRESHOLD) - plexpy.PLEX_REMOTE_ACCESS_UP = True + elif plexpy.PLEX_REMOTE_ACCESS_UP is False and not server_response['reason']: + logger.info("Tautulli ReachabilityHandler :: Plex remote access is back up.") - if ACTIVITY_SCHED.get_job('on_extdown'): - logger.debug("Tautulli ReachabilityHandler :: Cancelling scheduled remote access down callback.") - schedule_callback('on_extdown', remove_job=True) - else: - self.on_extup(server_response) + plexpy.PLEX_REMOTE_ACCESS_UP = True - elif plexpy.PLEX_REMOTE_ACCESS_UP is None: - plexpy.PLEX_REMOTE_ACCESS_UP = self.is_reachable() + if ACTIVITY_SCHED.get_job('on_extdown'): + logger.debug("Tautulli ReachabilityHandler :: Cancelling scheduled remote access down callback.") + schedule_callback('on_extdown', remove_job=True) + else: + self.on_extup(server_response) + + elif plexpy.PLEX_REMOTE_ACCESS_UP is None: + plexpy.PLEX_REMOTE_ACCESS_UP = self.is_reachable def del_keys(key): From 7cc78d448d6d3f907d203203be0d20d2a359f275 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Thu, 16 Feb 2023 17:06:46 -0800 Subject: [PATCH 152/583] Simplify set marker in database --- plexpy/activity_processor.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/plexpy/activity_processor.py b/plexpy/activity_processor.py index c821d23d..dcd1f138 100644 --- a/plexpy/activity_processor.py +++ b/plexpy/activity_processor.py @@ -661,15 +661,10 @@ class ActivityProcessor(object): [session['write_attempts'] + 1, session_key]) def set_marker(self, session_key=None, marker_idx=None, marker_type=None): - if marker_type == 'intro': - args = [1, 0] - elif marker_type == 'credits': - args = [0, 1] - else: - args = [0, 0] + marker_args = [int(marker_type == 'intro'), int(marker_type == 'credits')] self.db.action('UPDATE sessions SET intro = ?, credits = ?, marker = ? ' 'WHERE session_key = ?', - args + [marker_idx, session_key]) + marker_args + [marker_idx, session_key]) def set_watched(self, session_key=None): self.db.action('UPDATE sessions SET watched = ? ' From 9c6b8f1af5bd4f1e0c32a85d178253ae14014fd9 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Fri, 17 Feb 2023 09:28:44 -0800 Subject: [PATCH 153/583] Simplify metadata credits marker first flag --- plexpy/pmsconnect.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py index cc433027..347de513 100644 --- a/plexpy/pmsconnect.py +++ b/plexpy/pmsconnect.py @@ -780,17 +780,15 @@ class PmsConnect(object): for marker in metadata_main.getElementsByTagName('Marker'): marker_type = helpers.get_xml_attr(marker, 'type') if marker_type == 'credits': - if first is None: - first = True - elif first is True: - first = False + first = bool(first is None) + final = helpers.bool_true(helpers.get_xml_attr(marker, 'final')) markers.append({ 'id': helpers.cast_to_int(helpers.get_xml_attr(marker, 'id')), - 'type': helpers.get_xml_attr(marker, 'type'), + 'type': marker_type, 'start_time_offset': helpers.cast_to_int(helpers.get_xml_attr(marker, 'startTimeOffset')), 'end_time_offset': helpers.cast_to_int(helpers.get_xml_attr(marker, 'endTimeOffset')), - 'first': first if marker_type == 'credits' else False, - 'final': helpers.bool_true(helpers.get_xml_attr(marker, 'final')) + 'first': first if marker_type == 'credits' else None, + 'final': final if marker_type == 'credits' else None }) if metadata_type == 'movie': From 32bb98e8c1a0d3b790e269e28c15d259628e00d1 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Fri, 17 Feb 2023 09:29:08 -0800 Subject: [PATCH 154/583] Update get_metadata_details docs --- plexpy/webserve.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 8ad3e664..963f2ce1 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -5339,6 +5339,24 @@ class WebInterface(object): "last_viewed_at": "1462165717", "library_name": "TV Shows", "live": 0, + "markers": [ + { + "id": 908, + "type": "credits", + "start_time_offset": 2923863, + "end_time_offset": 2998197, + "first": true, + "final": true + }, + { + "id": 908, + "type": "intro", + "start_time_offset": 1622, + "end_time_offset": 109135, + "first": null, + "final": null + } + ], "media_index": "1", "media_info": [ { From 6b0b3a476fbfde4cdf3dbfa617ff8080cdf28234 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Fri, 17 Feb 2023 10:01:57 -0800 Subject: [PATCH 155/583] Add support for commercial marker triggers --- plexpy/__init__.py | 13 +++++++++++-- plexpy/activity_handler.py | 11 +++++++++-- plexpy/activity_processor.py | 8 ++++++-- plexpy/notification_handler.py | 4 ++-- plexpy/notifiers.py | 8 ++++++++ 5 files changed, 36 insertions(+), 8 deletions(-) diff --git a/plexpy/__init__.py b/plexpy/__init__.py index 09c16586..d3de0815 100644 --- a/plexpy/__init__.py +++ b/plexpy/__init__.py @@ -656,8 +656,8 @@ def dbcheck(): 'synced_version INTEGER, synced_version_profile TEXT, ' 'live INTEGER, live_uuid TEXT, channel_call_sign TEXT, channel_identifier TEXT, channel_thumb TEXT, ' 'secure INTEGER, relayed INTEGER, ' - 'buffer_count INTEGER DEFAULT 0, buffer_last_triggered INTEGER, last_paused INTEGER, ' - 'watched INTEGER DEFAULT 0, intro INTEGER DEFAULT 0, credits INTEGER DEFAULT 0, marker INTEGER DEFAULT 0, ' + 'buffer_count INTEGER DEFAULT 0, buffer_last_triggered INTEGER, last_paused INTEGER, watched INTEGER DEFAULT 0, ' + 'intro INTEGER DEFAULT 0, credits INTEGER DEFAULT 0, commercial INTEGER DEFAULT 0, marker INTEGER DEFAULT 0, ' 'initial_stream INTEGER DEFAULT 1, write_attempts INTEGER DEFAULT 0, raw_stream_info TEXT, ' 'rating_key_websocket TEXT)' ) @@ -1417,6 +1417,15 @@ def dbcheck(): 'ALTER TABLE sessions ADD COLUMN credits INTEGER DEFAULT 0' ) + # Upgrade sessions table from earlier versions + try: + c_db.execute('SELECT commercial FROM sessions') + except sqlite3.OperationalError: + logger.debug(u"Altering database. Updating database table sessions.") + c_db.execute( + 'ALTER TABLE sessions ADD COLUMN commercial INTEGER DEFAULT 0' + ) + # Upgrade sessions table from earlier versions try: c_db.execute('SELECT marker FROM sessions') diff --git a/plexpy/activity_handler.py b/plexpy/activity_handler.py index 5ed69e10..8933c1ac 100644 --- a/plexpy/activity_handler.py +++ b/plexpy/activity_handler.py @@ -247,13 +247,20 @@ class ActivityHandler(object): def on_intro(self, marker): if self.get_live_session(): - logger.debug("Tautulli ActivityHandler :: Session %s intro marker reached." % str(self.session_key)) + logger.debug("Tautulli ActivityHandler :: Session %s reached intro marker." % str(self.session_key)) self.put_notification('on_intro', marker=marker) + def on_commercial(self, marker): + if self.get_live_session(): + logger.debug("Tautulli ActivityHandler :: Session %s reached commercial marker." % str(self.session_key)) + + self.put_notification('on_commercial', marker=marker) + def on_credits(self, marker): if self.get_live_session(): - logger.debug("Tautulli ActivityHandler :: Session %s credits marker reached." % str(self.session_key)) + logger.debug("Tautulli ActivityHandler :: Session %s reached credits marker." % str(self.session_key)) + self.put_notification('on_credits', marker=marker) def on_watched(self): diff --git a/plexpy/activity_processor.py b/plexpy/activity_processor.py index dcd1f138..a8d8cdd4 100644 --- a/plexpy/activity_processor.py +++ b/plexpy/activity_processor.py @@ -661,8 +661,12 @@ class ActivityProcessor(object): [session['write_attempts'] + 1, session_key]) def set_marker(self, session_key=None, marker_idx=None, marker_type=None): - marker_args = [int(marker_type == 'intro'), int(marker_type == 'credits')] - self.db.action('UPDATE sessions SET intro = ?, credits = ?, marker = ? ' + marker_args = [ + int(marker_type == 'intro'), + int(marker_type == 'commercial'), + int(marker_type == 'credits') + ] + self.db.action('UPDATE sessions SET intro = ?, commercial = ?, credits = ?, marker = ? ' 'WHERE session_key = ?', marker_args + [marker_idx, session_key]) diff --git a/plexpy/notification_handler.py b/plexpy/notification_handler.py index c92415e5..4b727c5d 100644 --- a/plexpy/notification_handler.py +++ b/plexpy/notification_handler.py @@ -1023,8 +1023,8 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m 'live': notify_params['live'], 'marker_start': marker['start_time_offset'], 'marker_end': marker['end_time_offset'], - 'credits_marker_first': int(marker['first']), - 'credits_marker_final': int(marker['final']), + 'credits_marker_first': helpers.cast_to_int(marker['first']), + 'credits_marker_final': helpers.cast_to_int(marker['final']), 'channel_call_sign': notify_params['channel_call_sign'], 'channel_identifier': notify_params['channel_identifier'], 'channel_thumb': notify_params['channel_thumb'], diff --git a/plexpy/notifiers.py b/plexpy/notifiers.py index 383f4908..d717c8f6 100644 --- a/plexpy/notifiers.py +++ b/plexpy/notifiers.py @@ -348,6 +348,14 @@ def available_notification_actions(agent_id=None): 'icon': 'fa-bookmark', 'media_types': ('episode',) }, + {'label': 'Commercial Marker', + 'name': 'on_credits', + 'description': 'Trigger a notification when a video stream reaches any commercial marker.', + 'subject': 'Tautulli ({server_name})', + 'body': '{user} ({player}) has reached a commercial marker for {title}.', + 'icon': 'fa-bookmark', + 'media_types': ('movie', 'episode') + }, {'label': 'Credits Marker', 'name': 'on_credits', 'description': 'Trigger a notification when a video stream reaches any credits marker.', From 87d3c0ae8172ca65f3168d999bdbdb920265be14 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Fri, 17 Feb 2023 18:54:52 -0800 Subject: [PATCH 156/583] Fix missing on_commercial columns in database --- plexpy/__init__.py | 22 +++++++++++++++++++--- plexpy/notifiers.py | 2 +- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/plexpy/__init__.py b/plexpy/__init__.py index d3de0815..2827c5fa 100644 --- a/plexpy/__init__.py +++ b/plexpy/__init__.py @@ -754,7 +754,8 @@ def dbcheck(): 'agent_id INTEGER, agent_name TEXT, agent_label TEXT, friendly_name TEXT, notifier_config TEXT, ' 'on_play INTEGER DEFAULT 0, on_stop INTEGER DEFAULT 0, on_pause INTEGER DEFAULT 0, ' 'on_resume INTEGER DEFAULT 0, on_change INTEGER DEFAULT 0, on_buffer INTEGER DEFAULT 0, ' - 'on_error INTEGER DEFAULT 0, on_intro INTEGER DEFAULT 0, on_credits INTEGER DEFAULT 0, ' + 'on_error INTEGER DEFAULT 0, ' + 'on_intro INTEGER DEFAULT 0, on_credits INTEGER DEFAULT 0, on_commercial INTEGER DEFAULT 0, ' 'on_watched INTEGER DEFAULT 0, on_created INTEGER DEFAULT 0, ' 'on_extdown INTEGER DEFAULT 0, on_intdown INTEGER DEFAULT 0, ' 'on_extup INTEGER DEFAULT 0, on_intup INTEGER DEFAULT 0, on_pmsupdate INTEGER DEFAULT 0, ' @@ -762,14 +763,14 @@ def dbcheck(): 'on_plexpydbcorrupt INTEGER DEFAULT 0, ' 'on_play_subject TEXT, on_stop_subject TEXT, on_pause_subject TEXT, ' 'on_resume_subject TEXT, on_change_subject TEXT, on_buffer_subject TEXT, on_error_subject TEXT, ' - 'on_intro_subject TEXT, on_credits_subject TEXT, ' + 'on_intro_subject TEXT, on_credits_subject TEXT, on_commercial_subject TEXT,' 'on_watched_subject TEXT, on_created_subject TEXT, on_extdown_subject TEXT, on_intdown_subject TEXT, ' 'on_extup_subject TEXT, on_intup_subject TEXT, on_pmsupdate_subject TEXT, ' 'on_concurrent_subject TEXT, on_newdevice_subject TEXT, on_plexpyupdate_subject TEXT, ' 'on_plexpydbcorrupt_subject TEXT, ' 'on_play_body TEXT, on_stop_body TEXT, on_pause_body TEXT, ' 'on_resume_body TEXT, on_change_body TEXT, on_buffer_body TEXT, on_error_body TEXT, ' - 'on_intro_body TEXT, on_credits_body TEXT, ' + 'on_intro_body TEXT, on_credits_body TEXT, on_commercial_body TEXT, ' 'on_watched_body TEXT, on_created_body TEXT, on_extdown_body TEXT, on_intdown_body TEXT, ' 'on_extup_body TEXT, on_intup_body TEXT, on_pmsupdate_body TEXT, ' 'on_concurrent_body TEXT, on_newdevice_body TEXT, on_plexpyupdate_body TEXT, ' @@ -2429,6 +2430,21 @@ def dbcheck(): 'ALTER TABLE notifiers ADD COLUMN on_credits_body TEXT' ) + # Upgrade notifiers table from earlier versions + try: + c_db.execute('SELECT on_commercial FROM notifiers') + except sqlite3.OperationalError: + logger.debug("Altering database. Updating database table notifiers.") + c_db.execute( + 'ALTER TABLE notifiers ADD COLUMN on_commercial INTEGER DEFAULT 0' + ) + c_db.execute( + 'ALTER TABLE notifiers ADD COLUMN on_commercial_subject TEXT' + ) + c_db.execute( + 'ALTER TABLE notifiers ADD COLUMN on_commercial_body TEXT' + ) + # Upgrade tvmaze_lookup table from earlier versions try: c_db.execute('SELECT rating_key FROM tvmaze_lookup') diff --git a/plexpy/notifiers.py b/plexpy/notifiers.py index d717c8f6..f0f02bf0 100644 --- a/plexpy/notifiers.py +++ b/plexpy/notifiers.py @@ -349,7 +349,7 @@ def available_notification_actions(agent_id=None): 'media_types': ('episode',) }, {'label': 'Commercial Marker', - 'name': 'on_credits', + 'name': 'on_commercial', 'description': 'Trigger a notification when a video stream reaches any commercial marker.', 'subject': 'Tautulli ({server_name})', 'body': '{user} ({player}) has reached a commercial marker for {title}.', From 6807cebe51311f599a32f5df421c2dccc6e0c498 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Fri, 17 Feb 2023 18:55:28 -0800 Subject: [PATCH 157/583] Strip whitespace from condition values --- plexpy/notification_handler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plexpy/notification_handler.py b/plexpy/notification_handler.py index 4b727c5d..197314fc 100644 --- a/plexpy/notification_handler.py +++ b/plexpy/notification_handler.py @@ -294,7 +294,7 @@ def notify_custom_conditions(notifier_id=None, parameters=None): # Cast the condition values to the correct type try: if parameter_type == 'str': - values = ['' if v == '~' else str(v).lower() for v in values] + values = ['' if v == '~' else str(v).strip().lower() for v in values] elif parameter_type == 'int': values = [helpers.cast_to_int(v) for v in values] @@ -313,7 +313,7 @@ def notify_custom_conditions(notifier_id=None, parameters=None): # Cast the parameter value to the correct type try: if parameter_type == 'str': - parameter_value = str(parameter_value).lower() + parameter_value = str(parameter_value).strip().lower() elif parameter_type == 'int': parameter_value = helpers.cast_to_int(parameter_value) From c2abfce8e1bcc3a1f57a76a98e642eaa263edf6f Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Sun, 19 Feb 2023 17:41:48 -0800 Subject: [PATCH 158/583] Save credits markers offsets to session history --- plexpy/__init__.py | 15 ++++++++++++++- plexpy/activity_processor.py | 12 +++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/plexpy/__init__.py b/plexpy/__init__.py index 2827c5fa..d0aed7cf 100644 --- a/plexpy/__init__.py +++ b/plexpy/__init__.py @@ -715,7 +715,8 @@ def dbcheck(): 'art TEXT, media_type TEXT, year INTEGER, originally_available_at TEXT, added_at INTEGER, updated_at INTEGER, ' 'last_viewed_at INTEGER, content_rating TEXT, summary TEXT, tagline TEXT, rating TEXT, ' 'duration INTEGER DEFAULT 0, guid TEXT, directors TEXT, writers TEXT, actors TEXT, genres TEXT, studio TEXT, ' - 'labels TEXT, live INTEGER DEFAULT 0, channel_call_sign TEXT, channel_identifier TEXT, channel_thumb TEXT)' + 'labels TEXT, live INTEGER DEFAULT 0, channel_call_sign TEXT, channel_identifier TEXT, channel_thumb TEXT, ' + 'marker_credits_first INTEGER DEFAULT NULL, marker_credits_final INTEGER DEFAULT NULL)' ) # users table :: This table keeps record of the friends list @@ -1564,6 +1565,18 @@ def dbcheck(): 'ALTER TABLE session_history_metadata ADD COLUMN channel_thumb TEXT' ) + # Upgrade session_history_metadata table from earlier versions + try: + c_db.execute('SELECT marker_credits_first FROM session_history_metadata') + except sqlite3.OperationalError: + logger.debug("Altering database. Updating database table session_history_metadata.") + c_db.execute( + 'ALTER TABLE session_history_metadata ADD COLUMN marker_credits_first INTEGER DEFAULT NULL' + ) + c_db.execute( + 'ALTER TABLE session_history_metadata ADD COLUMN marker_credits_final INTEGER DEFAULT NULL' + ) + # Upgrade session_history_media_info table from earlier versions try: c_db.execute('SELECT transcode_decision FROM session_history_media_info') diff --git a/plexpy/activity_processor.py b/plexpy/activity_processor.py index a8d8cdd4..d55c6738 100644 --- a/plexpy/activity_processor.py +++ b/plexpy/activity_processor.py @@ -490,6 +490,14 @@ class ActivityProcessor(object): genres = ";".join(metadata['genres']) labels = ";".join(metadata['labels']) + marker_credits_first = None + marker_credits_final = None + for marker in metadata['markers']: + if marker['first']: + marker_credits_first = marker['start_time_offset'] + if marker['final']: + marker_credits_final = marker['start_time_offset'] + # logger.debug("Tautulli ActivityProcessor :: Attempting to write to sessionKey %s session_history_metadata table..." # % session['session_key']) keys = {'id': last_id} @@ -528,7 +536,9 @@ class ActivityProcessor(object): 'live': session['live'], 'channel_call_sign': media_info.get('channel_call_sign', ''), 'channel_identifier': media_info.get('channel_identifier', ''), - 'channel_thumb': media_info.get('channel_thumb', '') + 'channel_thumb': media_info.get('channel_thumb', ''), + 'marker_credits_first': marker_credits_first, + 'marker_credits_final': marker_credits_final } # logger.debug("Tautulli ActivityProcessor :: Writing sessionKey %s session_history_metadata transaction..." From b1dd28e39b648482cb55a0c6b8a827760b4aea90 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Mon, 20 Feb 2023 16:33:19 -0800 Subject: [PATCH 159/583] Add setting to change video watched completion behaviour --- data/interfaces/default/settings.html | 14 ++++++++++++++ plexpy/config.py | 4 +++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index f7c24211..fd234da2 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -213,6 +213,20 @@

    Set the percentage for a music track to be considered as listened. Minimum 50, Maximum 95.

    +
    + +
    +
    + +
    +
    +

    Decide whether to use end credits markers to determine the 'watched' state of video items. When markers are not available the selected threshold percentage will be used.

    +

    diff --git a/plexpy/config.py b/plexpy/config.py index 544cf79a..f094e88e 100644 --- a/plexpy/config.py +++ b/plexpy/config.py @@ -199,6 +199,7 @@ _CONFIG_DEFINITIONS = { 'UPGRADE_FLAG': (int, 'Advanced', 0), 'VERBOSE_LOGS': (int, 'Advanced', 1), 'VERIFY_SSL_CERT': (bool_int, 'Advanced', 1), + 'WATCHED_MARKER': (int, 'Monitoring', 3), 'WEBSOCKET_MONITOR_PING_PONG': (int, 'Advanced', 0), 'WEBSOCKET_CONNECTION_ATTEMPTS': (int, 'Advanced', 5), 'WEBSOCKET_CONNECTION_TIMEOUT': (int, 'Advanced', 5), @@ -298,7 +299,8 @@ SETTINGS = [ 'REFRESH_USERS_INTERVAL', 'SHOW_ADVANCED_SETTINGS', 'TIME_FORMAT', - 'TV_WATCHED_PERCENT' + 'TV_WATCHED_PERCENT', + 'WATCHED_MARKER' ] CHECKED_SETTINGS = [ From b2b12044e3342ccc77dbbaa9c90771e8e13a38dd Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Mon, 20 Feb 2023 17:14:35 -0800 Subject: [PATCH 160/583] Trigger on_watched based on credits markers --- plexpy/activity_handler.py | 92 +++++++++++++++++++++++++------------- plexpy/helpers.py | 40 +++++++++++++++++ 2 files changed, 101 insertions(+), 31 deletions(-) diff --git a/plexpy/activity_handler.py b/plexpy/activity_handler.py index 8933c1ac..851372d5 100644 --- a/plexpy/activity_handler.py +++ b/plexpy/activity_handler.py @@ -110,11 +110,13 @@ class ActivityHandler(object): self.set_session_state() self.get_db_session() - def set_session_state(self): - self.ap.set_session_state(session_key=self.session_key, - state=self.state, - view_offset=self.view_offset, - stopped=helpers.timestamp()) + def set_session_state(self, view_offset=None): + self.ap.set_session_state( + session_key=self.session_key, + state=self.state, + view_offset=view_offset or self.view_offset, + stopped=helpers.timestamp() + ) def put_notification(self, notify_action, **kwargs): notification = {'stream_data': self.db_session.copy(), 'notify_action': notify_action} @@ -246,26 +248,34 @@ class ActivityHandler(object): self.put_notification('on_change') def on_intro(self, marker): - if self.get_live_session(): - logger.debug("Tautulli ActivityHandler :: Session %s reached intro marker." % str(self.session_key)) + logger.debug("Tautulli ActivityHandler :: Session %s reached intro marker." % str(self.session_key)) - self.put_notification('on_intro', marker=marker) + self.set_session_state(view_offset=marker['start_time_offset']) + + self.put_notification('on_intro', marker=marker) def on_commercial(self, marker): - if self.get_live_session(): - logger.debug("Tautulli ActivityHandler :: Session %s reached commercial marker." % str(self.session_key)) + logger.debug("Tautulli ActivityHandler :: Session %s reached commercial marker." % str(self.session_key)) - self.put_notification('on_commercial', marker=marker) + self.set_session_state(view_offset=marker['start_time_offset']) + + self.put_notification('on_commercial', marker=marker) def on_credits(self, marker): - if self.get_live_session(): - logger.debug("Tautulli ActivityHandler :: Session %s reached credits marker." % str(self.session_key)) + logger.debug("Tautulli ActivityHandler :: Session %s reached credits marker." % str(self.session_key)) - self.put_notification('on_credits', marker=marker) + self.set_session_state(view_offset=marker['start_time_offset']) - def on_watched(self): + self.put_notification('on_credits', marker=marker) + + def on_watched(self, marker=None): logger.debug("Tautulli ActivityHandler :: Session %s watched." % str(self.session_key)) + if marker: + self.set_session_state(view_offset=marker['start_time_offset']) + else: + self.update_db_session() + watched_notifiers = notification_handler.get_notify_state_enabled( session=self.db_session, notify_action='on_watched', notified=False) @@ -368,38 +378,58 @@ class ActivityHandler(object): if self.db_session['marker'] != marker_idx: self.ap.set_marker(session_key=self.session_key, marker_idx=marker_idx, marker_type=marker['type']) - callback_func = getattr(self, 'on_{}'.format(marker['type'])) if self.view_offset < marker['start_time_offset']: # Schedule a callback for the exact offset of the marker schedule_callback( 'session_key-{}-marker-{}'.format(self.session_key, marker_idx), - func=callback_func, + func=self._marker_callback, args=[marker], milliseconds=marker['start_time_offset'] - self.view_offset ) else: - callback_func(marker) + self._marker_callback(marker) break if not marker_flag: self.ap.set_marker(session_key=self.session_key, marker_idx=0) - def check_watched(self): - # Monitor if the stream has reached the watch percentage for notifications - if not self.db_session['watched'] and self.timeline['state'] != 'buffering': - progress_percent = helpers.get_percent(self.timeline['viewOffset'], self.db_session['duration']) - watched_percent = { - 'movie': plexpy.CONFIG.MOVIE_WATCHED_PERCENT, - 'episode': plexpy.CONFIG.TV_WATCHED_PERCENT, - 'track': plexpy.CONFIG.MUSIC_WATCHED_PERCENT, - 'clip': plexpy.CONFIG.TV_WATCHED_PERCENT - } + def _marker_callback(self, marker): + if self.get_live_session(): + # Reset ActivityProcessor object for new database thread + self.ap = activity_processor.ActivityProcessor() - if progress_percent >= watched_percent.get(self.db_session['media_type'], 101): - self.ap.set_watched(session_key=self.session_key) - self.on_watched() + if marker['type'] == 'intro': + self.on_intro(marker) + elif marker['type'] == 'commercial': + self.on_commercial(marker) + elif marker['type'] == 'credits': + self.on_credits(marker) + + if not self.db_session['watched']: + if marker['final'] and plexpy.CONFIG.WATCHED_MARKER == 1: + self._marker_watched(marker) + elif marker['first'] and (plexpy.CONFIG.WATCHED_MARKER in (2, 3)): + self._marker_watched(marker) + + def _marker_watched(self, marker): + if not self.db_session['watched']: + self._watched_callback(marker) + + def check_watched(self): + if plexpy.CONFIG.WATCHED_MARKER == 1 or plexpy.CONFIG.WATCHED_MARKER == 2: + return + + # Monitor if the stream has reached the watch percentage for notifications + if not self.db_session['watched'] and self.state != 'buffering' and helpers.check_watched( + self.db_session['media_type'], self.view_offset, self.db_session['duration'] + ): + self._watched_callback() + + def _watched_callback(self, marker=None): + self.ap.set_watched(session_key=self.session_key) + self.on_watched(marker) class TimelineHandler(object): diff --git a/plexpy/helpers.py b/plexpy/helpers.py index b0995849..89b047fd 100644 --- a/plexpy/helpers.py +++ b/plexpy/helpers.py @@ -1733,3 +1733,43 @@ def short_season(title): if title.startswith('Season ') and title[7:].isdigit(): return 'S%s' % title[7:] return title + + +def get_first_final_marker(markers): + first = None + final = None + for marker in markers: + if marker['first']: + first = marker + if marker['final']: + final = marker + return first, final + + +def check_watched(media_type, view_offset, duration, marker_credits_first=None, marker_credits_final=None): + if isinstance(marker_credits_first, dict): + marker_credits_first = marker_credits_first['start_time_offset'] + if isinstance(marker_credits_final, dict): + marker_credits_final = marker_credits_final['start_time_offset'] + + view_offset = cast_to_int(view_offset) + duration = cast_to_int(duration) + + watched_percent = { + 'movie': plexpy.CONFIG.MOVIE_WATCHED_PERCENT, + 'episode': plexpy.CONFIG.TV_WATCHED_PERCENT, + 'track': plexpy.CONFIG.MUSIC_WATCHED_PERCENT, + 'clip': plexpy.CONFIG.TV_WATCHED_PERCENT + } + threshold = watched_percent.get(media_type, 0) / 100 * duration + if not threshold: + return False + + if plexpy.CONFIG.WATCHED_MARKER == 1 and marker_credits_final: + return view_offset >= marker_credits_final + elif plexpy.CONFIG.WATCHED_MARKER == 2 and marker_credits_first: + return view_offset >= marker_credits_first + elif plexpy.CONFIG.WATCHED_MARKER == 3 and marker_credits_first: + return view_offset >= min(threshold, marker_credits_first) + else: + return view_offset >= threshold From c5005c1ea9ec1575abd36fa51dfc9cb1d4dd8159 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Mon, 20 Feb 2023 16:36:31 -0800 Subject: [PATCH 161/583] Group watched history sessions based on credits markers --- plexpy/activity_processor.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/plexpy/activity_processor.py b/plexpy/activity_processor.py index d55c6738..71b6e3e0 100644 --- a/plexpy/activity_processor.py +++ b/plexpy/activity_processor.py @@ -327,7 +327,7 @@ class ActivityProcessor(object): # Get the last insert row id last_id = self.db.last_insert_id() new_session = prev_session = None - prev_progress_percent = media_watched_percent = 0 + watched = False if session['live']: # Check if we should group the session, select the last guid from the user @@ -369,12 +369,11 @@ class ActivityProcessor(object): 'view_offset': result[1]['view_offset'], 'reference_id': result[1]['reference_id']} - watched_percent = {'movie': plexpy.CONFIG.MOVIE_WATCHED_PERCENT, - 'episode': plexpy.CONFIG.TV_WATCHED_PERCENT, - 'track': plexpy.CONFIG.MUSIC_WATCHED_PERCENT - } - prev_progress_percent = helpers.get_percent(prev_session['view_offset'], session['duration']) - media_watched_percent = watched_percent.get(session['media_type'], 0) + marker_first, marker_final = helpers.get_first_final_marker(metadata['markers']) + watched = helpers.check_watched( + session['media_type'], session['view_offset'], session['duration'], + marker_first, marker_final + ) query = 'UPDATE session_history SET reference_id = ? WHERE id = ? ' @@ -384,8 +383,7 @@ class ActivityProcessor(object): # else set the reference_id to the new id if prev_session is None and new_session is None: args = [last_id, last_id] - elif prev_progress_percent < media_watched_percent and \ - prev_session['view_offset'] <= new_session['view_offset'] or \ + elif watched and prev_session['view_offset'] <= new_session['view_offset'] or \ session['live'] and prev_session['guid'] == new_session['guid']: args = [prev_session['reference_id'], new_session['id']] else: From 928e1d4b5edb2adec3049e38cccf3717463f65c2 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Mon, 20 Feb 2023 16:37:37 -0800 Subject: [PATCH 162/583] History table watched status based on credits markers --- plexpy/datafactory.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/plexpy/datafactory.py b/plexpy/datafactory.py index cf55a2c0..e0e9fdee 100644 --- a/plexpy/datafactory.py +++ b/plexpy/datafactory.py @@ -99,8 +99,9 @@ class DataFactory(object): 'MIN(started) AS started', 'MAX(stopped) AS stopped', 'SUM(CASE WHEN stopped > 0 THEN (stopped - started) ELSE 0 END) - \ - SUM(CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) AS duration', + SUM(CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) AS play_duration', 'SUM(CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) AS paused_counter', + 'session_history.view_offset', 'session_history.user_id', 'session_history.user', '(CASE WHEN users.friendly_name IS NULL OR TRIM(users.friendly_name) = "" \ @@ -139,6 +140,9 @@ class DataFactory(object): 'MAX((CASE WHEN (view_offset IS NULL OR view_offset = "") THEN 0.1 ELSE view_offset * 1.0 END) / \ (CASE WHEN (session_history_metadata.duration IS NULL OR session_history_metadata.duration = "") \ THEN 1.0 ELSE session_history_metadata.duration * 1.0 END) * 100) AS percent_complete', + 'session_history_metadata.duration', + 'session_history_metadata.marker_credits_first', + 'session_history_metadata.marker_credits_final', 'session_history_media_info.transcode_decision', 'COUNT(*) AS group_count', 'GROUP_CONCAT(session_history.id) AS group_ids', @@ -159,8 +163,9 @@ class DataFactory(object): 'started', 'stopped', 'SUM(CASE WHEN stopped > 0 THEN (stopped - started) ELSE (strftime("%s", "now") - started) END) - \ - SUM(CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) AS duration', + SUM(CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) AS play_duration', 'SUM(CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) AS paused_counter', + 'view_offset', 'user_id', 'user', '(CASE WHEN friendly_name IS NULL OR TRIM(friendly_name) = "" \ @@ -198,6 +203,9 @@ class DataFactory(object): 'MAX((CASE WHEN (view_offset IS NULL OR view_offset = "") THEN 0.1 ELSE view_offset * 1.0 END) / \ (CASE WHEN (duration IS NULL OR duration = "") \ THEN 1.0 ELSE duration * 1.0 END) * 100) AS percent_complete', + 'duration', + 'NULL AS marker_credits_first', + 'NULL AS marker_credits_final', 'transcode_decision', 'NULL AS group_count', 'NULL AS group_ids', @@ -262,7 +270,7 @@ class DataFactory(object): item['user_thumb'] = users_lookup.get(item['user_id']) - filter_duration += int(item['duration']) + filter_duration += int(item['play_duration']) if item['media_type'] == 'episode' and item['parent_thumb']: thumb = item['parent_thumb'] @@ -274,7 +282,10 @@ class DataFactory(object): if item['live']: item['percent_complete'] = 100 - if item['percent_complete'] >= watched_percent[item['media_type']]: + if helpers.check_watched( + item['media_type'], item['view_offset'], item['duration'], + item['marker_credits_first'], item['marker_credits_final'] + ): watched_status = 1 elif item['percent_complete'] >= watched_percent[item['media_type']] / 2.0: watched_status = 0.5 @@ -297,7 +308,7 @@ class DataFactory(object): 'date': item['date'], 'started': item['started'], 'stopped': item['stopped'], - 'duration': item['duration'], + 'duration': item['play_duration'], 'paused_counter': item['paused_counter'], 'user_id': item['user_id'], 'user': item['user'], From 2a1bf7847b32f01059d990f564ce89777ed7751e Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Mon, 20 Feb 2023 18:35:55 -0800 Subject: [PATCH 163/583] Last watched statistics card based on credits markers --- plexpy/datafactory.py | 60 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 12 deletions(-) diff --git a/plexpy/datafactory.py b/plexpy/datafactory.py index e0e9fdee..e51b8a46 100644 --- a/plexpy/datafactory.py +++ b/plexpy/datafactory.py @@ -382,10 +382,6 @@ class DataFactory(object): if user_id: where_id += 'AND session_history.user_id = %s ' % user_id - movie_watched_percent = plexpy.CONFIG.MOVIE_WATCHED_PERCENT - tv_watched_percent = plexpy.CONFIG.TV_WATCHED_PERCENT - music_watched_percent = plexpy.CONFIG.MUSIC_WATCHED_PERCENT - group_by = 'session_history.reference_id' if grouping else 'session_history.id' sort_type = 'total_duration' if stats_type == 'duration' else 'total_plays' @@ -919,6 +915,43 @@ class DataFactory(object): 'rows': session.mask_session_info(top_platform, mask_metadata=False)}) elif stat == 'last_watched': + + movie_watched_percent = plexpy.CONFIG.MOVIE_WATCHED_PERCENT + tv_watched_percent = plexpy.CONFIG.TV_WATCHED_PERCENT + + if plexpy.CONFIG.WATCHED_MARKER == 1: + watched_threshold = ( + '(CASE WHEN shm.marker_credits_final IS NULL ' + 'THEN sh._duration * (CASE WHEN sh.media_type = "movie" THEN %d ELSE %d END) / 100.0 ' + 'ELSE shm.marker_credits_final END) ' + 'AS watched_threshold' + ) % (movie_watched_percent, tv_watched_percent) + watched_where = '_view_offset >= watched_threshold' + elif plexpy.CONFIG.WATCHED_MARKER == 2: + watched_threshold = ( + '(CASE WHEN shm.marker_credits_first IS NULL ' + 'THEN sh._duration * (CASE WHEN sh.media_type = "movie" THEN %d ELSE %d END) / 100.0 ' + 'ELSE shm.marker_credits_first END) ' + 'AS watched_threshold' + ) % (movie_watched_percent, tv_watched_percent) + watched_where = '_view_offset >= watched_threshold' + elif plexpy.CONFIG.WATCHED_MARKER == 3: + watched_threshold = ( + 'MIN(' + '(CASE WHEN shm.marker_credits_first IS NULL ' + 'THEN sh._duration * (CASE WHEN sh.media_type = "movie" THEN %d ELSE %d END) / 100.0 ' + 'ELSE shm.marker_credits_first END), ' + 'sh._duration * (CASE WHEN sh.media_type = "movie" THEN %d ELSE %d END) / 100.0) ' + 'AS watched_threshold' + ) % (movie_watched_percent, tv_watched_percent, movie_watched_percent, tv_watched_percent) + watched_where = '_view_offset >= watched_threshold' + else: + watched_threshold = 'NULL AS watched_threshold' + watched_where = ( + 'sh.media_type == "movie" AND percent_complete >= %d ' + 'OR sh.media_type == "episode" AND percent_complete >= %d' + ) % (movie_watched_percent, tv_watched_percent) + last_watched = [] try: query = 'SELECT sh.id, shm.title, shm.grandparent_title, shm.full_title, shm.year, ' \ @@ -929,22 +962,25 @@ class DataFactory(object): '(CASE WHEN u.friendly_name IS NULL OR TRIM(u.friendly_name) = ""' \ ' THEN u.username ELSE u.friendly_name END) ' \ ' AS friendly_name, ' \ - 'MAX(sh.started) AS last_watch, ' \ - '((CASE WHEN sh.view_offset IS NULL THEN 0.1 ELSE sh.view_offset * 1.0 END) / ' \ - ' (CASE WHEN shm.duration IS NULL THEN 1.0 ELSE shm.duration * 1.0 END) * 100) ' \ - ' AS percent_complete ' \ - 'FROM (SELECT *, MAX(id) FROM session_history ' \ + 'MAX(sh.started) AS last_watch, sh._view_offset, sh._duration, ' \ + '(sh._view_offset / sh._duration * 100) AS percent_complete, ' \ + '%s ' \ + 'FROM (SELECT *, MAX(session_history.id), ' \ + ' (CASE WHEN view_offset IS NULL THEN 0.1 ELSE view_offset * 1.0 END) AS _view_offset, ' \ + ' (CASE WHEN duration IS NULL THEN 1.0 ELSE duration * 1.0 END) AS _duration ' \ + ' FROM session_history ' \ + ' JOIN session_history_metadata ON session_history_metadata.id = session_history.id ' \ ' WHERE session_history.stopped >= %s ' \ ' AND (session_history.media_type = "movie" ' \ ' OR session_history.media_type = "episode") %s ' \ ' GROUP BY %s) AS sh ' \ 'JOIN session_history_metadata AS shm ON shm.id = sh.id ' \ 'LEFT OUTER JOIN users AS u ON sh.user_id = u.user_id ' \ - 'WHERE sh.media_type == "movie" AND percent_complete >= %s ' \ - ' OR sh.media_type == "episode" AND percent_complete >= %s ' \ + 'WHERE %s ' \ 'GROUP BY sh.id ' \ 'ORDER BY last_watch DESC ' \ - 'LIMIT %s OFFSET %s' % (timestamp, where_id, group_by, movie_watched_percent, tv_watched_percent, + 'LIMIT %s OFFSET %s' % (watched_threshold, + timestamp, where_id, group_by, watched_where, stats_count, stats_start) result = monitor_db.select(query) except Exception as e: From ebe570d42f9a2e70a7bdbb7db2cdcf8577fc70f5 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Tue, 21 Feb 2023 11:12:56 -0800 Subject: [PATCH 164/583] Allow setting a custom Pushover sound * Closes #2005 --- data/interfaces/default/css/tautulli.css | 2 - .../interfaces/default/newsletter_config.html | 4 ++ data/interfaces/default/notifier_config.html | 10 +++ plexpy/newsletters.py | 3 +- plexpy/notifiers.py | 69 ++++++++++--------- 5 files changed, 54 insertions(+), 34 deletions(-) diff --git a/data/interfaces/default/css/tautulli.css b/data/interfaces/default/css/tautulli.css index ac99ae76..5f1d90a0 100644 --- a/data/interfaces/default/css/tautulli.css +++ b/data/interfaces/default/css/tautulli.css @@ -79,7 +79,6 @@ select.form-control { color: #eee !important; border: 0px solid #444 !important; background: #555 !important; - padding: 1px 2px; transition: background-color .3s; } .selectize-control.form-control .selectize-input { @@ -87,7 +86,6 @@ select.form-control { align-items: center; flex-wrap: wrap; margin-bottom: 4px; - padding-left: 5px; } .selectize-control.form-control.selectize-pms-ip .selectize-input { padding-left: 12px !important; diff --git a/data/interfaces/default/newsletter_config.html b/data/interfaces/default/newsletter_config.html index dc6de294..10583707 100644 --- a/data/interfaces/default/newsletter_config.html +++ b/data/interfaces/default/newsletter_config.html @@ -142,8 +142,10 @@

    + % if item['select_all']: + % endif % if isinstance(item['select_options'], dict): % for section, options in item['select_options'].items(): @@ -145,7 +147,9 @@ % endfor % else: + % if item['select_all']: + % endif % for option in sorted(item['select_options'], key=lambda x: x['text'].lower()): % endfor @@ -718,6 +722,12 @@ $('#pushover_priority').change( function () { pushoverPriority(); }); + + var $pushover_sound = $('#pushover_sound').selectize({ + create: true + }); + var pushover_sound = $pushover_sound[0].selectize; + pushover_sound.setValue(${json.dumps(next((c['value'] for c in notifier['config_options'] if c['name'] == 'pushover_sound'), [])) | n}); % elif notifier['agent_name'] == 'plexmobileapp': var $plexmobileapp_user_ids = $('#plexmobileapp_user_ids').selectize({ diff --git a/plexpy/newsletters.py b/plexpy/newsletters.py index 902eb69b..fdc1c5a8 100644 --- a/plexpy/newsletters.py +++ b/plexpy/newsletters.py @@ -971,7 +971,8 @@ class RecentlyAdded(Newsletter): 'description': 'Select the libraries to include in the newsletter.', 'name': 'newsletter_config_incl_libraries', 'input_type': 'selectize', - 'select_options': self._get_sections_options() + 'select_options': self._get_sections_options(), + 'select_all': True } ] diff --git a/plexpy/notifiers.py b/plexpy/notifiers.py index f0f02bf0..1a747be9 100644 --- a/plexpy/notifiers.py +++ b/plexpy/notifiers.py @@ -1435,21 +1435,24 @@ class EMAIL(Notifier): 'name': 'email_to', 'description': 'The email address(es) of the recipients.', 'input_type': 'selectize', - 'select_options': user_emails_to + 'select_options': user_emails_to, + 'select_all': True }, {'label': 'CC', 'value': self.config['cc'], 'name': 'email_cc', 'description': 'The email address(es) to CC.', 'input_type': 'selectize', - 'select_options': user_emails_cc + 'select_options': user_emails_cc, + 'select_all': True }, {'label': 'BCC', 'value': self.config['bcc'], 'name': 'email_bcc', 'description': 'The email address(es) to BCC.', 'input_type': 'selectize', - 'select_options': user_emails_bcc + 'select_options': user_emails_bcc, + 'select_all': True }, {'label': 'SMTP Server', 'value': self.config['smtp_server'], @@ -3216,31 +3219,34 @@ class PUSHOVER(Notifier): return self.make_request('https://api.pushover.net/1/messages.json', headers=headers, data=data, files=files) def get_sounds(self): - sounds = { - '': '', - 'alien': 'Alien Alarm (long)', - 'bike': 'Bike', - 'bugle': 'Bugle', - 'cashregister': 'Cash Register', - 'classical': 'Classical', - 'climb': 'Climb (long)', - 'cosmic': 'Cosmic', - 'echo': 'Pushover Echo (long)', - 'falling': 'Falling', - 'gamelan': 'Gamelan', - 'incoming': 'Incoming', - 'intermission': 'Intermission', - 'magic': 'Magic', - 'mechanical': 'Mechanical', - 'none': 'None (silent)', - 'persistent': 'Persistent (long)', - 'pianobar': 'Piano Bar', - 'pushover': 'Pushover (default)', - 'siren': 'Siren', - 'spacealarm': 'Space Alarm', - 'tugboat': 'Tug Boat', - 'updown': 'Up Down (long)' - } + sounds = [ + {'value': '', 'text': ''}, + {'value': 'alien', 'text': 'Alien Alarm (long)'}, + {'value': 'bike', 'text': 'Bike'}, + {'value': 'bugle', 'text': 'Bugle'}, + {'value': 'cashregister', 'text': 'Cash Register'}, + {'value': 'classical', 'text': 'Classical'}, + {'value': 'climb', 'text': 'Climb (long)'}, + {'value': 'cosmic', 'text': 'Cosmic'}, + {'value': 'echo', 'text': 'Pushover Echo (long)'}, + {'value': 'falling', 'text': 'Falling'}, + {'value': 'gamelan', 'text': 'Gamelan'}, + {'value': 'incoming', 'text': 'Incoming'}, + {'value': 'intermission', 'text': 'Intermission'}, + {'value': 'magic', 'text': 'Magic'}, + {'value': 'mechanical', 'text': 'Mechanical'}, + {'value': 'none', 'text': 'None (silent)'}, + {'value': 'persistent', 'text': 'Persistent (long)'}, + {'value': 'pianobar', 'text': 'Piano Bar'}, + {'value': 'pushover', 'text': 'Pushover (default)'}, + {'value': 'siren', 'text': 'Siren'}, + {'value': 'spacealarm', 'text': 'Space Alarm'}, + {'value': 'tugboat', 'text': 'Tug Boat'}, + {'value': 'updown', 'text': 'Up Down (long)'}, + {'value': 'vibrate', 'text': 'Vibrate Only'}, + ] + if self.config['sound'] not in [s['value'] for s in sounds]: + sounds.append({'value': self.config['sound'], 'text': self.config['sound']}) return sounds @@ -3281,9 +3287,10 @@ class PUSHOVER(Notifier): {'label': 'Sound', 'value': self.config['sound'], 'name': 'pushover_sound', - 'description': 'Set the notification sound. Leave blank for the default sound.', - 'input_type': 'select', - 'select_options': self.get_sounds() + 'description': 'Select a notification sound or enter a custom sound name. Leave blank for the default sound.', + 'input_type': 'selectize', + 'select_options': self.get_sounds(), + 'select_all': False }, {'label': 'Priority', 'value': self.config['priority'], From e6d1712afdec09630c1a141afc47a826175d51b9 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Sat, 25 Feb 2023 16:35:48 -0800 Subject: [PATCH 165/583] Fix styling on collection info pages --- data/interfaces/default/info.html | 12 ++--- .../default/info_children_list.html | 48 ++++++++++++++++++- .../default/js/tables/collections_table.js | 7 ++- plexpy/pmsconnect.py | 2 +- 4 files changed, 60 insertions(+), 9 deletions(-) diff --git a/data/interfaces/default/info.html b/data/interfaces/default/info.html index a7acb11b..05bccf52 100644 --- a/data/interfaces/default/info.html +++ b/data/interfaces/default/info.html @@ -213,7 +213,7 @@ DOCUMENTATION :: END % if _session['user_group'] == 'admin': % endif - % elif data['media_type'] in ('artist', 'album', 'track', 'playlist', 'photo_album', 'photo', 'clip'): + % elif data['media_type'] in ('artist', 'album', 'track', 'playlist', 'photo_album', 'photo', 'clip') or data['sub_media_type'] in ('artist', 'album', 'track'):
    @@ -283,14 +283,14 @@ DOCUMENTATION :: END padding_height = '' if data['media_type'] == 'movie' or data['live']: padding_height = 'height: 305px;' - elif data['media_type'] in ('show', 'season', 'collection'): - padding_height = 'height: 270px;' - elif data['media_type'] == 'episode': - padding_height = 'height: 70px;' - elif data['media_type'] in ('artist', 'album', 'playlist', 'photo_album', 'photo'): + elif data['media_type'] in ('artist', 'album', 'playlist', 'photo_album', 'photo') or data['sub_media_type'] in ('artist', 'album', 'track'): padding_height = 'height: 150px;' elif data['media_type'] in ('track', 'clip'): padding_height = 'height: 180px;' + elif data['media_type'] == 'episode': + padding_height = 'height: 70px;' + elif data['media_type'] in ('show', 'season', 'collection'): + padding_height = 'height: 270px;' %> +
    +

    + ${child['title']} +

    + % if media_type == 'collection': +

    + ${child['parent_title']} +

    + % endif +
    % elif child['media_type'] == 'episode': + % elif child['media_type'] == 'artist': + +
    +
    + % if _session['user_group'] == 'admin': + + % endif +
    +
    + % elif child['media_type'] == 'album': @@ -193,6 +226,11 @@ DOCUMENTATION :: END

    ${child['title']}

    + % if media_type == 'collection': +

    + ${child['parent_title']} +

    + % endif
    % elif child['media_type'] == 'track': <% e = 'even' if loop.index % 2 == 0 else 'odd' %> @@ -205,7 +243,15 @@ DOCUMENTATION :: END ${child['title']} - % if child['original_title']: + % if media_type == 'collection': + - + + + ${child['grandparent_title']} + + + (${child['parent_title']}) + % elif child['original_title']: - ${child['original_title']} % endif diff --git a/data/interfaces/default/js/tables/collections_table.js b/data/interfaces/default/js/tables/collections_table.js index b768c2d3..91eb68ab 100644 --- a/data/interfaces/default/js/tables/collections_table.js +++ b/data/interfaces/default/js/tables/collections_table.js @@ -32,7 +32,12 @@ collections_table_options = { if (rowData['smart']) { smart = ' ' } - var thumb_popover = '' + rowData['title'] + ''; + console.log(rowData['subtype']) + if (rowData['subtype'] === 'artist' || rowData['subtype'] === 'album' || rowData['subtype'] === 'track') { + var thumb_popover = '' + rowData['title'] + ''; + } else { + var thumb_popover = '' + rowData['title'] + ''; + } $(td).html(smart + '' + thumb_popover + ''); } }, diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py index 347de513..1d161a04 100644 --- a/plexpy/pmsconnect.py +++ b/plexpy/pmsconnect.py @@ -2545,7 +2545,7 @@ class PmsConnect(object): children_list.append(children_output) output = {'children_count': helpers.cast_to_int(helpers.get_xml_attr(xml_head[0], 'size')), - 'children_type': helpers.get_xml_attr(xml_head[0], 'viewGroup'), + 'children_type': helpers.get_xml_attr(xml_head[0], 'viewGroup') or (children_list[0]['media_type'] if children_list else ''), 'title': helpers.get_xml_attr(xml_head[0], 'title2'), 'children_list': children_list } From ecb6d8b74329ea07ca651a4b83efc9a024c3cd0a Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Sat, 25 Feb 2023 16:43:14 -0800 Subject: [PATCH 166/583] Move track artist to details tag on info page --- data/interfaces/default/info.html | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/data/interfaces/default/info.html b/data/interfaces/default/info.html index 05bccf52..dbce734b 100644 --- a/data/interfaces/default/info.html +++ b/data/interfaces/default/info.html @@ -267,7 +267,7 @@ DOCUMENTATION :: END

    ${data['parent_title']}

    ${data['title']}

    % elif data['media_type'] == 'track': -

    ${data['original_title'] or data['grandparent_title']}

    +

    ${data['grandparent_title']}

    ${data['parent_title']} - ${data['title']}

    % elif data['media_type'] in ('photo', 'clip'): @@ -369,6 +369,11 @@ DOCUMENTATION :: END Studio ${data['studio']} % endif
    +
    + % if data['media_type'] == 'track' and data['original_title']: + Track Artists ${data['original_title']} + % endif +
    % if data['media_type'] == 'movie': Year ${data['year']} @@ -812,7 +817,7 @@ DOCUMENTATION :: END % elif data['media_type'] == 'album': ${data['parent_title']}
    ${data['title']} % elif data['media_type'] == 'track': - ${data['original_title'] or data['grandparent_title']}
    ${data['title']}
    ${data['parent_title']} + ${data['grandparent_title']}
    ${data['title']}
    ${data['parent_title']} % endif

    From 42eeb90532289f30a19ee4bdb1c79ffc0e4b2cf4 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Sun, 26 Feb 2023 15:09:12 -0800 Subject: [PATCH 167/583] Add ga4mp library * Remove UniversalAnalytics --- lib/UniversalAnalytics/HTTPLog.py | 121 -------- lib/UniversalAnalytics/Tracker.py | 424 ----------------------------- lib/UniversalAnalytics/__init__.py | 1 - lib/ga4mp/__init__.py | 3 + lib/ga4mp/event.py | 44 +++ lib/ga4mp/ga4mp.py | 416 ++++++++++++++++++++++++++++ lib/ga4mp/item.py | 11 + lib/ga4mp/store.py | 116 ++++++++ lib/ga4mp/utils.py | 392 ++++++++++++++++++++++++++ requirements.txt | 1 + 10 files changed, 983 insertions(+), 546 deletions(-) delete mode 100644 lib/UniversalAnalytics/HTTPLog.py delete mode 100644 lib/UniversalAnalytics/Tracker.py delete mode 100644 lib/UniversalAnalytics/__init__.py create mode 100644 lib/ga4mp/__init__.py create mode 100644 lib/ga4mp/event.py create mode 100644 lib/ga4mp/ga4mp.py create mode 100644 lib/ga4mp/item.py create mode 100644 lib/ga4mp/store.py create mode 100644 lib/ga4mp/utils.py diff --git a/lib/UniversalAnalytics/HTTPLog.py b/lib/UniversalAnalytics/HTTPLog.py deleted file mode 100644 index a8fc906d..00000000 --- a/lib/UniversalAnalytics/HTTPLog.py +++ /dev/null @@ -1,121 +0,0 @@ -#!/usr/bin/python -############################################################################### -# Formatting filter for urllib2's HTTPHandler(debuglevel=1) output -# Copyright (c) 2013, Analytics Pros -# -# This project is free software, distributed under the BSD license. -# Analytics Pros offers consulting and integration services if your firm needs -# assistance in strategy, implementation, or auditing existing work. -############################################################################### - - -import sys, re, os -from io import StringIO - - - -class BufferTranslator(object): - """ Provides a buffer-compatible interface for filtering buffer content. - """ - parsers = [] - - def __init__(self, output): - self.output = output - self.encoding = getattr(output, 'encoding', None) - - def write(self, content): - content = self.translate(content) - self.output.write(content) - - - @staticmethod - def stripslashes(content): - return content.decode('string_escape') - - @staticmethod - def addslashes(content): - return content.encode('string_escape') - - def translate(self, line): - for pattern, method in self.parsers: - match = pattern.match(line) - if match: - return method(match) - - return line - - - -class LineBufferTranslator(BufferTranslator): - """ Line buffer implementation supports translation of line-format input - even when input is not already line-buffered. Caches input until newlines - occur, and then dispatches translated input to output buffer. - """ - def __init__(self, *a, **kw): - self._linepending = [] - super(LineBufferTranslator, self).__init__(*a, **kw) - - def write(self, _input): - lines = _input.splitlines(True) - for i in range(0, len(lines)): - last = i - if lines[i].endswith('\n'): - prefix = len(self._linepending) and ''.join(self._linepending) or '' - self.output.write(self.translate(prefix + lines[i])) - del self._linepending[0:] - last = -1 - - if last >= 0: - self._linepending.append(lines[ last ]) - - - def __del__(self): - if len(self._linepending): - self.output.write(self.translate(''.join(self._linepending))) - - -class HTTPTranslator(LineBufferTranslator): - """ Translates output from |urllib2| HTTPHandler(debuglevel = 1) into - HTTP-compatible, readible text structures for human analysis. - """ - - RE_LINE_PARSER = re.compile(r'^(?:([a-z]+):)\s*(\'?)([^\r\n]*)\2(?:[\r\n]*)$') - RE_LINE_BREAK = re.compile(r'(\r?\n|(?:\\r)?\\n)') - RE_HTTP_METHOD = re.compile(r'^(POST|GET|HEAD|DELETE|PUT|TRACE|OPTIONS)') - RE_PARAMETER_SPACER = re.compile(r'&([a-z0-9]+)=') - - @classmethod - def spacer(cls, line): - return cls.RE_PARAMETER_SPACER.sub(r' &\1= ', line) - - def translate(self, line): - - parsed = self.RE_LINE_PARSER.match(line) - - if parsed: - value = parsed.group(3) - stage = parsed.group(1) - - if stage == 'send': # query string is rendered here - return '\n# HTTP Request:\n' + self.stripslashes(value) - elif stage == 'reply': - return '\n\n# HTTP Response:\n' + self.stripslashes(value) - elif stage == 'header': - return value + '\n' - else: - return value - - - return line - - -def consume(outbuffer = None): # Capture standard output - sys.stdout = HTTPTranslator(outbuffer or sys.stdout) - return sys.stdout - - -if __name__ == '__main__': - consume(sys.stdout).write(sys.stdin.read()) - print('\n') - -# vim: set nowrap tabstop=4 shiftwidth=4 softtabstop=0 expandtab textwidth=0 filetype=python foldmethod=indent foldcolumn=4 diff --git a/lib/UniversalAnalytics/Tracker.py b/lib/UniversalAnalytics/Tracker.py deleted file mode 100644 index b7d9476e..00000000 --- a/lib/UniversalAnalytics/Tracker.py +++ /dev/null @@ -1,424 +0,0 @@ -from future.moves.urllib.request import urlopen, build_opener, install_opener -from future.moves.urllib.request import Request, HTTPSHandler -from future.moves.urllib.error import URLError, HTTPError -from future.moves.urllib.parse import urlencode - -import random -import datetime -import time -import uuid -import hashlib -import socket - - -def generate_uuid(basedata=None): - """ Provides a _random_ UUID with no input, or a UUID4-format MD5 checksum of any input data provided """ - if basedata is None: - return str(uuid.uuid4()) - elif isinstance(basedata, str): - checksum = hashlib.md5(str(basedata).encode('utf-8')).hexdigest() - return '%8s-%4s-%4s-%4s-%12s' % ( - checksum[0:8], checksum[8:12], checksum[12:16], checksum[16:20], checksum[20:32]) - - -class Time(datetime.datetime): - """ Wrappers and convenience methods for processing various time representations """ - - @classmethod - def from_unix(cls, seconds, milliseconds=0): - """ Produce a full |datetime.datetime| object from a Unix timestamp """ - base = list(time.gmtime(seconds))[0:6] - base.append(milliseconds * 1000) # microseconds - return cls(*base) - - @classmethod - def to_unix(cls, timestamp): - """ Wrapper over time module to produce Unix epoch time as a float """ - if not isinstance(timestamp, datetime.datetime): - raise TypeError('Time.milliseconds expects a datetime object') - base = time.mktime(timestamp.timetuple()) - return base - - @classmethod - def milliseconds_offset(cls, timestamp, now=None): - """ Offset time (in milliseconds) from a |datetime.datetime| object to now """ - if isinstance(timestamp, (int, float)): - base = timestamp - else: - base = cls.to_unix(timestamp) - base = base + (timestamp.microsecond / 1000000) - if now is None: - now = time.time() - return (now - base) * 1000 - - -class HTTPRequest(object): - """ URL Construction and request handling abstraction. - This is not intended to be used outside this module. - - Automates mapping of persistent state (i.e. query parameters) - onto transcient datasets for each query. - """ - - endpoint = 'https://www.google-analytics.com/collect' - - @staticmethod - def debug(): - """ Activate debugging on urllib2 """ - handler = HTTPSHandler(debuglevel=1) - opener = build_opener(handler) - install_opener(opener) - - # Store properties for all requests - def __init__(self, user_agent=None, *args, **opts): - self.user_agent = user_agent or 'Analytics Pros - Universal Analytics (Python)' - - @classmethod - def fixUTF8(cls, data): # Ensure proper encoding for UA's servers... - """ Convert all strings to UTF-8 """ - for key in data: - if isinstance(data[key], str): - data[key] = data[key].encode('utf-8') - return data - - # Apply stored properties to the given dataset & POST to the configured endpoint - def send(self, data): - request = Request( - self.endpoint + '?' + urlencode(self.fixUTF8(data)).encode('utf-8'), - headers={ - 'User-Agent': self.user_agent - } - ) - self.open(request) - - def open(self, request): - try: - return urlopen(request) - except HTTPError as e: - return False - except URLError as e: - self.cache_request(request) - return False - - def cache_request(self, request): - # TODO: implement a proper caching mechanism here for re-transmitting hits - # record = (Time.now(), request.get_full_url(), request.get_data(), request.headers) - pass - - -class HTTPPost(HTTPRequest): - - # Apply stored properties to the given dataset & POST to the configured endpoint - def send(self, data): - request = Request( - self.endpoint, - data=urlencode(self.fixUTF8(data)).encode('utf-8'), - headers={ - 'User-Agent': self.user_agent - } - ) - self.open(request) - - -class Tracker(object): - """ Primary tracking interface for Universal Analytics """ - params = None - parameter_alias = {} - valid_hittypes = ('pageview', 'event', 'social', 'screenview', 'transaction', 'item', 'exception', 'timing') - - @classmethod - def alias(cls, typemap, base, *names): - """ Declare an alternate (humane) name for a measurement protocol parameter """ - cls.parameter_alias[base] = (typemap, base) - for i in names: - cls.parameter_alias[i] = (typemap, base) - - @classmethod - def coerceParameter(cls, name, value=None): - if isinstance(name, str) and name[0] == '&': - return name[1:], str(value) - elif name in cls.parameter_alias: - typecast, param_name = cls.parameter_alias.get(name) - return param_name, typecast(value) - else: - raise KeyError('Parameter "{0}" is not recognized'.format(name)) - - def payload(self, data): - for key, value in data.items(): - try: - yield self.coerceParameter(key, value) - except KeyError: - continue - - option_sequence = { - 'pageview': [(str, 'dp')], - 'event': [(str, 'ec'), (str, 'ea'), (str, 'el'), (int, 'ev')], - 'social': [(str, 'sn'), (str, 'sa'), (str, 'st')], - 'timing': [(str, 'utc'), (str, 'utv'), (str, 'utt'), (str, 'utl')] - } - - @classmethod - def consume_options(cls, data, hittype, args): - """ Interpret sequential arguments related to known hittypes based on declared structures """ - opt_position = 0 - data['t'] = hittype # integrate hit type parameter - if hittype in cls.option_sequence: - for expected_type, optname in cls.option_sequence[hittype]: - if opt_position < len(args) and isinstance(args[opt_position], expected_type): - data[optname] = args[opt_position] - opt_position += 1 - - @classmethod - def hittime(cls, timestamp=None, age=None, milliseconds=None): - """ Returns an integer represeting the milliseconds offset for a given hit (relative to now) """ - if isinstance(timestamp, (int, float)): - return int(Time.milliseconds_offset(Time.from_unix(timestamp, milliseconds=milliseconds))) - if isinstance(timestamp, datetime.datetime): - return int(Time.milliseconds_offset(timestamp)) - if isinstance(age, (int, float)): - return int(age * 1000) + (milliseconds or 0) - - @property - def account(self): - return self.params.get('tid', None) - - def __init__(self, account, name=None, client_id=None, hash_client_id=False, user_id=None, user_agent=None, - use_post=True): - - if use_post is False: - self.http = HTTPRequest(user_agent=user_agent) - else: - self.http = HTTPPost(user_agent=user_agent) - - self.params = {'v': 1, 'tid': account} - - if client_id is None: - client_id = generate_uuid() - - self.params['cid'] = client_id - - self.hash_client_id = hash_client_id - - if user_id is not None: - self.params['uid'] = user_id - - def set_timestamp(self, data): - """ Interpret time-related options, apply queue-time parameter as needed """ - if 'hittime' in data: # an absolute timestamp - data['qt'] = self.hittime(timestamp=data.pop('hittime', None)) - if 'hitage' in data: # a relative age (in seconds) - data['qt'] = self.hittime(age=data.pop('hitage', None)) - - def send(self, hittype, *args, **data): - """ Transmit HTTP requests to Google Analytics using the measurement protocol """ - - if hittype not in self.valid_hittypes: - raise KeyError('Unsupported Universal Analytics Hit Type: {0}'.format(repr(hittype))) - - self.set_timestamp(data) - self.consume_options(data, hittype, args) - - for item in args: # process dictionary-object arguments of transcient data - if isinstance(item, dict): - for key, val in self.payload(item): - data[key] = val - - for k, v in self.params.items(): # update only absent parameters - if k not in data: - data[k] = v - - data = dict(self.payload(data)) - - if self.hash_client_id: - data['cid'] = generate_uuid(data['cid']) - - # Transmit the hit to Google... - self.http.send(data) - - # Setting persistent attibutes of the session/hit/etc (inc. custom dimensions/metrics) - def set(self, name, value=None): - if isinstance(name, dict): - for key, value in name.items(): - try: - param, value = self.coerceParameter(key, value) - self.params[param] = value - except KeyError: - pass - elif isinstance(name, str): - try: - param, value = self.coerceParameter(name, value) - self.params[param] = value - except KeyError: - pass - - def __getitem__(self, name): - param, value = self.coerceParameter(name, None) - return self.params.get(param, None) - - def __setitem__(self, name, value): - param, value = self.coerceParameter(name, value) - self.params[param] = value - - def __delitem__(self, name): - param, value = self.coerceParameter(name, None) - if param in self.params: - del self.params[param] - - -def safe_unicode(obj): - """ Safe convertion to the Unicode string version of the object """ - try: - return str(obj) - except UnicodeDecodeError: - return obj.decode('utf-8') - - -# Declaring name mappings for Measurement Protocol parameters -MAX_CUSTOM_DEFINITIONS = 200 -MAX_EC_LISTS = 11 # 1-based index -MAX_EC_PRODUCTS = 11 # 1-based index -MAX_EC_PROMOTIONS = 11 # 1-based index - -Tracker.alias(int, 'v', 'protocol-version') -Tracker.alias(safe_unicode, 'cid', 'client-id', 'clientId', 'clientid') -Tracker.alias(safe_unicode, 'tid', 'trackingId', 'account') -Tracker.alias(safe_unicode, 'uid', 'user-id', 'userId', 'userid') -Tracker.alias(safe_unicode, 'uip', 'user-ip', 'userIp', 'ipaddr') -Tracker.alias(safe_unicode, 'ua', 'userAgent', 'userAgentOverride', 'user-agent') -Tracker.alias(safe_unicode, 'dp', 'page', 'path') -Tracker.alias(safe_unicode, 'dt', 'title', 'pagetitle', 'pageTitle' 'page-title') -Tracker.alias(safe_unicode, 'dl', 'location') -Tracker.alias(safe_unicode, 'dh', 'hostname') -Tracker.alias(safe_unicode, 'sc', 'sessioncontrol', 'session-control', 'sessionControl') -Tracker.alias(safe_unicode, 'dr', 'referrer', 'referer') -Tracker.alias(int, 'qt', 'queueTime', 'queue-time') -Tracker.alias(safe_unicode, 't', 'hitType', 'hittype') -Tracker.alias(int, 'aip', 'anonymizeIp', 'anonIp', 'anonymize-ip') -Tracker.alias(safe_unicode, 'ds', 'dataSource', 'data-source') - -# Campaign attribution -Tracker.alias(safe_unicode, 'cn', 'campaign', 'campaignName', 'campaign-name') -Tracker.alias(safe_unicode, 'cs', 'source', 'campaignSource', 'campaign-source') -Tracker.alias(safe_unicode, 'cm', 'medium', 'campaignMedium', 'campaign-medium') -Tracker.alias(safe_unicode, 'ck', 'keyword', 'campaignKeyword', 'campaign-keyword') -Tracker.alias(safe_unicode, 'cc', 'content', 'campaignContent', 'campaign-content') -Tracker.alias(safe_unicode, 'ci', 'campaignId', 'campaignID', 'campaign-id') - -# Technical specs -Tracker.alias(safe_unicode, 'sr', 'screenResolution', 'screen-resolution', 'resolution') -Tracker.alias(safe_unicode, 'vp', 'viewport', 'viewportSize', 'viewport-size') -Tracker.alias(safe_unicode, 'de', 'encoding', 'documentEncoding', 'document-encoding') -Tracker.alias(int, 'sd', 'colors', 'screenColors', 'screen-colors') -Tracker.alias(safe_unicode, 'ul', 'language', 'user-language', 'userLanguage') - -# Mobile app -Tracker.alias(safe_unicode, 'an', 'appName', 'app-name', 'app') -Tracker.alias(safe_unicode, 'cd', 'contentDescription', 'screenName', 'screen-name', 'content-description') -Tracker.alias(safe_unicode, 'av', 'appVersion', 'app-version', 'version') -Tracker.alias(safe_unicode, 'aid', 'appID', 'appId', 'application-id', 'app-id', 'applicationId') -Tracker.alias(safe_unicode, 'aiid', 'appInstallerId', 'app-installer-id') - -# Ecommerce -Tracker.alias(safe_unicode, 'ta', 'affiliation', 'transactionAffiliation', 'transaction-affiliation') -Tracker.alias(safe_unicode, 'ti', 'transaction', 'transactionId', 'transaction-id') -Tracker.alias(float, 'tr', 'revenue', 'transactionRevenue', 'transaction-revenue') -Tracker.alias(float, 'ts', 'shipping', 'transactionShipping', 'transaction-shipping') -Tracker.alias(float, 'tt', 'tax', 'transactionTax', 'transaction-tax') -Tracker.alias(safe_unicode, 'cu', 'currency', 'transactionCurrency', - 'transaction-currency') # Currency code, e.g. USD, EUR -Tracker.alias(safe_unicode, 'in', 'item-name', 'itemName') -Tracker.alias(float, 'ip', 'item-price', 'itemPrice') -Tracker.alias(float, 'iq', 'item-quantity', 'itemQuantity') -Tracker.alias(safe_unicode, 'ic', 'item-code', 'sku', 'itemCode') -Tracker.alias(safe_unicode, 'iv', 'item-variation', 'item-category', 'itemCategory', 'itemVariation') - -# Events -Tracker.alias(safe_unicode, 'ec', 'event-category', 'eventCategory', 'category') -Tracker.alias(safe_unicode, 'ea', 'event-action', 'eventAction', 'action') -Tracker.alias(safe_unicode, 'el', 'event-label', 'eventLabel', 'label') -Tracker.alias(int, 'ev', 'event-value', 'eventValue', 'value') -Tracker.alias(int, 'ni', 'noninteractive', 'nonInteractive', 'noninteraction', 'nonInteraction') - -# Social -Tracker.alias(safe_unicode, 'sa', 'social-action', 'socialAction') -Tracker.alias(safe_unicode, 'sn', 'social-network', 'socialNetwork') -Tracker.alias(safe_unicode, 'st', 'social-target', 'socialTarget') - -# Exceptions -Tracker.alias(safe_unicode, 'exd', 'exception-description', 'exceptionDescription', 'exDescription') -Tracker.alias(int, 'exf', 'exception-fatal', 'exceptionFatal', 'exFatal') - -# User Timing -Tracker.alias(safe_unicode, 'utc', 'timingCategory', 'timing-category') -Tracker.alias(safe_unicode, 'utv', 'timingVariable', 'timing-variable') -Tracker.alias(float, 'utt', 'time', 'timingTime', 'timing-time') -Tracker.alias(safe_unicode, 'utl', 'timingLabel', 'timing-label') -Tracker.alias(float, 'dns', 'timingDNS', 'timing-dns') -Tracker.alias(float, 'pdt', 'timingPageLoad', 'timing-page-load') -Tracker.alias(float, 'rrt', 'timingRedirect', 'timing-redirect') -Tracker.alias(safe_unicode, 'tcp', 'timingTCPConnect', 'timing-tcp-connect') -Tracker.alias(safe_unicode, 'srt', 'timingServerResponse', 'timing-server-response') - -# Custom dimensions and metrics -for i in range(0, 200): - Tracker.alias(safe_unicode, 'cd{0}'.format(i), 'dimension{0}'.format(i)) - Tracker.alias(int, 'cm{0}'.format(i), 'metric{0}'.format(i)) - -# Content groups -for i in range(0, 5): - Tracker.alias(safe_unicode, 'cg{0}'.format(i), 'contentGroup{0}'.format(i)) - -# Enhanced Ecommerce -Tracker.alias(str, 'pa') # Product action -Tracker.alias(str, 'tcc') # Coupon code -Tracker.alias(str, 'pal') # Product action list -Tracker.alias(int, 'cos') # Checkout step -Tracker.alias(str, 'col') # Checkout step option - -Tracker.alias(str, 'promoa') # Promotion action - -for product_index in range(1, MAX_EC_PRODUCTS): - Tracker.alias(str, 'pr{0}id'.format(product_index)) # Product SKU - Tracker.alias(str, 'pr{0}nm'.format(product_index)) # Product name - Tracker.alias(str, 'pr{0}br'.format(product_index)) # Product brand - Tracker.alias(str, 'pr{0}ca'.format(product_index)) # Product category - Tracker.alias(str, 'pr{0}va'.format(product_index)) # Product variant - Tracker.alias(str, 'pr{0}pr'.format(product_index)) # Product price - Tracker.alias(int, 'pr{0}qt'.format(product_index)) # Product quantity - Tracker.alias(str, 'pr{0}cc'.format(product_index)) # Product coupon code - Tracker.alias(int, 'pr{0}ps'.format(product_index)) # Product position - - for custom_index in range(MAX_CUSTOM_DEFINITIONS): - Tracker.alias(str, 'pr{0}cd{1}'.format(product_index, custom_index)) # Product custom dimension - Tracker.alias(int, 'pr{0}cm{1}'.format(product_index, custom_index)) # Product custom metric - - for list_index in range(1, MAX_EC_LISTS): - Tracker.alias(str, 'il{0}pi{1}id'.format(list_index, product_index)) # Product impression SKU - Tracker.alias(str, 'il{0}pi{1}nm'.format(list_index, product_index)) # Product impression name - Tracker.alias(str, 'il{0}pi{1}br'.format(list_index, product_index)) # Product impression brand - Tracker.alias(str, 'il{0}pi{1}ca'.format(list_index, product_index)) # Product impression category - Tracker.alias(str, 'il{0}pi{1}va'.format(list_index, product_index)) # Product impression variant - Tracker.alias(int, 'il{0}pi{1}ps'.format(list_index, product_index)) # Product impression position - Tracker.alias(int, 'il{0}pi{1}pr'.format(list_index, product_index)) # Product impression price - - for custom_index in range(MAX_CUSTOM_DEFINITIONS): - Tracker.alias(str, 'il{0}pi{1}cd{2}'.format(list_index, product_index, - custom_index)) # Product impression custom dimension - Tracker.alias(int, 'il{0}pi{1}cm{2}'.format(list_index, product_index, - custom_index)) # Product impression custom metric - -for list_index in range(1, MAX_EC_LISTS): - Tracker.alias(str, 'il{0}nm'.format(list_index)) # Product impression list name - -for promotion_index in range(1, MAX_EC_PROMOTIONS): - Tracker.alias(str, 'promo{0}id'.format(promotion_index)) # Promotion ID - Tracker.alias(str, 'promo{0}nm'.format(promotion_index)) # Promotion name - Tracker.alias(str, 'promo{0}cr'.format(promotion_index)) # Promotion creative - Tracker.alias(str, 'promo{0}ps'.format(promotion_index)) # Promotion position - - -# Shortcut for creating trackers -def create(account, *args, **kwargs): - return Tracker(account, *args, **kwargs) - -# vim: set nowrap tabstop=4 shiftwidth=4 softtabstop=0 expandtab textwidth=0 filetype=python foldmethod=indent foldcolumn=4 diff --git a/lib/UniversalAnalytics/__init__.py b/lib/UniversalAnalytics/__init__.py deleted file mode 100644 index 0d8817d6..00000000 --- a/lib/UniversalAnalytics/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import Tracker \ No newline at end of file diff --git a/lib/ga4mp/__init__.py b/lib/ga4mp/__init__.py new file mode 100644 index 00000000..9a817b94 --- /dev/null +++ b/lib/ga4mp/__init__.py @@ -0,0 +1,3 @@ +from ga4mp.ga4mp import GtagMP, FirebaseMP + +__all__ = ['GtagMP','FirebaseMP'] \ No newline at end of file diff --git a/lib/ga4mp/event.py b/lib/ga4mp/event.py new file mode 100644 index 00000000..12f65a10 --- /dev/null +++ b/lib/ga4mp/event.py @@ -0,0 +1,44 @@ +from ga4mp.item import Item + +class Event(dict): + def __init__(self, name): + self.set_event_name(name) + + def set_event_name(self, name): + if len(name) > 40: + raise ValueError("Event name cannot exceed 40 characters.") + self["name"] = name + + def get_event_name(self): + return self.get("name") + + def set_event_param(self, name, value): + # Series of checks to comply with GA4 event collection limits: https://support.google.com/analytics/answer/9267744 + if len(name) > 40: + raise ValueError("Event parameter name cannot exceed 40 characters.") + if name in ["page_location", "page_referrer", "page_title"] and len(str(value)) > 300: + raise ValueError("Event parameter value for page info cannot exceed 300 characters.") + if name not in ["page_location", "page_referrer", "page_title"] and len(str(value)) > 100: + raise ValueError("Event parameter value cannot exceed 100 characters.") + if "params" not in self.keys(): + self["params"] = {} + if len(self["params"]) >= 100: + raise RuntimeError("Event cannot contain more than 100 parameters.") + self["params"][name] = value + + def get_event_params(self): + return self.get("params") + + def delete_event_param(self, name): + # Since only 25 event parameters are allowed, this will allow the user to delete a parameter if necessary. + self["params"].pop(name, None) + + def create_new_item(self, item_id=None, item_name=None): + return Item(item_id=item_id, item_name=item_name) + + def add_item_to_event(self, item): + if not isinstance(item, dict): + raise ValueError("'item' must be an instance of a dictionary.") + if "items" not in self["params"].keys(): + self.set_event_param("items", []) + self["params"]["items"].append(item) \ No newline at end of file diff --git a/lib/ga4mp/ga4mp.py b/lib/ga4mp/ga4mp.py new file mode 100644 index 00000000..45fdc842 --- /dev/null +++ b/lib/ga4mp/ga4mp.py @@ -0,0 +1,416 @@ +############################################################################### +# Google Analytics 4 Measurement Protocol for Python +# Copyright (c) 2022, Adswerve +# +# This project is free software, distributed under the BSD license. +# Adswerve offers consulting and integration services if your firm needs +# assistance in strategy, implementation, or auditing existing work. +############################################################################### + +import json +import logging +import urllib.request +import time +import datetime +import random +from ga4mp.utils import params_dict +from ga4mp.event import Event +from ga4mp.store import BaseStore, DictStore + +import os, sys +sys.path.append( + os.path.normpath(os.path.join(os.path.dirname(__file__), "..")) +) + +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) + +class BaseGa4mp(object): + """ + Parent class that provides an interface for sending data to Google Analytics, supporting the GA4 Measurement Protocol. + + Parameters + ---------- + api_secret : string + Generated through the Google Analytics UI. To create a new secret, navigate in the Google Analytics UI to: Admin > Data Streams > + [choose your stream] > Measurement Protocol API Secrets > Create + + See Also + -------- + + * Measurement Protocol (Google Analytics 4): https://developers.google.com/analytics/devguides/collection/protocol/ga4 + + Examples + -------- + # Initialize tracking object for gtag usage + >>> ga = gtagMP(api_secret = "API_SECRET", measurement_id = "MEASUREMENT_ID", client_id="CLIENT_ID") + + # Initialize tracking object for Firebase usage + >>> ga = firebaseMP(api_secret = "API_SECRET", firebase_app_id = "FIREBASE_APP_ID", app_instance_id="APP_INSTANCE_ID") + + # Build an event + >>> event_type = 'new_custom_event' + >>> event_parameters = {'parameter_key_1': 'parameter_1', 'parameter_key_2': 'parameter_2'} + >>> event = {'name': event_type, 'params': event_parameters } + >>> events = [event] + + # Send a custom event to GA4 immediately + >>> ga.send(events) + + # Postponed send of a custom event to GA4 + >>> ga.send(events, postpone=True) + >>> ga.postponed_send() + """ + + def __init__(self, api_secret, store: BaseStore = None): + self._initialization_time = time.time() # used for both session_id and calculating engagement time + self.api_secret = api_secret + self._event_list = [] + assert store is None or isinstance(store, BaseStore), "if supplied, store must be an instance of BaseStore" + self.store = store or DictStore() + self._check_store_requirements() + self._base_domain = "https://www.google-analytics.com/mp/collect" + self._validation_domain = "https://www.google-analytics.com/debug/mp/collect" + + def _check_store_requirements(self): + # Store must contain "session_id" and "last_interaction_time_msec" in order for tracking to work properly. + if self.store.get_session_parameter("session_id") is None: + self.store.set_session_parameter(name="session_id", value=int(self._initialization_time)) + # Note: "last_interaction_time_msec" factors into the required "engagement_time_msec" event parameter. + self.store.set_session_parameter(name="last_interaction_time_msec", value=int(self._initialization_time * 1000)) + + def create_new_event(self, name): + return Event(name=name) + + def send(self, events, validation_hit=False, postpone=False, date=None): + """ + Method to send an http post request to google analytics with the specified events. + + Parameters + ---------- + events : List[Dict] + A list of dictionaries of the events to be sent to Google Analytics. The list of dictionaries should adhere + to the following format: + + [{'name': 'level_end', + 'params' : {'level_name': 'First', + 'success': 'True'} + }, + {'name': 'level_up', + 'params': {'character': 'John Madden', + 'level': 'First'} + }] + + validation_hit : bool, optional + Boolean to depict if events should be tested against the Measurement Protocol Validation Server, by default False + postpone : bool, optional + Boolean to depict if provided event list should be postponed, by default False + date : datetime + Python datetime object for sending a historical event at the given date. Date cannot be in the future. + """ + + # check for any missing or invalid parameters among automatically collected and recommended event types + self._check_params(events) + self._check_date_not_in_future(date) + self._add_session_id_and_engagement_time(events) + + if postpone is True: + # build event list to send later + for event in events: + event["_timestamp_micros"] = self._get_timestamp(time.time()) + self._event_list.append(event) + else: + # batch events into sets of 25 events, the maximum allowed. + batched_event_list = [ + events[event : event + 25] for event in range(0, len(events), 25) + ] + # send http post request + self._http_post( + batched_event_list, validation_hit=validation_hit, date=date + ) + + def postponed_send(self): + """ + Method to send the events provided to Ga4mp.send(events,postpone=True) + """ + + for event in self._event_list: + self._http_post([event], postpone=True) + + # clear event_list for future use + self._event_list = [] + + def append_event_to_params_dict(self, new_name_and_parameters): + + """ + Method to append event name and parameters key-value pairing(s) to parameters dictionary. + + Parameters + ---------- + new_name_and_parameters : Dict + A dictionary with one key-value pair representing a new type of event to be sent to Google Analytics. + The dictionary should adhere to the following format: + + {'new_name': ['new_param_1', 'new_param_2', 'new_param_3']} + """ + + params_dict.update(new_name_and_parameters) + + def _http_post(self, batched_event_list, validation_hit=False, postpone=False, date=None): + """ + Method to send http POST request to google-analytics. + + Parameters + ---------- + batched_event_list : List[List[Dict]] + List of List of events. Places initial event payload into a list to send http POST in batches. + validation_hit : bool, optional + Boolean to depict if events should be tested against the Measurement Protocol Validation Server, by default False + postpone : bool, optional + Boolean to depict if provided event list should be postponed, by default False + date : datetime + Python datetime object for sending a historical event at the given date. Date cannot be in the future. + Timestamp micros supports up to 48 hours of backdating. + If date is specified, postpone must be False or an assertion will be thrown. + """ + self._check_date_not_in_future(date) + status_code = None # Default set to know if batch loop does not work and to bound status_code + + # set domain + domain = self._base_domain + if validation_hit is True: + domain = self._validation_domain + logger.info(f"Sending POST to: {domain}") + + # loop through events in batches of 25 + batch_number = 1 + for batch in batched_event_list: + # url and request slightly differ by subclass + url = self._build_url(domain=domain) + request = self._build_request(batch=batch) + self._add_user_props_to_hit(request) + + # make adjustments for postponed hit + request["events"] = ( + {"name": batch["name"], "params": batch["params"]} + if (postpone) + else batch + ) + + if date is not None: + logger.info(f"Setting event timestamp to: {date}") + assert ( + postpone is False + ), "Cannot send postponed historical hit, ensure postpone=False" + + ts = self._datetime_to_timestamp(date) + ts_micro = self._get_timestamp(ts) + request["timestamp_micros"] = int(ts_micro) + logger.info(f"Timestamp of request is: {request['timestamp_micros']}") + + if postpone: + # add timestamp to hit + request["timestamp_micros"] = batch["_timestamp_micros"] + + req = urllib.request.Request(url) + req.add_header("Content-Type", "application/json; charset=utf-8") + jsondata = json.dumps(request) + json_data_as_bytes = jsondata.encode("utf-8") # needs to be bytes + req.add_header("Content-Length", len(json_data_as_bytes)) + result = urllib.request.urlopen(req, json_data_as_bytes) + + status_code = result.status + logger.info(f"Batch Number: {batch_number}") + logger.info(f"Status code: {status_code}") + batch_number += 1 + + return status_code + + def _check_params(self, events): + + """ + Method to check whether the provided event payload parameters align with supported parameters. + + Parameters + ---------- + events : List[Dict] + A list of dictionaries of the events to be sent to Google Analytics. The list of dictionaries should adhere + to the following format: + + [{'name': 'level_end', + 'params' : {'level_name': 'First', + 'success': 'True'} + }, + {'name': 'level_up', + 'params': {'character': 'John Madden', + 'level': 'First'} + }] + """ + + # check to make sure it's a list of dictionaries with the right keys + + assert type(events) == list, "events should be a list" + + for event in events: + + assert isinstance(event, dict), "each event should be an instance of a dictionary" + + assert "name" in event, 'each event should have a "name" key' + + assert "params" in event, 'each event should have a "params" key' + + # check for any missing or invalid parameters + + for e in events: + event_name = e["name"] + event_params = e["params"] + if event_name in params_dict.keys(): + for parameter in params_dict[event_name]: + if parameter not in event_params.keys(): + logger.warning( + f"WARNING: Event parameters do not match event type.\nFor {event_name} event type, the correct parameter(s) are {params_dict[event_name]}.\nThe parameter '{parameter}' triggered this warning.\nFor a breakdown of currently supported event types and their parameters go here: https://support.google.com/analytics/answer/9267735\n" + ) + + def _add_session_id_and_engagement_time(self, events): + """ + Method to add the session_id and engagement_time_msec parameter to all events. + """ + for event in events: + current_time_in_milliseconds = int(time.time() * 1000) + + event_params = event["params"] + if "session_id" not in event_params.keys(): + event_params["session_id"] = self.store.get_session_parameter("session_id") + if "engagement_time_msec" not in event_params.keys(): + last_interaction_time = self.store.get_session_parameter("last_interaction_time_msec") + event_params["engagement_time_msec"] = current_time_in_milliseconds - last_interaction_time if current_time_in_milliseconds > last_interaction_time else 0 + self.store.set_session_parameter(name="last_interaction_time_msec", value=current_time_in_milliseconds) + + def _add_user_props_to_hit(self, hit): + + """ + Method is a helper function to add user properties to outgoing hits. + + Parameters + ---------- + hit : dict + """ + + for key in self.store.get_all_user_properties(): + try: + if key in ["user_id", "non_personalized_ads"]: + hit.update({key: self.store.get_user_property(key)}) + else: + if "user_properties" not in hit.keys(): + hit.update({"user_properties": {}}) + hit["user_properties"].update( + {key: {"value": self.store.get_user_property(key)}} + ) + except: + logger.info(f"Failed to add user property to outgoing hit: {key}") + + def _get_timestamp(self, timestamp): + """ + Method returns UNIX timestamp in microseconds for postponed hits. + + Parameters + ---------- + None + """ + return int(timestamp * 1e6) + + def _datetime_to_timestamp(self, dt): + """ + Private method to convert a datetime object into a timestamp + + Parameters + ---------- + dt : datetime + A datetime object in any format + + Returns + ------- + timestamp + A UNIX timestamp in milliseconds + """ + return time.mktime(dt.timetuple()) + + def _check_date_not_in_future(self, date): + """ + Method to check that provided date is not in the future. + + Parameters + ---------- + date : datetime + Python datetime object + """ + if date is None: + pass + else: + assert ( + date <= datetime.datetime.now() + ), "Provided date cannot be in the future" + + def _build_url(self, domain): + raise NotImplementedError("Subclass should be using this function, but it was called through the base class instead.") + + def _build_request(self, batch): + raise NotImplementedError("Subclass should be using this function, but it was called through the base class instead.") + +class GtagMP(BaseGa4mp): + """ + Subclass for users of gtag. See `Ga4mp` parent class for examples. + + Parameters + ---------- + measurement_id : string + The identifier for a Data Stream. Found in the Google Analytics UI under: Admin > Data Streams > [choose your stream] > Measurement ID (top-right) + client_id : string + A unique identifier for a client, representing a specific browser/device. + """ + + def __init__(self, api_secret, measurement_id, client_id,): + super().__init__(api_secret) + self.measurement_id = measurement_id + self.client_id = client_id + + def _build_url(self, domain): + return f"{domain}?measurement_id={self.measurement_id}&api_secret={self.api_secret}" + + def _build_request(self, batch): + return {"client_id": self.client_id, "events": batch} + + def random_client_id(self): + """ + Utility function for generating a new client ID matching the typical format of 10 random digits and the UNIX timestamp in seconds, joined by a period. + """ + return "%0.10d" % random.randint(0,9999999999) + "." + str(int(time.time())) + +class FirebaseMP(BaseGa4mp): + """ + Subclass for users of Firebase. See `Ga4mp` parent class for examples. + + Parameters + ---------- + firebase_app_id : string + The identifier for a Firebase app. Found in the Firebase console under: Project Settings > General > Your Apps > App ID. + app_instance_id : string + A unique identifier for a Firebase app instance. + * Android - getAppInstanceId() - https://firebase.google.com/docs/reference/android/com/google/firebase/analytics/FirebaseAnalytics#public-taskstring-getappinstanceid + * Kotlin - getAppInstanceId() - https://firebase.google.com/docs/reference/kotlin/com/google/firebase/analytics/FirebaseAnalytics#getappinstanceid + * Swift - appInstanceID() - https://firebase.google.com/docs/reference/swift/firebaseanalytics/api/reference/Classes/Analytics#appinstanceid + * Objective-C - appInstanceID - https://firebase.google.com/docs/reference/ios/firebaseanalytics/api/reference/Classes/FIRAnalytics#+appinstanceid + * C++ - GetAnalyticsInstanceId() - https://firebase.google.com/docs/reference/cpp/namespace/firebase/analytics#getanalyticsinstanceid + * Unity - GetAnalyticsInstanceIdAsync() - https://firebase.google.com/docs/reference/unity/class/firebase/analytics/firebase-analytics#getanalyticsinstanceidasync + """ + + def __init__(self, api_secret, firebase_app_id, app_instance_id): + super().__init__(api_secret) + self.firebase_app_id = firebase_app_id + self.app_instance_id = app_instance_id + + def _build_url(self, domain): + return f"{domain}?firebase_app_id={self.firebase_app_id}&api_secret={self.api_secret}" + + def _build_request(self, batch): + return {"app_instance_id": self.app_instance_id, "events": batch} \ No newline at end of file diff --git a/lib/ga4mp/item.py b/lib/ga4mp/item.py new file mode 100644 index 00000000..9c5ee9cd --- /dev/null +++ b/lib/ga4mp/item.py @@ -0,0 +1,11 @@ +class Item(dict): + def __init__(self, item_id=None, item_name=None): + if item_id is None and item_name is None: + raise ValueError("At least one of 'item_id' and 'item_name' is required.") + if item_id is not None: + self.set_parameter("item_id", str(item_id)) + if item_name is not None: + self.set_parameter("item_name", item_name) + + def set_parameter(self, name, value): + self[name] = value \ No newline at end of file diff --git a/lib/ga4mp/store.py b/lib/ga4mp/store.py new file mode 100644 index 00000000..d85bc0c2 --- /dev/null +++ b/lib/ga4mp/store.py @@ -0,0 +1,116 @@ +import json +import logging +from pathlib import Path + +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) + +class BaseStore(dict): + def __init__(self): + self.update([("user_properties", {}),("session_parameters", {})]) + + def save(self): + raise NotImplementedError("Subclass should be using this function, but it was called through the base class instead.") + + def _check_exists(self, key): + # Helper function to make sure a key exists before trying to work with values within it. + if key not in self.keys(): + self[key] = {} + + def _set(self, param_type, name, value): + # Helper function to set a single parameter (user or session or other). + self._check_exists(key=param_type) + self[param_type][name] = value + + def _get_one(self, param_type, name): + # Helper function to get a single parameter value (user or session). + self._check_exists(key=param_type) + return self[param_type].get(name, None) + + def _get_all(self, param_type=None): + # Helper function to get all user or session parameters - or the entire dictionary if not specified. + if param_type is not None: + return self[param_type] + else: + return self + + # While redundant, the following make sure the distinction between session and user items is easier for the end user. + def set_user_property(self, name, value): + self._set(param_type="user_properties", name=name, value=value) + + def get_user_property(self, name): + return self._get_one(param_type="user_properties", name=name) + + def get_all_user_properties(self): + return self._get_all(param_type="user_properties") + + def clear_user_properties(self): + self["user_properties"] = {} + + def set_session_parameter(self, name, value): + self._set(param_type="session_parameters", name=name, value=value) + + def get_session_parameter(self, name): + return self._get_one(param_type="session_parameters", name=name) + + def get_all_session_parameters(self): + return self._get_all(param_type="session_parameters") + + def clear_session_parameters(self): + self["session_parameters"] = {} + + # Similar functions for other items the user wants to store that don't fit the other two categories. + def set_other_parameter(self, name, value): + self._set(param_type="other", name=name, value=value) + + def get_other_parameter(self, name): + return self._get_one(param_type="other", name=name) + + def get_all_other_parameters(self): + return self._get_all(param_type="other") + + def clear_other_parameters(self): + self["other"] = {} + +class DictStore(BaseStore): + # Class for working with dictionaries that persist for the life of the class. + def __init__(self, data: dict = None): + super().__init__() + if data: + self.update(data) + + def save(self): + # Give the user back what's in the dictionary so they can decide how to save it. + self._get_all() + +class FileStore(BaseStore): + # Class for working with dictionaries that get saved to a JSON file. + def __init__(self, data_location: str = None): + super().__init__() + self.data_location = data_location + try: + self._load_file(data_location) + except: + logger.info(f"Failed to find file at location: {data_location}") + + def _load_file(self): + # Function to get data from the object's initialized location. + # If the provided or stored data_location exists, read the file and overwrite the object's contents. + if Path(self.data_location).exists(): + with open(self.data_location, "r") as json_file: + self = json.load(json_file) + # If the data_location doesn't exist, try to create a new starter JSON file at the location given. + else: + starter_dict = '{"user_properties":{}, "session_parameters":{}}' + starter_json = json.loads(starter_dict) + Path(self.data_location).touch() + with open(self.data_location, "w") as json_file: + json.dumps(starter_json, json_file) + + def save(self): + # Function to save the current dictionary to a JSON file at the object's initialized location. + try: + with open(self.data_location, "w") as outfile: + json.dump(self, outfile) + except: + logger.info(f"Failed to save file at location: {self.data_location}") \ No newline at end of file diff --git a/lib/ga4mp/utils.py b/lib/ga4mp/utils.py new file mode 100644 index 00000000..27fbca86 --- /dev/null +++ b/lib/ga4mp/utils.py @@ -0,0 +1,392 @@ +# all automatically collected and recommended event types +params_dict = { + "ad_click": [ + "ad_event_id" + ], + "ad_exposure": [ + "firebase_screen", + "firebase_screen_id", + "firebase_screen_class", + "exposure_time", + ], + "ad_impression": [ + "ad_event_id" + ], + "ad_query": [ + "ad_event_id" + ], + "ad_reward": [ + "ad_unit_id", + "reward_type", + "reward_value" + ], + "add_payment_info": [ + "coupon", + "currency", + "items", + "payment_type", + "value" + ], + "add_shipping_info": [ + "coupon", + "currency", + "items", + "shipping_tier", + "value" + ], + "add_to_cart": [ + "currency", + "items", + "value" + ], + "add_to_wishlist": [ + "currency", + "items", + "value" + ], + "adunit_exposure": [ + "firebase_screen", + "firebase_screen_id", + "firebase_screen_class", + "exposure_time", + ], + "app_clear_data": [], + "app_exception": [ + "fatal", + "timestamp", + "engagement_time_msec" + ], + "app_remove": [], + "app_store_refund": [ + "product_id", + "value", + "currency", + "quantity" + ], + "app_store_subscription_cancel": [ + "product_id", + "price", + "value", + "currency", + "cancellation_reason", + ], + "app_store_subscription_convert": [ + "product_id", + "price", + "value", + "currency", + "quantity", + ], + "app_store_subscription_renew": [ + "product_id", + "price", + "value", + "currency", + "quantity", + "renewal_count", + ], + "app_update": [ + "previous_app_version" + ], + "begin_checkout": [ + "coupon", + "currency", + "items", + "value" + ], + "click": [], + "dynamic_link_app_open": [ + "source", + "medium", + "campaign", + "link_id", + "accept_time" + ], + "dynamic_link_app_update": [ + "source", + "medium", + "campaign", + "link_id", + "accept_time", + ], + "dynamic_link_first_open": [ + "source", + "medium", + "campaign", + "link_id", + "accept_time", + ], + "earn_virtual_currency": [ + "virtual_currency_name", + "value" + ], + "error": [ + "firebase_error", + "firebase_error_value" + ], + "file_download": [ + "file_extension", + "file_name", + "link_classes", + "link_domain", + "link_id", + "link_text", + "link_url", + ], + "firebase_campaign": [ + "source", + "medium", + "campaign", + "term", + "content", + "gclid", + "aclid", + "cp1", + "anid", + "click_timestamp", + "campaign_info_source", + ], + "firebase_in_app_message_action": [ + "message_name", + "message_device_time", + "message_id", + ], + "firebase_in_app_message_dismiss": [ + "message_name", + "message_device_time", + "message_id", + ], + "firebase_in_app_message_impression": [ + "message_name", + "message_device_time", + "message_id", + ], + "first_open": [ + "previous_gmp_app_id", + "updated_with_analytics", + "previous_first_open_count", + "system_app", + "system_app_update", + "deferred_analytics_collection", + "reset_analytics_cause", + "engagement_time_msec", + ], + "first_visit": [], + "generate_lead": [ + "value", + "currency" + ], + "in_app_purchase": [ + "product_id", + "price", + "value", + "currency", + "quantity", + "subscription", + "free_trial", + "introductory_price", + ], + "join_group": [ + "group_id" + ], + "level_end": [ + "level_name", + "success" + ], + "level_start": [ + "level_name" + ], + "level_up": [ + "character", + "level" + ], + "login": [ + "method" + ], + "notification_dismiss": [ + "message_name", + "message_time", + "message_device_time", + "message_id", + "topic", + "label", + "message_channel", + ], + "notification_foreground": [ + "message_name", + "message_time", + "message_device_time", + "message_id", + "topic", + "label", + "message_channel", + "message_type", + ], + "notification_open": [ + "message_name", + "message_time", + "message_device_time", + "message_id", + "topic", + "label", + "message_channel", + ], + "notification_receive": [ + "message_name", + "message_time", + "message_device_time", + "message_id", + "topic", + "label", + "message_channel", + "message_type", + ], + "notification_send": [ + "message_name", + "message_time", + "message_device_time", + "message_id", + "topic", + "label", + "message_channel", + ], + "os_update": [ + "previous_os_version" + ], + "page_view": [ + "page_location", + "page_referrer" + ], + "post_score": [ + "level", + "character", + "score" + ], + "purchase": [ + "affiliation", + "coupon", + "currency", + "items", + "transaction_id", + "shipping", + "tax", + "value", + ], + "refund": [ + "transaction_id", + "value", + "currency", + "tax", + "shipping", + "items" + ], + "remove_from_cart": [ + "currency", + "items", + "value" + ], + "screen_view": [ + "firebase_screen", + "firebase_screen_class", + "firebase_screen_id", + "firebase_previous_screen", + "firebase_previous_class", + "firebase_previous_id", + "engagement_time_msec", + ], + "scroll": [], + "search": [ + "search_term" + ], + "select_content": [ + "content_type", + "item_id" + ], + "select_item": [ + "items", + "item_list_name", + "item_list_id" + ], + "select_promotion": [ + "items", + "promotion_id", + "promotion_name", + "creative_name", + "creative_slot", + "location_id", + ], + "session_start": [], + "share": [ + "content_type", + "item_id" + ], + "sign_up": [ + "method" + ], + "view_search_results": [ + "search_term" + ], + "spend_virtual_currency": [ + "item_name", + "virtual_currency_name", + "value" + ], + "tutorial_begin": [], + "tutorial_complete": [], + "unlock_achievement": [ + "achievement_id" + ], + "user_engagement": [ + "engagement_time_msec" + ], + "video_start": [ + "video_current_time", + "video_duration", + "video_percent", + "video_provider", + "video_title", + "video_url", + "visible", + ], + "video_progress": [ + "video_current_time", + "video_duration", + "video_percent", + "video_provider", + "video_title", + "video_url", + "visible", + ], + "video_complete": [ + "video_current_time", + "video_duration", + "video_percent", + "video_provider", + "video_title", + "video_url", + "visible", + ], + "view_cart": [ + "currency", + "items", + "value" + ], + "view_item": [ + "currency", + "items", + "value" + ], + "view_item_list": [ + "items", + "item_list_name", + "item_list_id" + ], + "view_promotion": [ + "items", + "promotion_id", + "promotion_name", + "creative_name", + "creative_slot", + "location_id", + ], +} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index b69f5fc2..6d3fa686 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,6 +14,7 @@ distro==1.8.0 dnspython==2.2.1 facebook-sdk==3.1.0 future==0.18.2 +ga4mp==2.0.4 gntp==1.0.3 html5lib==1.1 httpagentparser==1.9.5 From af3e5574f5442fb45a722462ba62965fe2dce247 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Sun, 26 Feb 2023 15:09:51 -0800 Subject: [PATCH 168/583] Migrate to Google Analytics 4 --- plexpy/__init__.py | 66 ++++++++++++++++++++++++---------------------- plexpy/plextv.py | 8 ++++++ 2 files changed, 42 insertions(+), 32 deletions(-) diff --git a/plexpy/__init__.py b/plexpy/__init__.py index d0aed7cf..31e3d51c 100644 --- a/plexpy/__init__.py +++ b/plexpy/__init__.py @@ -36,7 +36,7 @@ except ImportError: from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.triggers.interval import IntervalTrigger -from UniversalAnalytics import Tracker +from ga4mp import GtagMP import pytz PYTHON2 = sys.version_info[0] == 2 @@ -578,12 +578,12 @@ def start(): # Send system analytics events if not CONFIG.FIRST_RUN_COMPLETE: - analytics_event(category='system', action='install') + analytics_event(name='install') elif _UPDATE: - analytics_event(category='system', action='update') + analytics_event(name='update') - analytics_event(category='system', action='start') + analytics_event(name='start') _STARTED = True @@ -2843,44 +2843,46 @@ def generate_uuid(): def initialize_tracker(): - data = { - 'dataSource': 'server', - 'appName': common.PRODUCT, - 'appVersion': common.RELEASE, - 'appId': INSTALL_TYPE, - 'appInstallerId': CONFIG.GIT_BRANCH, - 'dimension1': '{} {}'.format(common.PLATFORM, common.PLATFORM_RELEASE), # App Platform - 'dimension2': common.PLATFORM_LINUX_DISTRO, # Linux Distro - 'dimension3': common.PYTHON_VERSION, - 'userLanguage': SYS_LANGUAGE, - 'documentEncoding': SYS_ENCODING, - 'noninteractive': True - } - - tracker = Tracker.create('UA-111522699-2', client_id=CONFIG.PMS_UUID, hash_client_id=True, - user_agent=common.USER_AGENT) - tracker.set(data) - + tracker = GtagMP( + api_secret='Cl_LjAKUT26AS22YZwqaPw', + measurement_id='G-NH1M4BYM2P', + client_id=CONFIG.PMS_UUID + ) return tracker -def analytics_event(category, action, label=None, value=None, **kwargs): - data = {'category': category, 'action': action} +def analytics_event(name, **kwargs): + event = TRACKER.create_new_event(name=name) + event.set_event_param('name', common.PRODUCT) + event.set_event_param('version', common.RELEASE) + event.set_event_param('install', INSTALL_TYPE) + event.set_event_param('branch', CONFIG.GIT_BRANCH) + event.set_event_param('platform', common.PLATFORM) + event.set_event_param('platformRelease', common.PLATFORM_RELEASE) + event.set_event_param('platformVersion', common.PLATFORM_VERSION) + event.set_event_param('linuxDistro', common.PLATFORM_LINUX_DISTRO) + event.set_event_param('pythonVersion', common.PYTHON_VERSION) + event.set_event_param('language', SYS_LANGUAGE) + event.set_event_param('encoding', SYS_ENCODING) + event.set_event_param('timezone', str(SYS_TIMEZONE)) + event.set_event_param('timezoneUTCOffset', f'UTC{SYS_UTC_OFFSET}') - if label is not None: - data['label'] = label + for key, value in kwargs.items(): + event.set_event_param(key, value) - if value is not None: - data['value'] = value + plex_tv = plextv.PlexTV() + ip_address = plex_tv.get_public_ip(output_format='text') + geolocation = plex_tv.get_geoip_lookup(ip_address) or {} - if kwargs: - data.update(kwargs) + event.set_event_param('country', geolocation.get('country', 'Unknown')) + event.set_event_param('countryCode', geolocation.get('code', 'Unknown')) if TRACKER: try: - TRACKER.send('event', data) + TRACKER.send(events=[event]) + pass except Exception as e: - logger.warn("Failed to send analytics event for category '%s', action '%s': %s" % (category, action, e)) + logger.warn("Failed to send analytics event for name '%s': %s" % (name, e)) def check_folder_writable(folder, fallback, name): diff --git a/plexpy/plextv.py b/plexpy/plextv.py index 1b596fc7..e7815756 100644 --- a/plexpy/plextv.py +++ b/plexpy/plextv.py @@ -331,6 +331,14 @@ class PlexTV(object): return request + def get_public_ip(self, output_format=''): + uri = '/:/ip' + request = self.request_handler.make_request(uri=uri, + request_type='GET', + output_format=output_format) + + return request + def get_plextv_geoip(self, ip_address='', output_format=''): uri = '/api/v2/geoip?ip_address=%s' % ip_address request = self.request_handler.make_request(uri=uri, From 731d5c9bafab5ad505d8f30afd8a5e3e1128803b Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Sun, 26 Feb 2023 15:17:04 -0800 Subject: [PATCH 169/583] Add multiprocessing semaphore prefix * Fixes #2007 --- Tautulli.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Tautulli.py b/Tautulli.py index eebfa55a..2c03eb3b 100755 --- a/Tautulli.py +++ b/Tautulli.py @@ -29,6 +29,7 @@ import appdirs import argparse import datetime import locale +import multiprocessing import pytz import signal import shutil @@ -47,6 +48,8 @@ elif common.PLATFORM == 'Darwin': signal.signal(signal.SIGINT, plexpy.sig_handler) signal.signal(signal.SIGTERM, plexpy.sig_handler) +multiprocessing.current_process()._config['semprefix'] = '/tautulli.tautulli.mp' + def main(): """ From cd3ff6eed76f0fb33a2e7b9a1ac754548d48e0b9 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Sun, 26 Feb 2023 16:06:44 -0800 Subject: [PATCH 170/583] Remove extra pass --- plexpy/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plexpy/__init__.py b/plexpy/__init__.py index 31e3d51c..993436b8 100644 --- a/plexpy/__init__.py +++ b/plexpy/__init__.py @@ -2880,7 +2880,6 @@ def analytics_event(name, **kwargs): if TRACKER: try: TRACKER.send(events=[event]) - pass except Exception as e: logger.warn("Failed to send analytics event for name '%s': %s" % (name, e)) From ce45321e3fd3d188ad29bebd277d9beff3a04278 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Sun, 26 Feb 2023 15:17:04 -0800 Subject: [PATCH 171/583] Revert "Add multiprocessing semaphore prefix" This reverts commit 731d5c9bafab5ad505d8f30afd8a5e3e1128803b. --- Tautulli.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/Tautulli.py b/Tautulli.py index 2c03eb3b..eebfa55a 100755 --- a/Tautulli.py +++ b/Tautulli.py @@ -29,7 +29,6 @@ import appdirs import argparse import datetime import locale -import multiprocessing import pytz import signal import shutil @@ -48,8 +47,6 @@ elif common.PLATFORM == 'Darwin': signal.signal(signal.SIGINT, plexpy.sig_handler) signal.signal(signal.SIGTERM, plexpy.sig_handler) -multiprocessing.current_process()._config['semprefix'] = '/tautulli.tautulli.mp' - def main(): """ From 993909fa089dcbb21b7d80c46d9f784ad6aeefe9 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Sun, 26 Feb 2023 16:17:58 -0800 Subject: [PATCH 172/583] Use private shared-memory for Snap --- snap/snapcraft.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 1220971e..030e72a9 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -15,6 +15,10 @@ architectures: - build-on: arm64 - build-on: armhf +plugs: + shared-memory: + private: true + parts: tautulli: plugin: dump From ae3d75bbe309b835f819d61fadfa46deb7c813e1 Mon Sep 17 00:00:00 2001 From: herby2212 <12448284+herby2212@users.noreply.github.com> Date: Mon, 27 Feb 2023 02:32:50 +0100 Subject: [PATCH 173/583] watch time & user stats for collections (#1982) * user_stats for collection * watch_time_stats for collection * check for media_type to be compatible with API * update API and datafactory optimizations * beautify webserve class * fix sql query build * filter on suitable collections * stats for collections of sub media type * optimize array creation --- data/interfaces/default/info.html | 15 ++++++-- plexpy/datafactory.py | 61 ++++++++++++++++++++++--------- plexpy/webserve.py | 22 +++++++---- 3 files changed, 69 insertions(+), 29 deletions(-) diff --git a/data/interfaces/default/info.html b/data/interfaces/default/info.html index dbce734b..471c8e7e 100644 --- a/data/interfaces/default/info.html +++ b/data/interfaces/default/info.html @@ -12,6 +12,7 @@ data :: Usable parameters (if not applicable for media type, blank value will be == Global keys == rating_key Returns the unique identifier for the media item. media_type Returns the type of media. Either 'movie', 'show', 'season', 'episode', 'artist', 'album', or 'track'. +sub_media_type Returns the subtype of media. Either 'movie', 'show', 'season', 'episode', 'artist', 'album', or 'track'. art Returns the location of the item's artwork title Returns the name of the movie, show, episode, artist, album, or track. edition_title Returns the edition title of a movie. @@ -553,7 +554,7 @@ DOCUMENTATION :: END
    % endif - % if data['media_type'] in ('movie', 'show', 'season', 'episode', 'artist', 'album', 'track'): + % if data['media_type'] in ('movie', 'show', 'season', 'episode', 'artist', 'album', 'track', 'collection'):
    @@ -936,13 +937,16 @@ DOCUMENTATION :: END }); % endif -% if data['media_type'] in ('movie', 'show', 'season', 'episode', 'artist', 'album', 'track'): +% if data['media_type'] in ('movie', 'show', 'season', 'episode', 'artist', 'album', 'track', 'collection'): % endif -% if data['media_type'] in ('movie', 'show', 'season', 'episode', 'artist', 'album', 'track', 'collection'): +% if data['media_type'] in ('movie', 'show', 'season', 'episode', 'artist', 'album', 'track', 'collection', 'playlist'): -% if data['media_type'] in ('movie', 'show', 'season', 'episode', 'artist', 'album', 'track'): +% if data['media_type'] in ('movie', 'show', 'season', 'episode', 'artist', 'album', 'track', 'collection', 'playlist'): @@ -373,14 +373,35 @@ type: 'get', dataType: "json", success: function (data) { - var select = $('#graph-user'); + let select = $('#graph-user'); + let by_id = {}; data.sort(function(a, b) { return a.friendly_name.localeCompare(b.friendly_name); }); data.forEach(function(item) { select.append(''); + by_id[item.user_id] = item.friendly_name; }); + select.selectpicker({ + countSelectedText: function(sel, total) { + if (sel === 0 || sel === total) { + return 'All users'; + } else if (sel > 1) { + return sel + ' users'; + } else { + return select.val().map(function(id) { + return by_id[id]; + }).join(', '); + } + }, + style: 'btn-dark', + actionsBox: true, + selectedTextFormat: 'count', + noneSelectedText: 'All users' + }); + select.selectpicker('render'); + select.selectpicker('selectAll'); } }); @@ -602,11 +623,6 @@ $('#nav-tabs-total').tab('show'); } - // Set initial state - if (current_tab === '#tabs-plays') { loadGraphsTab1(current_day_range, yaxis); } - if (current_tab === '#tabs-stream') { loadGraphsTab2(current_day_range, yaxis); } - if (current_tab === '#tabs-total') { loadGraphsTab3(current_month_range, yaxis); } - // Tab1 opened $('#nav-tabs-plays').on('shown.bs.tab', function (e) { e.preventDefault(); @@ -652,9 +668,20 @@ $('.months').text(current_month_range); }); + let graph_user_last_id = undefined; + // User changed $('#graph-user').on('change', function() { - selected_user_id = $(this).val() || null; + let val = $(this).val(); + if (val.length === 0 || val.length === $(this).children().length) { + selected_user_id = null; // if all users are selected, just send an empty list + } else { + selected_user_id = val.join(","); + } + if (selected_user_id === graph_user_last_id) { + return; + } + graph_user_last_id = selected_user_id; if (current_tab === '#tabs-plays') { loadGraphsTab1(current_day_range, yaxis); } if (current_tab === '#tabs-stream') { loadGraphsTab2(current_day_range, yaxis); } if (current_tab === '#tabs-total') { loadGraphsTab3(current_month_range, yaxis); } diff --git a/data/interfaces/default/history.html b/data/interfaces/default/history.html index 327b99b7..8ab8b19e 100644 --- a/data/interfaces/default/history.html +++ b/data/interfaces/default/history.html @@ -1,6 +1,7 @@ <%inherit file="base.html"/> <%def name="headIncludes()"> + @@ -31,9 +32,7 @@ % if _session['user_group'] == 'admin':
    @@ -121,6 +120,7 @@ <%def name="javascriptIncludes()"> + @@ -134,17 +134,40 @@ type: 'GET', dataType: 'json', success: function (data) { - var select = $('#history-user'); + let select = $('#history-user'); + let by_id = {}; data.sort(function (a, b) { return a.friendly_name.localeCompare(b.friendly_name); }); data.forEach(function (item) { select.append(''); + by_id[item.user_id] = item.friendly_name; }); + select.selectpicker({ + countSelectedText: function(sel, total) { + if (sel === 0 || sel === total) { + return 'All users'; + } else if (sel > 1) { + return sel + ' users'; + } else { + return select.val().map(function(id) { + return by_id[id]; + }).join(', '); + } + }, + style: 'btn-dark', + actionsBox: true, + selectedTextFormat: 'count', + noneSelectedText: 'All users' + }); + select.selectpicker('render'); + select.selectpicker('selectAll'); } }); + let history_user_last_id = undefined; + function loadHistoryTable(media_type, transcode_decision, selected_user_id) { history_table_options.ajax = { url: 'get_history', @@ -187,7 +210,16 @@ }); $('#history-user').on('change', function () { - selected_user_id = $(this).val() || null; + let val = $(this).val(); + if (val.length === 0 || val.length === $(this).children().length) { + selected_user_id = null; // if all users are selected, just send an empty list + } else { + selected_user_id = val.join(","); + } + if (selected_user_id === history_user_last_id) { + return; + } + history_user_last_id = selected_user_id; history_table.draw(); }); } diff --git a/data/interfaces/default/js/bootstrap-select.min.js b/data/interfaces/default/js/bootstrap-select.min.js new file mode 100644 index 00000000..92e3a32e --- /dev/null +++ b/data/interfaces/default/js/bootstrap-select.min.js @@ -0,0 +1,9 @@ +/*! + * Bootstrap-select v1.13.14 (https://developer.snapappointments.com/bootstrap-select) + * + * Copyright 2012-2020 SnapAppointments, LLC + * Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE) + */ + +!function(e,t){void 0===e&&void 0!==window&&(e=window),"function"==typeof define&&define.amd?define(["jquery"],function(e){return t(e)}):"object"==typeof module&&module.exports?module.exports=t(require("jquery")):t(e.jQuery)}(this,function(e){!function(z){"use strict";var d=["sanitize","whiteList","sanitizeFn"],r=["background","cite","href","itemtype","longdesc","poster","src","xlink:href"],e={"*":["class","dir","id","lang","role","tabindex","style",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},l=/^(?:(?:https?|mailto|ftp|tel|file):|[^&:/?#]*(?:[/?#]|$))/gi,a=/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[a-z0-9+/]+=*$/i;function v(e,t){var i=e.nodeName.toLowerCase();if(-1!==z.inArray(i,t))return-1===z.inArray(i,r)||Boolean(e.nodeValue.match(l)||e.nodeValue.match(a));for(var s=z(t).filter(function(e,t){return t instanceof RegExp}),n=0,o=s.length;n]+>/g,"")),s&&(a=w(a)),a=a.toUpperCase(),o="contains"===i?0<=a.indexOf(t):a.startsWith(t)))break}return o}function L(e){return parseInt(e,10)||0}z.fn.triggerNative=function(e){var t,i=this[0];i.dispatchEvent?(u?t=new Event(e,{bubbles:!0}):(t=document.createEvent("Event")).initEvent(e,!0,!1),i.dispatchEvent(t)):i.fireEvent?((t=document.createEventObject()).eventType=e,i.fireEvent("on"+e,t)):this.trigger(e)};var f={"\xc0":"A","\xc1":"A","\xc2":"A","\xc3":"A","\xc4":"A","\xc5":"A","\xe0":"a","\xe1":"a","\xe2":"a","\xe3":"a","\xe4":"a","\xe5":"a","\xc7":"C","\xe7":"c","\xd0":"D","\xf0":"d","\xc8":"E","\xc9":"E","\xca":"E","\xcb":"E","\xe8":"e","\xe9":"e","\xea":"e","\xeb":"e","\xcc":"I","\xcd":"I","\xce":"I","\xcf":"I","\xec":"i","\xed":"i","\xee":"i","\xef":"i","\xd1":"N","\xf1":"n","\xd2":"O","\xd3":"O","\xd4":"O","\xd5":"O","\xd6":"O","\xd8":"O","\xf2":"o","\xf3":"o","\xf4":"o","\xf5":"o","\xf6":"o","\xf8":"o","\xd9":"U","\xda":"U","\xdb":"U","\xdc":"U","\xf9":"u","\xfa":"u","\xfb":"u","\xfc":"u","\xdd":"Y","\xfd":"y","\xff":"y","\xc6":"Ae","\xe6":"ae","\xde":"Th","\xfe":"th","\xdf":"ss","\u0100":"A","\u0102":"A","\u0104":"A","\u0101":"a","\u0103":"a","\u0105":"a","\u0106":"C","\u0108":"C","\u010a":"C","\u010c":"C","\u0107":"c","\u0109":"c","\u010b":"c","\u010d":"c","\u010e":"D","\u0110":"D","\u010f":"d","\u0111":"d","\u0112":"E","\u0114":"E","\u0116":"E","\u0118":"E","\u011a":"E","\u0113":"e","\u0115":"e","\u0117":"e","\u0119":"e","\u011b":"e","\u011c":"G","\u011e":"G","\u0120":"G","\u0122":"G","\u011d":"g","\u011f":"g","\u0121":"g","\u0123":"g","\u0124":"H","\u0126":"H","\u0125":"h","\u0127":"h","\u0128":"I","\u012a":"I","\u012c":"I","\u012e":"I","\u0130":"I","\u0129":"i","\u012b":"i","\u012d":"i","\u012f":"i","\u0131":"i","\u0134":"J","\u0135":"j","\u0136":"K","\u0137":"k","\u0138":"k","\u0139":"L","\u013b":"L","\u013d":"L","\u013f":"L","\u0141":"L","\u013a":"l","\u013c":"l","\u013e":"l","\u0140":"l","\u0142":"l","\u0143":"N","\u0145":"N","\u0147":"N","\u014a":"N","\u0144":"n","\u0146":"n","\u0148":"n","\u014b":"n","\u014c":"O","\u014e":"O","\u0150":"O","\u014d":"o","\u014f":"o","\u0151":"o","\u0154":"R","\u0156":"R","\u0158":"R","\u0155":"r","\u0157":"r","\u0159":"r","\u015a":"S","\u015c":"S","\u015e":"S","\u0160":"S","\u015b":"s","\u015d":"s","\u015f":"s","\u0161":"s","\u0162":"T","\u0164":"T","\u0166":"T","\u0163":"t","\u0165":"t","\u0167":"t","\u0168":"U","\u016a":"U","\u016c":"U","\u016e":"U","\u0170":"U","\u0172":"U","\u0169":"u","\u016b":"u","\u016d":"u","\u016f":"u","\u0171":"u","\u0173":"u","\u0174":"W","\u0175":"w","\u0176":"Y","\u0177":"y","\u0178":"Y","\u0179":"Z","\u017b":"Z","\u017d":"Z","\u017a":"z","\u017c":"z","\u017e":"z","\u0132":"IJ","\u0133":"ij","\u0152":"Oe","\u0153":"oe","\u0149":"'n","\u017f":"s"},m=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,g=RegExp("[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff\\u1ab0-\\u1aff\\u1dc0-\\u1dff]","g");function b(e){return f[e]}function w(e){return(e=e.toString())&&e.replace(m,b).replace(g,"")}var I,x,y,$,S=(I={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},x="(?:"+Object.keys(I).join("|")+")",y=RegExp(x),$=RegExp(x,"g"),function(e){return e=null==e?"":""+e,y.test(e)?e.replace($,E):e});function E(e){return I[e]}var C={32:" ",48:"0",49:"1",50:"2",51:"3",52:"4",53:"5",54:"6",55:"7",56:"8",57:"9",59:";",65:"A",66:"B",67:"C",68:"D",69:"E",70:"F",71:"G",72:"H",73:"I",74:"J",75:"K",76:"L",77:"M",78:"N",79:"O",80:"P",81:"Q",82:"R",83:"S",84:"T",85:"U",86:"V",87:"W",88:"X",89:"Y",90:"Z",96:"0",97:"1",98:"2",99:"3",100:"4",101:"5",102:"6",103:"7",104:"8",105:"9"},N=27,D=13,H=32,W=9,B=38,M=40,R={success:!1,major:"3"};try{R.full=(z.fn.dropdown.Constructor.VERSION||"").split(" ")[0].split("."),R.major=R.full[0],R.success=!0}catch(e){}var U=0,j=".bs.select",V={DISABLED:"disabled",DIVIDER:"divider",SHOW:"open",DROPUP:"dropup",MENU:"dropdown-menu",MENURIGHT:"dropdown-menu-right",MENULEFT:"dropdown-menu-left",BUTTONCLASS:"btn-default",POPOVERHEADER:"popover-title",ICONBASE:"glyphicon",TICKICON:"glyphicon-ok"},F={MENU:"."+V.MENU},_={span:document.createElement("span"),i:document.createElement("i"),subtext:document.createElement("small"),a:document.createElement("a"),li:document.createElement("li"),whitespace:document.createTextNode("\xa0"),fragment:document.createDocumentFragment()};_.a.setAttribute("role","option"),"4"===R.major&&(_.a.className="dropdown-item"),_.subtext.className="text-muted",_.text=_.span.cloneNode(!1),_.text.className="text",_.checkMark=_.span.cloneNode(!1);var G=new RegExp(B+"|"+M),q=new RegExp("^"+W+"$|"+N),K={li:function(e,t,i){var s=_.li.cloneNode(!1);return e&&(1===e.nodeType||11===e.nodeType?s.appendChild(e):s.innerHTML=e),void 0!==t&&""!==t&&(s.className=t),null!=i&&s.classList.add("optgroup-"+i),s},a:function(e,t,i){var s=_.a.cloneNode(!0);return e&&(11===e.nodeType?s.appendChild(e):s.insertAdjacentHTML("beforeend",e)),void 0!==t&&""!==t&&s.classList.add.apply(s.classList,t.split(" ")),i&&s.setAttribute("style",i),s},text:function(e,t){var i,s,n=_.text.cloneNode(!1);if(e.content)n.innerHTML=e.content;else{if(n.textContent=e.text,e.icon){var o=_.whitespace.cloneNode(!1);(s=(!0===t?_.i:_.span).cloneNode(!1)).className=this.options.iconBase+" "+e.icon,_.fragment.appendChild(s),_.fragment.appendChild(o)}e.subtext&&((i=_.subtext.cloneNode(!1)).textContent=e.subtext,n.appendChild(i))}if(!0===t)for(;0'},maxOptions:!1,mobile:!1,selectOnTab:!1,dropdownAlignRight:!1,windowPadding:0,virtualScroll:600,display:!1,sanitize:!0,sanitizeFn:null,whiteList:e},Y.prototype={constructor:Y,init:function(){var i=this,e=this.$element.attr("id");U++,this.selectId="bs-select-"+U,this.$element[0].classList.add("bs-select-hidden"),this.multiple=this.$element.prop("multiple"),this.autofocus=this.$element.prop("autofocus"),this.$element[0].classList.contains("show-tick")&&(this.options.showTick=!0),this.$newElement=this.createDropdown(),this.buildData(),this.$element.after(this.$newElement).prependTo(this.$newElement),this.$button=this.$newElement.children("button"),this.$menu=this.$newElement.children(F.MENU),this.$menuInner=this.$menu.children(".inner"),this.$searchbox=this.$menu.find("input"),this.$element[0].classList.remove("bs-select-hidden"),!0===this.options.dropdownAlignRight&&this.$menu[0].classList.add(V.MENURIGHT),void 0!==e&&this.$button.attr("data-id",e),this.checkDisabled(),this.clickListener(),this.options.liveSearch?(this.liveSearchListener(),this.focusedParent=this.$searchbox[0]):this.focusedParent=this.$menuInner[0],this.setStyle(),this.render(),this.setWidth(),this.options.container?this.selectPosition():this.$element.on("hide"+j,function(){if(i.isVirtual()){var e=i.$menuInner[0],t=e.firstChild.cloneNode(!1);e.replaceChild(t,e.firstChild),e.scrollTop=0}}),this.$menu.data("this",this),this.$newElement.data("this",this),this.options.mobile&&this.mobile(),this.$newElement.on({"hide.bs.dropdown":function(e){i.$element.trigger("hide"+j,e)},"hidden.bs.dropdown":function(e){i.$element.trigger("hidden"+j,e)},"show.bs.dropdown":function(e){i.$element.trigger("show"+j,e)},"shown.bs.dropdown":function(e){i.$element.trigger("shown"+j,e)}}),i.$element[0].hasAttribute("required")&&this.$element.on("invalid"+j,function(){i.$button[0].classList.add("bs-invalid"),i.$element.on("shown"+j+".invalid",function(){i.$element.val(i.$element.val()).off("shown"+j+".invalid")}).on("rendered"+j,function(){this.validity.valid&&i.$button[0].classList.remove("bs-invalid"),i.$element.off("rendered"+j)}),i.$button.on("blur"+j,function(){i.$element.trigger("focus").trigger("blur"),i.$button.off("blur"+j)})}),setTimeout(function(){i.buildList(),i.$element.trigger("loaded"+j)})},createDropdown:function(){var e=this.multiple||this.options.showTick?" show-tick":"",t=this.multiple?' aria-multiselectable="true"':"",i="",s=this.autofocus?" autofocus":"";R.major<4&&this.$element.parent().hasClass("input-group")&&(i=" input-group-btn");var n,o="",r="",l="",a="";return this.options.header&&(o='
    '+this.options.header+"
    "),this.options.liveSearch&&(r=''),this.multiple&&this.options.actionsBox&&(l='
    "),this.multiple&&this.options.doneButton&&(a='
    "),n='",z(n)},setPositionData:function(){this.selectpicker.view.canHighlight=[];for(var e=this.selectpicker.view.size=0;e=this.options.virtualScroll||!0===this.options.virtualScroll},createView:function(A,e,t){var L,N,D=this,i=0,H=[];if(this.selectpicker.isSearching=A,this.selectpicker.current=A?this.selectpicker.search:this.selectpicker.main,this.setPositionData(),e)if(t)i=this.$menuInner[0].scrollTop;else if(!D.multiple){var s=D.$element[0],n=(s.options[s.selectedIndex]||{}).liIndex;if("number"==typeof n&&!1!==D.options.size){var o=D.selectpicker.main.data[n],r=o&&o.position;r&&(i=r-(D.sizeInfo.menuInnerHeight+D.sizeInfo.liHeight)/2)}}function l(e,t){var i,s,n,o,r,l,a,c,d=D.selectpicker.current.elements.length,h=[],p=!0,u=D.isVirtual();D.selectpicker.view.scrollTop=e,i=Math.ceil(D.sizeInfo.menuInnerHeight/D.sizeInfo.liHeight*1.5),s=Math.round(d/i)||1;for(var f=0;fd-1?0:D.selectpicker.current.data[d-1].position-D.selectpicker.current.data[D.selectpicker.view.position1-1].position,b.firstChild.style.marginTop=v+"px",b.firstChild.style.marginBottom=g+"px"):(b.firstChild.style.marginTop=0,b.firstChild.style.marginBottom=0),b.firstChild.appendChild(w),!0===u&&D.sizeInfo.hasScrollBar){var C=b.firstChild.offsetWidth;if(t&&CD.sizeInfo.selectWidth)b.firstChild.style.minWidth=D.sizeInfo.menuInnerInnerWidth+"px";else if(C>D.sizeInfo.menuInnerInnerWidth){D.$menu[0].style.minWidth=0;var O=b.firstChild.offsetWidth;O>D.sizeInfo.menuInnerInnerWidth&&(D.sizeInfo.menuInnerInnerWidth=O,b.firstChild.style.minWidth=D.sizeInfo.menuInnerInnerWidth+"px"),D.$menu[0].style.minWidth=""}}}if(D.prevActiveIndex=D.activeIndex,D.options.liveSearch){if(A&&t){var z,T=0;D.selectpicker.view.canHighlight[T]||(T=1+D.selectpicker.view.canHighlight.slice(1).indexOf(!0)),z=D.selectpicker.view.visibleElements[T],D.defocusItem(D.selectpicker.view.currentActive),D.activeIndex=(D.selectpicker.current.data[T]||{}).index,D.focusItem(z)}}else D.$menuInner.trigger("focus")}l(i,!0),this.$menuInner.off("scroll.createView").on("scroll.createView",function(e,t){D.noScroll||l(this.scrollTop,t),D.noScroll=!1}),z(window).off("resize"+j+"."+this.selectId+".createView").on("resize"+j+"."+this.selectId+".createView",function(){D.$newElement.hasClass(V.SHOW)&&l(D.$menuInner[0].scrollTop)})},focusItem:function(e,t,i){if(e){t=t||this.selectpicker.main.data[this.activeIndex];var s=e.firstChild;s&&(s.setAttribute("aria-setsize",this.selectpicker.view.size),s.setAttribute("aria-posinset",t.posinset),!0!==i&&(this.focusedParent.setAttribute("aria-activedescendant",s.id),e.classList.add("active"),s.classList.add("active")))}},defocusItem:function(e){e&&(e.classList.remove("active"),e.firstChild&&e.firstChild.classList.remove("active"))},setPlaceholder:function(){var e=!1;if(this.options.title&&!this.multiple){this.selectpicker.view.titleOption||(this.selectpicker.view.titleOption=document.createElement("option")),e=!0;var t=this.$element[0],i=!1,s=!this.selectpicker.view.titleOption.parentNode;if(s)this.selectpicker.view.titleOption.className="bs-title-option",this.selectpicker.view.titleOption.value="",i=void 0===z(t.options[t.selectedIndex]).attr("selected")&&void 0===this.$element.data("selected");!s&&0===this.selectpicker.view.titleOption.index||t.insertBefore(this.selectpicker.view.titleOption,t.firstChild),i&&(t.selectedIndex=0)}return e},buildData:function(){var p=':not([hidden]):not([data-hidden="true"])',u=[],f=0,e=this.setPlaceholder()?1:0;this.options.hideDisabled&&(p+=":not(:disabled)");var t=this.$element[0].querySelectorAll("select > *"+p);function m(e){var t=u[u.length-1];t&&"divider"===t.type&&(t.optID||e.optID)||((e=e||{}).type="divider",u.push(e))}function v(e,t){if((t=t||{}).divider="true"===e.getAttribute("data-divider"),t.divider)m({optID:t.optID});else{var i=u.length,s=e.style.cssText,n=s?S(s):"",o=(e.className||"")+(t.optgroupClass||"");t.optID&&(o="opt "+o),t.optionClass=o.trim(),t.inlineStyle=n,t.text=e.textContent,t.content=e.getAttribute("data-content"),t.tokens=e.getAttribute("data-tokens"),t.subtext=e.getAttribute("data-subtext"),t.icon=e.getAttribute("data-icon"),e.liIndex=i,t.display=t.content||t.text,t.type="option",t.index=i,t.option=e,t.selected=!!e.selected,t.disabled=t.disabled||!!e.disabled,u.push(t)}}function i(e,t){var i=t[e],s=t[e-1],n=t[e+1],o=i.querySelectorAll("option"+p);if(o.length){var r,l,a={display:S(i.label),subtext:i.getAttribute("data-subtext"),icon:i.getAttribute("data-icon"),type:"optgroup-label",optgroupClass:" "+(i.className||"")};f++,s&&m({optID:f}),a.optID=f,u.push(a);for(var c=0,d=o.length;c li")},render:function(){var e,t=this,i=this.$element[0],s=this.setPlaceholder()&&0===i.selectedIndex,n=O(i,this.options.hideDisabled),o=n.length,r=this.$button[0],l=r.querySelector(".filter-option-inner-inner"),a=document.createTextNode(this.options.multipleSeparator),c=_.fragment.cloneNode(!1),d=!1;if(r.classList.toggle("bs-placeholder",t.multiple?!o:!T(i,n)),this.tabIndex(),"static"===this.options.selectedTextFormat)c=K.text.call(this,{text:this.options.title},!0);else if(!1===(this.multiple&&-1!==this.options.selectedTextFormat.indexOf("count")&&1")).length&&o>e[1]||1===e.length&&2<=o))){if(!s){for(var h=0;h option"+m+", optgroup"+m+" option"+m).length,g="function"==typeof this.options.countSelectedText?this.options.countSelectedText(o,v):this.options.countSelectedText;c=K.text.call(this,{text:g.replace("{0}",o.toString()).replace("{1}",v.toString())},!0)}if(null==this.options.title&&(this.options.title=this.$element.attr("title")),c.childNodes.length||(c=K.text.call(this,{text:void 0!==this.options.title?this.options.title:this.options.noneSelectedText},!0)),r.title=c.textContent.replace(/<[^>]*>?/g,"").trim(),this.options.sanitize&&d&&P([c],t.options.whiteList,t.options.sanitizeFn),l.innerHTML="",l.appendChild(c),R.major<4&&this.$newElement[0].classList.contains("bs3-has-addon")){var b=r.querySelector(".filter-expand"),w=l.cloneNode(!0);w.className="filter-expand",b?r.replaceChild(w,b):r.appendChild(w)}this.$element.trigger("rendered"+j)},setStyle:function(e,t){var i,s=this.$button[0],n=this.$newElement[0],o=this.options.style.trim();this.$element.attr("class")&&this.$newElement.addClass(this.$element.attr("class").replace(/selectpicker|mobile-device|bs-select-hidden|validate\[.*\]/gi,"")),R.major<4&&(n.classList.add("bs3"),n.parentNode.classList.contains("input-group")&&(n.previousElementSibling||n.nextElementSibling)&&(n.previousElementSibling||n.nextElementSibling).classList.contains("input-group-addon")&&n.classList.add("bs3-has-addon")),i=e?e.trim():o,"add"==t?i&&s.classList.add.apply(s.classList,i.split(" ")):"remove"==t?i&&s.classList.remove.apply(s.classList,i.split(" ")):(o&&s.classList.remove.apply(s.classList,o.split(" ")),i&&s.classList.add.apply(s.classList,i.split(" ")))},liHeight:function(e){if(e||!1!==this.options.size&&!Object.keys(this.sizeInfo).length){var t=document.createElement("div"),i=document.createElement("div"),s=document.createElement("div"),n=document.createElement("ul"),o=document.createElement("li"),r=document.createElement("li"),l=document.createElement("li"),a=document.createElement("a"),c=document.createElement("span"),d=this.options.header&&0this.sizeInfo.menuExtras.vert&&l+this.sizeInfo.menuExtras.vert+50>this.sizeInfo.selectOffsetBot,!0===this.selectpicker.isSearching&&(a=this.selectpicker.dropup),this.$newElement.toggleClass(V.DROPUP,a),this.selectpicker.dropup=a),"auto"===this.options.size)n=3this.options.size){for(var b=0;bthis.sizeInfo.menuInnerHeight&&(this.sizeInfo.hasScrollBar=!0,this.sizeInfo.totalMenuWidth=this.sizeInfo.menuWidth+this.sizeInfo.scrollBarWidth),"auto"===this.options.dropdownAlignRight&&this.$menu.toggleClass(V.MENURIGHT,this.sizeInfo.selectOffsetLeft>this.sizeInfo.selectOffsetRight&&this.sizeInfo.selectOffsetRightthis.options.size&&i.off("resize"+j+"."+this.selectId+".setMenuSize scroll"+j+"."+this.selectId+".setMenuSize")}this.createView(!1,!0,e)},setWidth:function(){var i=this;"auto"===this.options.width?requestAnimationFrame(function(){i.$menu.css("min-width","0"),i.$element.on("loaded"+j,function(){i.liHeight(),i.setMenuSize();var e=i.$newElement.clone().appendTo("body"),t=e.css("width","auto").children("button").outerWidth();e.remove(),i.sizeInfo.selectWidth=Math.max(i.sizeInfo.totalMenuWidth,t),i.$newElement.css("width",i.sizeInfo.selectWidth+"px")})}):"fit"===this.options.width?(this.$menu.css("min-width",""),this.$newElement.css("width","").addClass("fit-width")):this.options.width?(this.$menu.css("min-width",""),this.$newElement.css("width",this.options.width)):(this.$menu.css("min-width",""),this.$newElement.css("width","")),this.$newElement.hasClass("fit-width")&&"fit"!==this.options.width&&this.$newElement[0].classList.remove("fit-width")},selectPosition:function(){this.$bsContainer=z('
    ');function e(e){var t={},i=r.options.display||!!z.fn.dropdown.Constructor.Default&&z.fn.dropdown.Constructor.Default.display;r.$bsContainer.addClass(e.attr("class").replace(/form-control|fit-width/gi,"")).toggleClass(V.DROPUP,e.hasClass(V.DROPUP)),s=e.offset(),l.is("body")?n={top:0,left:0}:((n=l.offset()).top+=parseInt(l.css("borderTopWidth"))-l.scrollTop(),n.left+=parseInt(l.css("borderLeftWidth"))-l.scrollLeft()),o=e.hasClass(V.DROPUP)?0:e[0].offsetHeight,(R.major<4||"static"===i)&&(t.top=s.top-n.top+o,t.left=s.left-n.left),t.width=e[0].offsetWidth,r.$bsContainer.css(t)}var s,n,o,r=this,l=z(this.options.container);this.$button.on("click.bs.dropdown.data-api",function(){r.isDisabled()||(e(r.$newElement),r.$bsContainer.appendTo(r.options.container).toggleClass(V.SHOW,!r.$button.hasClass(V.SHOW)).append(r.$menu))}),z(window).off("resize"+j+"."+this.selectId+" scroll"+j+"."+this.selectId).on("resize"+j+"."+this.selectId+" scroll"+j+"."+this.selectId,function(){r.$newElement.hasClass(V.SHOW)&&e(r.$newElement)}),this.$element.on("hide"+j,function(){r.$menu.data("height",r.$menu.height()),r.$bsContainer.detach()})},setOptionStatus:function(e){var t=this;if(t.noScroll=!1,t.selectpicker.view.visibleElements&&t.selectpicker.view.visibleElements.length)for(var i=0;i
    ');y[2]&&($=$.replace("{var}",y[2][1"+$+"
    ")),d=!1,C.$element.trigger("maxReached"+j)),g&&w&&(E.append(z("
    "+S+"
    ")),d=!1,C.$element.trigger("maxReachedGrp"+j)),setTimeout(function(){C.setSelected(r,!1)},10),E[0].classList.add("fadeOut"),setTimeout(function(){E.remove()},1050)}}}else c&&(c.selected=!1),h.selected=!0,C.setSelected(r,!0);!C.multiple||C.multiple&&1===C.options.maxOptions?C.$button.trigger("focus"):C.options.liveSearch&&C.$searchbox.trigger("focus"),d&&(!C.multiple&&a===s.selectedIndex||(A=[h.index,p.prop("selected"),l],C.$element.triggerNative("change")))}}),this.$menu.on("click","li."+V.DISABLED+" a, ."+V.POPOVERHEADER+", ."+V.POPOVERHEADER+" :not(.close)",function(e){e.currentTarget==this&&(e.preventDefault(),e.stopPropagation(),C.options.liveSearch&&!z(e.target).hasClass("close")?C.$searchbox.trigger("focus"):C.$button.trigger("focus"))}),this.$menuInner.on("click",".divider, .dropdown-header",function(e){e.preventDefault(),e.stopPropagation(),C.options.liveSearch?C.$searchbox.trigger("focus"):C.$button.trigger("focus")}),this.$menu.on("click","."+V.POPOVERHEADER+" .close",function(){C.$button.trigger("click")}),this.$searchbox.on("click",function(e){e.stopPropagation()}),this.$menu.on("click",".actions-btn",function(e){C.options.liveSearch?C.$searchbox.trigger("focus"):C.$button.trigger("focus"),e.preventDefault(),e.stopPropagation(),z(this).hasClass("bs-select-all")?C.selectAll():C.deselectAll()}),this.$element.on("change"+j,function(){C.render(),C.$element.trigger("changed"+j,A),A=null}).on("focus"+j,function(){C.options.mobile||C.$button.trigger("focus")})},liveSearchListener:function(){var u=this,f=document.createElement("li");this.$button.on("click.bs.dropdown.data-api",function(){u.$searchbox.val()&&u.$searchbox.val("")}),this.$searchbox.on("click.bs.dropdown.data-api focus.bs.dropdown.data-api touchend.bs.dropdown.data-api",function(e){e.stopPropagation()}),this.$searchbox.on("input propertychange",function(){var e=u.$searchbox.val();if(u.selectpicker.search.elements=[],u.selectpicker.search.data=[],e){var t=[],i=e.toUpperCase(),s={},n=[],o=u._searchStyle(),r=u.options.liveSearchNormalize;r&&(i=w(i));for(var l=0;l=a.selectpicker.view.canHighlight.length&&(t=0),a.selectpicker.view.canHighlight[t+f]||(t=t+1+a.selectpicker.view.canHighlight.slice(t+f+1).indexOf(!0))),e.preventDefault();var m=f+t;e.which===B?0===f&&t===c.length-1?(a.$menuInner[0].scrollTop=a.$menuInner[0].scrollHeight,m=a.selectpicker.current.elements.length-1):d=(o=(n=a.selectpicker.current.data[m]).position-n.height)u+a.sizeInfo.menuInnerHeight),s=a.selectpicker.main.elements[v],a.activeIndex=b[x],a.focusItem(s),s&&s.firstChild.focus(),d&&(a.$menuInner[0].scrollTop=o),r.trigger("focus")}}i&&(e.which===H&&!a.selectpicker.keydown.keyHistory||e.which===D||e.which===W&&a.options.selectOnTab)&&(e.which!==H&&e.preventDefault(),a.options.liveSearch&&e.which===H||(a.$menuInner.find(".active a").trigger("click",!0),r.trigger("focus"),a.options.liveSearch||(e.preventDefault(),z(document).data("spaceSelect",!0))))}},mobile:function(){this.$element[0].classList.add("mobile-device")},refresh:function(){var e=z.extend({},this.options,this.$element.data());this.options=e,this.checkDisabled(),this.setStyle(),this.render(),this.buildData(),this.buildList(),this.setWidth(),this.setSize(!0),this.$element.trigger("refreshed"+j)},hide:function(){this.$newElement.hide()},show:function(){this.$newElement.show()},remove:function(){this.$newElement.remove(),this.$element.remove()},destroy:function(){this.$newElement.before(this.$element).remove(),this.$bsContainer?this.$bsContainer.remove():this.$menu.remove(),this.$element.off(j).removeData("selectpicker").removeClass("bs-select-hidden selectpicker"),z(window).off(j+"."+this.selectId)}};var J=z.fn.selectpicker;z.fn.selectpicker=Z,z.fn.selectpicker.Constructor=Y,z.fn.selectpicker.noConflict=function(){return z.fn.selectpicker=J,this};var Q=z.fn.dropdown.Constructor._dataApiKeydownHandler||z.fn.dropdown.Constructor.prototype.keydown;z(document).off("keydown.bs.dropdown.data-api").on("keydown.bs.dropdown.data-api",':not(.bootstrap-select) > [data-toggle="dropdown"]',Q).on("keydown.bs.dropdown.data-api",":not(.bootstrap-select) > .dropdown-menu",Q).on("keydown"+j,'.bootstrap-select [data-toggle="dropdown"], .bootstrap-select [role="listbox"], .bootstrap-select .bs-searchbox input',Y.prototype.keydown).on("focusin.modal",'.bootstrap-select [data-toggle="dropdown"], .bootstrap-select [role="listbox"], .bootstrap-select .bs-searchbox input',function(e){e.stopPropagation()}),z(window).on("load"+j+".data-api",function(){z(".selectpicker").each(function(){var e=z(this);Z.call(e,e.data())})})}(e)}); +//# sourceMappingURL=bootstrap-select.min.js.map \ No newline at end of file diff --git a/plexpy/graphs.py b/plexpy/graphs.py index 49dfee57..58a199c0 100644 --- a/plexpy/graphs.py +++ b/plexpy/graphs.py @@ -51,11 +51,7 @@ class Graphs(object): time_range = helpers.cast_to_int(time_range) or 30 timestamp = helpers.timestamp() - time_range * 24 * 60 * 60 - user_cond = '' - if session.get_session_user_id() and user_id and user_id != str(session.get_session_user_id()): - user_cond = 'AND session_history.user_id = %s ' % session.get_session_user_id() - elif user_id and user_id.isdigit(): - user_cond = 'AND session_history.user_id = %s ' % user_id + user_cond = self._make_user_cond(user_id) if grouping is None: grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES @@ -171,11 +167,7 @@ class Graphs(object): time_range = helpers.cast_to_int(time_range) or 30 timestamp = helpers.timestamp() - time_range * 24 * 60 * 60 - user_cond = '' - if session.get_session_user_id() and user_id and user_id != str(session.get_session_user_id()): - user_cond = "AND session_history.user_id = %s " % session.get_session_user_id() - elif user_id and user_id.isdigit(): - user_cond = "AND session_history.user_id = %s " % user_id + user_cond = self._make_user_cond(user_id) if grouping is None: grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES @@ -308,11 +300,7 @@ class Graphs(object): time_range = helpers.cast_to_int(time_range) or 30 timestamp = helpers.timestamp() - time_range * 24 * 60 * 60 - user_cond = '' - if session.get_session_user_id() and user_id and user_id != str(session.get_session_user_id()): - user_cond = 'AND session_history.user_id = %s ' % session.get_session_user_id() - elif user_id and user_id.isdigit(): - user_cond = 'AND session_history.user_id = %s ' % user_id + user_cond = self._make_user_cond(user_id) if grouping is None: grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES @@ -427,11 +415,7 @@ class Graphs(object): time_range = helpers.cast_to_int(time_range) or 12 timestamp = arrow.get(helpers.timestamp()).shift(months=-time_range).floor('month').timestamp() - user_cond = '' - if session.get_session_user_id() and user_id and user_id != str(session.get_session_user_id()): - user_cond = 'AND session_history.user_id = %s ' % session.get_session_user_id() - elif user_id and user_id.isdigit(): - user_cond = 'AND session_history.user_id = %s ' % user_id + user_cond = self._make_user_cond(user_id) if grouping is None: grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES @@ -554,11 +538,7 @@ class Graphs(object): time_range = helpers.cast_to_int(time_range) or 30 timestamp = helpers.timestamp() - time_range * 24 * 60 * 60 - user_cond = '' - if session.get_session_user_id() and user_id and user_id != str(session.get_session_user_id()): - user_cond = 'AND session_history.user_id = %s ' % session.get_session_user_id() - elif user_id and user_id.isdigit(): - user_cond = 'AND session_history.user_id = %s ' % user_id + user_cond = self._make_user_cond(user_id) if grouping is None: grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES @@ -653,11 +633,7 @@ class Graphs(object): time_range = helpers.cast_to_int(time_range) or 30 timestamp = helpers.timestamp() - time_range * 24 * 60 * 60 - user_cond = '' - if session.get_session_user_id() and user_id and user_id != str(session.get_session_user_id()): - user_cond = 'AND session_history.user_id = %s ' % session.get_session_user_id() - elif user_id and user_id.isdigit(): - user_cond = 'AND session_history.user_id = %s ' % user_id + user_cond = self._make_user_cond(user_id) if grouping is None: grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES @@ -763,11 +739,7 @@ class Graphs(object): time_range = helpers.cast_to_int(time_range) or 30 timestamp = helpers.timestamp() - time_range * 24 * 60 * 60 - user_cond = '' - if session.get_session_user_id() and user_id and user_id != str(session.get_session_user_id()): - user_cond = 'AND session_history.user_id = %s ' % session.get_session_user_id() - elif user_id and user_id.isdigit(): - user_cond = 'AND session_history.user_id = %s ' % user_id + user_cond = self._make_user_cond(user_id) if grouping is None: grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES @@ -860,11 +832,7 @@ class Graphs(object): time_range = helpers.cast_to_int(time_range) or 30 timestamp = helpers.timestamp() - time_range * 24 * 60 * 60 - user_cond = '' - if session.get_session_user_id() and user_id and user_id != str(session.get_session_user_id()): - user_cond = 'AND session_history.user_id = %s ' % session.get_session_user_id() - elif user_id and user_id.isdigit(): - user_cond = 'AND session_history.user_id = %s ' % user_id + user_cond = self._make_user_cond(user_id) if grouping is None: grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES @@ -941,11 +909,7 @@ class Graphs(object): time_range = helpers.cast_to_int(time_range) or 30 timestamp = helpers.timestamp() - time_range * 24 * 60 * 60 - user_cond = '' - if session.get_session_user_id() and user_id and user_id != str(session.get_session_user_id()): - user_cond = 'AND session_history.user_id = %s ' % session.get_session_user_id() - elif user_id and user_id.isdigit(): - user_cond = 'AND session_history.user_id = %s ' % user_id + user_cond = self._make_user_cond(user_id) if grouping is None: grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES @@ -1048,11 +1012,7 @@ class Graphs(object): time_range = helpers.cast_to_int(time_range) or 30 timestamp = helpers.timestamp() - time_range * 24 * 60 * 60 - user_cond = '' - if session.get_session_user_id() and user_id and user_id != str(session.get_session_user_id()): - user_cond = 'AND session_history.user_id = %s ' % session.get_session_user_id() - elif user_id and user_id.isdigit(): - user_cond = 'AND session_history.user_id = %s ' % user_id + user_cond = self._make_user_cond(user_id) if grouping is None: grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES @@ -1128,11 +1088,7 @@ class Graphs(object): time_range = helpers.cast_to_int(time_range) or 30 timestamp = helpers.timestamp() - time_range * 24 * 60 * 60 - user_cond = '' - if session.get_session_user_id() and user_id and user_id != str(session.get_session_user_id()): - user_cond = 'AND session_history.user_id = %s ' % session.get_session_user_id() - elif user_id and user_id.isdigit(): - user_cond = 'AND session_history.user_id = %s ' % user_id + user_cond = self._make_user_cond(user_id) if grouping is None: grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES @@ -1212,3 +1168,16 @@ class Graphs(object): 'series': [series_1_output, series_2_output, series_3_output]} return output + + def _make_user_cond(self, user_id): + """ + Expects user_id to be a comma-separated list of ints. + """ + user_cond = '' + if session.get_session_user_id() and user_id and user_id != str(session.get_session_user_id()): + user_cond = 'AND session_history.user_id = %s ' % session.get_session_user_id() + elif user_id: + user_ids = helpers.split_strip(user_id) + if all(id.isdigit() for id in user_ids): + user_cond = 'AND session_history.user_id IN (%s) ' % ','.join(user_ids) + return user_cond diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 06a9a802..7549aefa 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -2258,7 +2258,7 @@ class WebInterface(object): Optional parameters: time_range (str): The number of days of data to return y_axis (str): "plays" or "duration" - user_id (str): The user id to filter the data + user_id (str): Comma separated list of user id to filter the data grouping (int): 0 or 1 Returns: @@ -2302,7 +2302,7 @@ class WebInterface(object): Optional parameters: time_range (str): The number of days of data to return y_axis (str): "plays" or "duration" - user_id (str): The user id to filter the data + user_id (str): Comma separated list of user id to filter the data grouping (int): 0 or 1 Returns: @@ -2346,7 +2346,7 @@ class WebInterface(object): Optional parameters: time_range (str): The number of days of data to return y_axis (str): "plays" or "duration" - user_id (str): The user id to filter the data + user_id (str): Comma separated list of user id to filter the data grouping (int): 0 or 1 Returns: @@ -2390,7 +2390,7 @@ class WebInterface(object): Optional parameters: time_range (str): The number of months of data to return y_axis (str): "plays" or "duration" - user_id (str): The user id to filter the data + user_id (str): Comma separated list of user id to filter the data grouping (int): 0 or 1 Returns: @@ -2434,7 +2434,7 @@ class WebInterface(object): Optional parameters: time_range (str): The number of days of data to return y_axis (str): "plays" or "duration" - user_id (str): The user id to filter the data + user_id (str): Comma separated list of user id to filter the data grouping (int): 0 or 1 Returns: @@ -2478,7 +2478,7 @@ class WebInterface(object): Optional parameters: time_range (str): The number of days of data to return y_axis (str): "plays" or "duration" - user_id (str): The user id to filter the data + user_id (str): Comma separated list of user id to filter the data grouping (int): 0 or 1 Returns: @@ -2522,7 +2522,7 @@ class WebInterface(object): Optional parameters: time_range (str): The number of days of data to return y_axis (str): "plays" or "duration" - user_id (str): The user id to filter the data + user_id (str): Comma separated list of user id to filter the data grouping (int): 0 or 1 Returns: @@ -2565,7 +2565,7 @@ class WebInterface(object): Optional parameters: time_range (str): The number of days of data to return y_axis (str): "plays" or "duration" - user_id (str): The user id to filter the data + user_id (str): Comma separated list of user id to filter the data grouping (int): 0 or 1 Returns: @@ -2608,7 +2608,7 @@ class WebInterface(object): Optional parameters: time_range (str): The number of days of data to return y_axis (str): "plays" or "duration" - user_id (str): The user id to filter the data + user_id (str): Comma separated list of user id to filter the data grouping (int): 0 or 1 Returns: @@ -2651,7 +2651,7 @@ class WebInterface(object): Optional parameters: time_range (str): The number of days of data to return y_axis (str): "plays" or "duration" - user_id (str): The user id to filter the data + user_id (str): Comma separated list of user id to filter the data grouping (int): 0 or 1 Returns: @@ -2694,7 +2694,7 @@ class WebInterface(object): Optional parameters: time_range (str): The number of days of data to return y_axis (str): "plays" or "duration" - user_id (str): The user id to filter the data + user_id (str): Comma separated list of user id to filter the data grouping (int): 0 or 1 Returns: From d91e561a56a14728970b3f581a3329a40cff0abf Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Fri, 7 Jul 2023 17:47:38 -0700 Subject: [PATCH 245/583] Regroup history in separate thread and improve logging --- data/interfaces/default/settings.html | 4 ++-- plexpy/activity_processor.py | 12 +++++++++++- plexpy/webserve.py | 8 +++----- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index 7cd614e0..c5d8fe37 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -2498,9 +2498,9 @@ $(document).ready(function() { }); $("#regroup_history").click(function () { - var msg = 'Are you sure you want to regroup play history in the database?'; + var msg = 'Are you sure you want to regroup play history in the database?

    This make take a long time for large databases.
    Regrouping will continue in the background.
    '; var url = 'regroup_history'; - confirmAjaxCall(url, msg, null, 'Regrouping play history...'); + confirmAjaxCall(url, msg); }); $("#delete_temp_sessions").click(function () { diff --git a/plexpy/activity_processor.py b/plexpy/activity_processor.py index b1558a56..0437d2d5 100644 --- a/plexpy/activity_processor.py +++ b/plexpy/activity_processor.py @@ -719,8 +719,14 @@ class ActivityProcessor(object): "JOIN session_history_metadata ON session_history.id = session_history_metadata.id" ) results = self.db.select(query) + count = len(results) + progress = 0 + + for i, session in enumerate(results, start=1): + if int(i / count * 10) > progress: + progress = int(i / count * 10) + logger.info("Tautulli ActivityProcessor :: Regrouping session history: %d%%", progress * 10) - for session in results: try: self.group_history(session['id'], session) except Exception as e: @@ -729,3 +735,7 @@ class ActivityProcessor(object): logger.info("Tautulli ActivityProcessor :: Regrouping session history complete.") return True + + +def regroup_history(): + ActivityProcessor().regroup_history() diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 7549aefa..b98c9e7c 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -443,12 +443,10 @@ class WebInterface(object): def regroup_history(self, **kwargs): """ Regroup play history in the database.""" - result = activity_processor.ActivityProcessor().regroup_history() + threading.Thread(target=activity_processor.regroup_history).start() - if result: - return {'result': 'success', 'message': 'Regrouped play history.'} - else: - return {'result': 'error', 'message': 'Regrouping play history failed.'} + return {'result': 'success', + 'message': 'Regrouping play history started. Check the logs to monitor any problems.'} @cherrypy.expose @cherrypy.tools.json_out() From 6010e406c817ecfb11d873824e0adcc22f5bcc80 Mon Sep 17 00:00:00 2001 From: David Pooley Date: Sun, 9 Jul 2023 00:32:42 +0100 Subject: [PATCH 246/583] Fix simultaneous streams per IP not behaving as expected with IPv6 (#2096) * Fix IPv6 comparisson for concurrent streams * Update regex to allow numbers in config variables * Remove additional logging for local testing * Update plexpy/notification_handler.py Co-authored-by: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> --------- Co-authored-by: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> --- plexpy/config.py | 3 ++- plexpy/helpers.py | 16 ++++++++++++++++ plexpy/notification_handler.py | 9 ++++++++- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/plexpy/config.py b/plexpy/config.py index 7b583d8d..6f2926d9 100644 --- a/plexpy/config.py +++ b/plexpy/config.py @@ -177,6 +177,7 @@ _CONFIG_DEFINITIONS = { 'NOTIFY_RECENTLY_ADDED_UPGRADE': (int, 'Monitoring', 0), 'NOTIFY_REMOTE_ACCESS_THRESHOLD': (int, 'Monitoring', 60), 'NOTIFY_CONCURRENT_BY_IP': (int, 'Monitoring', 0), + 'NOTIFY_CONCURRENT_IPV6_CIDR': (str, 'Monitoring', '/64'), 'NOTIFY_CONCURRENT_THRESHOLD': (int, 'Monitoring', 2), 'NOTIFY_NEW_DEVICE_INITIAL_ONLY': (int, 'Monitoring', 1), 'NOTIFY_SERVER_CONNECTION_THRESHOLD': (int, 'Monitoring', 60), @@ -536,7 +537,7 @@ class Config(object): Returns something from the ini unless it is a real property of the configuration object or is not all caps. """ - if not re.match(r'[A-Z_]+$', name): + if not re.match(r'[A-Z0-9_]+$', name): return super(Config, self).__getattr__(name) else: return self.check_setting(name) diff --git a/plexpy/helpers.py b/plexpy/helpers.py index 9cfb9c45..085dfc12 100644 --- a/plexpy/helpers.py +++ b/plexpy/helpers.py @@ -33,6 +33,7 @@ from functools import reduce, wraps import hashlib import imghdr from future.moves.itertools import islice, zip_longest +from ipaddress import ip_address, ip_network, IPv4Address import ipwhois import ipwhois.exceptions import ipwhois.utils @@ -1777,3 +1778,18 @@ def check_watched(media_type, view_offset, duration, marker_credits_first=None, def pms_name(): return plexpy.CONFIG.PMS_NAME_OVERRIDE or plexpy.CONFIG.PMS_NAME + + +def ip_type(ip: str) -> str: + try: + return "IPv4" if type(ip_address(ip)) is IPv4Address else "IPv6" + except ValueError: + return "Invalid" + + +def get_ipv6_network_address(ip: str) -> str: + cidr = "/64" + cidr_pattern = re.compile(r'^/(1([0-1]\d|2[0-8]))$|^/(\d\d)$|^/[1-9]$') + if cidr_pattern.match(plexpy.CONFIG.NOTIFY_CONCURRENT_IPV6_CIDR): + cidr = plexpy.CONFIG.NOTIFY_CONCURRENT_IPV6_CIDR + return str(ip_network(ip+cidr, strict=False).network_address) diff --git a/plexpy/notification_handler.py b/plexpy/notification_handler.py index 7dd81627..8b4b8583 100644 --- a/plexpy/notification_handler.py +++ b/plexpy/notification_handler.py @@ -160,6 +160,7 @@ def add_notifier_each(notifier_id=None, notify_action=None, stream_data=None, ti def notify_conditions(notify_action=None, stream_data=None, timeline_data=None, **kwargs): logger.debug("Tautulli NotificationHandler :: Checking global notification conditions.") + evaluated = False # Activity notifications if stream_data: @@ -187,7 +188,13 @@ def notify_conditions(notify_action=None, stream_data=None, timeline_data=None, user_sessions = [s for s in result['sessions'] if s['user_id'] == stream_data['user_id']] if plexpy.CONFIG.NOTIFY_CONCURRENT_BY_IP: - evaluated = len(Counter(s['ip_address'] for s in user_sessions)) >= plexpy.CONFIG.NOTIFY_CONCURRENT_THRESHOLD + ip_addresses = set() + for s in user_sessions: + if helpers.ip_type(s['ip_address']) == 'IPv6': + ip_addresses.add(helpers.get_ipv6_network_address(s['ip_address'])) + elif helpers.ip_type(s['ip_address']) == 'IPv4': + ip_addresses.add(s['ip_address']) + evaluated = len(ip_addresses) >= plexpy.CONFIG.NOTIFY_CONCURRENT_THRESHOLD else: evaluated = len(user_sessions) >= plexpy.CONFIG.NOTIFY_CONCURRENT_THRESHOLD From 571a6b6d2df91d209907800af3f1b5c7356e2577 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Mon, 10 Jul 2023 08:56:27 -0700 Subject: [PATCH 247/583] Cast view_offset to int for regrouping history --- plexpy/activity_processor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plexpy/activity_processor.py b/plexpy/activity_processor.py index 0437d2d5..9115f332 100644 --- a/plexpy/activity_processor.py +++ b/plexpy/activity_processor.py @@ -519,12 +519,12 @@ class ActivityProcessor(object): if len(result) > 1: new_session = {'id': result[0]['id'], 'rating_key': result[0]['rating_key'], - 'view_offset': result[0]['view_offset'], + 'view_offset': helpers.cast_to_int(result[0]['view_offset']), 'reference_id': result[0]['reference_id']} prev_session = {'id': result[1]['id'], 'rating_key': result[1]['rating_key'], - 'view_offset': result[1]['view_offset'], + 'view_offset': helpers.cast_to_int(result[1]['view_offset']), 'reference_id': result[1]['reference_id']} if metadata: From b953b951fb46400f7d30a22cdc2791faa8d6e233 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Thu, 13 Jul 2023 15:50:39 -0700 Subject: [PATCH 248/583] v2.12.6 --- CHANGELOG.md | 17 +++++++++++++++++ plexpy/version.py | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24baf072..974e69ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Changelog +## v2.12.5 (2023-07-13) + +* Activity: + * New: Added d3d11va to list of hardware decoders. +* History: + * Fix: Incorrect grouping of play history. + * New: Added button in settings to regroup play history. +* Notifications: + * Fix: Incorrect concurrent streams notifications by IP addresss for IPv6 addresses (#2096) (Thanks @pooley182) +* UI: + * Fix: Occasional UI crashing on Python 3.11. + * New: Added multiselect user filters to History and Graphs pages. (#2090) (Thanks @zdimension) +* API: + * New: Added regroup_history API command. + * Change: Updated graph API commands to accept a comma separated list of user IDs. + + ## v2.12.4 (2023-05-23) * History: diff --git a/plexpy/version.py b/plexpy/version.py index 119e0b07..116f4687 100644 --- a/plexpy/version.py +++ b/plexpy/version.py @@ -18,4 +18,4 @@ from __future__ import unicode_literals PLEXPY_BRANCH = "master" -PLEXPY_RELEASE_VERSION = "v2.12.4" \ No newline at end of file +PLEXPY_RELEASE_VERSION = "v2.12.5" \ No newline at end of file From 765804c93b8304629b19140f1ed58b7dbace1c4e Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Thu, 20 Jul 2023 14:19:05 -0700 Subject: [PATCH 249/583] Don't expose do_state_change --- plexpy/webserve.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/plexpy/webserve.py b/plexpy/webserve.py index b98c9e7c..b643f84b 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -4302,8 +4302,6 @@ class WebInterface(object): return update - @cherrypy.expose - @requireAuth(member_of("admin")) def do_state_change(self, signal, title, timer, **kwargs): message = title quote = self.random_arnold_quotes() From d701d18a813246eff84034205c6ced99f7c09c63 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Thu, 27 Jul 2023 20:04:03 -0700 Subject: [PATCH 250/583] Update workflows action version refs --- .github/workflows/publish-installers.yml | 8 ++++---- .github/workflows/publish-snap.yml | 2 +- .github/workflows/pull-requests.yml | 1 - 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/publish-installers.yml b/.github/workflows/publish-installers.yml index 49d53233..0b6eec36 100644 --- a/.github/workflows/publish-installers.yml +++ b/.github/workflows/publish-installers.yml @@ -68,7 +68,7 @@ jobs: pyinstaller -y ./package/Tautulli-${{ matrix.os }}.spec - name: Create Windows Installer - uses: joncloud/makensis-action@v3.7 + uses: joncloud/makensis-action@v4 if: matrix.os == 'windows' with: script-file: ./package/Tautulli.nsi @@ -100,10 +100,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Get Build Job Status - uses: technote-space/workflow-conclusion-action@v3.0 + uses: technote-space/workflow-conclusion-action@v3 - name: Checkout Code - uses: actions/checkout@v3.2.0 + uses: actions/checkout@v3 - name: Set Release Version id: get_version @@ -168,7 +168,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Get Build Job Status - uses: technote-space/workflow-conclusion-action@v3.0 + uses: technote-space/workflow-conclusion-action@v3 - name: Combine Job Status id: status diff --git a/.github/workflows/publish-snap.yml b/.github/workflows/publish-snap.yml index 9df4d2fd..dd74c3a3 100644 --- a/.github/workflows/publish-snap.yml +++ b/.github/workflows/publish-snap.yml @@ -70,7 +70,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Get Build Job Status - uses: technote-space/workflow-conclusion-action@v3.0 + uses: technote-space/workflow-conclusion-action@v3 - name: Combine Job Status id: status diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml index 58cb4ee4..1a24cf24 100644 --- a/.github/workflows/pull-requests.yml +++ b/.github/workflows/pull-requests.yml @@ -18,7 +18,6 @@ jobs: with: message: Pull requests must be made to the `nightly` branch. Thanks. repo-token: ${{ secrets.GITHUB_TOKEN }} - repo-token-user-login: 'github-actions[bot]' - name: Fail Workflow if: github.base_ref != 'nightly' From b984a99d512b32795031fd6ed3816afe11d516db Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Thu, 27 Jul 2023 20:04:27 -0700 Subject: [PATCH 251/583] Update workflows action version refs --- .github/workflows/publish-docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml index 6480575f..6d91bbf6 100644 --- a/.github/workflows/publish-docker.yml +++ b/.github/workflows/publish-docker.yml @@ -95,7 +95,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Get Build Job Status - uses: technote-space/workflow-conclusion-action@v3.0 + uses: technote-space/workflow-conclusion-action@v3 - name: Combine Job Status id: status From e2cb15ef49ed6df5493b35e95f51835829879446 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Wed, 2 Aug 2023 16:51:20 -0700 Subject: [PATCH 252/583] Add notification image option for iOS Tautulli Remote App --- plexpy/notifiers.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plexpy/notifiers.py b/plexpy/notifiers.py index a2fa6341..2611aaea 100644 --- a/plexpy/notifiers.py +++ b/plexpy/notifiers.py @@ -3967,6 +3967,14 @@ class TAUTULLIREMOTEAPP(Notifier): 2: 'Large image (Non-expandable text)' } }) + elif platform == 'ios': + config_option.append({ + 'label': 'Include Poster Image', + 'value': self.config['notification_type'], + 'name': 'remoteapp_notification_type', + 'description': 'Include a poster with the notifications.', + 'input_type': 'checkbox' + }) return config_option From 842e36485a6b4e718ad870cf0024c23bdc7d8d16 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Aug 2023 08:42:01 -0700 Subject: [PATCH 253/583] Bump actions/stale from 7 to 8 (#2025) Bumps [actions/stale](https://github.com/actions/stale) from 7 to 8. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/v7...v8) --- updated-dependencies: - dependency-name: actions/stale dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> [skip ci] --- .github/workflows/issues-stale.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/issues-stale.yml b/.github/workflows/issues-stale.yml index 0643cb0a..26b8aa5f 100644 --- a/.github/workflows/issues-stale.yml +++ b/.github/workflows/issues-stale.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Stale - uses: actions/stale@v7 + uses: actions/stale@v8 with: stale-issue-message: > This issue is stale because it has been open for 30 days with no activity. @@ -30,7 +30,7 @@ jobs: days-before-close: 5 - name: Invalid Template - uses: actions/stale@v7 + uses: actions/stale@v8 with: stale-issue-message: > Invalid issues template. From 31543d267f4f793689be599fa66a1ce17879bab6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Aug 2023 21:19:39 -0700 Subject: [PATCH 254/583] Bump pywin32 from 305 to 306 (#2028) Bumps [pywin32](https://github.com/mhammond/pywin32) from 305 to 306. - [Release notes](https://github.com/mhammond/pywin32/releases) - [Changelog](https://github.com/mhammond/pywin32/blob/main/CHANGES.txt) - [Commits](https://github.com/mhammond/pywin32/commits) --- updated-dependencies: - dependency-name: pywin32 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> [skip ci] --- package/requirements-package.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/requirements-package.txt b/package/requirements-package.txt index e8ccd0b8..d3c6d57b 100644 --- a/package/requirements-package.txt +++ b/package/requirements-package.txt @@ -8,4 +8,4 @@ pycryptodomex==3.17 pyobjc-core==9.0.1; platform_system == "Darwin" pyobjc-framework-Cocoa==9.0.1; platform_system == "Darwin" -pywin32==305; platform_system == "Windows" +pywin32==306; platform_system == "Windows" From 6fa8bb376828cdb954ecf08eda804d70747e7502 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Aug 2023 21:20:05 -0700 Subject: [PATCH 255/583] Bump pyopenssl from 23.0.0 to 23.2.0 (#2081) Bumps [pyopenssl](https://github.com/pyca/pyopenssl) from 23.0.0 to 23.2.0. - [Changelog](https://github.com/pyca/pyopenssl/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/pyopenssl/compare/23.0.0...23.2.0) --- updated-dependencies: - dependency-name: pyopenssl dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> [skip ci] --- package/requirements-package.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/requirements-package.txt b/package/requirements-package.txt index d3c6d57b..f432c59b 100644 --- a/package/requirements-package.txt +++ b/package/requirements-package.txt @@ -2,7 +2,7 @@ apscheduler==3.10.1 importlib-metadata==6.0.0 importlib-resources==5.12.0 pyinstaller==5.8.0 -pyopenssl==23.0.0 +pyopenssl==23.2.0 pycryptodomex==3.17 pyobjc-core==9.0.1; platform_system == "Darwin" From b7c0b104e94b9ea77a6230192d67c22d0ab08325 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Aug 2023 21:20:54 -0700 Subject: [PATCH 256/583] Bump pycryptodomex from 3.17 to 3.18.0 (#2076) Bumps [pycryptodomex](https://github.com/Legrandin/pycryptodome) from 3.17 to 3.18.0. - [Release notes](https://github.com/Legrandin/pycryptodome/releases) - [Changelog](https://github.com/Legrandin/pycryptodome/blob/master/Changelog.rst) - [Commits](https://github.com/Legrandin/pycryptodome/compare/v3.17.0...v3.18.0) --- updated-dependencies: - dependency-name: pycryptodomex dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> [skip ci] --- package/requirements-package.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/requirements-package.txt b/package/requirements-package.txt index f432c59b..ee3cc800 100644 --- a/package/requirements-package.txt +++ b/package/requirements-package.txt @@ -3,7 +3,7 @@ importlib-metadata==6.0.0 importlib-resources==5.12.0 pyinstaller==5.8.0 pyopenssl==23.2.0 -pycryptodomex==3.17 +pycryptodomex==3.18.0 pyobjc-core==9.0.1; platform_system == "Darwin" pyobjc-framework-Cocoa==9.0.1; platform_system == "Darwin" From a84b5b51ed23354cff8903b0dab038fa384b6a32 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Aug 2023 21:21:16 -0700 Subject: [PATCH 257/583] Bump pyobjc-core from 9.0.1 to 9.2 (#2082) Bumps [pyobjc-core](https://github.com/ronaldoussoren/pyobjc) from 9.0.1 to 9.2. - [Release notes](https://github.com/ronaldoussoren/pyobjc/releases) - [Changelog](https://github.com/ronaldoussoren/pyobjc/blob/master/docs/changelog.rst) - [Commits](https://github.com/ronaldoussoren/pyobjc/compare/v9.0.1...v9.2) --- updated-dependencies: - dependency-name: pyobjc-core dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> [skip ci] --- package/requirements-package.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/requirements-package.txt b/package/requirements-package.txt index ee3cc800..70cd4cb0 100644 --- a/package/requirements-package.txt +++ b/package/requirements-package.txt @@ -5,7 +5,7 @@ pyinstaller==5.8.0 pyopenssl==23.2.0 pycryptodomex==3.18.0 -pyobjc-core==9.0.1; platform_system == "Darwin" +pyobjc-core==9.2; platform_system == "Darwin" pyobjc-framework-Cocoa==9.0.1; platform_system == "Darwin" pywin32==306; platform_system == "Windows" From e11a4c50bafacd0dad8ef192a6b9533a033d883b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Aug 2023 21:21:55 -0700 Subject: [PATCH 258/583] Bump pyobjc-framework-cocoa from 9.0.1 to 9.2 (#2083) Bumps [pyobjc-framework-cocoa](https://github.com/ronaldoussoren/pyobjc) from 9.0.1 to 9.2. - [Release notes](https://github.com/ronaldoussoren/pyobjc/releases) - [Changelog](https://github.com/ronaldoussoren/pyobjc/blob/master/docs/changelog.rst) - [Commits](https://github.com/ronaldoussoren/pyobjc/compare/v9.0.1...v9.2) --- updated-dependencies: - dependency-name: pyobjc-framework-cocoa dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> [skip ci] --- package/requirements-package.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/requirements-package.txt b/package/requirements-package.txt index 70cd4cb0..064d2246 100644 --- a/package/requirements-package.txt +++ b/package/requirements-package.txt @@ -6,6 +6,6 @@ pyopenssl==23.2.0 pycryptodomex==3.18.0 pyobjc-core==9.2; platform_system == "Darwin" -pyobjc-framework-Cocoa==9.0.1; platform_system == "Darwin" +pyobjc-framework-Cocoa==9.2; platform_system == "Darwin" pywin32==306; platform_system == "Windows" From f80cd739825051224a48ff4288cc9b148d32b7cd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Aug 2023 21:22:24 -0700 Subject: [PATCH 259/583] Bump importlib-metadata from 6.0.0 to 6.8.0 (#2112) Bumps [importlib-metadata](https://github.com/python/importlib_metadata) from 6.0.0 to 6.8.0. - [Release notes](https://github.com/python/importlib_metadata/releases) - [Changelog](https://github.com/python/importlib_metadata/blob/main/NEWS.rst) - [Commits](https://github.com/python/importlib_metadata/compare/v6.0.0...v6.8.0) --- updated-dependencies: - dependency-name: importlib-metadata dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> [skip ci] --- package/requirements-package.txt | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package/requirements-package.txt b/package/requirements-package.txt index 064d2246..075aa916 100644 --- a/package/requirements-package.txt +++ b/package/requirements-package.txt @@ -1,5 +1,5 @@ apscheduler==3.10.1 -importlib-metadata==6.0.0 +importlib-metadata==6.8.0 importlib-resources==5.12.0 pyinstaller==5.8.0 pyopenssl==23.2.0 diff --git a/requirements.txt b/requirements.txt index 49c32ff6..ee495cb7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,7 +19,7 @@ gntp==1.0.3 html5lib==1.1 httpagentparser==1.9.5 idna==3.4 -importlib-metadata==6.0.0 +importlib-metadata==6.8.0 importlib-resources==5.12.0 git+https://github.com/Tautulli/ipwhois.git@master#egg=ipwhois IPy==1.01 From a21fffd227617d17f3c184965797c0a864df433a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Aug 2023 21:23:35 -0700 Subject: [PATCH 260/583] Bump pyinstaller from 5.8.0 to 5.13.0 (#2114) Bumps [pyinstaller](https://github.com/pyinstaller/pyinstaller) from 5.8.0 to 5.13.0. - [Release notes](https://github.com/pyinstaller/pyinstaller/releases) - [Changelog](https://github.com/pyinstaller/pyinstaller/blob/develop/doc/CHANGES.rst) - [Commits](https://github.com/pyinstaller/pyinstaller/compare/v5.8.0...v5.13.0) --- updated-dependencies: - dependency-name: pyinstaller dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package/requirements-package.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/requirements-package.txt b/package/requirements-package.txt index 075aa916..5847d597 100644 --- a/package/requirements-package.txt +++ b/package/requirements-package.txt @@ -1,7 +1,7 @@ apscheduler==3.10.1 importlib-metadata==6.8.0 importlib-resources==5.12.0 -pyinstaller==5.8.0 +pyinstaller==5.13.0 pyopenssl==23.2.0 pycryptodomex==3.18.0 From 835ea34bea88fa9f2fb31d26e210b75e42952b80 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Aug 2023 21:38:17 -0700 Subject: [PATCH 261/583] Bump pytz from 2022.7.1 to 2023.3 (#2031) * Bump pytz from 2022.7.1 to 2023.3 Bumps [pytz](https://github.com/stub42/pytz) from 2022.7.1 to 2023.3. - [Release notes](https://github.com/stub42/pytz/releases) - [Commits](https://github.com/stub42/pytz/compare/release_2022.7.1...release_2023.3) --- updated-dependencies: - dependency-name: pytz dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Update pytz==2023.3 --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> [skip ci] --- lib/pytz/__init__.py | 5 +- lib/pytz/zoneinfo/Africa/Cairo | Bin 1955 -> 2399 bytes lib/pytz/zoneinfo/Africa/Casablanca | Bin 2429 -> 2429 bytes lib/pytz/zoneinfo/Africa/El_Aaiun | Bin 2295 -> 2295 bytes lib/pytz/zoneinfo/America/Godthab | Bin 1448 -> 1903 bytes lib/pytz/zoneinfo/America/Nuuk | Bin 1448 -> 1903 bytes lib/pytz/zoneinfo/America/Yellowknife | Bin 2136 -> 2332 bytes lib/pytz/zoneinfo/Asia/Gaza | Bin 2422 -> 3808 bytes lib/pytz/zoneinfo/Asia/Hebron | Bin 2450 -> 3836 bytes lib/pytz/zoneinfo/Egypt | Bin 1955 -> 2399 bytes lib/pytz/zoneinfo/Europe/Kirov | Bin 1139 -> 1185 bytes lib/pytz/zoneinfo/Europe/Volgograd | Bin 1151 -> 1193 bytes lib/pytz/zoneinfo/iso3166.tab | 2 +- lib/pytz/zoneinfo/leapseconds | 8 +- lib/pytz/zoneinfo/tzdata.zi | 130 ++++++++++++++++++++------ lib/pytz/zoneinfo/zone.tab | 29 +++--- lib/pytz/zoneinfo/zone1970.tab | 58 ++++++------ requirements.txt | 2 +- 18 files changed, 156 insertions(+), 78 deletions(-) diff --git a/lib/pytz/__init__.py b/lib/pytz/__init__.py index 11d2e2ca..98b66553 100644 --- a/lib/pytz/__init__.py +++ b/lib/pytz/__init__.py @@ -22,8 +22,8 @@ from pytz.tzfile import build_tzinfo # The IANA (nee Olson) database is updated several times a year. -OLSON_VERSION = '2022g' -VERSION = '2022.7.1' # pip compatible version number. +OLSON_VERSION = '2023c' +VERSION = '2023.3' # pip compatible version number. __version__ = VERSION OLSEN_VERSION = OLSON_VERSION # Old releases had this misspelling @@ -1311,7 +1311,6 @@ common_timezones = \ 'America/Whitehorse', 'America/Winnipeg', 'America/Yakutat', - 'America/Yellowknife', 'Antarctica/Casey', 'Antarctica/Davis', 'Antarctica/DumontDUrville', diff --git a/lib/pytz/zoneinfo/Africa/Cairo b/lib/pytz/zoneinfo/Africa/Cairo index d3f819623fc9ef90d327380fad15341ec1a0e202..dd538c65db6ed0a0e47feb7b6001640516958e19 100644 GIT binary patch delta 474 zcmZ9|ze@sP9LMqQ5()`|)}#g_C`?V1NTPf2+OCzEiJOxf*im=0fa4E=C(-Y4lMpd*9WH zyDl@G4=VF|E>~Y_YOQf?XYbC`daZ14REuiUD%e{m^J+Vnws*1xkxNGHd^|07r{-kA z7ZpX%q%8G#M5)y;_ujt6{?nH{xVOdOO;aA#Tk81YR+{B!rEBfqn$Tm`jNWY}x`v(i zfOVW7BY-v%9jZHBmPJr=u3^jBzuE+ezcM1{!)jJC}8WEX5MVEn`c7D#2O1qn=k3Z%47&TP?GTKadZk@7@9O*@5aZfP8je=4qA+lTA5vnFYQoY)<8nU<3g6DJ6&i delta 105 zcmew>^jBzuE+ez==>?Mw7;TxKM<{GAVEn`c7D#2O1qn=k3ZxsJESQ|iTFWd|2;>9l u&3}P>wpwQ2E+8LBKNkb?*@5a71NrQ}%=3#EOg81vWtP%kusM}Of)N0ak|(nO diff --git a/lib/pytz/zoneinfo/Africa/El_Aaiun b/lib/pytz/zoneinfo/Africa/El_Aaiun index 64f1b7694418a8c284febe195659c4dd53359b1a..066fbed008cf662455eeca2c012ab8cb5bf1731a 100644 GIT binary patch delta 105 zcmew^_+4-VBO|lRM1{#LjJC}8WEX69VVubX7I?s13lf-|z*5UxzDi;810XGE2IRBW vGOs!e?Nn7;TxKM<{G|VVubX7I?s13lf-|z*5WH@MOW{2S8e?5Xfh( uW#0T3$OqECT|hn?P@Nc%52PCw1NrQ}%=3#EOy0n*%Pgh8VDkfZaYg`5l_!q? diff --git a/lib/pytz/zoneinfo/America/Godthab b/lib/pytz/zoneinfo/America/Godthab index 883dfa055532c8319a833d80c82707b3ca5d4d5a..adb7934aadf5f45928ce67e82efa92ca92391703 100644 GIT binary patch delta 653 zcmb8sJ4nMo9LMooeNv^IgE(m?x3oEdD^)>o@7jjwFtB!9>t9o(dJ zDvCI{h=@3etAmR!PFg2%koy0nZX(oNF5k-yf%~K}^yX8|*?h)Oeq95+gNdum6Ki>@ z&I@}X)}+kfIc3L*vM+~}`zlfX)}qBnlM0PFTDmF9<^33~)YEb`=cl#3h%C-TWhpZz z*Yydxk@%G55k*!)qf}`<$*S*zs`uB@dVQeHle*lxyP)mb740;SXxBO>`@kmA>*@I! zPEAbt`K7P_@8LX)$?10cy(NZ|DN_k(2G1~0olhYI?oR%5__L(v(qr=n6bPVDKq29l zHBgA4P(dMsLI;Hq3MCX$D6~+Bp-@91hq7M5U4Rf9U_?RbmN`+7x@A@rv?zE{5Tjs5 qL5+eN?T|Yp(%ncWx{>xq`47ay{M6%W$_Pe+S}35aDP41yn)e%>`jLMC delta 176 zcmaFQw}M+IBq}q_hyeqz0YyL<1R{VK#OB$klfXE6B3t-mR(4evAKwrLT?1nv25Dzx c1QKA96azQEWIDhEGf@GTiCi{7JM4_O00VIpA^-pY diff --git a/lib/pytz/zoneinfo/America/Nuuk b/lib/pytz/zoneinfo/America/Nuuk index 883dfa055532c8319a833d80c82707b3ca5d4d5a..adb7934aadf5f45928ce67e82efa92ca92391703 100644 GIT binary patch delta 653 zcmb8sJ4nMo9LMooeNv^IgE(m?x3oEdD^)>o@7jjwFtB!9>t9o(dJ zDvCI{h=@3etAmR!PFg2%koy0nZX(oNF5k-yf%~K}^yX8|*?h)Oeq95+gNdum6Ki>@ z&I@}X)}+kfIc3L*vM+~}`zlfX)}qBnlM0PFTDmF9<^33~)YEb`=cl#3h%C-TWhpZz z*Yydxk@%G55k*!)qf}`<$*S*zs`uB@dVQeHle*lxyP)mb740;SXxBO>`@kmA>*@I! zPEAbt`K7P_@8LX)$?10cy(NZ|DN_k(2G1~0olhYI?oR%5__L(v(qr=n6bPVDKq29l zHBgA4P(dMsLI;Hq3MCX$D6~+Bp-@91hq7M5U4Rf9U_?RbmN`+7x@A@rv?zE{5Tjs5 qL5+eN?T|Yp(%ncWx{>xq`47ay{M6%W$_Pe+S}35aDP41yn)e%>`jLMC delta 176 zcmaFQw}M+IBq}q_hyeqz0YyL<1R{VK#OB$klfXE6B3t-mR(4evAKwrLT?1nv25Dzx c1QKA96azQEWIDhEGf@GTiCi{7JM4_O00VIpA^-pY diff --git a/lib/pytz/zoneinfo/America/Yellowknife b/lib/pytz/zoneinfo/America/Yellowknife index efc9ab037fea7aa699c70b59d011150043c51cd9..cd78a6f8be1dd55ac5afd25bbae39bd5706e42d1 100644 GIT binary patch delta 680 zcmca1Fh^*Da=nF_L&EBwJ#>5E=F10o+>UuF@aSI#6lB`n` zmYN=JSQh+TV7chhh84yq1y=k!#BjQkXTl}rF9nwpB^a)TJ{Gv9aA%{nAX7aPBNAk0 zLE<1w(2O07!@~dnr!HV%`2ThWX}bW<{3a80eE7I3xgGqsjdM delta 503 zcmbOubVFc*vP1_10|P4%i|o^qU^v~$GvSi*mx4=)5*v#Jnd+GtnV4Ay4!Xjv209Q3 z{{LUSfRP1Ea{T{4cLDU-o0K~yS>=FW0%?Q#0Bqk_pz&-gN2tXbM y(IC%)Xpo1&bfUy&K^8@pf%bJQRea3_3lx`8==d5M>KW=8Z~*|6j!`oJ diff --git a/lib/pytz/zoneinfo/Asia/Gaza b/lib/pytz/zoneinfo/Asia/Gaza index 22c47593d4698743bc60ad98e60e45e3c48da7b1..c9b2ff90823a5809d877fb68c10494a38ab183b1 100644 GIT binary patch delta 1402 zcmYMycT5vu7zXf^N6 zDp}*!%t9tjl|$qsqU5RAc}JkQ_?|`w?V1hA#V2$iqf8Al&7U{orQkfn%z_rSezjRh4_78D8%=rppf9EgcDAM zBTw8IM++>m!k4>Wi~M3xAT057nWW3-uAYc~(h46qS>+EW@AlWyO>&IR{##%yWyUTH zQU;cwkh=IM7E<@m6=|S%T#j7-a6Ixfra_+OZQ*`98F_lqVvz>v){$_=(ACH@8vnqV z7hREO=A7W}ekaoBvrAX|FffFDL&0!@KO=^s1RRhRRgXMd_zhn^{6eOiD*-toy&JdG z!8y}L!?_ELaPF_W6tioibnrD>2AuaP1ioH*5}(iVyCSue6r}G(;YNi9EU%erqfiywNPDb0@)h!$&^-9Q|5mtma|~QJ^E>)=J7$YCF#X9wUO%rL`GXez z2Q~~jhP=^OjDBOA(2s#x<&6ci#Sd-@^F*O3k3V!x18&G4mdr=~Xnhm>Nburr_d>rp z$pwDwaTtE=cZIuX0^D-+3*7R)2Yw=`H!-@NB=U!@bzS397^ZDr%zTzv8K{o|2tay4G)O-PfXo2XAaf?OZxsfb1LA|s0s$oRS^=Ow BCfNW0 diff --git a/lib/pytz/zoneinfo/Asia/Hebron b/lib/pytz/zoneinfo/Asia/Hebron index 0ee464803029ae4885814acae9e3291771401e62..64194fd85c676a5407878a197f6f9a011f2ff067 100644 GIT binary patch delta 1395 zcmX}sc}SCS7zgm5n=pFjLpXd91-YI)oy7R=Go31kF;0Sgo zaE+vL?_rU%=Sh*$QBk`OB| z+nh`bEUht!drXZyJt7R&^7}Harh>b57Wx^h0^v+W2%H%nqNa{|DD~o8TK^ zO5`_V7Qt2PSBo^L4xIwmD81lX|BrB;oPUJ%UXBv=>R-M=-pD?{jYgAzL6ao|3(b}M zslIhR0EL#MPTFIwaj%fKMU}(1L$u3%;Y@zI%~K1LL0p604ZN6{l#=nwo1_vb9; zHbsau7}(JY56&J$KB&@h>nFqJLq7EJkTBD{5VJPYt29G^jFnIhl8@4zaV9PoW zkp`AudU&|yIP&4qof2I0isfRS2jb#0QxH0zh*ncQgH)e1}f~0Lm;Q AHvj+t diff --git a/lib/pytz/zoneinfo/Egypt b/lib/pytz/zoneinfo/Egypt index d3f819623fc9ef90d327380fad15341ec1a0e202..dd538c65db6ed0a0e47feb7b6001640516958e19 100644 GIT binary patch delta 474 zcmZ9|ze@sP9LMqQ5()`|)}#g_C`?V1NTPf2+OCzEiJOxf*im=0fa4E=C(-Y4lMpd*9WH zyDl@G4=VF|E>~Y_YOQf?XYbC`daZ14REuiUD%e{m^J+Vnws*1xkxNGHd^|07r{-kA z7ZpX%q%8G#M5)y;_ujt6{?nH{xVOdOO;aA#Tk81YR+{B!rEBfqn$Tm`jNWY}x`v(i zfOVW7BY-v%9jZHBmPJr=u39=HHB_Oc0|Ch%}1J256C;t}zz?p*aw~ diff --git a/lib/pytz/zoneinfo/Europe/Volgograd b/lib/pytz/zoneinfo/Europe/Volgograd index 5539bac6ced7d584136fc705e8dcb80b300ad6b4..9d51a38c0ea719c5042837d47e0f427057b845f0 100644 GIT binary patch delta 180 zcmey*v66Fwv>+D)1ULdI5L;rSjw2%@$K*Ul9a8}Y1}y^y5e5be2M`Hj`}l@1Xd4&< nu?Y~HGWZ6&0I@gN3`Q`(X6ELnjIK-&Lv6@1lnZEwt}zz?S>6{H delta 138 zcmZ3<`JZEgv>*oq1ULdI5L;lQjw2%@%Vb9u9eE&M%YcE!$2WvQ+rSuzO@P=Gtez1F Zu<73Xo6(gCqFsZ4b}k#Bp?12)TmZ?I5as{? diff --git a/lib/pytz/zoneinfo/iso3166.tab b/lib/pytz/zoneinfo/iso3166.tab index 911af5e8..be3348d1 100644 --- a/lib/pytz/zoneinfo/iso3166.tab +++ b/lib/pytz/zoneinfo/iso3166.tab @@ -238,7 +238,7 @@ SY Syria SZ Eswatini (Swaziland) TC Turks & Caicos Is TD Chad -TF French Southern Territories +TF French S. Terr. TG Togo TH Thailand TJ Tajikistan diff --git a/lib/pytz/zoneinfo/leapseconds b/lib/pytz/zoneinfo/leapseconds index 6826ac4a..a6a170aa 100644 --- a/lib/pytz/zoneinfo/leapseconds +++ b/lib/pytz/zoneinfo/leapseconds @@ -72,11 +72,11 @@ Leap 2016 Dec 31 23:59:60 + S # Any additional leap seconds will come after this. # This Expires line is commented out for now, # so that pre-2020a zic implementations do not reject this file. -#Expires 2023 Jun 28 00:00:00 +#Expires 2023 Dec 28 00:00:00 # POSIX timestamps for the data in this file: #updated 1467936000 (2016-07-08 00:00:00 UTC) -#expires 1687910400 (2023-06-28 00:00:00 UTC) +#expires 1703721600 (2023-12-28 00:00:00 UTC) -# Updated through IERS Bulletin C64 -# File expires on: 28 June 2023 +# Updated through IERS Bulletin C65 +# File expires on: 28 December 2023 diff --git a/lib/pytz/zoneinfo/tzdata.zi b/lib/pytz/zoneinfo/tzdata.zi index 7c88530c..23d99be4 100644 --- a/lib/pytz/zoneinfo/tzdata.zi +++ b/lib/pytz/zoneinfo/tzdata.zi @@ -75,6 +75,8 @@ R K 2014 o - May 15 24 1 S R K 2014 o - Jun 26 24 0 - R K 2014 o - Jul 31 24 1 S R K 2014 o - S lastTh 24 0 - +R K 2023 ma - Ap lastF 0 1 S +R K 2023 ma - O lastTh 24 0 - Z Africa/Cairo 2:5:9 - LMT 1900 O 2 K EE%sT Z Africa/Bissau -1:2:20 - LMT 1912 Ja 1 1u @@ -172,7 +174,7 @@ R M 2021 o - May 16 2 0 - R M 2022 o - Mar 27 3 -1 - R M 2022 o - May 8 2 0 - R M 2023 o - Mar 19 3 -1 - -R M 2023 o - Ap 30 2 0 - +R M 2023 o - Ap 23 2 0 - R M 2024 o - Mar 10 3 -1 - R M 2024 o - Ap 14 2 0 - R M 2025 o - F 23 3 -1 - @@ -188,7 +190,7 @@ R M 2029 o - F 18 2 0 - R M 2029 o - D 30 3 -1 - R M 2030 o - F 10 2 0 - R M 2030 o - D 22 3 -1 - -R M 2031 o - F 2 2 0 - +R M 2031 o - Ja 26 2 0 - R M 2031 o - D 14 3 -1 - R M 2032 o - Ja 18 2 0 - R M 2032 o - N 28 3 -1 - @@ -204,7 +206,7 @@ R M 2036 o - N 23 2 0 - R M 2037 o - O 4 3 -1 - R M 2037 o - N 15 2 0 - R M 2038 o - S 26 3 -1 - -R M 2038 o - N 7 2 0 - +R M 2038 o - O 31 2 0 - R M 2039 o - S 18 3 -1 - R M 2039 o - O 23 2 0 - R M 2040 o - S 2 3 -1 - @@ -220,7 +222,7 @@ R M 2044 o - Au 28 2 0 - R M 2045 o - Jul 9 3 -1 - R M 2045 o - Au 20 2 0 - R M 2046 o - Jul 1 3 -1 - -R M 2046 o - Au 12 2 0 - +R M 2046 o - Au 5 2 0 - R M 2047 o - Jun 23 3 -1 - R M 2047 o - Jul 28 2 0 - R M 2048 o - Jun 7 3 -1 - @@ -236,7 +238,7 @@ R M 2052 o - Jun 2 2 0 - R M 2053 o - Ap 13 3 -1 - R M 2053 o - May 25 2 0 - R M 2054 o - Ap 5 3 -1 - -R M 2054 o - May 17 2 0 - +R M 2054 o - May 10 2 0 - R M 2055 o - Mar 28 3 -1 - R M 2055 o - May 2 2 0 - R M 2056 o - Mar 12 3 -1 - @@ -252,7 +254,7 @@ R M 2060 o - Mar 7 2 0 - R M 2061 o - Ja 16 3 -1 - R M 2061 o - F 27 2 0 - R M 2062 o - Ja 8 3 -1 - -R M 2062 o - F 19 2 0 - +R M 2062 o - F 12 2 0 - R M 2062 o - D 31 3 -1 - R M 2063 o - F 4 2 0 - R M 2063 o - D 16 3 -1 - @@ -268,7 +270,7 @@ R M 2067 o - D 11 2 0 - R M 2068 o - O 21 3 -1 - R M 2068 o - D 2 2 0 - R M 2069 o - O 13 3 -1 - -R M 2069 o - N 24 2 0 - +R M 2069 o - N 17 2 0 - R M 2070 o - O 5 3 -1 - R M 2070 o - N 9 2 0 - R M 2071 o - S 20 3 -1 - @@ -284,7 +286,7 @@ R M 2075 o - S 15 2 0 - R M 2076 o - Jul 26 3 -1 - R M 2076 o - S 6 2 0 - R M 2077 o - Jul 18 3 -1 - -R M 2077 o - Au 29 2 0 - +R M 2077 o - Au 22 2 0 - R M 2078 o - Jul 10 3 -1 - R M 2078 o - Au 14 2 0 - R M 2079 o - Jun 25 3 -1 - @@ -294,13 +296,13 @@ R M 2080 o - Jul 21 2 0 - R M 2081 o - Jun 1 3 -1 - R M 2081 o - Jul 13 2 0 - R M 2082 o - May 24 3 -1 - -R M 2082 o - Jul 5 2 0 - +R M 2082 o - Jun 28 2 0 - R M 2083 o - May 16 3 -1 - R M 2083 o - Jun 20 2 0 - R M 2084 o - Ap 30 3 -1 - R M 2084 o - Jun 11 2 0 - R M 2085 o - Ap 22 3 -1 - -R M 2085 o - Jun 3 2 0 - +R M 2085 o - May 27 2 0 - R M 2086 o - Ap 14 3 -1 - R M 2086 o - May 19 2 0 - R M 2087 o - Mar 30 3 -1 - @@ -997,8 +999,86 @@ R P 2020 2021 - Mar Sa<=30 0 1 S R P 2020 o - O 24 1 0 - R P 2021 o - O 29 1 0 - R P 2022 o - Mar 27 0 1 S -R P 2022 ma - O Sa<=30 2 0 - -R P 2023 ma - Mar Sa<=30 2 1 S +R P 2022 2035 - O Sa<=30 2 0 - +R P 2023 o - Ap 29 2 1 S +R P 2024 o - Ap 13 2 1 S +R P 2025 o - Ap 5 2 1 S +R P 2026 2054 - Mar Sa<=30 2 1 S +R P 2036 o - O 18 2 0 - +R P 2037 o - O 10 2 0 - +R P 2038 o - S 25 2 0 - +R P 2039 o - S 17 2 0 - +R P 2039 o - O 22 2 1 S +R P 2039 2067 - O Sa<=30 2 0 - +R P 2040 o - S 1 2 0 - +R P 2040 o - O 13 2 1 S +R P 2041 o - Au 24 2 0 - +R P 2041 o - S 28 2 1 S +R P 2042 o - Au 16 2 0 - +R P 2042 o - S 20 2 1 S +R P 2043 o - Au 1 2 0 - +R P 2043 o - S 12 2 1 S +R P 2044 o - Jul 23 2 0 - +R P 2044 o - Au 27 2 1 S +R P 2045 o - Jul 15 2 0 - +R P 2045 o - Au 19 2 1 S +R P 2046 o - Jun 30 2 0 - +R P 2046 o - Au 11 2 1 S +R P 2047 o - Jun 22 2 0 - +R P 2047 o - Jul 27 2 1 S +R P 2048 o - Jun 6 2 0 - +R P 2048 o - Jul 18 2 1 S +R P 2049 o - May 29 2 0 - +R P 2049 o - Jul 3 2 1 S +R P 2050 o - May 21 2 0 - +R P 2050 o - Jun 25 2 1 S +R P 2051 o - May 6 2 0 - +R P 2051 o - Jun 17 2 1 S +R P 2052 o - Ap 27 2 0 - +R P 2052 o - Jun 1 2 1 S +R P 2053 o - Ap 12 2 0 - +R P 2053 o - May 24 2 1 S +R P 2054 o - Ap 4 2 0 - +R P 2054 o - May 16 2 1 S +R P 2055 o - May 1 2 1 S +R P 2056 o - Ap 22 2 1 S +R P 2057 o - Ap 7 2 1 S +R P 2058 ma - Mar Sa<=30 2 1 S +R P 2068 o - O 20 2 0 - +R P 2069 o - O 12 2 0 - +R P 2070 o - O 4 2 0 - +R P 2071 o - S 19 2 0 - +R P 2072 o - S 10 2 0 - +R P 2072 o - O 15 2 1 S +R P 2073 o - S 2 2 0 - +R P 2073 o - O 7 2 1 S +R P 2074 o - Au 18 2 0 - +R P 2074 o - S 29 2 1 S +R P 2075 o - Au 10 2 0 - +R P 2075 o - S 14 2 1 S +R P 2075 ma - O Sa<=30 2 0 - +R P 2076 o - Jul 25 2 0 - +R P 2076 o - S 5 2 1 S +R P 2077 o - Jul 17 2 0 - +R P 2077 o - Au 28 2 1 S +R P 2078 o - Jul 9 2 0 - +R P 2078 o - Au 13 2 1 S +R P 2079 o - Jun 24 2 0 - +R P 2079 o - Au 5 2 1 S +R P 2080 o - Jun 15 2 0 - +R P 2080 o - Jul 20 2 1 S +R P 2081 o - Jun 7 2 0 - +R P 2081 o - Jul 12 2 1 S +R P 2082 o - May 23 2 0 - +R P 2082 o - Jul 4 2 1 S +R P 2083 o - May 15 2 0 - +R P 2083 o - Jun 19 2 1 S +R P 2084 o - Ap 29 2 0 - +R P 2084 o - Jun 10 2 1 S +R P 2085 o - Ap 21 2 0 - +R P 2085 o - Jun 2 2 1 S +R P 2086 o - Ap 13 2 0 - +R P 2086 o - May 18 2 1 S Z Asia/Gaza 2:17:52 - LMT 1900 O 2 Z EET/EEST 1948 May 15 2 K EE%sT 1967 Jun 5 @@ -1754,8 +1834,8 @@ Z America/Scoresbysund -1:27:52 - LMT 1916 Jul 28 -1 E -01/+00 Z America/Nuuk -3:26:56 - LMT 1916 Jul 28 -3 - -03 1980 Ap 6 2 --3 E -03/-02 2023 Mar 25 22 --2 - -02 +-3 E -03/-02 2023 O 29 1u +-2 E -02/-01 Z America/Thule -4:35:8 - LMT 1916 Jul 28 -4 Th A%sT Z Europe/Tallinn 1:39 - LMT 1880 @@ -2175,13 +2255,13 @@ Z Europe/Volgograd 2:57:40 - LMT 1920 Ja 3 3 - +03 1930 Jun 21 4 - +04 1961 N 11 4 R +04/+05 1988 Mar 27 2s -3 R +03/+04 1991 Mar 31 2s +3 R MSK/MSD 1991 Mar 31 2s 4 - +04 1992 Mar 29 2s -3 R +03/+04 2011 Mar 27 2s -4 - +04 2014 O 26 2s -3 - +03 2018 O 28 2s +3 R MSK/MSD 2011 Mar 27 2s +4 - MSK 2014 O 26 2s +3 - MSK 2018 O 28 2s 4 - +04 2020 D 27 2s -3 - +03 +3 - MSK Z Europe/Saratov 3:4:18 - LMT 1919 Jul 1 0u 3 - +03 1930 Jun 21 4 R +04/+05 1988 Mar 27 2s @@ -2194,11 +2274,11 @@ Z Europe/Saratov 3:4:18 - LMT 1919 Jul 1 0u Z Europe/Kirov 3:18:48 - LMT 1919 Jul 1 0u 3 - +03 1930 Jun 21 4 R +04/+05 1989 Mar 26 2s -3 R +03/+04 1991 Mar 31 2s +3 R MSK/MSD 1991 Mar 31 2s 4 - +04 1992 Mar 29 2s -3 R +03/+04 2011 Mar 27 2s -4 - +04 2014 O 26 2s -3 - +03 +3 R MSK/MSD 2011 Mar 27 2s +4 - MSK 2014 O 26 2s +3 - MSK Z Europe/Samara 3:20:20 - LMT 1919 Jul 1 0u 3 - +03 1930 Jun 21 4 - +04 1935 Ja 27 @@ -3070,9 +3150,6 @@ Z America/Cambridge_Bay 0 - -00 1920 -5 - EST 2000 N 5 -6 - CST 2001 Ap 1 3 -7 C M%sT -Z America/Yellowknife 0 - -00 1935 --7 Y M%sT 1980 --7 C M%sT Z America/Inuvik 0 - -00 1953 -8 Y P%sT 1979 Ap lastSu 2 -7 Y M%sT 1980 @@ -4171,6 +4248,7 @@ L America/Argentina/Cordoba America/Rosario L America/Tijuana America/Santa_Isabel L America/Denver America/Shiprock L America/Toronto America/Thunder_Bay +L America/Edmonton America/Yellowknife L Pacific/Auckland Antarctica/South_Pole L Asia/Shanghai Asia/Chongqing L Asia/Shanghai Asia/Harbin diff --git a/lib/pytz/zoneinfo/zone.tab b/lib/pytz/zoneinfo/zone.tab index 6e5adb9f..dbcb6179 100644 --- a/lib/pytz/zoneinfo/zone.tab +++ b/lib/pytz/zoneinfo/zone.tab @@ -121,9 +121,8 @@ CA +744144-0944945 America/Resolute Central - NU (Resolute) CA +624900-0920459 America/Rankin_Inlet Central - NU (central) CA +5024-10439 America/Regina CST - SK (most areas) CA +5017-10750 America/Swift_Current CST - SK (midwest) -CA +5333-11328 America/Edmonton Mountain - AB; BC (E); SK (W) +CA +5333-11328 America/Edmonton Mountain - AB; BC (E); NT (E); SK (W) CA +690650-1050310 America/Cambridge_Bay Mountain - NU (west) -CA +6227-11421 America/Yellowknife Mountain - NT (central) CA +682059-1334300 America/Inuvik Mountain - NT (west) CA +4906-11631 America/Creston MST - BC (Creston) CA +5546-12014 America/Dawson_Creek MST - BC (Dawson Cr, Ft St John) @@ -139,7 +138,7 @@ CG -0416+01517 Africa/Brazzaville CH +4723+00832 Europe/Zurich CI +0519-00402 Africa/Abidjan CK -2114-15946 Pacific/Rarotonga -CL -3327-07040 America/Santiago Chile (most areas) +CL -3327-07040 America/Santiago most of Chile CL -5309-07055 America/Punta_Arenas Region of Magallanes CL -2709-10926 Pacific/Easter Easter Island CM +0403+00942 Africa/Douala @@ -151,10 +150,10 @@ CU +2308-08222 America/Havana CV +1455-02331 Atlantic/Cape_Verde CW +1211-06900 America/Curacao CX -1025+10543 Indian/Christmas -CY +3510+03322 Asia/Nicosia Cyprus (most areas) +CY +3510+03322 Asia/Nicosia most of Cyprus CY +3507+03357 Asia/Famagusta Northern Cyprus CZ +5005+01426 Europe/Prague -DE +5230+01322 Europe/Berlin Germany (most areas) +DE +5230+01322 Europe/Berlin most of Germany DE +4742+00841 Europe/Busingen Busingen DJ +1136+04309 Africa/Djibouti DK +5540+01235 Europe/Copenhagen @@ -187,7 +186,7 @@ GF +0456-05220 America/Cayenne GG +492717-0023210 Europe/Guernsey GH +0533-00013 Africa/Accra GI +3608-00521 Europe/Gibraltar -GL +6411-05144 America/Nuuk Greenland (most areas) +GL +6411-05144 America/Nuuk most of Greenland GL +7646-01840 America/Danmarkshavn National Park (east coast) GL +7029-02158 America/Scoresbysund Scoresbysund/Ittoqqortoormiit GL +7634-06847 America/Thule Thule/Pituffik @@ -235,7 +234,7 @@ KP +3901+12545 Asia/Pyongyang KR +3733+12658 Asia/Seoul KW +2920+04759 Asia/Kuwait KY +1918-08123 America/Cayman -KZ +4315+07657 Asia/Almaty Kazakhstan (most areas) +KZ +4315+07657 Asia/Almaty most of Kazakhstan KZ +4448+06528 Asia/Qyzylorda Qyzylorda/Kyzylorda/Kzyl-Orda KZ +5312+06337 Asia/Qostanay Qostanay/Kostanay/Kustanay KZ +5017+05710 Asia/Aqtobe Aqtobe/Aktobe @@ -259,12 +258,12 @@ MD +4700+02850 Europe/Chisinau ME +4226+01916 Europe/Podgorica MF +1804-06305 America/Marigot MG -1855+04731 Indian/Antananarivo -MH +0709+17112 Pacific/Majuro Marshall Islands (most areas) +MH +0709+17112 Pacific/Majuro most of Marshall Islands MH +0905+16720 Pacific/Kwajalein Kwajalein MK +4159+02126 Europe/Skopje ML +1239-00800 Africa/Bamako MM +1647+09610 Asia/Yangon -MN +4755+10653 Asia/Ulaanbaatar Mongolia (most areas) +MN +4755+10653 Asia/Ulaanbaatar most of Mongolia MN +4801+09139 Asia/Hovd Bayan-Olgiy, Govi-Altai, Hovd, Uvs, Zavkhan MN +4804+11430 Asia/Choibalsan Dornod, Sukhbaatar MO +221150+1133230 Asia/Macau @@ -302,7 +301,7 @@ NO +5955+01045 Europe/Oslo NP +2743+08519 Asia/Kathmandu NR -0031+16655 Pacific/Nauru NU -1901-16955 Pacific/Niue -NZ -3652+17446 Pacific/Auckland New Zealand (most areas) +NZ -3652+17446 Pacific/Auckland most of New Zealand NZ -4357-17633 Pacific/Chatham Chatham Islands OM +2336+05835 Asia/Muscat PA +0858-07932 America/Panama @@ -310,7 +309,7 @@ PE -1203-07703 America/Lima PF -1732-14934 Pacific/Tahiti Society Islands PF -0900-13930 Pacific/Marquesas Marquesas Islands PF -2308-13457 Pacific/Gambier Gambier Islands -PG -0930+14710 Pacific/Port_Moresby Papua New Guinea (most areas) +PG -0930+14710 Pacific/Port_Moresby most of Papua New Guinea PG -0613+15534 Pacific/Bougainville Bougainville PH +1435+12100 Asia/Manila PK +2452+06703 Asia/Karachi @@ -356,7 +355,7 @@ RU +4310+13156 Asia/Vladivostok MSK+07 - Amur River RU +643337+1431336 Asia/Ust-Nera MSK+07 - Oymyakonsky RU +5934+15048 Asia/Magadan MSK+08 - Magadan RU +4658+14242 Asia/Sakhalin MSK+08 - Sakhalin Island -RU +6728+15343 Asia/Srednekolymsk MSK+08 - Sakha (E); North Kuril Is +RU +6728+15343 Asia/Srednekolymsk MSK+08 - Sakha (E); N Kuril Is RU +5301+15839 Asia/Kamchatka MSK+09 - Kamchatka RU +6445+17729 Asia/Anadyr MSK+09 - Bering Sea RW -0157+03004 Africa/Kigali @@ -397,7 +396,7 @@ TT +1039-06131 America/Port_of_Spain TV -0831+17913 Pacific/Funafuti TW +2503+12130 Asia/Taipei TZ -0648+03917 Africa/Dar_es_Salaam -UA +5026+03031 Europe/Kyiv Ukraine (most areas) +UA +5026+03031 Europe/Kyiv most of Ukraine UG +0019+03225 Africa/Kampala UM +2813-17722 Pacific/Midway Midway Islands UM +1917+16637 Pacific/Wake Wake Island @@ -420,7 +419,7 @@ US +465042-1012439 America/North_Dakota/New_Salem Central - ND (Morton rural) US +471551-1014640 America/North_Dakota/Beulah Central - ND (Mercer) US +394421-1045903 America/Denver Mountain (most areas) US +433649-1161209 America/Boise Mountain - ID (south); OR (east) -US +332654-1120424 America/Phoenix MST - Arizona (except Navajo) +US +332654-1120424 America/Phoenix MST - AZ (except Navajo) US +340308-1181434 America/Los_Angeles Pacific US +611305-1495401 America/Anchorage Alaska (most areas) US +581807-1342511 America/Juneau Alaska - Juneau area @@ -428,7 +427,7 @@ US +571035-1351807 America/Sitka Alaska - Sitka area US +550737-1313435 America/Metlakatla Alaska - Annette Island US +593249-1394338 America/Yakutat Alaska - Yakutat US +643004-1652423 America/Nome Alaska (west) -US +515248-1763929 America/Adak Aleutian Islands +US +515248-1763929 America/Adak Alaska - western Aleutians US +211825-1575130 Pacific/Honolulu Hawaii UY -345433-0561245 America/Montevideo UZ +3940+06648 Asia/Samarkand Uzbekistan (west) diff --git a/lib/pytz/zoneinfo/zone1970.tab b/lib/pytz/zoneinfo/zone1970.tab index a9b36d36..1f1cecb8 100644 --- a/lib/pytz/zoneinfo/zone1970.tab +++ b/lib/pytz/zoneinfo/zone1970.tab @@ -18,7 +18,10 @@ # Please see the theory.html file for how these names are chosen. # If multiple timezones overlap a country, each has a row in the # table, with each column 1 containing the country code. -# 4. Comments; present if and only if a country has multiple timezones. +# 4. Comments; present if and only if countries have multiple timezones, +# and useful only for those countries. For example, the comments +# for the row with countries CH,DE,LI and name Europe/Zurich +# are useful only for DE, since CH and LI have no other timezones. # # If a timezone covers multiple countries, the most-populous city is used, # and that country is listed first in column 1; any other countries @@ -34,7 +37,7 @@ #country- #codes coordinates TZ comments AD +4230+00131 Europe/Andorra -AE,OM,RE,SC,TF +2518+05518 Asia/Dubai UAE, Oman, Réunion, Seychelles, Crozet, Scattered Is +AE,OM,RE,SC,TF +2518+05518 Asia/Dubai Crozet, Scattered Is AF +3431+06912 Asia/Kabul AL +4120+01950 Europe/Tirane AM +4011+04430 Asia/Yerevan @@ -45,7 +48,7 @@ AQ -6448-06406 Antarctica/Palmer Palmer AQ -6734-06808 Antarctica/Rothera Rothera AQ -720041+0023206 Antarctica/Troll Troll AR -3436-05827 America/Argentina/Buenos_Aires Buenos Aires (BA, CF) -AR -3124-06411 America/Argentina/Cordoba Argentina (most areas: CB, CC, CN, ER, FM, MN, SE, SF) +AR -3124-06411 America/Argentina/Cordoba most areas: CB, CC, CN, ER, FM, MN, SE, SF AR -2447-06525 America/Argentina/Salta Salta (SA, LP, NQ, RN) AR -2411-06518 America/Argentina/Jujuy Jujuy (JY) AR -2649-06513 America/Argentina/Tucuman Tucumán (TM) @@ -56,7 +59,7 @@ AR -3253-06849 America/Argentina/Mendoza Mendoza (MZ) AR -3319-06621 America/Argentina/San_Luis San Luis (SL) AR -5138-06913 America/Argentina/Rio_Gallegos Santa Cruz (SC) AR -5448-06818 America/Argentina/Ushuaia Tierra del Fuego (TF) -AS,UM -1416-17042 Pacific/Pago_Pago Samoa, Midway +AS,UM -1416-17042 Pacific/Pago_Pago Midway AT +4813+01620 Europe/Vienna AU -3133+15905 Australia/Lord_Howe Lord Howe Island AU -5430+15857 Antarctica/Macquarie Macquarie Island @@ -101,26 +104,25 @@ CA +4439-06336 America/Halifax Atlantic - NS (most areas); PE CA +4612-05957 America/Glace_Bay Atlantic - NS (Cape Breton) CA +4606-06447 America/Moncton Atlantic - New Brunswick CA +5320-06025 America/Goose_Bay Atlantic - Labrador (most areas) -CA,BS +4339-07923 America/Toronto Eastern - ON, QC (most areas), Bahamas +CA,BS +4339-07923 America/Toronto Eastern - ON, QC (most areas) CA +6344-06828 America/Iqaluit Eastern - NU (most areas) CA +4953-09709 America/Winnipeg Central - ON (west); Manitoba CA +744144-0944945 America/Resolute Central - NU (Resolute) CA +624900-0920459 America/Rankin_Inlet Central - NU (central) CA +5024-10439 America/Regina CST - SK (most areas) CA +5017-10750 America/Swift_Current CST - SK (midwest) -CA +5333-11328 America/Edmonton Mountain - AB; BC (E); SK (W) +CA +5333-11328 America/Edmonton Mountain - AB; BC (E); NT (E); SK (W) CA +690650-1050310 America/Cambridge_Bay Mountain - NU (west) -CA +6227-11421 America/Yellowknife Mountain - NT (central) CA +682059-1334300 America/Inuvik Mountain - NT (west) CA +5546-12014 America/Dawson_Creek MST - BC (Dawson Cr, Ft St John) CA +5848-12242 America/Fort_Nelson MST - BC (Ft Nelson) CA +6043-13503 America/Whitehorse MST - Yukon (east) CA +6404-13925 America/Dawson MST - Yukon (west) CA +4916-12307 America/Vancouver Pacific - BC (most areas) -CH,DE,LI +4723+00832 Europe/Zurich Swiss time +CH,DE,LI +4723+00832 Europe/Zurich Büsingen CI,BF,GH,GM,GN,IS,ML,MR,SH,SL,SN,TG +0519-00402 Africa/Abidjan CK -2114-15946 Pacific/Rarotonga -CL -3327-07040 America/Santiago Chile (most areas) +CL -3327-07040 America/Santiago most of Chile CL -5309-07055 America/Punta_Arenas Region of Magallanes CL -2709-10926 Pacific/Easter Easter Island CN +3114+12128 Asia/Shanghai Beijing Time @@ -129,10 +131,10 @@ CO +0436-07405 America/Bogota CR +0956-08405 America/Costa_Rica CU +2308-08222 America/Havana CV +1455-02331 Atlantic/Cape_Verde -CY +3510+03322 Asia/Nicosia Cyprus (most areas) +CY +3510+03322 Asia/Nicosia most of Cyprus CY +3507+03357 Asia/Famagusta Northern Cyprus CZ,SK +5005+01426 Europe/Prague -DE,DK,NO,SE,SJ +5230+01322 Europe/Berlin Germany (most areas), Scandinavia +DE,DK,NO,SE,SJ +5230+01322 Europe/Berlin most of Germany DO +1828-06954 America/Santo_Domingo DZ +3647+00303 Africa/Algiers EC -0210-07950 America/Guayaquil Ecuador (mainland) @@ -153,7 +155,7 @@ GB,GG,IM,JE +513030-0000731 Europe/London GE +4143+04449 Asia/Tbilisi GF +0456-05220 America/Cayenne GI +3608-00521 Europe/Gibraltar -GL +6411-05144 America/Nuuk Greenland (most areas) +GL +6411-05144 America/Nuuk most of Greenland GL +7646-01840 America/Danmarkshavn National Park (east coast) GL +7029-02158 America/Scoresbysund Scoresbysund/Ittoqqortoormiit GL +7634-06847 America/Thule Thule/Pituffik @@ -183,12 +185,12 @@ JO +3157+03556 Asia/Amman JP +353916+1394441 Asia/Tokyo KE,DJ,ER,ET,KM,MG,SO,TZ,UG,YT -0117+03649 Africa/Nairobi KG +4254+07436 Asia/Bishkek -KI,MH,TV,UM,WF +0125+17300 Pacific/Tarawa Gilberts, Marshalls, Tuvalu, Wallis & Futuna, Wake +KI,MH,TV,UM,WF +0125+17300 Pacific/Tarawa Gilberts, Marshalls, Wake KI -0247-17143 Pacific/Kanton Phoenix Islands KI +0152-15720 Pacific/Kiritimati Line Islands KP +3901+12545 Asia/Pyongyang KR +3733+12658 Asia/Seoul -KZ +4315+07657 Asia/Almaty Kazakhstan (most areas) +KZ +4315+07657 Asia/Almaty most of Kazakhstan KZ +4448+06528 Asia/Qyzylorda Qyzylorda/Kyzylorda/Kzyl-Orda KZ +5312+06337 Asia/Qostanay Qostanay/Kostanay/Kustanay KZ +5017+05710 Asia/Aqtobe Aqtöbe/Aktobe @@ -205,14 +207,14 @@ MA +3339-00735 Africa/Casablanca MD +4700+02850 Europe/Chisinau MH +0905+16720 Pacific/Kwajalein Kwajalein MM,CC +1647+09610 Asia/Yangon -MN +4755+10653 Asia/Ulaanbaatar Mongolia (most areas) +MN +4755+10653 Asia/Ulaanbaatar most of Mongolia MN +4801+09139 Asia/Hovd Bayan-Ölgii, Govi-Altai, Hovd, Uvs, Zavkhan MN +4804+11430 Asia/Choibalsan Dornod, Sükhbaatar MO +221150+1133230 Asia/Macau MQ +1436-06105 America/Martinique MT +3554+01431 Europe/Malta MU -2010+05730 Indian/Mauritius -MV,TF +0410+07330 Indian/Maldives Maldives, Kerguelen, St Paul I, Amsterdam I +MV,TF +0410+07330 Indian/Maldives Kerguelen, St Paul I, Amsterdam I MX +1924-09909 America/Mexico_City Central Mexico MX +2105-08646 America/Cancun Quintana Roo MX +2058-08937 America/Merida Campeche, Yucatán @@ -225,7 +227,7 @@ MX +2313-10625 America/Mazatlan Baja California Sur, Nayarit (most areas), Sinal MX +2048-10515 America/Bahia_Banderas Bahía de Banderas MX +2904-11058 America/Hermosillo Sonora MX +3232-11701 America/Tijuana Baja California -MY,BN +0133+11020 Asia/Kuching Sabah, Sarawak, Brunei +MY,BN +0133+11020 Asia/Kuching Sabah, Sarawak MZ,BI,BW,CD,MW,RW,ZM,ZW -2558+03235 Africa/Maputo Central Africa Time NA -2234+01706 Africa/Windhoek NC -2216+16627 Pacific/Noumea @@ -237,7 +239,7 @@ NR -0031+16655 Pacific/Nauru NU -1901-16955 Pacific/Niue NZ,AQ -3652+17446 Pacific/Auckland New Zealand time NZ -4357-17633 Pacific/Chatham Chatham Islands -PA,CA,KY +0858-07932 America/Panama EST - Panama, Cayman, ON (Atikokan), NU (Coral H) +PA,CA,KY +0858-07932 America/Panama EST - ON (Atikokan), NU (Coral H) PE -1203-07703 America/Lima PF -1732-14934 Pacific/Tahiti Society Islands PF -0900-13930 Pacific/Marquesas Marquesas Islands @@ -285,13 +287,13 @@ RU +4310+13156 Asia/Vladivostok MSK+07 - Amur River RU +643337+1431336 Asia/Ust-Nera MSK+07 - Oymyakonsky RU +5934+15048 Asia/Magadan MSK+08 - Magadan RU +4658+14242 Asia/Sakhalin MSK+08 - Sakhalin Island -RU +6728+15343 Asia/Srednekolymsk MSK+08 - Sakha (E); North Kuril Is +RU +6728+15343 Asia/Srednekolymsk MSK+08 - Sakha (E); N Kuril Is RU +5301+15839 Asia/Kamchatka MSK+09 - Kamchatka RU +6445+17729 Asia/Anadyr MSK+09 - Bering Sea -SA,AQ,KW,YE +2438+04643 Asia/Riyadh Arabia, Syowa -SB,FM -0932+16012 Pacific/Guadalcanal Solomons, Pohnpei +SA,AQ,KW,YE +2438+04643 Asia/Riyadh Syowa +SB,FM -0932+16012 Pacific/Guadalcanal Pohnpei SD +1536+03232 Africa/Khartoum -SG,MY +0117+10351 Asia/Singapore Singapore, peninsular Malaysia +SG,MY +0117+10351 Asia/Singapore peninsular Malaysia SR +0550-05510 America/Paramaribo SS +0451+03137 Africa/Juba ST +0020+00644 Africa/Sao_Tome @@ -299,7 +301,7 @@ SV +1342-08912 America/El_Salvador SY +3330+03618 Asia/Damascus TC +2128-07108 America/Grand_Turk TD +1207+01503 Africa/Ndjamena -TH,CX,KH,LA,VN +1345+10031 Asia/Bangkok Indochina (most areas) +TH,CX,KH,LA,VN +1345+10031 Asia/Bangkok north Vietnam TJ +3835+06848 Asia/Dushanbe TK -0922-17114 Pacific/Fakaofo TL -0833+12535 Asia/Dili @@ -308,7 +310,7 @@ TN +3648+01011 Africa/Tunis TO -210800-1751200 Pacific/Tongatapu TR +4101+02858 Europe/Istanbul TW +2503+12130 Asia/Taipei -UA +5026+03031 Europe/Kyiv Ukraine (most areas) +UA +5026+03031 Europe/Kyiv most of Ukraine US +404251-0740023 America/New_York Eastern (most areas) US +421953-0830245 America/Detroit Eastern - MI (most areas) US +381515-0854534 America/Kentucky/Louisville Eastern - KY (Louisville area) @@ -328,7 +330,7 @@ US +465042-1012439 America/North_Dakota/New_Salem Central - ND (Morton rural) US +471551-1014640 America/North_Dakota/Beulah Central - ND (Mercer) US +394421-1045903 America/Denver Mountain (most areas) US +433649-1161209 America/Boise Mountain - ID (south); OR (east) -US,CA +332654-1120424 America/Phoenix MST - Arizona (except Navajo), Creston BC +US,CA +332654-1120424 America/Phoenix MST - AZ (most areas), Creston BC US +340308-1181434 America/Los_Angeles Pacific US +611305-1495401 America/Anchorage Alaska (most areas) US +581807-1342511 America/Juneau Alaska - Juneau area @@ -336,13 +338,13 @@ US +571035-1351807 America/Sitka Alaska - Sitka area US +550737-1313435 America/Metlakatla Alaska - Annette Island US +593249-1394338 America/Yakutat Alaska - Yakutat US +643004-1652423 America/Nome Alaska (west) -US +515248-1763929 America/Adak Aleutian Islands -US,UM +211825-1575130 Pacific/Honolulu Hawaii +US +515248-1763929 America/Adak Alaska - western Aleutians +US +211825-1575130 Pacific/Honolulu Hawaii UY -345433-0561245 America/Montevideo UZ +3940+06648 Asia/Samarkand Uzbekistan (west) UZ +4120+06918 Asia/Tashkent Uzbekistan (east) VE +1030-06656 America/Caracas -VN +1045+10640 Asia/Ho_Chi_Minh Vietnam (south) +VN +1045+10640 Asia/Ho_Chi_Minh south Vietnam VU -1740+16825 Pacific/Efate WS -1350-17144 Pacific/Apia ZA,LS,SZ -2615+02800 Africa/Johannesburg diff --git a/requirements.txt b/requirements.txt index ee495cb7..00e17bde 100644 --- a/requirements.txt +++ b/requirements.txt @@ -35,7 +35,7 @@ PyJWT==2.6.0 pyparsing==3.0.9 python-dateutil==2.8.2 python-twitter==3.5 -pytz==2022.7.1 +pytz==2023.3 requests==2.28.2 requests-oauthlib==1.3.1 rumps==0.4.0; platform_system == "Darwin" From 70fb00280bb40e6d51455078d0ade91be6aeec66 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Aug 2023 21:38:28 -0700 Subject: [PATCH 262/583] Bump tzdata from 2022.7 to 2023.3 (#2032) * Bump tzdata from 2022.7 to 2023.3 Bumps [tzdata](https://github.com/python/tzdata) from 2022.7 to 2023.3. - [Release notes](https://github.com/python/tzdata/releases) - [Changelog](https://github.com/python/tzdata/blob/master/NEWS.md) - [Commits](https://github.com/python/tzdata/compare/2022.7...2023.3) --- updated-dependencies: - dependency-name: tzdata dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Update tzdata==2023.3 --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> [skip ci] --- lib/tzdata/__init__.py | 4 +- lib/tzdata/zoneinfo/Africa/Cairo | Bin 1276 -> 1309 bytes lib/tzdata/zoneinfo/Africa/Casablanca | Bin 1919 -> 1919 bytes lib/tzdata/zoneinfo/Africa/El_Aaiun | Bin 1830 -> 1830 bytes lib/tzdata/zoneinfo/America/Godthab | Bin 931 -> 965 bytes lib/tzdata/zoneinfo/America/Nuuk | Bin 931 -> 965 bytes lib/tzdata/zoneinfo/America/Yellowknife | Bin 844 -> 970 bytes lib/tzdata/zoneinfo/Asia/Gaza | Bin 1258 -> 2518 bytes lib/tzdata/zoneinfo/Asia/Hebron | Bin 1276 -> 2536 bytes lib/tzdata/zoneinfo/Egypt | Bin 1276 -> 1309 bytes lib/tzdata/zoneinfo/Europe/Kirov | Bin 717 -> 735 bytes lib/tzdata/zoneinfo/Europe/Volgograd | Bin 735 -> 753 bytes lib/tzdata/zoneinfo/iso3166.tab | 2 +- lib/tzdata/zoneinfo/leapseconds | 8 +- lib/tzdata/zoneinfo/tzdata.zi | 132 +++++++++++++++++++----- lib/tzdata/zoneinfo/zone.tab | 29 +++--- lib/tzdata/zoneinfo/zone1970.tab | 58 ++++++----- lib/tzdata/zones | 2 +- requirements.txt | 2 +- 19 files changed, 158 insertions(+), 79 deletions(-) diff --git a/lib/tzdata/__init__.py b/lib/tzdata/__init__.py index 96456857..a7d6b0b5 100644 --- a/lib/tzdata/__init__.py +++ b/lib/tzdata/__init__.py @@ -1,6 +1,6 @@ # IANA versions like 2020a are not valid PEP 440 identifiers; the recommended # way to translate the version is to use YYYY.n where `n` is a 0-based index. -__version__ = "2022.7" +__version__ = "2023.3" # This exposes the original IANA version number. -IANA_VERSION = "2022g" +IANA_VERSION = "2023c" diff --git a/lib/tzdata/zoneinfo/Africa/Cairo b/lib/tzdata/zoneinfo/Africa/Cairo index ea38c970086c6ddca48d6f5703cee91ed1ac98b4..1e6d48d1ca4e5416913c41e8814dc045c57d5b58 100644 GIT binary patch delta 58 zcmeyvIhSif7-Pf2a25^*AV~50kT5xsrF3!u3-{z}EUJ>OuE8NXz9xF6dZzjYI=+Sm LK$eNVkqH+7|I-hF delta 23 fcmbQs^@npp7-RjyaF)r5EOm@blkc;rGI9X`W1|Ny diff --git a/lib/tzdata/zoneinfo/Africa/Casablanca b/lib/tzdata/zoneinfo/Africa/Casablanca index 0263c90bd0d82565a86d414b0aa86205e01dad85..240ebb2bfb22642570a6053445a6e71864e7f48a 100644 GIT binary patch delta 88 zcmey*_n&V=3KO%-M1{%eOtsASWEV_+4y4Oh0r||e%z|b>K9F8@7|3U-Wp?og@`3c- gJ3u}wP+bO)&+5xO%~D~qIh!uCz*mLM>1+~=0NOGj4FCWD delta 88 zcmey*_n&V=3KO&M=>?P1nQEDzM<`5w4x}5N0Qtb%7 diff --git a/lib/tzdata/zoneinfo/Africa/El_Aaiun b/lib/tzdata/zoneinfo/Africa/El_Aaiun index 772e23c4cd8bb1942efdc18155585f9b8f0e8429..909c5f9682927c14e3e64e21da75559ad2e598f9 100644 GIT binary patch delta 88 zcmZ3+w~TKC4->P?M1{%xOtsASWEV{C2h!!MfPCg!WQR=>?PdnQEDzM<`702ht5sfPCg!W~o9TA4qTh3*@uZGW&J``9S)) g7?95jRJRz&XZ2;CU%X)QeO6s&Dg6bT`Psx70cmF*H2?qr diff --git a/lib/tzdata/zoneinfo/America/Godthab b/lib/tzdata/zoneinfo/America/Godthab index 79d7a45464b3f1dac470b9ea5f6a12b1344c8173..00b57bb13fbfa802e61f8b93d08622163a35a9a4 100644 GIT binary patch delta 85 zcmZ3?ew3XxBq}q_c%q=#L9bZF3JwrVME&yiRjywPW delta 183 zcmX@beuiyAn1(k40|P4%i~I+IeOeL>Ao_GC&jb*CN%>0wm`;?~_|B54o|%z}nT0wK xXeAK*|G#^y(5S4mEXxtt4a4?5=fBQB@A8G{ zcsVRt{RNgxJHb*H1w$9o9p7P@jV&ze8?ey5%<>D)Tse$9^X(LzRU89nnf2BdAz9+t zb}X`c1{u1L^G9L1?hxzeT3DfPfOCA)EVP#+Xk*>o2481#VWq>CT6w}LAeIt_unaK$EjxYEM_SN`dsF{!qb!quataLr5u%PzX1LJHER8_XbN*d;f}}+Zr4f8s!Ih#Nx} zee>nW``w=*e>lW`zyWR|a+9GIebduP*v#`1s-3wT)PCGh^){}(TF7<%Z!T6BagYB4 D!F(-F delta 25 hcmca6{EBlzC=)})#_$u&lN&h{CeP=HnB2@63;=&u2~Pk3 diff --git a/lib/tzdata/zoneinfo/Asia/Hebron b/lib/tzdata/zoneinfo/Asia/Hebron index 3ce1bac63134c393682a2a91f5d13c7b05ab2db4..fcf923bd283a4a6f21d789551c29ff5e2680108d 100644 GIT binary patch delta 1178 zcmZA0dq`7Z7zXgqwvt6Gtqi$0-o0sXF zm*#5oo|>0rTAG*K2$3iZMWeFPlq^SJHw@eNoc}uiyyy2m&v)3^*&=UdUH;i+HY-{} z$2ekQy`lmm=^&SV{VjCz9f#;3(Ob5|(Ut^;I%bgzUomf{Bv&V8k)vZaxO544;B;Yc3IbrRo=ibf$@p(m}+@708(|S2#UD%)=sM9}kO6rxqN@4E)48 zWGyV&_yv~CIl)pF1w$9oz29M(jV&zeAGFZD%<>D)T0eq3>-{X8T^b8#n|0O}A=%=E z4lHtdhZwq$^T%Pi_BiX;8d#xggmeAUEwq;_XlLEi4&Pz&V5Nf_`pQEQv__O#HS)X* z@yPQg)yVT5Ij}fCO@uRk4eJ)W+>sfbiLceIgFI>zEhKnPD zh4iLaE``;u=V0~h9=OEoH~J+PcQMw5m}qQ$P|!TzfI=zZqfqKS!us=87*7Mf)zfv}Uf=>L`x*+6U z+&c@M_d^T}o%*0Y*s#MMuJY8wRew5ZOls_;aLsr*Tst2P*L9`B^r4?i&S z;l^a^7oh*YCQSeq&EYO6JQTUZEr$;dw$b z|57FL0gqS6pAEAgaFCmX+@xdesvvH-%AaemVz_SqOOuE8NXz9xF6dZzjYI=+Sm LK$eNVkqH+7|I-hF delta 23 fcmbQs^@npp7-RjyaF)r5EOm@blkc;rGI9X`W1|Ny diff --git a/lib/tzdata/zoneinfo/Europe/Kirov b/lib/tzdata/zoneinfo/Europe/Kirov index d1c93c540c2670f5fd6d04cf837f31d9c2b0c530..bfac56111d9cc81a93cce85205c685880433b96f 100644 GIT binary patch delta 134 zcmX@hdY^Sd6e~Le1B1lIcy6XrRu)!f79c?wvavES=*<9XWY98TU}0cz3t;46V6bpt o-~o}00$_0wu!x9{ZwQ07fiV!90=1 23s 0 - @@ -75,6 +75,8 @@ R K 2014 o - May 15 24 1 S R K 2014 o - Jun 26 24 0 - R K 2014 o - Jul 31 24 1 S R K 2014 o - S lastTh 24 0 - +R K 2023 ma - Ap lastF 0 1 S +R K 2023 ma - O lastTh 24 0 - Z Africa/Cairo 2:5:9 - LMT 1900 O 2 K EE%sT Z Africa/Bissau -1:2:20 - LMT 1912 Ja 1 1u @@ -172,7 +174,7 @@ R M 2021 o - May 16 2 0 - R M 2022 o - Mar 27 3 -1 - R M 2022 o - May 8 2 0 - R M 2023 o - Mar 19 3 -1 - -R M 2023 o - Ap 30 2 0 - +R M 2023 o - Ap 23 2 0 - R M 2024 o - Mar 10 3 -1 - R M 2024 o - Ap 14 2 0 - R M 2025 o - F 23 3 -1 - @@ -188,7 +190,7 @@ R M 2029 o - F 18 2 0 - R M 2029 o - D 30 3 -1 - R M 2030 o - F 10 2 0 - R M 2030 o - D 22 3 -1 - -R M 2031 o - F 2 2 0 - +R M 2031 o - Ja 26 2 0 - R M 2031 o - D 14 3 -1 - R M 2032 o - Ja 18 2 0 - R M 2032 o - N 28 3 -1 - @@ -204,7 +206,7 @@ R M 2036 o - N 23 2 0 - R M 2037 o - O 4 3 -1 - R M 2037 o - N 15 2 0 - R M 2038 o - S 26 3 -1 - -R M 2038 o - N 7 2 0 - +R M 2038 o - O 31 2 0 - R M 2039 o - S 18 3 -1 - R M 2039 o - O 23 2 0 - R M 2040 o - S 2 3 -1 - @@ -220,7 +222,7 @@ R M 2044 o - Au 28 2 0 - R M 2045 o - Jul 9 3 -1 - R M 2045 o - Au 20 2 0 - R M 2046 o - Jul 1 3 -1 - -R M 2046 o - Au 12 2 0 - +R M 2046 o - Au 5 2 0 - R M 2047 o - Jun 23 3 -1 - R M 2047 o - Jul 28 2 0 - R M 2048 o - Jun 7 3 -1 - @@ -236,7 +238,7 @@ R M 2052 o - Jun 2 2 0 - R M 2053 o - Ap 13 3 -1 - R M 2053 o - May 25 2 0 - R M 2054 o - Ap 5 3 -1 - -R M 2054 o - May 17 2 0 - +R M 2054 o - May 10 2 0 - R M 2055 o - Mar 28 3 -1 - R M 2055 o - May 2 2 0 - R M 2056 o - Mar 12 3 -1 - @@ -252,7 +254,7 @@ R M 2060 o - Mar 7 2 0 - R M 2061 o - Ja 16 3 -1 - R M 2061 o - F 27 2 0 - R M 2062 o - Ja 8 3 -1 - -R M 2062 o - F 19 2 0 - +R M 2062 o - F 12 2 0 - R M 2062 o - D 31 3 -1 - R M 2063 o - F 4 2 0 - R M 2063 o - D 16 3 -1 - @@ -268,7 +270,7 @@ R M 2067 o - D 11 2 0 - R M 2068 o - O 21 3 -1 - R M 2068 o - D 2 2 0 - R M 2069 o - O 13 3 -1 - -R M 2069 o - N 24 2 0 - +R M 2069 o - N 17 2 0 - R M 2070 o - O 5 3 -1 - R M 2070 o - N 9 2 0 - R M 2071 o - S 20 3 -1 - @@ -284,7 +286,7 @@ R M 2075 o - S 15 2 0 - R M 2076 o - Jul 26 3 -1 - R M 2076 o - S 6 2 0 - R M 2077 o - Jul 18 3 -1 - -R M 2077 o - Au 29 2 0 - +R M 2077 o - Au 22 2 0 - R M 2078 o - Jul 10 3 -1 - R M 2078 o - Au 14 2 0 - R M 2079 o - Jun 25 3 -1 - @@ -294,13 +296,13 @@ R M 2080 o - Jul 21 2 0 - R M 2081 o - Jun 1 3 -1 - R M 2081 o - Jul 13 2 0 - R M 2082 o - May 24 3 -1 - -R M 2082 o - Jul 5 2 0 - +R M 2082 o - Jun 28 2 0 - R M 2083 o - May 16 3 -1 - R M 2083 o - Jun 20 2 0 - R M 2084 o - Ap 30 3 -1 - R M 2084 o - Jun 11 2 0 - R M 2085 o - Ap 22 3 -1 - -R M 2085 o - Jun 3 2 0 - +R M 2085 o - May 27 2 0 - R M 2086 o - Ap 14 3 -1 - R M 2086 o - May 19 2 0 - R M 2087 o - Mar 30 3 -1 - @@ -997,8 +999,86 @@ R P 2020 2021 - Mar Sa<=30 0 1 S R P 2020 o - O 24 1 0 - R P 2021 o - O 29 1 0 - R P 2022 o - Mar 27 0 1 S -R P 2022 ma - O Sa<=30 2 0 - -R P 2023 ma - Mar Sa<=30 2 1 S +R P 2022 2035 - O Sa<=30 2 0 - +R P 2023 o - Ap 29 2 1 S +R P 2024 o - Ap 13 2 1 S +R P 2025 o - Ap 5 2 1 S +R P 2026 2054 - Mar Sa<=30 2 1 S +R P 2036 o - O 18 2 0 - +R P 2037 o - O 10 2 0 - +R P 2038 o - S 25 2 0 - +R P 2039 o - S 17 2 0 - +R P 2039 o - O 22 2 1 S +R P 2039 2067 - O Sa<=30 2 0 - +R P 2040 o - S 1 2 0 - +R P 2040 o - O 13 2 1 S +R P 2041 o - Au 24 2 0 - +R P 2041 o - S 28 2 1 S +R P 2042 o - Au 16 2 0 - +R P 2042 o - S 20 2 1 S +R P 2043 o - Au 1 2 0 - +R P 2043 o - S 12 2 1 S +R P 2044 o - Jul 23 2 0 - +R P 2044 o - Au 27 2 1 S +R P 2045 o - Jul 15 2 0 - +R P 2045 o - Au 19 2 1 S +R P 2046 o - Jun 30 2 0 - +R P 2046 o - Au 11 2 1 S +R P 2047 o - Jun 22 2 0 - +R P 2047 o - Jul 27 2 1 S +R P 2048 o - Jun 6 2 0 - +R P 2048 o - Jul 18 2 1 S +R P 2049 o - May 29 2 0 - +R P 2049 o - Jul 3 2 1 S +R P 2050 o - May 21 2 0 - +R P 2050 o - Jun 25 2 1 S +R P 2051 o - May 6 2 0 - +R P 2051 o - Jun 17 2 1 S +R P 2052 o - Ap 27 2 0 - +R P 2052 o - Jun 1 2 1 S +R P 2053 o - Ap 12 2 0 - +R P 2053 o - May 24 2 1 S +R P 2054 o - Ap 4 2 0 - +R P 2054 o - May 16 2 1 S +R P 2055 o - May 1 2 1 S +R P 2056 o - Ap 22 2 1 S +R P 2057 o - Ap 7 2 1 S +R P 2058 ma - Mar Sa<=30 2 1 S +R P 2068 o - O 20 2 0 - +R P 2069 o - O 12 2 0 - +R P 2070 o - O 4 2 0 - +R P 2071 o - S 19 2 0 - +R P 2072 o - S 10 2 0 - +R P 2072 o - O 15 2 1 S +R P 2073 o - S 2 2 0 - +R P 2073 o - O 7 2 1 S +R P 2074 o - Au 18 2 0 - +R P 2074 o - S 29 2 1 S +R P 2075 o - Au 10 2 0 - +R P 2075 o - S 14 2 1 S +R P 2075 ma - O Sa<=30 2 0 - +R P 2076 o - Jul 25 2 0 - +R P 2076 o - S 5 2 1 S +R P 2077 o - Jul 17 2 0 - +R P 2077 o - Au 28 2 1 S +R P 2078 o - Jul 9 2 0 - +R P 2078 o - Au 13 2 1 S +R P 2079 o - Jun 24 2 0 - +R P 2079 o - Au 5 2 1 S +R P 2080 o - Jun 15 2 0 - +R P 2080 o - Jul 20 2 1 S +R P 2081 o - Jun 7 2 0 - +R P 2081 o - Jul 12 2 1 S +R P 2082 o - May 23 2 0 - +R P 2082 o - Jul 4 2 1 S +R P 2083 o - May 15 2 0 - +R P 2083 o - Jun 19 2 1 S +R P 2084 o - Ap 29 2 0 - +R P 2084 o - Jun 10 2 1 S +R P 2085 o - Ap 21 2 0 - +R P 2085 o - Jun 2 2 1 S +R P 2086 o - Ap 13 2 0 - +R P 2086 o - May 18 2 1 S Z Asia/Gaza 2:17:52 - LMT 1900 O 2 Z EET/EEST 1948 May 15 2 K EE%sT 1967 Jun 5 @@ -1754,8 +1834,8 @@ Z America/Scoresbysund -1:27:52 - LMT 1916 Jul 28 -1 E -01/+00 Z America/Nuuk -3:26:56 - LMT 1916 Jul 28 -3 - -03 1980 Ap 6 2 --3 E -03/-02 2023 Mar 25 22 --2 - -02 +-3 E -03/-02 2023 O 29 1u +-2 E -02/-01 Z America/Thule -4:35:8 - LMT 1916 Jul 28 -4 Th A%sT Z Europe/Tallinn 1:39 - LMT 1880 @@ -2175,13 +2255,13 @@ Z Europe/Volgograd 2:57:40 - LMT 1920 Ja 3 3 - +03 1930 Jun 21 4 - +04 1961 N 11 4 R +04/+05 1988 Mar 27 2s -3 R +03/+04 1991 Mar 31 2s +3 R MSK/MSD 1991 Mar 31 2s 4 - +04 1992 Mar 29 2s -3 R +03/+04 2011 Mar 27 2s -4 - +04 2014 O 26 2s -3 - +03 2018 O 28 2s +3 R MSK/MSD 2011 Mar 27 2s +4 - MSK 2014 O 26 2s +3 - MSK 2018 O 28 2s 4 - +04 2020 D 27 2s -3 - +03 +3 - MSK Z Europe/Saratov 3:4:18 - LMT 1919 Jul 1 0u 3 - +03 1930 Jun 21 4 R +04/+05 1988 Mar 27 2s @@ -2194,11 +2274,11 @@ Z Europe/Saratov 3:4:18 - LMT 1919 Jul 1 0u Z Europe/Kirov 3:18:48 - LMT 1919 Jul 1 0u 3 - +03 1930 Jun 21 4 R +04/+05 1989 Mar 26 2s -3 R +03/+04 1991 Mar 31 2s +3 R MSK/MSD 1991 Mar 31 2s 4 - +04 1992 Mar 29 2s -3 R +03/+04 2011 Mar 27 2s -4 - +04 2014 O 26 2s -3 - +03 +3 R MSK/MSD 2011 Mar 27 2s +4 - MSK 2014 O 26 2s +3 - MSK Z Europe/Samara 3:20:20 - LMT 1919 Jul 1 0u 3 - +03 1930 Jun 21 4 - +04 1935 Ja 27 @@ -3070,9 +3150,6 @@ Z America/Cambridge_Bay 0 - -00 1920 -5 - EST 2000 N 5 -6 - CST 2001 Ap 1 3 -7 C M%sT -Z America/Yellowknife 0 - -00 1935 --7 Y M%sT 1980 --7 C M%sT Z America/Inuvik 0 - -00 1953 -8 Y P%sT 1979 Ap lastSu 2 -7 Y M%sT 1980 @@ -4171,6 +4248,7 @@ L America/Argentina/Cordoba America/Rosario L America/Tijuana America/Santa_Isabel L America/Denver America/Shiprock L America/Toronto America/Thunder_Bay +L America/Edmonton America/Yellowknife L Pacific/Auckland Antarctica/South_Pole L Asia/Shanghai Asia/Chongqing L Asia/Shanghai Asia/Harbin diff --git a/lib/tzdata/zoneinfo/zone.tab b/lib/tzdata/zoneinfo/zone.tab index 6e5adb9f..dbcb6179 100644 --- a/lib/tzdata/zoneinfo/zone.tab +++ b/lib/tzdata/zoneinfo/zone.tab @@ -121,9 +121,8 @@ CA +744144-0944945 America/Resolute Central - NU (Resolute) CA +624900-0920459 America/Rankin_Inlet Central - NU (central) CA +5024-10439 America/Regina CST - SK (most areas) CA +5017-10750 America/Swift_Current CST - SK (midwest) -CA +5333-11328 America/Edmonton Mountain - AB; BC (E); SK (W) +CA +5333-11328 America/Edmonton Mountain - AB; BC (E); NT (E); SK (W) CA +690650-1050310 America/Cambridge_Bay Mountain - NU (west) -CA +6227-11421 America/Yellowknife Mountain - NT (central) CA +682059-1334300 America/Inuvik Mountain - NT (west) CA +4906-11631 America/Creston MST - BC (Creston) CA +5546-12014 America/Dawson_Creek MST - BC (Dawson Cr, Ft St John) @@ -139,7 +138,7 @@ CG -0416+01517 Africa/Brazzaville CH +4723+00832 Europe/Zurich CI +0519-00402 Africa/Abidjan CK -2114-15946 Pacific/Rarotonga -CL -3327-07040 America/Santiago Chile (most areas) +CL -3327-07040 America/Santiago most of Chile CL -5309-07055 America/Punta_Arenas Region of Magallanes CL -2709-10926 Pacific/Easter Easter Island CM +0403+00942 Africa/Douala @@ -151,10 +150,10 @@ CU +2308-08222 America/Havana CV +1455-02331 Atlantic/Cape_Verde CW +1211-06900 America/Curacao CX -1025+10543 Indian/Christmas -CY +3510+03322 Asia/Nicosia Cyprus (most areas) +CY +3510+03322 Asia/Nicosia most of Cyprus CY +3507+03357 Asia/Famagusta Northern Cyprus CZ +5005+01426 Europe/Prague -DE +5230+01322 Europe/Berlin Germany (most areas) +DE +5230+01322 Europe/Berlin most of Germany DE +4742+00841 Europe/Busingen Busingen DJ +1136+04309 Africa/Djibouti DK +5540+01235 Europe/Copenhagen @@ -187,7 +186,7 @@ GF +0456-05220 America/Cayenne GG +492717-0023210 Europe/Guernsey GH +0533-00013 Africa/Accra GI +3608-00521 Europe/Gibraltar -GL +6411-05144 America/Nuuk Greenland (most areas) +GL +6411-05144 America/Nuuk most of Greenland GL +7646-01840 America/Danmarkshavn National Park (east coast) GL +7029-02158 America/Scoresbysund Scoresbysund/Ittoqqortoormiit GL +7634-06847 America/Thule Thule/Pituffik @@ -235,7 +234,7 @@ KP +3901+12545 Asia/Pyongyang KR +3733+12658 Asia/Seoul KW +2920+04759 Asia/Kuwait KY +1918-08123 America/Cayman -KZ +4315+07657 Asia/Almaty Kazakhstan (most areas) +KZ +4315+07657 Asia/Almaty most of Kazakhstan KZ +4448+06528 Asia/Qyzylorda Qyzylorda/Kyzylorda/Kzyl-Orda KZ +5312+06337 Asia/Qostanay Qostanay/Kostanay/Kustanay KZ +5017+05710 Asia/Aqtobe Aqtobe/Aktobe @@ -259,12 +258,12 @@ MD +4700+02850 Europe/Chisinau ME +4226+01916 Europe/Podgorica MF +1804-06305 America/Marigot MG -1855+04731 Indian/Antananarivo -MH +0709+17112 Pacific/Majuro Marshall Islands (most areas) +MH +0709+17112 Pacific/Majuro most of Marshall Islands MH +0905+16720 Pacific/Kwajalein Kwajalein MK +4159+02126 Europe/Skopje ML +1239-00800 Africa/Bamako MM +1647+09610 Asia/Yangon -MN +4755+10653 Asia/Ulaanbaatar Mongolia (most areas) +MN +4755+10653 Asia/Ulaanbaatar most of Mongolia MN +4801+09139 Asia/Hovd Bayan-Olgiy, Govi-Altai, Hovd, Uvs, Zavkhan MN +4804+11430 Asia/Choibalsan Dornod, Sukhbaatar MO +221150+1133230 Asia/Macau @@ -302,7 +301,7 @@ NO +5955+01045 Europe/Oslo NP +2743+08519 Asia/Kathmandu NR -0031+16655 Pacific/Nauru NU -1901-16955 Pacific/Niue -NZ -3652+17446 Pacific/Auckland New Zealand (most areas) +NZ -3652+17446 Pacific/Auckland most of New Zealand NZ -4357-17633 Pacific/Chatham Chatham Islands OM +2336+05835 Asia/Muscat PA +0858-07932 America/Panama @@ -310,7 +309,7 @@ PE -1203-07703 America/Lima PF -1732-14934 Pacific/Tahiti Society Islands PF -0900-13930 Pacific/Marquesas Marquesas Islands PF -2308-13457 Pacific/Gambier Gambier Islands -PG -0930+14710 Pacific/Port_Moresby Papua New Guinea (most areas) +PG -0930+14710 Pacific/Port_Moresby most of Papua New Guinea PG -0613+15534 Pacific/Bougainville Bougainville PH +1435+12100 Asia/Manila PK +2452+06703 Asia/Karachi @@ -356,7 +355,7 @@ RU +4310+13156 Asia/Vladivostok MSK+07 - Amur River RU +643337+1431336 Asia/Ust-Nera MSK+07 - Oymyakonsky RU +5934+15048 Asia/Magadan MSK+08 - Magadan RU +4658+14242 Asia/Sakhalin MSK+08 - Sakhalin Island -RU +6728+15343 Asia/Srednekolymsk MSK+08 - Sakha (E); North Kuril Is +RU +6728+15343 Asia/Srednekolymsk MSK+08 - Sakha (E); N Kuril Is RU +5301+15839 Asia/Kamchatka MSK+09 - Kamchatka RU +6445+17729 Asia/Anadyr MSK+09 - Bering Sea RW -0157+03004 Africa/Kigali @@ -397,7 +396,7 @@ TT +1039-06131 America/Port_of_Spain TV -0831+17913 Pacific/Funafuti TW +2503+12130 Asia/Taipei TZ -0648+03917 Africa/Dar_es_Salaam -UA +5026+03031 Europe/Kyiv Ukraine (most areas) +UA +5026+03031 Europe/Kyiv most of Ukraine UG +0019+03225 Africa/Kampala UM +2813-17722 Pacific/Midway Midway Islands UM +1917+16637 Pacific/Wake Wake Island @@ -420,7 +419,7 @@ US +465042-1012439 America/North_Dakota/New_Salem Central - ND (Morton rural) US +471551-1014640 America/North_Dakota/Beulah Central - ND (Mercer) US +394421-1045903 America/Denver Mountain (most areas) US +433649-1161209 America/Boise Mountain - ID (south); OR (east) -US +332654-1120424 America/Phoenix MST - Arizona (except Navajo) +US +332654-1120424 America/Phoenix MST - AZ (except Navajo) US +340308-1181434 America/Los_Angeles Pacific US +611305-1495401 America/Anchorage Alaska (most areas) US +581807-1342511 America/Juneau Alaska - Juneau area @@ -428,7 +427,7 @@ US +571035-1351807 America/Sitka Alaska - Sitka area US +550737-1313435 America/Metlakatla Alaska - Annette Island US +593249-1394338 America/Yakutat Alaska - Yakutat US +643004-1652423 America/Nome Alaska (west) -US +515248-1763929 America/Adak Aleutian Islands +US +515248-1763929 America/Adak Alaska - western Aleutians US +211825-1575130 Pacific/Honolulu Hawaii UY -345433-0561245 America/Montevideo UZ +3940+06648 Asia/Samarkand Uzbekistan (west) diff --git a/lib/tzdata/zoneinfo/zone1970.tab b/lib/tzdata/zoneinfo/zone1970.tab index a9b36d36..1f1cecb8 100644 --- a/lib/tzdata/zoneinfo/zone1970.tab +++ b/lib/tzdata/zoneinfo/zone1970.tab @@ -18,7 +18,10 @@ # Please see the theory.html file for how these names are chosen. # If multiple timezones overlap a country, each has a row in the # table, with each column 1 containing the country code. -# 4. Comments; present if and only if a country has multiple timezones. +# 4. Comments; present if and only if countries have multiple timezones, +# and useful only for those countries. For example, the comments +# for the row with countries CH,DE,LI and name Europe/Zurich +# are useful only for DE, since CH and LI have no other timezones. # # If a timezone covers multiple countries, the most-populous city is used, # and that country is listed first in column 1; any other countries @@ -34,7 +37,7 @@ #country- #codes coordinates TZ comments AD +4230+00131 Europe/Andorra -AE,OM,RE,SC,TF +2518+05518 Asia/Dubai UAE, Oman, Réunion, Seychelles, Crozet, Scattered Is +AE,OM,RE,SC,TF +2518+05518 Asia/Dubai Crozet, Scattered Is AF +3431+06912 Asia/Kabul AL +4120+01950 Europe/Tirane AM +4011+04430 Asia/Yerevan @@ -45,7 +48,7 @@ AQ -6448-06406 Antarctica/Palmer Palmer AQ -6734-06808 Antarctica/Rothera Rothera AQ -720041+0023206 Antarctica/Troll Troll AR -3436-05827 America/Argentina/Buenos_Aires Buenos Aires (BA, CF) -AR -3124-06411 America/Argentina/Cordoba Argentina (most areas: CB, CC, CN, ER, FM, MN, SE, SF) +AR -3124-06411 America/Argentina/Cordoba most areas: CB, CC, CN, ER, FM, MN, SE, SF AR -2447-06525 America/Argentina/Salta Salta (SA, LP, NQ, RN) AR -2411-06518 America/Argentina/Jujuy Jujuy (JY) AR -2649-06513 America/Argentina/Tucuman Tucumán (TM) @@ -56,7 +59,7 @@ AR -3253-06849 America/Argentina/Mendoza Mendoza (MZ) AR -3319-06621 America/Argentina/San_Luis San Luis (SL) AR -5138-06913 America/Argentina/Rio_Gallegos Santa Cruz (SC) AR -5448-06818 America/Argentina/Ushuaia Tierra del Fuego (TF) -AS,UM -1416-17042 Pacific/Pago_Pago Samoa, Midway +AS,UM -1416-17042 Pacific/Pago_Pago Midway AT +4813+01620 Europe/Vienna AU -3133+15905 Australia/Lord_Howe Lord Howe Island AU -5430+15857 Antarctica/Macquarie Macquarie Island @@ -101,26 +104,25 @@ CA +4439-06336 America/Halifax Atlantic - NS (most areas); PE CA +4612-05957 America/Glace_Bay Atlantic - NS (Cape Breton) CA +4606-06447 America/Moncton Atlantic - New Brunswick CA +5320-06025 America/Goose_Bay Atlantic - Labrador (most areas) -CA,BS +4339-07923 America/Toronto Eastern - ON, QC (most areas), Bahamas +CA,BS +4339-07923 America/Toronto Eastern - ON, QC (most areas) CA +6344-06828 America/Iqaluit Eastern - NU (most areas) CA +4953-09709 America/Winnipeg Central - ON (west); Manitoba CA +744144-0944945 America/Resolute Central - NU (Resolute) CA +624900-0920459 America/Rankin_Inlet Central - NU (central) CA +5024-10439 America/Regina CST - SK (most areas) CA +5017-10750 America/Swift_Current CST - SK (midwest) -CA +5333-11328 America/Edmonton Mountain - AB; BC (E); SK (W) +CA +5333-11328 America/Edmonton Mountain - AB; BC (E); NT (E); SK (W) CA +690650-1050310 America/Cambridge_Bay Mountain - NU (west) -CA +6227-11421 America/Yellowknife Mountain - NT (central) CA +682059-1334300 America/Inuvik Mountain - NT (west) CA +5546-12014 America/Dawson_Creek MST - BC (Dawson Cr, Ft St John) CA +5848-12242 America/Fort_Nelson MST - BC (Ft Nelson) CA +6043-13503 America/Whitehorse MST - Yukon (east) CA +6404-13925 America/Dawson MST - Yukon (west) CA +4916-12307 America/Vancouver Pacific - BC (most areas) -CH,DE,LI +4723+00832 Europe/Zurich Swiss time +CH,DE,LI +4723+00832 Europe/Zurich Büsingen CI,BF,GH,GM,GN,IS,ML,MR,SH,SL,SN,TG +0519-00402 Africa/Abidjan CK -2114-15946 Pacific/Rarotonga -CL -3327-07040 America/Santiago Chile (most areas) +CL -3327-07040 America/Santiago most of Chile CL -5309-07055 America/Punta_Arenas Region of Magallanes CL -2709-10926 Pacific/Easter Easter Island CN +3114+12128 Asia/Shanghai Beijing Time @@ -129,10 +131,10 @@ CO +0436-07405 America/Bogota CR +0956-08405 America/Costa_Rica CU +2308-08222 America/Havana CV +1455-02331 Atlantic/Cape_Verde -CY +3510+03322 Asia/Nicosia Cyprus (most areas) +CY +3510+03322 Asia/Nicosia most of Cyprus CY +3507+03357 Asia/Famagusta Northern Cyprus CZ,SK +5005+01426 Europe/Prague -DE,DK,NO,SE,SJ +5230+01322 Europe/Berlin Germany (most areas), Scandinavia +DE,DK,NO,SE,SJ +5230+01322 Europe/Berlin most of Germany DO +1828-06954 America/Santo_Domingo DZ +3647+00303 Africa/Algiers EC -0210-07950 America/Guayaquil Ecuador (mainland) @@ -153,7 +155,7 @@ GB,GG,IM,JE +513030-0000731 Europe/London GE +4143+04449 Asia/Tbilisi GF +0456-05220 America/Cayenne GI +3608-00521 Europe/Gibraltar -GL +6411-05144 America/Nuuk Greenland (most areas) +GL +6411-05144 America/Nuuk most of Greenland GL +7646-01840 America/Danmarkshavn National Park (east coast) GL +7029-02158 America/Scoresbysund Scoresbysund/Ittoqqortoormiit GL +7634-06847 America/Thule Thule/Pituffik @@ -183,12 +185,12 @@ JO +3157+03556 Asia/Amman JP +353916+1394441 Asia/Tokyo KE,DJ,ER,ET,KM,MG,SO,TZ,UG,YT -0117+03649 Africa/Nairobi KG +4254+07436 Asia/Bishkek -KI,MH,TV,UM,WF +0125+17300 Pacific/Tarawa Gilberts, Marshalls, Tuvalu, Wallis & Futuna, Wake +KI,MH,TV,UM,WF +0125+17300 Pacific/Tarawa Gilberts, Marshalls, Wake KI -0247-17143 Pacific/Kanton Phoenix Islands KI +0152-15720 Pacific/Kiritimati Line Islands KP +3901+12545 Asia/Pyongyang KR +3733+12658 Asia/Seoul -KZ +4315+07657 Asia/Almaty Kazakhstan (most areas) +KZ +4315+07657 Asia/Almaty most of Kazakhstan KZ +4448+06528 Asia/Qyzylorda Qyzylorda/Kyzylorda/Kzyl-Orda KZ +5312+06337 Asia/Qostanay Qostanay/Kostanay/Kustanay KZ +5017+05710 Asia/Aqtobe Aqtöbe/Aktobe @@ -205,14 +207,14 @@ MA +3339-00735 Africa/Casablanca MD +4700+02850 Europe/Chisinau MH +0905+16720 Pacific/Kwajalein Kwajalein MM,CC +1647+09610 Asia/Yangon -MN +4755+10653 Asia/Ulaanbaatar Mongolia (most areas) +MN +4755+10653 Asia/Ulaanbaatar most of Mongolia MN +4801+09139 Asia/Hovd Bayan-Ölgii, Govi-Altai, Hovd, Uvs, Zavkhan MN +4804+11430 Asia/Choibalsan Dornod, Sükhbaatar MO +221150+1133230 Asia/Macau MQ +1436-06105 America/Martinique MT +3554+01431 Europe/Malta MU -2010+05730 Indian/Mauritius -MV,TF +0410+07330 Indian/Maldives Maldives, Kerguelen, St Paul I, Amsterdam I +MV,TF +0410+07330 Indian/Maldives Kerguelen, St Paul I, Amsterdam I MX +1924-09909 America/Mexico_City Central Mexico MX +2105-08646 America/Cancun Quintana Roo MX +2058-08937 America/Merida Campeche, Yucatán @@ -225,7 +227,7 @@ MX +2313-10625 America/Mazatlan Baja California Sur, Nayarit (most areas), Sinal MX +2048-10515 America/Bahia_Banderas Bahía de Banderas MX +2904-11058 America/Hermosillo Sonora MX +3232-11701 America/Tijuana Baja California -MY,BN +0133+11020 Asia/Kuching Sabah, Sarawak, Brunei +MY,BN +0133+11020 Asia/Kuching Sabah, Sarawak MZ,BI,BW,CD,MW,RW,ZM,ZW -2558+03235 Africa/Maputo Central Africa Time NA -2234+01706 Africa/Windhoek NC -2216+16627 Pacific/Noumea @@ -237,7 +239,7 @@ NR -0031+16655 Pacific/Nauru NU -1901-16955 Pacific/Niue NZ,AQ -3652+17446 Pacific/Auckland New Zealand time NZ -4357-17633 Pacific/Chatham Chatham Islands -PA,CA,KY +0858-07932 America/Panama EST - Panama, Cayman, ON (Atikokan), NU (Coral H) +PA,CA,KY +0858-07932 America/Panama EST - ON (Atikokan), NU (Coral H) PE -1203-07703 America/Lima PF -1732-14934 Pacific/Tahiti Society Islands PF -0900-13930 Pacific/Marquesas Marquesas Islands @@ -285,13 +287,13 @@ RU +4310+13156 Asia/Vladivostok MSK+07 - Amur River RU +643337+1431336 Asia/Ust-Nera MSK+07 - Oymyakonsky RU +5934+15048 Asia/Magadan MSK+08 - Magadan RU +4658+14242 Asia/Sakhalin MSK+08 - Sakhalin Island -RU +6728+15343 Asia/Srednekolymsk MSK+08 - Sakha (E); North Kuril Is +RU +6728+15343 Asia/Srednekolymsk MSK+08 - Sakha (E); N Kuril Is RU +5301+15839 Asia/Kamchatka MSK+09 - Kamchatka RU +6445+17729 Asia/Anadyr MSK+09 - Bering Sea -SA,AQ,KW,YE +2438+04643 Asia/Riyadh Arabia, Syowa -SB,FM -0932+16012 Pacific/Guadalcanal Solomons, Pohnpei +SA,AQ,KW,YE +2438+04643 Asia/Riyadh Syowa +SB,FM -0932+16012 Pacific/Guadalcanal Pohnpei SD +1536+03232 Africa/Khartoum -SG,MY +0117+10351 Asia/Singapore Singapore, peninsular Malaysia +SG,MY +0117+10351 Asia/Singapore peninsular Malaysia SR +0550-05510 America/Paramaribo SS +0451+03137 Africa/Juba ST +0020+00644 Africa/Sao_Tome @@ -299,7 +301,7 @@ SV +1342-08912 America/El_Salvador SY +3330+03618 Asia/Damascus TC +2128-07108 America/Grand_Turk TD +1207+01503 Africa/Ndjamena -TH,CX,KH,LA,VN +1345+10031 Asia/Bangkok Indochina (most areas) +TH,CX,KH,LA,VN +1345+10031 Asia/Bangkok north Vietnam TJ +3835+06848 Asia/Dushanbe TK -0922-17114 Pacific/Fakaofo TL -0833+12535 Asia/Dili @@ -308,7 +310,7 @@ TN +3648+01011 Africa/Tunis TO -210800-1751200 Pacific/Tongatapu TR +4101+02858 Europe/Istanbul TW +2503+12130 Asia/Taipei -UA +5026+03031 Europe/Kyiv Ukraine (most areas) +UA +5026+03031 Europe/Kyiv most of Ukraine US +404251-0740023 America/New_York Eastern (most areas) US +421953-0830245 America/Detroit Eastern - MI (most areas) US +381515-0854534 America/Kentucky/Louisville Eastern - KY (Louisville area) @@ -328,7 +330,7 @@ US +465042-1012439 America/North_Dakota/New_Salem Central - ND (Morton rural) US +471551-1014640 America/North_Dakota/Beulah Central - ND (Mercer) US +394421-1045903 America/Denver Mountain (most areas) US +433649-1161209 America/Boise Mountain - ID (south); OR (east) -US,CA +332654-1120424 America/Phoenix MST - Arizona (except Navajo), Creston BC +US,CA +332654-1120424 America/Phoenix MST - AZ (most areas), Creston BC US +340308-1181434 America/Los_Angeles Pacific US +611305-1495401 America/Anchorage Alaska (most areas) US +581807-1342511 America/Juneau Alaska - Juneau area @@ -336,13 +338,13 @@ US +571035-1351807 America/Sitka Alaska - Sitka area US +550737-1313435 America/Metlakatla Alaska - Annette Island US +593249-1394338 America/Yakutat Alaska - Yakutat US +643004-1652423 America/Nome Alaska (west) -US +515248-1763929 America/Adak Aleutian Islands -US,UM +211825-1575130 Pacific/Honolulu Hawaii +US +515248-1763929 America/Adak Alaska - western Aleutians +US +211825-1575130 Pacific/Honolulu Hawaii UY -345433-0561245 America/Montevideo UZ +3940+06648 Asia/Samarkand Uzbekistan (west) UZ +4120+06918 Asia/Tashkent Uzbekistan (east) VE +1030-06656 America/Caracas -VN +1045+10640 Asia/Ho_Chi_Minh Vietnam (south) +VN +1045+10640 Asia/Ho_Chi_Minh south Vietnam VU -1740+16825 Pacific/Efate WS -1350-17144 Pacific/Apia ZA,LS,SZ -2615+02800 Africa/Johannesburg diff --git a/lib/tzdata/zones b/lib/tzdata/zones index 8d9892ed..9300ebb0 100644 --- a/lib/tzdata/zones +++ b/lib/tzdata/zones @@ -243,7 +243,6 @@ America/Iqaluit America/Resolute America/Rankin_Inlet America/Cambridge_Bay -America/Yellowknife America/Inuvik America/Whitehorse America/Dawson @@ -561,6 +560,7 @@ America/Rosario America/Santa_Isabel America/Shiprock America/Thunder_Bay +America/Yellowknife Antarctica/South_Pole Asia/Chongqing Asia/Harbin diff --git a/requirements.txt b/requirements.txt index 00e17bde..55053413 100644 --- a/requirements.txt +++ b/requirements.txt @@ -44,7 +44,7 @@ six==1.16.0 soupsieve==2.4 tempora==5.2.1 tokenize-rt==5.0.0 -tzdata==2022.7 +tzdata==2023.3 tzlocal==4.2 urllib3==1.26.15 webencodings==0.5.1 From 1798594569686658df7a1418af032332efb30be6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Aug 2023 21:38:39 -0700 Subject: [PATCH 263/583] Bump simplejson from 3.18.3 to 3.19.1 (#2036) * Bump simplejson from 3.18.3 to 3.19.1 Bumps [simplejson](https://github.com/simplejson/simplejson) from 3.18.3 to 3.19.1. - [Release notes](https://github.com/simplejson/simplejson/releases) - [Changelog](https://github.com/simplejson/simplejson/blob/master/CHANGES.txt) - [Commits](https://github.com/simplejson/simplejson/compare/v3.18.3...v3.19.1) --- updated-dependencies: - dependency-name: simplejson dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Update simplejson==3.19.1 --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> [skip ci] --- lib/simplejson/__init__.py | 106 ++++++++++-------------- lib/simplejson/decoder.py | 82 ++++++++++-------- lib/simplejson/encoder.py | 30 +++---- lib/simplejson/scanner.py | 6 +- lib/simplejson/tests/test_decode.py | 8 ++ lib/simplejson/tests/test_fail.py | 4 +- lib/simplejson/tests/test_float.py | 7 +- lib/simplejson/tests/test_scanstring.py | 4 +- requirements.txt | 2 +- 9 files changed, 128 insertions(+), 121 deletions(-) diff --git a/lib/simplejson/__init__.py b/lib/simplejson/__init__.py index 993d64d3..7e533a24 100644 --- a/lib/simplejson/__init__.py +++ b/lib/simplejson/__init__.py @@ -118,7 +118,7 @@ Serializing multiple objects to JSON lines (newline-delimited JSON):: """ from __future__ import absolute_import -__version__ = '3.18.3' +__version__ = '3.19.1' __all__ = [ 'dump', 'dumps', 'load', 'loads', 'JSONDecoder', 'JSONDecodeError', 'JSONEncoder', @@ -149,28 +149,10 @@ def _import_c_make_encoder(): except ImportError: return None -_default_encoder = JSONEncoder( - skipkeys=False, - ensure_ascii=True, - check_circular=True, - allow_nan=True, - indent=None, - separators=None, - encoding='utf-8', - default=None, - use_decimal=True, - namedtuple_as_object=True, - tuple_as_array=True, - iterable_as_array=False, - bigint_as_string=False, - item_sort_key=None, - for_json=False, - ignore_nan=False, - int_as_string_bitcount=None, -) +_default_encoder = JSONEncoder() def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True, - allow_nan=True, cls=None, indent=None, separators=None, + allow_nan=False, cls=None, indent=None, separators=None, encoding='utf-8', default=None, use_decimal=True, namedtuple_as_object=True, tuple_as_array=True, bigint_as_string=False, sort_keys=False, item_sort_key=None, @@ -187,10 +169,10 @@ def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True, contain non-ASCII characters, so long as they do not need to be escaped by JSON. When it is true, all non-ASCII characters are escaped. - If *allow_nan* is false, then it will be a ``ValueError`` to - serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) - in strict compliance of the original JSON specification, instead of using - the JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``). See + If *allow_nan* is true (default: ``False``), then out of range ``float`` + values (``nan``, ``inf``, ``-inf``) will be serialized to + their JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``) + instead of raising a ValueError. See *ignore_nan* for ECMA-262 compliant behavior. If *indent* is a string, then JSON array elements and object members @@ -258,7 +240,7 @@ def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True, """ # cached encoder if (not skipkeys and ensure_ascii and - check_circular and allow_nan and + check_circular and not allow_nan and cls is None and indent is None and separators is None and encoding == 'utf-8' and default is None and use_decimal and namedtuple_as_object and tuple_as_array and not iterable_as_array @@ -292,7 +274,7 @@ def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True, def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True, - allow_nan=True, cls=None, indent=None, separators=None, + allow_nan=False, cls=None, indent=None, separators=None, encoding='utf-8', default=None, use_decimal=True, namedtuple_as_object=True, tuple_as_array=True, bigint_as_string=False, sort_keys=False, item_sort_key=None, @@ -312,10 +294,11 @@ def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True, for container types will be skipped and a circular reference will result in an ``OverflowError`` (or worse). - If ``allow_nan`` is false, then it will be a ``ValueError`` to - serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) in - strict compliance of the JSON specification, instead of using the - JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``). + If *allow_nan* is true (default: ``False``), then out of range ``float`` + values (``nan``, ``inf``, ``-inf``) will be serialized to + their JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``) + instead of raising a ValueError. See + *ignore_nan* for ECMA-262 compliant behavior. If ``indent`` is a string, then JSON array elements and object members will be pretty-printed with a newline followed by that string repeated @@ -383,7 +366,7 @@ def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True, """ # cached encoder if (not skipkeys and ensure_ascii and - check_circular and allow_nan and + check_circular and not allow_nan and cls is None and indent is None and separators is None and encoding == 'utf-8' and default is None and use_decimal and namedtuple_as_object and tuple_as_array and not iterable_as_array @@ -412,14 +395,12 @@ def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True, **kw).encode(obj) -_default_decoder = JSONDecoder(encoding=None, object_hook=None, - object_pairs_hook=None) +_default_decoder = JSONDecoder() def load(fp, encoding=None, cls=None, object_hook=None, parse_float=None, parse_int=None, parse_constant=None, object_pairs_hook=None, - use_decimal=False, namedtuple_as_object=True, tuple_as_array=True, - **kw): + use_decimal=False, allow_nan=False, **kw): """Deserialize ``fp`` (a ``.read()``-supporting file-like object containing a JSON document as `str` or `bytes`) to a Python object. @@ -442,23 +423,27 @@ def load(fp, encoding=None, cls=None, object_hook=None, parse_float=None, takes priority. *parse_float*, if specified, will be called with the string of every - JSON float to be decoded. By default, this is equivalent to + JSON float to be decoded. By default, this is equivalent to ``float(num_str)``. This can be used to use another datatype or parser for JSON floats (e.g. :class:`decimal.Decimal`). *parse_int*, if specified, will be called with the string of every - JSON int to be decoded. By default, this is equivalent to + JSON int to be decoded. By default, this is equivalent to ``int(num_str)``. This can be used to use another datatype or parser for JSON integers (e.g. :class:`float`). - *parse_constant*, if specified, will be called with one of the - following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``. This - can be used to raise an exception if invalid JSON numbers are - encountered. + *allow_nan*, if True (default false), will allow the parser to + accept the non-standard floats ``NaN``, ``Infinity``, and ``-Infinity`` + and enable the use of the deprecated *parse_constant*. If *use_decimal* is true (default: ``False``) then it implies parse_float=decimal.Decimal for parity with ``dump``. + *parse_constant*, if specified, will be + called with one of the following strings: ``'-Infinity'``, + ``'Infinity'``, ``'NaN'``. It is not recommended to use this feature, + as it is rare to parse non-compliant JSON containing these values. + To use a custom ``JSONDecoder`` subclass, specify it with the ``cls`` kwarg. NOTE: You should use *object_hook* or *object_pairs_hook* instead of subclassing whenever possible. @@ -468,12 +453,12 @@ def load(fp, encoding=None, cls=None, object_hook=None, parse_float=None, encoding=encoding, cls=cls, object_hook=object_hook, parse_float=parse_float, parse_int=parse_int, parse_constant=parse_constant, object_pairs_hook=object_pairs_hook, - use_decimal=use_decimal, **kw) + use_decimal=use_decimal, allow_nan=allow_nan, **kw) def loads(s, encoding=None, cls=None, object_hook=None, parse_float=None, parse_int=None, parse_constant=None, object_pairs_hook=None, - use_decimal=False, **kw): + use_decimal=False, allow_nan=False, **kw): """Deserialize ``s`` (a ``str`` or ``unicode`` instance containing a JSON document) to a Python object. @@ -505,14 +490,18 @@ def loads(s, encoding=None, cls=None, object_hook=None, parse_float=None, ``int(num_str)``. This can be used to use another datatype or parser for JSON integers (e.g. :class:`float`). - *parse_constant*, if specified, will be called with one of the - following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``. This - can be used to raise an exception if invalid JSON numbers are - encountered. + *allow_nan*, if True (default false), will allow the parser to + accept the non-standard floats ``NaN``, ``Infinity``, and ``-Infinity`` + and enable the use of the deprecated *parse_constant*. If *use_decimal* is true (default: ``False``) then it implies parse_float=decimal.Decimal for parity with ``dump``. + *parse_constant*, if specified, will be + called with one of the following strings: ``'-Infinity'``, + ``'Infinity'``, ``'NaN'``. It is not recommended to use this feature, + as it is rare to parse non-compliant JSON containing these values. + To use a custom ``JSONDecoder`` subclass, specify it with the ``cls`` kwarg. NOTE: You should use *object_hook* or *object_pairs_hook* instead of subclassing whenever possible. @@ -521,7 +510,7 @@ def loads(s, encoding=None, cls=None, object_hook=None, parse_float=None, if (cls is None and encoding is None and object_hook is None and parse_int is None and parse_float is None and parse_constant is None and object_pairs_hook is None - and not use_decimal and not kw): + and not use_decimal and not allow_nan and not kw): return _default_decoder.decode(s) if cls is None: cls = JSONDecoder @@ -539,6 +528,8 @@ def loads(s, encoding=None, cls=None, object_hook=None, parse_float=None, if parse_float is not None: raise TypeError("use_decimal=True implies parse_float=Decimal") kw['parse_float'] = Decimal + if allow_nan: + kw['allow_nan'] = True return cls(encoding=encoding, **kw).decode(s) @@ -560,22 +551,9 @@ def _toggle_speedups(enabled): scan.make_scanner = scan.py_make_scanner dec.make_scanner = scan.make_scanner global _default_decoder - _default_decoder = JSONDecoder( - encoding=None, - object_hook=None, - object_pairs_hook=None, - ) + _default_decoder = JSONDecoder() global _default_encoder - _default_encoder = JSONEncoder( - skipkeys=False, - ensure_ascii=True, - check_circular=True, - allow_nan=True, - indent=None, - separators=None, - encoding='utf-8', - default=None, - ) + _default_encoder = JSONEncoder() def simple_first(kv): """Helper function to pass to item_sort_key to sort simple diff --git a/lib/simplejson/decoder.py b/lib/simplejson/decoder.py index 1a8f772f..c99a976d 100644 --- a/lib/simplejson/decoder.py +++ b/lib/simplejson/decoder.py @@ -46,9 +46,35 @@ BACKSLASH = { DEFAULT_ENCODING = "utf-8" +if hasattr(sys, 'get_int_max_str_digits'): + bounded_int = int +else: + def bounded_int(s, INT_MAX_STR_DIGITS=4300): + """Backport of the integer string length conversion limitation + + https://docs.python.org/3/library/stdtypes.html#int-max-str-digits + """ + if len(s) > INT_MAX_STR_DIGITS: + raise ValueError("Exceeds the limit (%s) for integer string conversion: value has %s digits" % (INT_MAX_STR_DIGITS, len(s))) + return int(s) + + +def scan_four_digit_hex(s, end, _m=re.compile(r'^[0-9a-fA-F]{4}$').match): + """Scan a four digit hex number from s[end:end + 4] + """ + msg = "Invalid \\uXXXX escape sequence" + esc = s[end:end + 4] + if not _m(esc): + raise JSONDecodeError(msg, s, end - 2) + try: + return int(esc, 16), end + 4 + except ValueError: + raise JSONDecodeError(msg, s, end - 2) + def py_scanstring(s, end, encoding=None, strict=True, _b=BACKSLASH, _m=STRINGCHUNK.match, _join=u''.join, - _PY3=PY3, _maxunicode=sys.maxunicode): + _PY3=PY3, _maxunicode=sys.maxunicode, + _scan_four_digit_hex=scan_four_digit_hex): """Scan the string s for a JSON string. End is the index of the character in s after the quote that started the JSON string. Unescapes all valid JSON string escape sequences and raises ValueError @@ -67,6 +93,7 @@ def py_scanstring(s, end, encoding=None, strict=True, if chunk is None: raise JSONDecodeError( "Unterminated string starting at", s, begin) + prev_end = end end = chunk.end() content, terminator = chunk.groups() # Content is contains zero or more unescaped string characters @@ -81,7 +108,7 @@ def py_scanstring(s, end, encoding=None, strict=True, elif terminator != '\\': if strict: msg = "Invalid control character %r at" - raise JSONDecodeError(msg, s, end) + raise JSONDecodeError(msg, s, prev_end) else: _append(terminator) continue @@ -100,35 +127,18 @@ def py_scanstring(s, end, encoding=None, strict=True, end += 1 else: # Unicode escape sequence - msg = "Invalid \\uXXXX escape sequence" - esc = s[end + 1:end + 5] - escX = esc[1:2] - if len(esc) != 4 or escX == 'x' or escX == 'X': - raise JSONDecodeError(msg, s, end - 1) - try: - uni = int(esc, 16) - except ValueError: - raise JSONDecodeError(msg, s, end - 1) - if uni < 0 or uni > _maxunicode: - raise JSONDecodeError(msg, s, end - 1) - end += 5 + uni, end = _scan_four_digit_hex(s, end + 1) # Check for surrogate pair on UCS-4 systems # Note that this will join high/low surrogate pairs # but will also pass unpaired surrogates through if (_maxunicode > 65535 and uni & 0xfc00 == 0xd800 and s[end:end + 2] == '\\u'): - esc2 = s[end + 2:end + 6] - escX = esc2[1:2] - if len(esc2) == 4 and not (escX == 'x' or escX == 'X'): - try: - uni2 = int(esc2, 16) - except ValueError: - raise JSONDecodeError(msg, s, end) - if uni2 & 0xfc00 == 0xdc00: - uni = 0x10000 + (((uni - 0xd800) << 10) | - (uni2 - 0xdc00)) - end += 6 + uni2, end2 = _scan_four_digit_hex(s, end + 2) + if uni2 & 0xfc00 == 0xdc00: + uni = 0x10000 + (((uni - 0xd800) << 10) | + (uni2 - 0xdc00)) + end = end2 char = unichr(uni) # Append the unescaped character _append(char) @@ -169,7 +179,7 @@ def JSONObject(state, encoding, strict, scan_once, object_hook, return pairs, end + 1 elif nextchar != '"': raise JSONDecodeError( - "Expecting property name enclosed in double quotes", + "Expecting property name enclosed in double quotes or '}'", s, end) end += 1 while True: @@ -296,14 +306,15 @@ class JSONDecoder(object): | null | None | +---------------+-------------------+ - It also understands ``NaN``, ``Infinity``, and ``-Infinity`` as + When allow_nan=True, it also understands + ``NaN``, ``Infinity``, and ``-Infinity`` as their corresponding ``float`` values, which is outside the JSON spec. """ def __init__(self, encoding=None, object_hook=None, parse_float=None, parse_int=None, parse_constant=None, strict=True, - object_pairs_hook=None): + object_pairs_hook=None, allow_nan=False): """ *encoding* determines the encoding used to interpret any :class:`str` objects decoded by this instance (``'utf-8'`` by @@ -336,10 +347,13 @@ class JSONDecoder(object): ``int(num_str)``. This can be used to use another datatype or parser for JSON integers (e.g. :class:`float`). - *parse_constant*, if specified, will be called with one of the - following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``. This - can be used to raise an exception if invalid JSON numbers are - encountered. + *allow_nan*, if True (default false), will allow the parser to + accept the non-standard floats ``NaN``, ``Infinity``, and ``-Infinity``. + + *parse_constant*, if specified, will be + called with one of the following strings: ``'-Infinity'``, + ``'Infinity'``, ``'NaN'``. It is not recommended to use this feature, + as it is rare to parse non-compliant JSON containing these values. *strict* controls the parser's behavior when it encounters an invalid control character in a string. The default setting of @@ -353,8 +367,8 @@ class JSONDecoder(object): self.object_hook = object_hook self.object_pairs_hook = object_pairs_hook self.parse_float = parse_float or float - self.parse_int = parse_int or int - self.parse_constant = parse_constant or _CONSTANTS.__getitem__ + self.parse_int = parse_int or bounded_int + self.parse_constant = parse_constant or (allow_nan and _CONSTANTS.__getitem__ or None) self.strict = strict self.parse_object = JSONObject self.parse_array = JSONArray diff --git a/lib/simplejson/encoder.py b/lib/simplejson/encoder.py index e93fe43f..661ff361 100644 --- a/lib/simplejson/encoder.py +++ b/lib/simplejson/encoder.py @@ -5,7 +5,7 @@ import re from operator import itemgetter # Do not import Decimal directly to avoid reload issues import decimal -from .compat import unichr, binary_type, text_type, string_types, integer_types, PY3 +from .compat import binary_type, text_type, string_types, integer_types, PY3 def _import_speedups(): try: from . import _speedups @@ -140,7 +140,7 @@ class JSONEncoder(object): key_separator = ': ' def __init__(self, skipkeys=False, ensure_ascii=True, - check_circular=True, allow_nan=True, sort_keys=False, + check_circular=True, allow_nan=False, sort_keys=False, indent=None, separators=None, encoding='utf-8', default=None, use_decimal=True, namedtuple_as_object=True, tuple_as_array=True, bigint_as_string=False, @@ -161,10 +161,11 @@ class JSONEncoder(object): prevent an infinite recursion (which would cause an OverflowError). Otherwise, no such check takes place. - If allow_nan is true, then NaN, Infinity, and -Infinity will be - encoded as such. This behavior is not JSON specification compliant, - but is consistent with most JavaScript based encoders and decoders. - Otherwise, it will be a ValueError to encode such floats. + If allow_nan is true (default: False), then out of range float + values (nan, inf, -inf) will be serialized to + their JavaScript equivalents (NaN, Infinity, -Infinity) + instead of raising a ValueError. See + ignore_nan for ECMA-262 compliant behavior. If sort_keys is true, then the output of dictionaries will be sorted by key; this is useful for regression tests to ensure @@ -294,7 +295,7 @@ class JSONEncoder(object): # This doesn't pass the iterator directly to ''.join() because the # exceptions aren't as detailed. The list call should be roughly # equivalent to the PySequence_Fast that ''.join() would do. - chunks = self.iterencode(o, _one_shot=True) + chunks = self.iterencode(o) if not isinstance(chunks, (list, tuple)): chunks = list(chunks) if self.ensure_ascii: @@ -302,7 +303,7 @@ class JSONEncoder(object): else: return u''.join(chunks) - def iterencode(self, o, _one_shot=False): + def iterencode(self, o): """Encode the given object and yield each string representation as available. @@ -356,8 +357,7 @@ class JSONEncoder(object): key_memo = {} int_as_string_bitcount = ( 53 if self.bigint_as_string else self.int_as_string_bitcount) - if (_one_shot and c_make_encoder is not None - and self.indent is None): + if (c_make_encoder is not None and self.indent is None): _iterencode = c_make_encoder( markers, self.default, _encoder, self.indent, self.key_separator, self.item_separator, self.sort_keys, @@ -370,7 +370,7 @@ class JSONEncoder(object): _iterencode = _make_iterencode( markers, self.default, _encoder, self.indent, floatstr, self.key_separator, self.item_separator, self.sort_keys, - self.skipkeys, _one_shot, self.use_decimal, + self.skipkeys, self.use_decimal, self.namedtuple_as_object, self.tuple_as_array, int_as_string_bitcount, self.item_sort_key, self.encoding, self.for_json, @@ -398,14 +398,14 @@ class JSONEncoderForHTML(JSONEncoder): def encode(self, o): # Override JSONEncoder.encode because it has hacks for # performance that make things more complicated. - chunks = self.iterencode(o, True) + chunks = self.iterencode(o) if self.ensure_ascii: return ''.join(chunks) else: return u''.join(chunks) - def iterencode(self, o, _one_shot=False): - chunks = super(JSONEncoderForHTML, self).iterencode(o, _one_shot) + def iterencode(self, o): + chunks = super(JSONEncoderForHTML, self).iterencode(o) for chunk in chunks: chunk = chunk.replace('&', '\\u0026') chunk = chunk.replace('<', '\\u003c') @@ -419,7 +419,7 @@ class JSONEncoderForHTML(JSONEncoder): def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, - _key_separator, _item_separator, _sort_keys, _skipkeys, _one_shot, + _key_separator, _item_separator, _sort_keys, _skipkeys, _use_decimal, _namedtuple_as_object, _tuple_as_array, _int_as_string_bitcount, _item_sort_key, _encoding,_for_json, diff --git a/lib/simplejson/scanner.py b/lib/simplejson/scanner.py index 85e385e1..34710d68 100644 --- a/lib/simplejson/scanner.py +++ b/lib/simplejson/scanner.py @@ -60,11 +60,11 @@ def py_make_scanner(context): else: res = parse_int(integer) return res, m.end() - elif nextchar == 'N' and string[idx:idx + 3] == 'NaN': + elif parse_constant and nextchar == 'N' and string[idx:idx + 3] == 'NaN': return parse_constant('NaN'), idx + 3 - elif nextchar == 'I' and string[idx:idx + 8] == 'Infinity': + elif parse_constant and nextchar == 'I' and string[idx:idx + 8] == 'Infinity': return parse_constant('Infinity'), idx + 8 - elif nextchar == '-' and string[idx:idx + 9] == '-Infinity': + elif parse_constant and nextchar == '-' and string[idx:idx + 9] == '-Infinity': return parse_constant('-Infinity'), idx + 9 else: raise JSONDecodeError(errmsg, string, idx) diff --git a/lib/simplejson/tests/test_decode.py b/lib/simplejson/tests/test_decode.py index 6960ee58..317b4f98 100644 --- a/lib/simplejson/tests/test_decode.py +++ b/lib/simplejson/tests/test_decode.py @@ -2,6 +2,7 @@ from __future__ import absolute_import import decimal from unittest import TestCase +import sys import simplejson as json from simplejson.compat import StringIO, b, binary_type from simplejson import OrderedDict @@ -117,3 +118,10 @@ class TestDecode(TestCase): diff = id(x) - id(y) self.assertRaises(ValueError, j.scan_once, y, diff) self.assertRaises(ValueError, j.raw_decode, y, i) + + def test_bounded_int(self): + # SJ-PT-23-03, limit quadratic number parsing per Python 3.11 + max_str_digits = getattr(sys, 'get_int_max_str_digits', lambda: 4300)() + s = '1' + '0' * (max_str_digits - 1) + self.assertEqual(json.loads(s), int(s)) + self.assertRaises(ValueError, json.loads, s + '0') diff --git a/lib/simplejson/tests/test_fail.py b/lib/simplejson/tests/test_fail.py index 788f3a52..5f9a8f69 100644 --- a/lib/simplejson/tests/test_fail.py +++ b/lib/simplejson/tests/test_fail.py @@ -145,7 +145,7 @@ class TestFail(TestCase): ('["spam', 'Unterminated string starting at', 1), ('["spam"', "Expecting ',' delimiter", 7), ('["spam",', 'Expecting value', 8), - ('{', 'Expecting property name enclosed in double quotes', 1), + ('{', "Expecting property name enclosed in double quotes or '}'", 1), ('{"', 'Unterminated string starting at', 1), ('{"spam', 'Unterminated string starting at', 1), ('{"spam"', "Expecting ':' delimiter", 7), @@ -156,6 +156,8 @@ class TestFail(TestCase): ('"', 'Unterminated string starting at', 0), ('"spam', 'Unterminated string starting at', 0), ('[,', "Expecting value", 1), + ('--', 'Expecting value', 0), + ('"\x18d', "Invalid control character %r", 1), ] for data, msg, idx in test_cases: try: diff --git a/lib/simplejson/tests/test_float.py b/lib/simplejson/tests/test_float.py index e382ec21..a9779694 100644 --- a/lib/simplejson/tests/test_float.py +++ b/lib/simplejson/tests/test_float.py @@ -7,9 +7,9 @@ from simplejson.decoder import NaN, PosInf, NegInf class TestFloat(TestCase): def test_degenerates_allow(self): for inf in (PosInf, NegInf): - self.assertEqual(json.loads(json.dumps(inf)), inf) + self.assertEqual(json.loads(json.dumps(inf, allow_nan=True), allow_nan=True), inf) # Python 2.5 doesn't have math.isnan - nan = json.loads(json.dumps(NaN)) + nan = json.loads(json.dumps(NaN, allow_nan=True), allow_nan=True) self.assertTrue((0 + nan) != nan) def test_degenerates_ignore(self): @@ -19,6 +19,9 @@ class TestFloat(TestCase): def test_degenerates_deny(self): for f in (PosInf, NegInf, NaN): self.assertRaises(ValueError, json.dumps, f, allow_nan=False) + for s in ('Infinity', '-Infinity', 'NaN'): + self.assertRaises(ValueError, json.loads, s, allow_nan=False) + self.assertRaises(ValueError, json.loads, s) def test_floats(self): for num in [1617161771.7650001, math.pi, math.pi**100, diff --git a/lib/simplejson/tests/test_scanstring.py b/lib/simplejson/tests/test_scanstring.py index c6c53b81..1f544834 100644 --- a/lib/simplejson/tests/test_scanstring.py +++ b/lib/simplejson/tests/test_scanstring.py @@ -132,7 +132,9 @@ class TestScanString(TestCase): self.assertRaises(ValueError, scanstring, '\\ud834\\x0123"', 0, None, True) - self.assertRaises(json.JSONDecodeError, scanstring, "\\u-123", 0, None, True) + self.assertRaises(json.JSONDecodeError, scanstring, '\\u-123"', 0, None, True) + # SJ-PT-23-01: Invalid Handling of Broken Unicode Escape Sequences + self.assertRaises(json.JSONDecodeError, scanstring, '\\u EDD"', 0, None, True) def test_issue3623(self): self.assertRaises(ValueError, json.decoder.scanstring, "xxx", 1, diff --git a/requirements.txt b/requirements.txt index 55053413..b9a561d1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -39,7 +39,7 @@ pytz==2023.3 requests==2.28.2 requests-oauthlib==1.3.1 rumps==0.4.0; platform_system == "Darwin" -simplejson==3.18.3 +simplejson==3.19.1 six==1.16.0 soupsieve==2.4 tempora==5.2.1 From e70e08c3f5ab27daf8ad3f7531819f07d97b5351 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Aug 2023 21:38:49 -0700 Subject: [PATCH 264/583] Bump beautifulsoup4 from 4.11.2 to 4.12.2 (#2037) * Bump beautifulsoup4 from 4.11.2 to 4.12.2 Bumps [beautifulsoup4](https://www.crummy.com/software/BeautifulSoup/bs4/) from 4.11.2 to 4.12.2. --- updated-dependencies: - dependency-name: beautifulsoup4 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Update beautifulsoup4==4.12.2 --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> [skip ci] --- lib/bs4/__init__.py | 59 ++- lib/bs4/builder/_htmlparser.py | 25 +- lib/bs4/css.py | 280 ++++++++++ lib/bs4/diagnose.py | 15 - lib/bs4/element.py | 470 ++++++++++------- lib/bs4/formatter.py | 2 +- lib/bs4/tests/__init__.py | 40 +- ...mized-bs4_fuzzer-4818336571064320.testcase | 1 + ...mized-bs4_fuzzer-4999465949331456.testcase | 1 + ...mized-bs4_fuzzer-5167584867909632.testcase | Bin 0 -> 19469 bytes ...mized-bs4_fuzzer-5703933063462912.testcase | 2 + ...mized-bs4_fuzzer-5843991618256896.testcase | 1 + ...mized-bs4_fuzzer-5984173902397440.testcase | Bin 0 -> 51495 bytes ...mized-bs4_fuzzer-6124268085182464.testcase | 1 + ...mized-bs4_fuzzer-6241471367348224.testcase | 1 + ...mized-bs4_fuzzer-6450958476902400.testcase | Bin 0 -> 3546 bytes ...mized-bs4_fuzzer-6600557255327744.testcase | Bin 0 -> 124 bytes ...0c8ed8bcd0785b67000fcd5dea1d33f08.testcase | Bin 0 -> 2607 bytes lib/bs4/tests/test_css.py | 487 ++++++++++++++++++ lib/bs4/tests/test_formatter.py | 20 +- lib/bs4/tests/test_fuzz.py | 91 ++++ lib/bs4/tests/test_htmlparser.py | 24 + lib/bs4/tests/test_lxml.py | 12 +- lib/bs4/tests/test_pageelement.py | 480 ++--------------- lib/bs4/tests/test_soup.py | 42 ++ lib/soupsieve/__init__.py | 30 +- lib/soupsieve/__meta__.py | 2 +- lib/soupsieve/css_match.py | 54 +- lib/soupsieve/css_parser.py | 22 +- lib/soupsieve/css_types.py | 24 +- lib/soupsieve/util.py | 6 +- requirements.txt | 2 +- 32 files changed, 1439 insertions(+), 755 deletions(-) create mode 100644 lib/bs4/css.py create mode 100644 lib/bs4/tests/fuzz/clusterfuzz-testcase-minimized-bs4_fuzzer-4818336571064320.testcase create mode 100644 lib/bs4/tests/fuzz/clusterfuzz-testcase-minimized-bs4_fuzzer-4999465949331456.testcase create mode 100644 lib/bs4/tests/fuzz/clusterfuzz-testcase-minimized-bs4_fuzzer-5167584867909632.testcase create mode 100644 lib/bs4/tests/fuzz/clusterfuzz-testcase-minimized-bs4_fuzzer-5703933063462912.testcase create mode 100644 lib/bs4/tests/fuzz/clusterfuzz-testcase-minimized-bs4_fuzzer-5843991618256896.testcase create mode 100644 lib/bs4/tests/fuzz/clusterfuzz-testcase-minimized-bs4_fuzzer-5984173902397440.testcase create mode 100644 lib/bs4/tests/fuzz/clusterfuzz-testcase-minimized-bs4_fuzzer-6124268085182464.testcase create mode 100644 lib/bs4/tests/fuzz/clusterfuzz-testcase-minimized-bs4_fuzzer-6241471367348224.testcase create mode 100644 lib/bs4/tests/fuzz/clusterfuzz-testcase-minimized-bs4_fuzzer-6450958476902400.testcase create mode 100644 lib/bs4/tests/fuzz/clusterfuzz-testcase-minimized-bs4_fuzzer-6600557255327744.testcase create mode 100644 lib/bs4/tests/fuzz/crash-0d306a50c8ed8bcd0785b67000fcd5dea1d33f08.testcase create mode 100644 lib/bs4/tests/test_css.py create mode 100644 lib/bs4/tests/test_fuzz.py diff --git a/lib/bs4/__init__.py b/lib/bs4/__init__.py index db71cc7c..3d2ab09a 100644 --- a/lib/bs4/__init__.py +++ b/lib/bs4/__init__.py @@ -15,7 +15,7 @@ documentation: http://www.crummy.com/software/BeautifulSoup/bs4/doc/ """ __author__ = "Leonard Richardson (leonardr@segfault.org)" -__version__ = "4.11.2" +__version__ = "4.12.2" __copyright__ = "Copyright (c) 2004-2023 Leonard Richardson" # Use of this source code is governed by the MIT license. __license__ = "MIT" @@ -38,11 +38,13 @@ from .builder import ( builder_registry, ParserRejectedMarkup, XMLParsedAsHTMLWarning, + HTMLParserTreeBuilder ) from .dammit import UnicodeDammit from .element import ( CData, Comment, + CSS, DEFAULT_OUTPUT_ENCODING, Declaration, Doctype, @@ -116,7 +118,7 @@ class BeautifulSoup(Tag): ASCII_SPACES = '\x20\x0a\x09\x0c\x0d' NO_PARSER_SPECIFIED_WARNING = "No parser was explicitly specified, so I'm using the best available %(markup_type)s parser for this system (\"%(parser)s\"). This usually isn't a problem, but if you run this code on another system, or in a different virtual environment, it may use a different parser and behave differently.\n\nThe code that caused this warning is on line %(line_number)s of the file %(filename)s. To get rid of this warning, pass the additional argument 'features=\"%(parser)s\"' to the BeautifulSoup constructor.\n" - + def __init__(self, markup="", features=None, builder=None, parse_only=None, from_encoding=None, exclude_encodings=None, element_classes=None, **kwargs): @@ -348,25 +350,49 @@ class BeautifulSoup(Tag): self.markup = None self.builder.soup = None - def __copy__(self): - """Copy a BeautifulSoup object by converting the document to a string and parsing it again.""" - copy = type(self)( - self.encode('utf-8'), builder=self.builder, from_encoding='utf-8' - ) + def _clone(self): + """Create a new BeautifulSoup object with the same TreeBuilder, + but not associated with any markup. - # Although we encoded the tree to UTF-8, that may not have - # been the encoding of the original markup. Set the copy's - # .original_encoding to reflect the original object's - # .original_encoding. - copy.original_encoding = self.original_encoding - return copy + This is the first step of the deepcopy process. + """ + clone = type(self)("", None, self.builder) + # Keep track of the encoding of the original document, + # since we won't be parsing it again. + clone.original_encoding = self.original_encoding + return clone + def __getstate__(self): # Frequently a tree builder can't be pickled. d = dict(self.__dict__) if 'builder' in d and d['builder'] is not None and not self.builder.picklable: - d['builder'] = None + d['builder'] = type(self.builder) + # Store the contents as a Unicode string. + d['contents'] = [] + d['markup'] = self.decode() + + # If _most_recent_element is present, it's a Tag object left + # over from initial parse. It might not be picklable and we + # don't need it. + if '_most_recent_element' in d: + del d['_most_recent_element'] return d + + def __setstate__(self, state): + # If necessary, restore the TreeBuilder by looking it up. + self.__dict__ = state + if isinstance(self.builder, type): + self.builder = self.builder() + elif not self.builder: + # We don't know which builder was used to build this + # parse tree, so use a default we know is always available. + self.builder = HTMLParserTreeBuilder() + self.builder.soup = self + self.reset() + self._feed() + return state + @classmethod def _decode_markup(cls, markup): @@ -468,6 +494,7 @@ class BeautifulSoup(Tag): self.open_tag_counter = Counter() self.preserve_whitespace_tag_stack = [] self.string_container_stack = [] + self._most_recent_element = None self.pushTag(self) def new_tag(self, name, namespace=None, nsprefix=None, attrs={}, @@ -749,7 +776,7 @@ class BeautifulSoup(Tag): def decode(self, pretty_print=False, eventual_encoding=DEFAULT_OUTPUT_ENCODING, - formatter="minimal"): + formatter="minimal", iterator=None): """Returns a string or Unicode representation of the parse tree as an HTML or XML document. @@ -776,7 +803,7 @@ class BeautifulSoup(Tag): else: indent_level = 0 return prefix + super(BeautifulSoup, self).decode( - indent_level, eventual_encoding, formatter) + indent_level, eventual_encoding, formatter, iterator) # Aliases to make it easier to get started quickly, e.g. 'from bs4 import _soup' _s = BeautifulSoup diff --git a/lib/bs4/builder/_htmlparser.py b/lib/bs4/builder/_htmlparser.py index e48b6a0e..e065096b 100644 --- a/lib/bs4/builder/_htmlparser.py +++ b/lib/bs4/builder/_htmlparser.py @@ -24,6 +24,7 @@ from bs4.dammit import EntitySubstitution, UnicodeDammit from bs4.builder import ( DetectsXMLParsedAsHTML, + ParserRejectedMarkup, HTML, HTMLTreeBuilder, STRICT, @@ -70,6 +71,22 @@ class BeautifulSoupHTMLParser(HTMLParser, DetectsXMLParsedAsHTML): self._initialize_xml_detector() + def error(self, message): + # NOTE: This method is required so long as Python 3.9 is + # supported. The corresponding code is removed from HTMLParser + # in 3.5, but not removed from ParserBase until 3.10. + # https://github.com/python/cpython/issues/76025 + # + # The original implementation turned the error into a warning, + # but in every case I discovered, this made HTMLParser + # immediately crash with an error message that was less + # helpful than the warning. The new implementation makes it + # more clear that html.parser just can't parse this + # markup. The 3.10 implementation does the same, though it + # raises AssertionError rather than calling a method. (We + # catch this error and wrap it in a ParserRejectedMarkup.) + raise ParserRejectedMarkup(message) + def handle_startendtag(self, name, attrs): """Handle an incoming empty-element tag. @@ -359,6 +376,12 @@ class HTMLParserTreeBuilder(HTMLTreeBuilder): args, kwargs = self.parser_args parser = BeautifulSoupHTMLParser(*args, **kwargs) parser.soup = self.soup - parser.feed(markup) + try: + parser.feed(markup) + except AssertionError as e: + # html.parser raises AssertionError in rare cases to + # indicate a fatal problem with the markup, especially + # when there's an error in the doctype declaration. + raise ParserRejectedMarkup(e) parser.close() parser.already_closed_empty_element = [] diff --git a/lib/bs4/css.py b/lib/bs4/css.py new file mode 100644 index 00000000..245ac601 --- /dev/null +++ b/lib/bs4/css.py @@ -0,0 +1,280 @@ +"""Integration code for CSS selectors using Soup Sieve (pypi: soupsieve).""" + +import warnings +try: + import soupsieve +except ImportError as e: + soupsieve = None + warnings.warn( + 'The soupsieve package is not installed. CSS selectors cannot be used.' + ) + + +class CSS(object): + """A proxy object against the soupsieve library, to simplify its + CSS selector API. + + Acquire this object through the .css attribute on the + BeautifulSoup object, or on the Tag you want to use as the + starting point for a CSS selector. + + The main advantage of doing this is that the tag to be selected + against doesn't need to be explicitly specified in the function + calls, since it's already scoped to a tag. + """ + + def __init__(self, tag, api=soupsieve): + """Constructor. + + You don't need to instantiate this class yourself; instead, + access the .css attribute on the BeautifulSoup object, or on + the Tag you want to use as the starting point for your CSS + selector. + + :param tag: All CSS selectors will use this as their starting + point. + + :param api: A plug-in replacement for the soupsieve module, + designed mainly for use in tests. + """ + if api is None: + raise NotImplementedError( + "Cannot execute CSS selectors because the soupsieve package is not installed." + ) + self.api = api + self.tag = tag + + def escape(self, ident): + """Escape a CSS identifier. + + This is a simple wrapper around soupselect.escape(). See the + documentation for that function for more information. + """ + if soupsieve is None: + raise NotImplementedError( + "Cannot escape CSS identifiers because the soupsieve package is not installed." + ) + return self.api.escape(ident) + + def _ns(self, ns, select): + """Normalize a dictionary of namespaces.""" + if not isinstance(select, self.api.SoupSieve) and ns is None: + # If the selector is a precompiled pattern, it already has + # a namespace context compiled in, which cannot be + # replaced. + ns = self.tag._namespaces + return ns + + def _rs(self, results): + """Normalize a list of results to a Resultset. + + A ResultSet is more consistent with the rest of Beautiful + Soup's API, and ResultSet.__getattr__ has a helpful error + message if you try to treat a list of results as a single + result (a common mistake). + """ + # Import here to avoid circular import + from bs4.element import ResultSet + return ResultSet(None, results) + + def compile(self, select, namespaces=None, flags=0, **kwargs): + """Pre-compile a selector and return the compiled object. + + :param selector: A CSS selector. + + :param namespaces: A dictionary mapping namespace prefixes + used in the CSS selector to namespace URIs. By default, + Beautiful Soup will use the prefixes it encountered while + parsing the document. + + :param flags: Flags to be passed into Soup Sieve's + soupsieve.compile() method. + + :param kwargs: Keyword arguments to be passed into SoupSieve's + soupsieve.compile() method. + + :return: A precompiled selector object. + :rtype: soupsieve.SoupSieve + """ + return self.api.compile( + select, self._ns(namespaces, select), flags, **kwargs + ) + + def select_one(self, select, namespaces=None, flags=0, **kwargs): + """Perform a CSS selection operation on the current Tag and return the + first result. + + This uses the Soup Sieve library. For more information, see + that library's documentation for the soupsieve.select_one() + method. + + :param selector: A CSS selector. + + :param namespaces: A dictionary mapping namespace prefixes + used in the CSS selector to namespace URIs. By default, + Beautiful Soup will use the prefixes it encountered while + parsing the document. + + :param flags: Flags to be passed into Soup Sieve's + soupsieve.select_one() method. + + :param kwargs: Keyword arguments to be passed into SoupSieve's + soupsieve.select_one() method. + + :return: A Tag, or None if the selector has no match. + :rtype: bs4.element.Tag + + """ + return self.api.select_one( + select, self.tag, self._ns(namespaces, select), flags, **kwargs + ) + + def select(self, select, namespaces=None, limit=0, flags=0, **kwargs): + """Perform a CSS selection operation on the current Tag. + + This uses the Soup Sieve library. For more information, see + that library's documentation for the soupsieve.select() + method. + + :param selector: A string containing a CSS selector. + + :param namespaces: A dictionary mapping namespace prefixes + used in the CSS selector to namespace URIs. By default, + Beautiful Soup will pass in the prefixes it encountered while + parsing the document. + + :param limit: After finding this number of results, stop looking. + + :param flags: Flags to be passed into Soup Sieve's + soupsieve.select() method. + + :param kwargs: Keyword arguments to be passed into SoupSieve's + soupsieve.select() method. + + :return: A ResultSet of Tag objects. + :rtype: bs4.element.ResultSet + + """ + if limit is None: + limit = 0 + + return self._rs( + self.api.select( + select, self.tag, self._ns(namespaces, select), limit, flags, + **kwargs + ) + ) + + def iselect(self, select, namespaces=None, limit=0, flags=0, **kwargs): + """Perform a CSS selection operation on the current Tag. + + This uses the Soup Sieve library. For more information, see + that library's documentation for the soupsieve.iselect() + method. It is the same as select(), but it returns a generator + instead of a list. + + :param selector: A string containing a CSS selector. + + :param namespaces: A dictionary mapping namespace prefixes + used in the CSS selector to namespace URIs. By default, + Beautiful Soup will pass in the prefixes it encountered while + parsing the document. + + :param limit: After finding this number of results, stop looking. + + :param flags: Flags to be passed into Soup Sieve's + soupsieve.iselect() method. + + :param kwargs: Keyword arguments to be passed into SoupSieve's + soupsieve.iselect() method. + + :return: A generator + :rtype: types.GeneratorType + """ + return self.api.iselect( + select, self.tag, self._ns(namespaces, select), limit, flags, **kwargs + ) + + def closest(self, select, namespaces=None, flags=0, **kwargs): + """Find the Tag closest to this one that matches the given selector. + + This uses the Soup Sieve library. For more information, see + that library's documentation for the soupsieve.closest() + method. + + :param selector: A string containing a CSS selector. + + :param namespaces: A dictionary mapping namespace prefixes + used in the CSS selector to namespace URIs. By default, + Beautiful Soup will pass in the prefixes it encountered while + parsing the document. + + :param flags: Flags to be passed into Soup Sieve's + soupsieve.closest() method. + + :param kwargs: Keyword arguments to be passed into SoupSieve's + soupsieve.closest() method. + + :return: A Tag, or None if there is no match. + :rtype: bs4.Tag + + """ + return self.api.closest( + select, self.tag, self._ns(namespaces, select), flags, **kwargs + ) + + def match(self, select, namespaces=None, flags=0, **kwargs): + """Check whether this Tag matches the given CSS selector. + + This uses the Soup Sieve library. For more information, see + that library's documentation for the soupsieve.match() + method. + + :param: a CSS selector. + + :param namespaces: A dictionary mapping namespace prefixes + used in the CSS selector to namespace URIs. By default, + Beautiful Soup will pass in the prefixes it encountered while + parsing the document. + + :param flags: Flags to be passed into Soup Sieve's + soupsieve.match() method. + + :param kwargs: Keyword arguments to be passed into SoupSieve's + soupsieve.match() method. + + :return: True if this Tag matches the selector; False otherwise. + :rtype: bool + """ + return self.api.match( + select, self.tag, self._ns(namespaces, select), flags, **kwargs + ) + + def filter(self, select, namespaces=None, flags=0, **kwargs): + """Filter this Tag's direct children based on the given CSS selector. + + This uses the Soup Sieve library. It works the same way as + passing this Tag into that library's soupsieve.filter() + method. More information, for more information see the + documentation for soupsieve.filter(). + + :param namespaces: A dictionary mapping namespace prefixes + used in the CSS selector to namespace URIs. By default, + Beautiful Soup will pass in the prefixes it encountered while + parsing the document. + + :param flags: Flags to be passed into Soup Sieve's + soupsieve.filter() method. + + :param kwargs: Keyword arguments to be passed into SoupSieve's + soupsieve.filter() method. + + :return: A ResultSet of Tag objects. + :rtype: bs4.element.ResultSet + + """ + return self._rs( + self.api.filter( + select, self.tag, self._ns(namespaces, select), flags, **kwargs + ) + ) diff --git a/lib/bs4/diagnose.py b/lib/bs4/diagnose.py index 3bf583f5..e079772e 100644 --- a/lib/bs4/diagnose.py +++ b/lib/bs4/diagnose.py @@ -59,21 +59,6 @@ def diagnose(data): if hasattr(data, 'read'): data = data.read() - elif data.startswith("http:") or data.startswith("https:"): - print(('"%s" looks like a URL. Beautiful Soup is not an HTTP client.' % data)) - print("You need to use some other library to get the document behind the URL, and feed that document to Beautiful Soup.") - return - else: - try: - if os.path.exists(data): - print(('"%s" looks like a filename. Reading data from the file.' % data)) - with open(data) as fp: - data = fp.read() - except ValueError: - # This can happen on some platforms when the 'filename' is - # too long. Assume it's data and not a filename. - pass - print("") for parser in basic_parsers: print(("Trying to parse your markup with %s" % parser)) diff --git a/lib/bs4/element.py b/lib/bs4/element.py index 583d0e8a..9c73957c 100644 --- a/lib/bs4/element.py +++ b/lib/bs4/element.py @@ -8,14 +8,8 @@ except ImportError as e: import re import sys import warnings -try: - import soupsieve -except ImportError as e: - soupsieve = None - warnings.warn( - 'The soupsieve package is not installed. CSS selectors cannot be used.' - ) +from bs4.css import CSS from bs4.formatter import ( Formatter, HTMLFormatter, @@ -69,13 +63,13 @@ PYTHON_SPECIFIC_ENCODINGS = set([ "string-escape", "string_escape", ]) - + class NamespacedAttribute(str): """A namespaced string (e.g. 'xml:lang') that remembers the namespace ('xml') and the name ('lang') that were used to create it. """ - + def __new__(cls, prefix, name=None, namespace=None): if not name: # This is the default namespace. Its name "has no value" @@ -146,14 +140,19 @@ class ContentMetaAttributeValue(AttributeValueWithCharsetSubstitution): return match.group(1) + encoding return self.CHARSET_RE.sub(rewrite, self.original_value) - + class PageElement(object): """Contains the navigational information for some part of the page: that is, its current location in the parse tree. NavigableString, Tag, etc. are all subclasses of PageElement. """ - + + # In general, we can't tell just by looking at an element whether + # it's contained in an XML document or an HTML document. But for + # Tags (q.v.) we can store this information at parse time. + known_xml = None + def setup(self, parent=None, previous_element=None, next_element=None, previous_sibling=None, next_sibling=None): """Sets up the initial relations between this element and @@ -163,7 +162,7 @@ class PageElement(object): :param previous_element: The element parsed immediately before this one. - + :param next_element: The element parsed immediately before this one. @@ -257,11 +256,11 @@ class PageElement(object): default = object() def _all_strings(self, strip=False, types=default): """Yield all strings of certain classes, possibly stripping them. - + This is implemented differently in Tag and NavigableString. """ raise NotImplementedError() - + @property def stripped_strings(self): """Yield all strings in this PageElement, stripping them first. @@ -294,11 +293,11 @@ class PageElement(object): strip, types=types)]) getText = get_text text = property(get_text) - + def replace_with(self, *args): - """Replace this PageElement with one or more PageElements, keeping the + """Replace this PageElement with one or more PageElements, keeping the rest of the tree the same. - + :param args: One or more PageElements. :return: `self`, no longer part of the tree. """ @@ -410,7 +409,7 @@ class PageElement(object): This works the same way as `list.insert`. :param position: The numeric position that should be occupied - in `self.children` by the new PageElement. + in `self.children` by the new PageElement. :param new_child: A PageElement. """ if new_child is None: @@ -546,7 +545,7 @@ class PageElement(object): "Element has no parent, so 'after' has no meaning.") if any(x is self for x in args): raise ValueError("Can't insert an element after itself.") - + offset = 0 for successor in args: # Extract first so that the index won't be screwed up if they @@ -912,7 +911,7 @@ class PageElement(object): :rtype: bool """ return getattr(self, '_decomposed', False) or False - + # Old non-property versions of the generators, for backwards # compatibility with BS3. def nextGenerator(self): @@ -936,16 +935,11 @@ class NavigableString(str, PageElement): When Beautiful Soup parses the markup penguin, it will create a NavigableString for the string "penguin". - """ + """ PREFIX = '' SUFFIX = '' - # We can't tell just by looking at a string whether it's contained - # in an XML document or an HTML document. - - known_xml = None - def __new__(cls, value): """Create a new NavigableString. @@ -961,12 +955,22 @@ class NavigableString(str, PageElement): u.setup() return u - def __copy__(self): + def __deepcopy__(self, memo, recursive=False): """A copy of a NavigableString has the same contents and class as the original, but it is not connected to the parse tree. + + :param recursive: This parameter is ignored; it's only defined + so that NavigableString.__deepcopy__ implements the same + signature as Tag.__deepcopy__. """ return type(self)(self) + def __copy__(self): + """A copy of a NavigableString can only be a deep copy, because + only one PageElement can occupy a given place in a parse tree. + """ + return self.__deepcopy__({}) + def __getnewargs__(self): return (str(self),) @@ -1059,10 +1063,10 @@ class PreformattedString(NavigableString): as comments (the Comment class) and CDATA blocks (the CData class). """ - + PREFIX = '' SUFFIX = '' - + def output_ready(self, formatter=None): """Make this string ready for output by adding any subclass-specific prefix or suffix. @@ -1144,7 +1148,7 @@ class Stylesheet(NavigableString): """ pass - + class Script(NavigableString): """A NavigableString representing an executable script (probably Javascript). @@ -1250,7 +1254,7 @@ class Tag(PageElement): if ((not builder or builder.store_line_numbers) and (sourceline is not None or sourcepos is not None)): self.sourceline = sourceline - self.sourcepos = sourcepos + self.sourcepos = sourcepos if attrs is None: attrs = {} elif attrs: @@ -1308,13 +1312,49 @@ class Tag(PageElement): self.interesting_string_types = builder.string_containers[self.name] else: self.interesting_string_types = self.DEFAULT_INTERESTING_STRING_TYPES - + parserClass = _alias("parser_class") # BS3 - def __copy__(self): - """A copy of a Tag is a new Tag, unconnected to the parse tree. + def __deepcopy__(self, memo, recursive=True): + """A deepcopy of a Tag is a new Tag, unconnected to the parse tree. Its contents are a copy of the old Tag's contents. """ + clone = self._clone() + + if recursive: + # Clone this tag's descendants recursively, but without + # making any recursive function calls. + tag_stack = [clone] + for event, element in self._event_stream(self.descendants): + if event is Tag.END_ELEMENT_EVENT: + # Stop appending incoming Tags to the Tag that was + # just closed. + tag_stack.pop() + else: + descendant_clone = element.__deepcopy__( + memo, recursive=False + ) + # Add to its parent's .contents + tag_stack[-1].append(descendant_clone) + + if event is Tag.START_ELEMENT_EVENT: + # Add the Tag itself to the stack so that its + # children will be .appended to it. + tag_stack.append(descendant_clone) + return clone + + def __copy__(self): + """A copy of a Tag must always be a deep copy, because a Tag's + children can only have one parent at a time. + """ + return self.__deepcopy__({}) + + def _clone(self): + """Create a new Tag just like this one, but with no + contents and unattached to any parse tree. + + This is the first step in the deepcopy process. + """ clone = type(self)( None, self.builder, self.name, self.namespace, self.prefix, self.attrs, is_xml=self._is_xml, @@ -1326,8 +1366,6 @@ class Tag(PageElement): ) for attr in ('can_be_empty_element', 'hidden'): setattr(clone, attr, getattr(self, attr)) - for child in self.contents: - clone.append(child.__copy__()) return clone @property @@ -1433,7 +1471,7 @@ class Tag(PageElement): i.contents = [] i._decomposed = True i = n - + def clear(self, decompose=False): """Wipe out all children of this PageElement by calling extract() on them. @@ -1521,7 +1559,7 @@ class Tag(PageElement): if not isinstance(value, list): value = [value] return value - + def has_attr(self, key): """Does this PageElement have an attribute with the given name?""" return key in self.attrs @@ -1608,7 +1646,7 @@ class Tag(PageElement): def __repr__(self, encoding="unicode-escape"): """Renders this PageElement as a string. - :param encoding: The encoding to use (Python 2 only). + :param encoding: The encoding to use (Python 2 only). TODO: This is now ignored and a warning should be issued if a value is provided. :return: A (Unicode) string. @@ -1650,106 +1688,212 @@ class Tag(PageElement): def decode(self, indent_level=None, eventual_encoding=DEFAULT_OUTPUT_ENCODING, - formatter="minimal"): - """Render a Unicode representation of this PageElement and its - contents. - - :param indent_level: Each line of the rendering will be - indented this many spaces. Used internally in - recursive calls while pretty-printing. - :param eventual_encoding: The tag is destined to be - encoded into this encoding. This method is _not_ - responsible for performing that encoding. This information - is passed in so that it can be substituted in if the - document contains a tag that mentions the document's - encoding. - :param formatter: A Formatter object, or a string naming one of - the standard formatters. - """ - + formatter="minimal", + iterator=None): + pieces = [] # First off, turn a non-Formatter `formatter` into a Formatter # object. This will stop the lookup from happening over and # over again. if not isinstance(formatter, Formatter): formatter = self.formatter_for_name(formatter) - attributes = formatter.attributes(self) - attrs = [] - for key, val in attributes: - if val is None: - decoded = key + + if indent_level is True: + indent_level = 0 + + # The currently active tag that put us into string literal + # mode. Until this element is closed, children will be treated + # as string literals and not pretty-printed. String literal + # mode is turned on immediately after this tag begins, and + # turned off immediately before it's closed. This means there + # will be whitespace before and after the tag itself. + string_literal_tag = None + + for event, element in self._event_stream(iterator): + if event in (Tag.START_ELEMENT_EVENT, Tag.EMPTY_ELEMENT_EVENT): + piece = element._format_tag( + eventual_encoding, formatter, opening=True + ) + elif event is Tag.END_ELEMENT_EVENT: + piece = element._format_tag( + eventual_encoding, formatter, opening=False + ) + if indent_level is not None: + indent_level -= 1 else: - if isinstance(val, list) or isinstance(val, tuple): - val = ' '.join(val) - elif not isinstance(val, str): - val = str(val) - elif ( - isinstance(val, AttributeValueWithCharsetSubstitution) - and eventual_encoding is not None - ): - val = val.encode(eventual_encoding) + piece = element.output_ready(formatter) - text = formatter.attribute_value(val) - decoded = ( - str(key) + '=' - + formatter.quoted_attribute_value(text)) - attrs.append(decoded) - close = '' - closeTag = '' + # Now we need to apply the 'prettiness' -- extra + # whitespace before and/or after this tag. This can get + # complicated because certain tags, like
     and
    +            #  for you 
    +
    """ + + expect = """
    +
    some
    + for you 
    +
    +
    +""" + soup = self.soup(markup) + assert expect == soup.div.prettify() def test_prettify_accepts_formatter_function(self): soup = BeautifulSoup("foo", 'html.parser') @@ -216,429 +249,6 @@ class TestFormatters(SoupTest): assert soup.contents[0].name == 'pre' -@pytest.mark.skipif(not SOUP_SIEVE_PRESENT, reason="Soup Sieve not installed") -class TestCSSSelectors(SoupTest): - """Test basic CSS selector functionality. - - This functionality is implemented in soupsieve, which has a much - more comprehensive test suite, so this is basically an extra check - that soupsieve works as expected. - """ - - HTML = """ - - - -The title - - - -Hello there. -
    -
    -

    An H1

    -

    Some text

    -

    Some more text

    -

    An H2

    -

    Another

    -Bob -

    Another H2

    -me - -span1a1 -span1a2 test - -span2a1 - - - -
    - -
    - - - - - - - - -

    English

    -

    English UK

    -

    English US

    -

    French

    -
    - - -""" - - def setup_method(self): - self.soup = BeautifulSoup(self.HTML, 'html.parser') - - def assert_selects(self, selector, expected_ids, **kwargs): - el_ids = [el['id'] for el in self.soup.select(selector, **kwargs)] - el_ids.sort() - expected_ids.sort() - assert expected_ids == el_ids, "Selector %s, expected [%s], got [%s]" % ( - selector, ', '.join(expected_ids), ', '.join(el_ids) - ) - - assertSelect = assert_selects - - def assert_select_multiple(self, *tests): - for selector, expected_ids in tests: - self.assert_selects(selector, expected_ids) - - def test_one_tag_one(self): - els = self.soup.select('title') - assert len(els) == 1 - assert els[0].name == 'title' - assert els[0].contents == ['The title'] - - def test_one_tag_many(self): - els = self.soup.select('div') - assert len(els) == 4 - for div in els: - assert div.name == 'div' - - el = self.soup.select_one('div') - assert 'main' == el['id'] - - def test_select_one_returns_none_if_no_match(self): - match = self.soup.select_one('nonexistenttag') - assert None == match - - - def test_tag_in_tag_one(self): - els = self.soup.select('div div') - self.assert_selects('div div', ['inner', 'data1']) - - def test_tag_in_tag_many(self): - for selector in ('html div', 'html body div', 'body div'): - self.assert_selects(selector, ['data1', 'main', 'inner', 'footer']) - - - def test_limit(self): - self.assert_selects('html div', ['main'], limit=1) - self.assert_selects('html body div', ['inner', 'main'], limit=2) - self.assert_selects('body div', ['data1', 'main', 'inner', 'footer'], - limit=10) - - def test_tag_no_match(self): - assert len(self.soup.select('del')) == 0 - - def test_invalid_tag(self): - with pytest.raises(SelectorSyntaxError): - self.soup.select('tag%t') - - def test_select_dashed_tag_ids(self): - self.assert_selects('custom-dashed-tag', ['dash1', 'dash2']) - - def test_select_dashed_by_id(self): - dashed = self.soup.select('custom-dashed-tag[id=\"dash2\"]') - assert dashed[0].name == 'custom-dashed-tag' - assert dashed[0]['id'] == 'dash2' - - def test_dashed_tag_text(self): - assert self.soup.select('body > custom-dashed-tag')[0].text == 'Hello there.' - - def test_select_dashed_matches_find_all(self): - assert self.soup.select('custom-dashed-tag') == self.soup.find_all('custom-dashed-tag') - - def test_header_tags(self): - self.assert_select_multiple( - ('h1', ['header1']), - ('h2', ['header2', 'header3']), - ) - - def test_class_one(self): - for selector in ('.onep', 'p.onep', 'html p.onep'): - els = self.soup.select(selector) - assert len(els) == 1 - assert els[0].name == 'p' - assert els[0]['class'] == ['onep'] - - def test_class_mismatched_tag(self): - els = self.soup.select('div.onep') - assert len(els) == 0 - - def test_one_id(self): - for selector in ('div#inner', '#inner', 'div div#inner'): - self.assert_selects(selector, ['inner']) - - def test_bad_id(self): - els = self.soup.select('#doesnotexist') - assert len(els) == 0 - - def test_items_in_id(self): - els = self.soup.select('div#inner p') - assert len(els) == 3 - for el in els: - assert el.name == 'p' - assert els[1]['class'] == ['onep'] - assert not els[0].has_attr('class') - - def test_a_bunch_of_emptys(self): - for selector in ('div#main del', 'div#main div.oops', 'div div#main'): - assert len(self.soup.select(selector)) == 0 - - def test_multi_class_support(self): - for selector in ('.class1', 'p.class1', '.class2', 'p.class2', - '.class3', 'p.class3', 'html p.class2', 'div#inner .class2'): - self.assert_selects(selector, ['pmulti']) - - def test_multi_class_selection(self): - for selector in ('.class1.class3', '.class3.class2', - '.class1.class2.class3'): - self.assert_selects(selector, ['pmulti']) - - def test_child_selector(self): - self.assert_selects('.s1 > a', ['s1a1', 's1a2']) - self.assert_selects('.s1 > a span', ['s1a2s1']) - - def test_child_selector_id(self): - self.assert_selects('.s1 > a#s1a2 span', ['s1a2s1']) - - def test_attribute_equals(self): - self.assert_select_multiple( - ('p[class="onep"]', ['p1']), - ('p[id="p1"]', ['p1']), - ('[class="onep"]', ['p1']), - ('[id="p1"]', ['p1']), - ('link[rel="stylesheet"]', ['l1']), - ('link[type="text/css"]', ['l1']), - ('link[href="blah.css"]', ['l1']), - ('link[href="no-blah.css"]', []), - ('[rel="stylesheet"]', ['l1']), - ('[type="text/css"]', ['l1']), - ('[href="blah.css"]', ['l1']), - ('[href="no-blah.css"]', []), - ('p[href="no-blah.css"]', []), - ('[href="no-blah.css"]', []), - ) - - def test_attribute_tilde(self): - self.assert_select_multiple( - ('p[class~="class1"]', ['pmulti']), - ('p[class~="class2"]', ['pmulti']), - ('p[class~="class3"]', ['pmulti']), - ('[class~="class1"]', ['pmulti']), - ('[class~="class2"]', ['pmulti']), - ('[class~="class3"]', ['pmulti']), - ('a[rel~="friend"]', ['bob']), - ('a[rel~="met"]', ['bob']), - ('[rel~="friend"]', ['bob']), - ('[rel~="met"]', ['bob']), - ) - - def test_attribute_startswith(self): - self.assert_select_multiple( - ('[rel^="style"]', ['l1']), - ('link[rel^="style"]', ['l1']), - ('notlink[rel^="notstyle"]', []), - ('[rel^="notstyle"]', []), - ('link[rel^="notstyle"]', []), - ('link[href^="bla"]', ['l1']), - ('a[href^="http://"]', ['bob', 'me']), - ('[href^="http://"]', ['bob', 'me']), - ('[id^="p"]', ['pmulti', 'p1']), - ('[id^="m"]', ['me', 'main']), - ('div[id^="m"]', ['main']), - ('a[id^="m"]', ['me']), - ('div[data-tag^="dashed"]', ['data1']) - ) - - def test_attribute_endswith(self): - self.assert_select_multiple( - ('[href$=".css"]', ['l1']), - ('link[href$=".css"]', ['l1']), - ('link[id$="1"]', ['l1']), - ('[id$="1"]', ['data1', 'l1', 'p1', 'header1', 's1a1', 's2a1', 's1a2s1', 'dash1']), - ('div[id$="1"]', ['data1']), - ('[id$="noending"]', []), - ) - - def test_attribute_contains(self): - self.assert_select_multiple( - # From test_attribute_startswith - ('[rel*="style"]', ['l1']), - ('link[rel*="style"]', ['l1']), - ('notlink[rel*="notstyle"]', []), - ('[rel*="notstyle"]', []), - ('link[rel*="notstyle"]', []), - ('link[href*="bla"]', ['l1']), - ('[href*="http://"]', ['bob', 'me']), - ('[id*="p"]', ['pmulti', 'p1']), - ('div[id*="m"]', ['main']), - ('a[id*="m"]', ['me']), - # From test_attribute_endswith - ('[href*=".css"]', ['l1']), - ('link[href*=".css"]', ['l1']), - ('link[id*="1"]', ['l1']), - ('[id*="1"]', ['data1', 'l1', 'p1', 'header1', 's1a1', 's1a2', 's2a1', 's1a2s1', 'dash1']), - ('div[id*="1"]', ['data1']), - ('[id*="noending"]', []), - # New for this test - ('[href*="."]', ['bob', 'me', 'l1']), - ('a[href*="."]', ['bob', 'me']), - ('link[href*="."]', ['l1']), - ('div[id*="n"]', ['main', 'inner']), - ('div[id*="nn"]', ['inner']), - ('div[data-tag*="edval"]', ['data1']) - ) - - def test_attribute_exact_or_hypen(self): - self.assert_select_multiple( - ('p[lang|="en"]', ['lang-en', 'lang-en-gb', 'lang-en-us']), - ('[lang|="en"]', ['lang-en', 'lang-en-gb', 'lang-en-us']), - ('p[lang|="fr"]', ['lang-fr']), - ('p[lang|="gb"]', []), - ) - - def test_attribute_exists(self): - self.assert_select_multiple( - ('[rel]', ['l1', 'bob', 'me']), - ('link[rel]', ['l1']), - ('a[rel]', ['bob', 'me']), - ('[lang]', ['lang-en', 'lang-en-gb', 'lang-en-us', 'lang-fr']), - ('p[class]', ['p1', 'pmulti']), - ('[blah]', []), - ('p[blah]', []), - ('div[data-tag]', ['data1']) - ) - - def test_quoted_space_in_selector_name(self): - html = """
    nope
    -
    yes
    - """ - soup = BeautifulSoup(html, 'html.parser') - [chosen] = soup.select('div[style="display: right"]') - assert "yes" == chosen.string - - def test_unsupported_pseudoclass(self): - with pytest.raises(NotImplementedError): - self.soup.select("a:no-such-pseudoclass") - - with pytest.raises(SelectorSyntaxError): - self.soup.select("a:nth-of-type(a)") - - def test_nth_of_type(self): - # Try to select first paragraph - els = self.soup.select('div#inner p:nth-of-type(1)') - assert len(els) == 1 - assert els[0].string == 'Some text' - - # Try to select third paragraph - els = self.soup.select('div#inner p:nth-of-type(3)') - assert len(els) == 1 - assert els[0].string == 'Another' - - # Try to select (non-existent!) fourth paragraph - els = self.soup.select('div#inner p:nth-of-type(4)') - assert len(els) == 0 - - # Zero will select no tags. - els = self.soup.select('div p:nth-of-type(0)') - assert len(els) == 0 - - def test_nth_of_type_direct_descendant(self): - els = self.soup.select('div#inner > p:nth-of-type(1)') - assert len(els) == 1 - assert els[0].string == 'Some text' - - def test_id_child_selector_nth_of_type(self): - self.assert_selects('#inner > p:nth-of-type(2)', ['p1']) - - def test_select_on_element(self): - # Other tests operate on the tree; this operates on an element - # within the tree. - inner = self.soup.find("div", id="main") - selected = inner.select("div") - # The
    tag was selected. The