EME Logger

Inject EME interface and log its function calls.

当前为 2022-10-02 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name EME Logger
  3. // @namespace http://greasyfork.org/
  4. // @version 1.0
  5. // @description Inject EME interface and log its function calls.
  6. // @author cramer
  7. // @match *://*/*
  8. // @run-at document-start
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (async () => {
  13. const indent = (s,n=4) => s.split('\n').map(l=>Array(n).fill(' ').join('')+l).join('\n');
  14.  
  15. const b64 = {
  16. decode: s => Uint8Array.from(atob(s), c => c.charCodeAt(0)),
  17. encode: b => btoa(String.fromCharCode(...new Uint8Array(b)))
  18. };
  19.  
  20. const fnproxy = (object, func) => new Proxy(object, { apply: func });
  21.  
  22. const proxy = (object, key, func) => Object.hasOwnProperty.call(object, key) && Object.defineProperty(object, key, {
  23. value: fnproxy(object[key], func)
  24. });
  25.  
  26. function messageHandler(event) {
  27. const keySession = event.target;
  28. const {sessionId} = keySession;
  29. const {message, messageType} = event;
  30. const listeners = keySession.getEventListeners('message').filter(l => l !== messageHandler);
  31. console.groupCollapsed(
  32. `[EME] MediaKeySession::message\n` +
  33. ` Session ID: ${sessionId || '(not available)'}\n` +
  34. ` Message Type: ${messageType}\n` +
  35. ` Message: ${b64.encode(message)}` +
  36. '\n Listeners:', listeners
  37. );
  38. console.trace();
  39. console.groupEnd();
  40. }
  41.  
  42. function keystatuseschangeHandler(event) {
  43. const keySession = event.target;
  44. const {sessionId} = keySession;
  45. const listeners = keySession.getEventListeners('keystatuseschange').filter(l => l !== keystatuseschangeHandler);
  46. console.groupCollapsed(
  47. `[EME] MediaKeySession::keystatuseschange\n` +
  48. ` Session ID: ${sessionId || '(not available)'}\n` +
  49. Array.from(keySession.keyStatuses).map(([keyId, status]) =>
  50. ` [${status.toUpperCase()}] ${b64.encode(keyId)}`
  51. ).join('\n') +
  52. '\n Listeners:', listeners
  53. );
  54. console.trace();
  55. console.groupEnd();
  56. }
  57.  
  58. function getEventListeners(type) {
  59. if (this == null) return [];
  60. const store = this[Symbol.for(getEventListeners)];
  61. if (store == null || store[type] == null) return [];
  62. return store[type];
  63. }
  64.  
  65. EventTarget.prototype.getEventListeners = getEventListeners;
  66.  
  67. typeof Navigator !== 'undefined' && proxy(Navigator.prototype, 'requestMediaKeySystemAccess', async (_target, _this, _args) => {
  68. const [keySystem, supportedConfigurations] = _args;
  69. console.groupCollapsed(
  70. `[EME] Navigator::requestMediaKeySystemAccess\n` +
  71. ` Key System: ${keySystem}\n` +
  72. ` Supported Configurations:\n` +
  73. indent(JSON.stringify(supportedConfigurations, null, ' '))
  74. );
  75. console.trace();
  76. console.groupEnd();
  77. return _target.apply(_this, _args);
  78. });
  79.  
  80. typeof MediaKeySystemAccess !== 'undefined' && proxy(MediaKeySystemAccess.prototype, 'createMediaKeys', async (_target, _this, _args) => {
  81. console.groupCollapsed(
  82. `[EME] MediaKeySystemAccess::createMediaKeys\n` +
  83. ` Key System: ${_this.keySystem}\n` +
  84. ` Configurations:\n` +
  85. indent(JSON.stringify(_this.getConfiguration(), null, ' '))
  86. );
  87. console.trace();
  88. console.groupEnd();
  89. return _target.apply(_this, _args);
  90. });
  91.  
  92. if (typeof MediaKeys !== 'undefined') {
  93. proxy(MediaKeys.prototype, 'setServerCertificate', async (_target, _this, _args) => {
  94. const [serverCertificate] = _args;
  95. console.groupCollapsed(
  96. `[EME] MediaKeys::setServerCertificate\n` +
  97. ` Server Certificate: ${b64.encode(serverCertificate)}`
  98. );
  99. console.trace();
  100. console.groupEnd();
  101. return _target.apply(_this, _args);
  102. });
  103.  
  104. proxy(MediaKeys.prototype, 'createSession', (_target, _this, _args) => {
  105. const [sessionType] = _args;
  106. console.groupCollapsed(
  107. `[EME] MediaKeys::createSession\n` +
  108. ` Session Type: ${sessionType || 'temporary (default)'}`
  109. );
  110. console.trace();
  111. console.groupEnd();
  112. const session = _target.apply(_this, _args);
  113. session.addEventListener('message', messageHandler);
  114. session.addEventListener('keystatuseschange', keystatuseschangeHandler);
  115. return session;
  116. });
  117. }
  118.  
  119. if (typeof EventTarget !== 'undefined') {
  120. proxy(EventTarget.prototype, 'addEventListener', async (_target, _this, _args) => {
  121. if (_this != null) {
  122. const [type, listener] = _args;
  123. const storeKey = Symbol.for(getEventListeners);
  124. if (!(storeKey in _this)) _this[storeKey] = {};
  125. const store = _this[storeKey];
  126. if (!(type in store)) store[type] = [];
  127. const listeners = store[type];
  128. if (listeners.indexOf(listener) < 0) {
  129. listeners.push(listener);
  130. }
  131. }
  132. return _target.apply(_this, _args);
  133. });
  134.  
  135. proxy(EventTarget.prototype, 'removeEventListener', async (_target, _this, _args) => {
  136. if (_this != null) {
  137. const [type, listener] = _args;
  138. const storeKey = Symbol.for(getEventListeners);
  139. if (!(storeKey in _this)) return;
  140. const store = _this[storeKey];
  141. if (!(type in store)) return;
  142. const listeners = store[type];
  143. const index = listeners.indexOf(listener);
  144. if (index >= 0) {
  145. if (listeners.length === 1) {
  146. delete store[type];
  147. } else {
  148. listeners.splice(index, 1);
  149. }
  150. }
  151. }
  152. return _target.apply(_this, _args);
  153. });
  154. }
  155.  
  156. if (typeof MediaKeySession !== 'undefined') {
  157. proxy(MediaKeySession.prototype, 'generateRequest', async (_target, _this, _args) => {
  158. const [initDataType, initData] = _args;
  159. console.groupCollapsed(
  160. `[EME] MediaKeySession::generateRequest\n` +
  161. ` Session ID: ${_this.sessionId || '(not available)'}\n` +
  162. ` Init Data Type: ${initDataType}\n` +
  163. ` Init Data: ${b64.encode(initData)}`
  164. );
  165. console.trace();
  166. console.groupEnd();
  167. return _target.apply(_this, _args);
  168. });
  169.  
  170. proxy(MediaKeySession.prototype, 'load', async (_target, _this, _args) => {
  171. const [sessionId] = _args;
  172. console.groupCollapsed(
  173. `[EME] MediaKeySession::load\n` +
  174. ` Session ID: ${sessionId || '(not available)'}`
  175. );
  176. console.trace();
  177. console.groupEnd();
  178. return _target.apply(_this, _args);
  179. });
  180.  
  181. proxy(MediaKeySession.prototype, 'update', async (_target, _this, _args) => {
  182. const [response] = _args;
  183. console.groupCollapsed(
  184. `[EME] MediaKeySession::update\n` +
  185. ` Session ID: ${_this.sessionId || '(not available)'}\n` +
  186. ` Response: ${b64.encode(response)}`
  187. );
  188. console.trace();
  189. console.groupEnd();
  190. return _target.apply(_this, _args);
  191. });
  192.  
  193. proxy(MediaKeySession.prototype, 'close', async (_target, _this, _args) => {
  194. console.groupCollapsed(
  195. `[EME] MediaKeySession::close\n` +
  196. ` Session ID: ${_this.sessionId || '(not available)'}`
  197. );
  198. console.trace();
  199. console.groupEnd();
  200. return _target.apply(_this, _args);
  201. });
  202.  
  203. proxy(MediaKeySession.prototype, 'remove', async (_target, _this, _args) => {
  204. console.groupCollapsed(
  205. `[EME] MediaKeySession::remove\n` +
  206. ` Session ID: ${_this.sessionId || '(not available)'}`
  207. );
  208. console.trace();
  209. console.groupEnd();
  210. return _target.apply(_this, _args);
  211. });
  212. }
  213. })();