hwm_google_api_wrapper

Обёртка gapi с интерфейсом для HWM

Dieses Skript sollte nicht direkt installiert werden. Es handelt sich hier um eine Bibliothek für andere Skripte, welche über folgenden Befehl in den Metadaten eines Skriptes eingebunden wird // @require https://update.greatest.deepsurf.us/scripts/465885/1188144/hwm_google_api_wrapper.js

  1. // ==UserScript==
  2. // @name hwm_google_api_wrapper
  3. // @namespace https://github.com/bonArt0/hwm_scripts
  4. // @version 0.1.1
  5. // @description Обёртка gapi с интерфейсом для HWM
  6. // @author bonArt
  7. // @license GPL-3.0-only
  8. // @icon https://cdn-icons-png.flaticon.com/512/2991/2991148.png
  9. // @match https://*.heroeswm.ru/*
  10. // @match https://178.248.235.15/*
  11. // @match https://www.lordswm.com/*
  12. // @match https://my.lordswm.com/*
  13. // @run-at document-body
  14. // @supportURL https://www.heroeswm.ru/sms-create.php?mailto_id=117282
  15. // ==/UserScript==
  16.  
  17. /**
  18. * @type {GapiWrapper} _GapiWrapperInstance
  19. * @private
  20. */
  21. let _GapiWrapperInstance;
  22.  
  23. class GAWCredentialsNotSetError extends Error {
  24. constructor() {
  25. super();
  26.  
  27. this.message = 'GoogleAPI credentials not set';
  28. }
  29. }
  30.  
  31. class GAWAlreadyInitializedError extends Error {
  32. constructor() {
  33. super();
  34.  
  35. this.message = 'GoogleAPI Wrapper already initialized';
  36. }
  37. }
  38.  
  39. /**
  40. * @see https://developers.google.com/sheets/api/quickstart/js?hl=ru
  41. */
  42. class GapiWrapper
  43. {
  44. /** Discovery doc URL for APIs used by the quickstart */
  45. static DISCOVERY_DOC = 'https://sheets.googleapis.com/$discovery/rest?version=v4';
  46.  
  47. /** Authorization scopes required by the API; multiple scopes can be included, separated by spaces. */
  48. static SCOPE = 'https://www.googleapis.com/auth/spreadsheets';
  49.  
  50. /**
  51. * @type {boolean}
  52. * @private
  53. */
  54. _initialized = false;
  55.  
  56. /** @var {object} tokenClient */
  57. tokenClient;
  58.  
  59. /** @var {object} gapiClient */
  60. gapiClient;
  61.  
  62. /**
  63. * @return {GapiWrapper}
  64. */
  65. static init() {
  66. if (!_GapiWrapperInstance || !_GapiWrapperInstance?._initialized) {
  67. console.info('GoogleAPI Wrapper initialization started');
  68.  
  69. const gapiApiKey = window.localStorage.getItem(GapiControls.GAPI_API_KEY_CONFIG_NAME);
  70. const gapiClientId = window.localStorage.getItem(GapiControls.GAPI_CLIENT_ID_CONFIG_NAME);
  71. if (!gapiApiKey || !gapiClientId) {
  72. throw new GAWCredentialsNotSetError();
  73. }
  74.  
  75. GapiControls.init();
  76.  
  77. try {
  78. _GapiWrapperInstance = new GapiWrapper(gapiApiKey, gapiClientId);
  79. } catch (e) {
  80. if (e instanceof GAWAlreadyInitializedError) {
  81. console.info(e.message);
  82. return _GapiWrapperInstance;
  83. }
  84.  
  85. console.error('Something happen while GoogleAPI Wrapper initializing', e.context);
  86. throw e;
  87. }
  88.  
  89. console.info('GoogleAPI Wrapper initialized');
  90. }
  91.  
  92. return _GapiWrapperInstance;
  93. }
  94.  
  95. constructor(apiKey, clientId, scope) {
  96. this._loadScript(
  97. 'https://apis.google.com/js/api.js',
  98. () => this._gapiLoaded(apiKey),
  99. );
  100. this._loadScript(
  101. 'https://accounts.google.com/gsi/client',
  102. () => this.tokenClient = this._gisLoaded(clientId, scope),
  103. );
  104. }
  105.  
  106. _loadScript(src, onLoad) {
  107. let script = document.createElement('script');
  108. script.src = src;
  109. script.defer = true;
  110. script.async = true;
  111. script.addEventListener('load', onLoad);
  112. document.head.appendChild(script);
  113. }
  114.  
  115. /**
  116. * @private
  117. *
  118. * @param {string} apiKey
  119. * @param {string[]} discoveryDocs
  120. *
  121. * Callback after api.js is loaded.
  122. */
  123. _gapiLoaded(apiKey, discoveryDocs = [GapiWrapper.DISCOVERY_DOC]) {
  124. gapi.load('client', () => {
  125. const result = gapi.client.init({
  126. apiKey: apiKey,
  127. discoveryDocs: discoveryDocs,
  128. });
  129.  
  130. if (!result || true) { // TODO: resolve 'Pe' value and check for apiKey error
  131. this.gapiClient = gapi.client;
  132. return;
  133. }
  134.  
  135. throw new Error(result.Pe.error.message);
  136. });
  137. }
  138.  
  139. /**
  140. @private
  141.  
  142. @param {string} clientId
  143. @param {string} scope
  144. @return {object}
  145.  
  146. * Callback after Google Identity Services are loaded.
  147. */
  148. _gisLoaded(clientId, scope = GapiWrapper.SCOPE) {
  149. return google.accounts.oauth2.initTokenClient({
  150. client_id: clientId,
  151. scope: scope,
  152. callback: (resp) => console.debug(resp), // TODO: defined later
  153. });
  154. }
  155. }
  156.  
  157. class GapiControls
  158. {
  159. static GAPI_API_KEY_CONFIG_NAME = 'gapi_api_key';
  160. static GAPI_CLIENT_ID_CONFIG_NAME = 'gapi_client_id';
  161. static MODAL_CLASSNAME = 'gapi_controls_modal';
  162. static MODAL_OPEN_BUTTON_ICON = 'https://cdn-icons-png.flaticon.com/512/2991/2991148.png';
  163. static MODAL_OPEN_BUTTON_CLASSNAME = 'gapi_controls_button';
  164.  
  165. /**
  166. * @type {boolean}
  167. * @private
  168. */
  169. static _initialized = false;
  170.  
  171. static init() {
  172. if (!GapiControls._initialized) {
  173. const controlsModal = GapiControls.buildControlsModal();
  174. const openModalButton = GapiControls.buildControlsModalSwitch(controlsModal);
  175. document.body.append(controlsModal);
  176. document.body.append(openModalButton);
  177. }
  178. }
  179.  
  180. /**
  181. * @return {HTMLDivElement}
  182. */
  183. static buildControlsModal() {
  184. const modal = document.createElement('div');
  185. const clientIdBox = GapiControls.buildTextboxLabel(
  186. 'clientId',
  187. window.localStorage.getItem(GapiControls.GAPI_CLIENT_ID_CONFIG_NAME),
  188. 'Client ID',
  189. );
  190. const apiKeyBox = GapiControls.buildTextboxLabel(
  191. 'apiKey',
  192. window.localStorage.getItem(GapiControls.GAPI_API_KEY_CONFIG_NAME),
  193. 'API Key',
  194. );
  195. const closeButton = GapiControls.buildCloseButton(
  196. function () {
  197. window.localStorage.setItem(GapiControls.GAPI_CLIENT_ID_CONFIG_NAME, clientIdBox.lastChild.value);
  198. window.localStorage.setItem(GapiControls.GAPI_API_KEY_CONFIG_NAME, apiKeyBox.lastChild.value);
  199. // TODO: display to classname
  200. modal.style.display = 'none';
  201. }
  202. );
  203.  
  204. modal.className = `${GapiControls.MODAL_CLASSNAME} wbwhite`;
  205. // TODO: style to css
  206. // TODO: display to classname
  207. modal.style.display = 'none';
  208. modal.style.position = 'absolute';
  209. modal.style.top = '114px';
  210. modal.style.right = '50px';
  211. modal.style.width = '200px';
  212. modal.style.height = '105px';
  213. modal.style.zIndex = '9';
  214. modal.append(clientIdBox);
  215. modal.append(apiKeyBox);
  216. modal.append(closeButton);
  217.  
  218. return modal;
  219. }
  220.  
  221. /**
  222. * @param {string} name
  223. * @param {string} value
  224. * @param {string} innerHTML
  225. * @returns {HTMLLabelElement}
  226. */
  227. static buildTextboxLabel(name, value, innerHTML) {
  228. const textbox = document.createElement('input');
  229. textbox.type = 'password';
  230. textbox.autocomplete = 'off';
  231. textbox.value = value;
  232. textbox.name = name;
  233. textbox.style.display = 'block';
  234.  
  235. const label = document.createElement('label');
  236. label.style.display = 'block';
  237. label.style.margin = '10px';
  238. label.append(innerHTML);
  239. label.append(textbox);
  240.  
  241. return label;
  242. }
  243.  
  244. /**
  245. * @param {function} onClick
  246. * @return {HTMLButtonElement}
  247. */
  248. static buildCloseButton(onClick) {
  249. const button = document.createElement('button');
  250. button.textContent = '☓';
  251. button.style.position = 'absolute';
  252. button.style.top = '5px';
  253. button.style.right = '5px';
  254. button.addEventListener('click', onClick);
  255.  
  256. return button;
  257. }
  258.  
  259. /**
  260. * @param {HTMLDivElement} controlsModal
  261. * @return {HTMLImageElement}
  262. */
  263. static buildControlsModalSwitch(controlsModal) {
  264. const openModalButton = document.createElement('img');
  265. openModalButton.className = GapiControls.MODAL_OPEN_BUTTON_CLASSNAME;
  266. openModalButton.src = GapiControls.MODAL_OPEN_BUTTON_ICON;
  267. // TODO: style to css
  268. // TODO: display to classname
  269. openModalButton.style.display = 'block';
  270. openModalButton.style.position = 'absolute';
  271. openModalButton.style.top = '114px';
  272. openModalButton.style.right = '125px';
  273. openModalButton.style.width = '25px';
  274. openModalButton.style.height = '25px';
  275. openModalButton.style.cursor = 'pointer';
  276. // TODO: display to classname
  277. openModalButton.addEventListener('click', () => controlsModal.style.display = 'inline-block');
  278.  
  279. return openModalButton;
  280. }
  281. }
  282.  
  283. GapiWrapper.init();