Greasy Fork is available in English.

hwm_google_api_wrapper

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

Tento skript by neměl být instalován přímo. Jedná se o knihovnu, kterou by měly jiné skripty využívat pomocí meta příkazu // @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();