Plex Freebox

Add a "play on freebox server" button on plex interface.

  1. // ==UserScript==
  2. // @name Plex Freebox
  3. // @namespace Kalmac
  4. // @description Add a "play on freebox server" button on plex interface.
  5. // @include http://plex.tv/web/app*
  6. // @include https://plex.tv/web/app*
  7. // @version 1.01
  8. // @grant GM_xmlhttpRequest
  9. // @require https://cdn.jsdelivr.net/jquery.cookie/1.4.1/jquery.cookie.min.js
  10. // @require https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/hmac-sha1.js
  11. // @license GPL version 3 or any later version; http://www.gnu.org/copyleft/gpl.html
  12. // ==/UserScript==
  13. (function () {
  14. var HOST = 'http://mafreebox.freebox.fr';
  15. var APP_ID = 'fr.freebox.kalmac';
  16. var APP_NAME = 'Plex 4 Freebox';
  17. var APP_VERSION = '0.1';
  18. var DEVICE_NAME = 'Plex remote';
  19. var COOKIE_TRACK_ID = 'freebox_plex_track_id';
  20. var COOKIE_TOKEN = 'freebox_plex_token';
  21. var COOKIE_AIRPLAY_PASS = 'freebox_plex_airplay_pass';
  22. var STATE_UNINITIALIZED = 0;
  23. var STATE_INITIALIZED = 1;
  24. var STATE_UNAUTHORIZED = 2;
  25. var STATE_PENDING = 3;
  26. var STATE_AUTHORIZED = 4;
  27. var STATE_ERROR = 10;
  28. var track_id, token, state = STATE_UNINITIALIZED;
  29. function callFreeboxApi(path, callback, params, method, headers) {
  30. if (typeof(params) != 'object') {
  31. params = {};
  32. }
  33. if (method !== 'POST') {
  34. method = 'GET';
  35. }
  36. if (typeof(headers) != 'object') {
  37. headers = {};
  38. }
  39. GM_xmlhttpRequest({
  40. url: HOST + path,
  41. data: JSON.stringify(params),
  42. method: method,
  43. headers: headers,
  44. onload:function(response) {
  45. callback(JSON.parse(response.responseText));
  46. },
  47. onerror: function(response) {
  48. state = STATE_ERROR;
  49. core();
  50. }
  51. });
  52. }
  53. function init() {
  54. track_id = $.cookie(COOKIE_TRACK_ID);
  55. token = $.cookie(COOKIE_TOKEN);
  56. if (typeof(track_id) != 'undefined' && typeof(token) != 'undefined') {
  57. state = STATE_PENDING;
  58. } else {
  59. state = STATE_UNAUTHORIZED;
  60. }
  61. }
  62. function authorize() {
  63. callFreeboxApi('/api/v3/login/authorize/', function(response) {
  64. if (response.success) {
  65. $.cookie(COOKIE_TRACK_ID, response.result.track_id, { expires: 1000, path: '/' });
  66. $.cookie(COOKIE_TOKEN, response.result.app_token, { expires: 1000, path: '/' });
  67. state = STATE_INITIALIZED;
  68. core();
  69. }
  70. }, {
  71. 'app_id': APP_ID,
  72. 'app_name': APP_NAME,
  73. 'app_version': APP_VERSION,
  74. 'device_name': DEVICE_NAME }, 'POST');
  75. var airplay_password = prompt("Veuillez autoriser l\'appli sur votre Freebox (fleche de droite) et saisir le mot de passe AirPlay...", "1111");
  76. $.cookie(COOKIE_AIRPLAY_PASS, airplay_password, { expires: 1000, path: '/' });
  77. updateAdminButton('Veuillez autoriser l\'appli sur votre Freebox (fleche de droite).', 'refresh', false);
  78. }
  79. function pending() {
  80. callFreeboxApi('/api/v3/login/authorize/' + track_id, function(response) {
  81. if (response.success) {
  82. switch(response.result.status) {
  83. case 'pending':
  84. break;
  85. case 'granted':
  86. state = STATE_AUTHORIZED;
  87. break;
  88. default:
  89. console.log('Freebox returned ' + response.result.status);
  90. $.removeCookie(COOKIE_TRACK_ID, { path: '/' });
  91. $.removeCookie(COOKIE_TOKEN, { path: '/' });
  92. state = STATE_INITIALIZED;
  93. break;
  94. }
  95. }
  96. });
  97. }
  98. function insertAdminButton() {
  99. var result = false;
  100. if (!$('li#plex-freebox-admin').length) {
  101. var elt = $('li#primary-player-dropdown');
  102. if (elt.length == 1) {
  103. elt.after('<li id="plex-freebox-admin"><a class="settings-btn" href="#" title="Plex 4 Freebox" data-toggle="tooltip" data-original-title="Plex 4 Freebox"><i class="glyphicon refresh"></i></a></li>');
  104. state = STATE_INITIALIZED;
  105. }
  106. }
  107. return result;
  108. }
  109. function updateAdminButton(text, glyph, callback) {
  110. var button = $('li#plex-freebox-admin > a');
  111. var icon = $('li#plex-freebox-admin > a > i');
  112. button.unbind('click');
  113. icon.attr('class', 'glyphicon');
  114. button.attr('data-original-title', text);
  115. button.attr('title', text);
  116. icon.addClass(glyph);
  117. if (callback) {
  118. button.click(callback);
  119. }
  120. }
  121. function decoratePlayer() {
  122. if ($('button#plex-freebox-play').length == 0) {
  123. $('div.video-controls-right span#player-quality-dropdown').before('<button id="plex-freebox-play" class="btn-link recommend-btn pull-left" title="Play 4 Freebox"><i class="glyphicon play">F</i></button>');
  124. $('button#plex-freebox-play').click(function(event) {
  125. play(event);
  126. event.preventDefault();
  127. return false;
  128. });
  129. }
  130. }
  131. function play(event) {
  132. login(function(challenge) {
  133. session(challenge, function(session_token) {
  134. var pass = $.cookie(COOKIE_AIRPLAY_PASS);
  135. var video = $('video#html-video').attr('src');
  136. var params = {
  137. 'action':'start',
  138. 'media_type': 'video',
  139. 'media': video,
  140. 'password': typeof(pass) == 'undefined'?'1111':pass
  141. };
  142. callFreeboxApi('/api/v3/airmedia/receivers/Freebox%20Player/', function(response) {
  143. if (!response.success) {
  144. alert(response.msg);
  145. }
  146. }, params, 'POST', {'X-Fbx-App-Auth': session_token});
  147. });
  148. });
  149. event.preventDefault();
  150. return false;
  151. }
  152. function login(callback) {
  153. callFreeboxApi('/api/v3/login/', function(response) {
  154. callback(response.result.challenge);
  155. });
  156. }
  157. function session(challenge, callback) {
  158. var params = {
  159. 'app_id':APP_ID,
  160. 'password': CryptoJS.HmacSHA1(challenge, token).toString(CryptoJS.enc.Hex)
  161. };
  162. callFreeboxApi('/api/v3/login/session/', function(response) {
  163. callback(response.result.session_token);
  164. }, params, 'POST');
  165. }
  166. function core() {
  167. var retry = false;
  168. switch(state) {
  169. case STATE_UNINITIALIZED:
  170. insertAdminButton();
  171. retry = 1000;
  172. break;
  173. case STATE_INITIALIZED:
  174. updateAdminButton('Plex 4 Freebox en attente ...', 'refresh', false);
  175. init();
  176. retry = 2000;
  177. break;
  178. case STATE_UNAUTHORIZED:
  179. updateAdminButton('Cliquez ici pour authoriser Plex 4 Freebox', 'router', authorize);
  180. // no retry core until button is clicked.
  181. break;
  182. case STATE_PENDING:
  183. updateAdminButton('Plex 4 Freebox en attente ...', 'refresh', false);
  184. pending();
  185. retry = 2000;
  186. break;
  187. case STATE_AUTHORIZED:
  188. updateAdminButton('Plex 4 Freebox', 'ok', false);
  189. decoratePlayer();
  190. retry = 2000;
  191. break;
  192. case STATE_ERROR:
  193. updateAdminButton('Plex 4 Freebox : ERREUR FATALE!', 'remove', false);
  194. break;
  195. }
  196. console.log('core with state = ' + state);
  197. if (retry) {
  198. setTimeout(core, retry);
  199. }
  200. }
  201. $( window ).load(function() {
  202. core();
  203. });
  204. }) ();