google-spreadsheet 3.2.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.eslintrc.js DELETED
@@ -1,61 +0,0 @@
1
- // https://eslint.org/docs/user-guide/configuring
2
- module.exports = {
3
- root: true,
4
- parserOptions: {
5
- sourceType: 'module',
6
- ecmaVersion: 2018,
7
- },
8
- env: {
9
- es6: true,
10
- node: true,
11
- },
12
- extends: [
13
- 'airbnb-base',
14
- ],
15
- plugins: [
16
- 'async-await',
17
- ],
18
- // add your custom rules here
19
- rules: {
20
- 'no-underscore-dangle': 0,
21
- 'no-plusplus': 0, // i++ OK :D
22
- 'class-methods-use-this': 0,
23
- 'radix': 0,
24
- 'prefer-destructuring': 0,
25
- 'no-param-reassign': 0, // sometimes it's just much easier
26
- 'lines-between-class-members': 0, // grouping related one-liners can be nice
27
- 'no-continue': 0,
28
- // override airbnb - breaks old version of node - https://github.com/eslint/eslint/issues/7749
29
- 'comma-dangle': ['error', {
30
- arrays: 'always-multiline',
31
- objects: 'always-multiline',
32
- imports: 'always-multiline',
33
- exports: 'always-multiline',
34
- functions: 'never', // this breaks
35
- }],
36
- 'no-multiple-empty-lines': 0, // sometimes helpful to break up sections of code
37
- },
38
- overrides: [
39
- { // extra jest related rules for tests
40
- files: 'test/*',
41
- plugins: ["jest"],
42
- extends: ["plugin:jest/recommended"],
43
- env: {
44
- "jest/globals": true,
45
- },
46
- rules: {
47
- "jest/consistent-test-it": "error",
48
- 'jest/expect-expect': 0, // sometimes the lack of an error thrown is a good test
49
- 'no-await-in-loop': 0,
50
-
51
- }
52
- },
53
- { // relaxed rules for examples
54
- files: 'examples/*',
55
- rules: {
56
- 'no-console': 0,
57
- 'no-unused-vars': 0,
58
- },
59
- },
60
- ],
61
- }
package/.nvmrc DELETED
@@ -1 +0,0 @@
1
- 11.15.0
package/Changelog.md DELETED
@@ -1,10 +0,0 @@
1
- # Changelog
2
-
3
- TODO: set this up to be automated and pulled from git commits... for now just going to leave some notes manually!
4
-
5
- ### 3.2.0 (2021-11-07)
6
-
7
- - Added `insertDimension` functionality
8
- - Added custom header row index for row-based API
9
- - Bumped dependency versions
10
- - Readme/docs cleanup
package/TODO DELETED
@@ -1,73 +0,0 @@
1
- - support more cell functionality
2
- - data validation rules - https://github.com/theoephraim/node-google-spreadsheet/issues/487
3
-
4
- - dont explode if sheet contains charts - https://github.com/theoephraim/node-google-spreadsheet/issues/444
5
- - add row.toJSON()
6
- https://github.com/theoephraim/node-google-spreadsheet/issues/247
7
- - google auth - Application Default Creds
8
- https://github.com/theoephraim/node-google-spreadsheet/issues/215
9
- https://github.com/theoephraim/node-google-spreadsheet/issues/345
10
-
11
- - escape issues for strings starting with = `'=not a formula`
12
- https://github.com/theoephraim/node-google-spreadsheet/issues/323
13
-
14
-
15
-
16
- - browser/react native, older ES support
17
- https://github.com/theoephraim/node-google-spreadsheet/issues/339
18
- https://github.com/theoephraim/node-google-spreadsheet/issues/382
19
- https://github.com/theoephraim/node-google-spreadsheet/issues/406
20
- https://github.com/theoephraim/node-google-spreadsheet/issues/404
21
-
22
-
23
- - nice tips here - https://github.com/mikro-orm/mikro-orm/issues/12
24
- - start using semantic release? or similar https://semantic-release.gitbook.io/semantic-release/
25
- - better commit messages to autogenerate changelog
26
- - commitizen - https://github.com/commitizen/cz-cli
27
- - https://gist.github.com/brianclements/841ea7bffdb01346392c
28
- - commitlint checks - https://github.com/conventional-changelog/commitlint
29
- - https://github.com/conventional-changelog/conventional-changelog
30
-
31
-
32
-
33
-
34
-
35
- BUGS
36
- - fetching single row via GRO has issues
37
- https://github.com/theoephraim/node-google-spreadsheet/issues/347
38
-
39
-
40
- MISSING FUNCTIONS
41
-
42
- - support batchGet
43
- https://github.com/theoephraim/node-google-spreadsheet/issues/326
44
-
45
- - update filters
46
- https://github.com/theoephraim/node-google-spreadsheet/issues/328
47
- - insertDimension
48
- https://github.com/theoephraim/node-google-spreadsheet/issues/366
49
- - conditional formatting
50
- https://github.com/theoephraim/node-google-spreadsheet/issues/410
51
-
52
-
53
- OTHER
54
- - row to cell interface
55
- https://github.com/theoephraim/node-google-spreadsheet/issues/343
56
- - reading from draftValue
57
- https://github.com/theoephraim/node-google-spreadsheet/issues/350
58
- https://github.com/theoephraim/node-google-spreadsheet/issues/402
59
- - heroku config docs
60
- https://github.com/theoephraim/node-google-spreadsheet/issues/354
61
- - document metadata from drive api
62
- https://github.com/theoephraim/node-google-spreadsheet/issues/364
63
- - date formatting, escaping issues
64
- https://github.com/theoephraim/node-google-spreadsheet/issues/367
65
- https://github.com/theoephraim/node-google-spreadsheet/issues/363
66
- - release notes
67
- https://github.com/theoephraim/node-google-spreadsheet/issues/384
68
- - delete rows from google forms
69
- https://github.com/theoephraim/node-google-spreadsheet/issues/393
70
- - better handling of merged cells
71
- https://github.com/theoephraim/node-google-spreadsheet/issues/400
72
- - specify header row
73
- https://github.com/theoephraim/node-google-spreadsheet/issues/407
package/UNLICENSE DELETED
@@ -1,24 +0,0 @@
1
- This is free and unencumbered software released into the public domain.
2
-
3
- Anyone is free to copy, modify, publish, use, compile, sell, or
4
- distribute this software, either in source code form or as a compiled
5
- binary, for any purpose, commercial or non-commercial, and by any
6
- means.
7
-
8
- In jurisdictions that recognize copyright laws, the author or authors
9
- of this software dedicate any and all copyright interest in the
10
- software to the public domain. We make this dedication for the benefit
11
- of the public at large and to the detriment of our heirs and
12
- successors. We intend this dedication to be an overt act of
13
- relinquishment in perpetuity of all present and future rights to this
14
- software under copyright law.
15
-
16
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
- IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20
- OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21
- ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
- OTHER DEALINGS IN THE SOFTWARE.
23
-
24
- For more information, please refer to <http://unlicense.org/>
package/index.js DELETED
@@ -1,13 +0,0 @@
1
- const GoogleSpreadsheet = require('./lib/GoogleSpreadsheet');
2
- const GoogleSpreadsheetWorksheet = require('./lib/GoogleSpreadsheetWorksheet');
3
- const GoogleSpreadsheetRow = require('./lib/GoogleSpreadsheetRow');
4
-
5
- const { GoogleSpreadsheetFormulaError } = require('./lib/errors');
6
-
7
- module.exports = {
8
- GoogleSpreadsheet,
9
- GoogleSpreadsheetWorksheet,
10
- GoogleSpreadsheetRow,
11
-
12
- GoogleSpreadsheetFormulaError,
13
- };
@@ -1,387 +0,0 @@
1
- const _ = require('lodash');
2
- const { JWT } = require('google-auth-library');
3
- const Axios = require('axios');
4
-
5
- const GoogleSpreadsheetWorksheet = require('./GoogleSpreadsheetWorksheet');
6
- const { getFieldMask } = require('./utils');
7
-
8
- const GOOGLE_AUTH_SCOPES = [
9
- 'https://www.googleapis.com/auth/spreadsheets',
10
-
11
- // the list from the sheets v4 auth for spreadsheets.get
12
- // 'https://www.googleapis.com/auth/drive',
13
- // 'https://www.googleapis.com/auth/drive.readonly',
14
- // 'https://www.googleapis.com/auth/drive.file',
15
- // 'https://www.googleapis.com/auth/spreadsheets',
16
- // 'https://www.googleapis.com/auth/spreadsheets.readonly',
17
- ];
18
-
19
- const AUTH_MODES = {
20
- JWT: 'JWT',
21
- API_KEY: 'API_KEY',
22
- RAW_ACCESS_TOKEN: 'RAW_ACCESS_TOKEN',
23
- OAUTH: 'OAUTH',
24
- };
25
-
26
- class GoogleSpreadsheet {
27
- constructor(sheetId) {
28
- this.spreadsheetId = sheetId;
29
- this.authMode = null;
30
- this._rawSheets = {};
31
- this._rawProperties = null;
32
-
33
- // create an axios instance with sheet root URL and interceptors to handle auth
34
- this.axios = Axios.create({
35
- baseURL: `https://sheets.googleapis.com/v4/spreadsheets/${sheetId || ''}`,
36
- // send arrays in params with duplicate keys - ie `?thing=1&thing=2` vs `?thing[]=1...`
37
- // solution taken from https://github.com/axios/axios/issues/604
38
- paramsSerializer(params) {
39
- let options = '';
40
- _.keys(params).forEach((key) => {
41
- const isParamTypeObject = typeof params[key] === 'object';
42
- const isParamTypeArray = isParamTypeObject && (params[key].length >= 0);
43
- if (!isParamTypeObject) options += `${key}=${encodeURIComponent(params[key])}&`;
44
- if (isParamTypeObject && isParamTypeArray) {
45
- _.each(params[key], (val) => {
46
- options += `${key}=${encodeURIComponent(val)}&`;
47
- });
48
- }
49
- });
50
- return options ? options.slice(0, -1) : options;
51
- },
52
- });
53
- // have to use bind here or the functions dont have access to `this` :(
54
- this.axios.interceptors.request.use(this._setAxiosRequestAuth.bind(this));
55
- this.axios.interceptors.response.use(
56
- this._handleAxiosResponse.bind(this),
57
- this._handleAxiosErrors.bind(this)
58
- );
59
-
60
- return this;
61
- }
62
-
63
- // CREATE NEW DOC ////////////////////////////////////////////////////////////////////////////////
64
- async createNewSpreadsheetDocument(properties) {
65
- // see updateProperties for more info about available properties
66
-
67
- if (this.spreadsheetId) {
68
- throw new Error('Only call `createNewSpreadsheetDocument()` on a GoogleSpreadsheet object that has no spreadsheetId set');
69
- }
70
- const response = await this.axios.post(this.url, {
71
- properties,
72
- });
73
- this.spreadsheetId = response.data.spreadsheetId;
74
- this.axios.defaults.baseURL += this.spreadsheetId;
75
-
76
- this._rawProperties = response.data.properties;
77
- _.each(response.data.sheets, (s) => this._updateOrCreateSheet(s));
78
- }
79
-
80
- // AUTH RELATED FUNCTIONS ////////////////////////////////////////////////////////////////////////
81
- async useApiKey(key) {
82
- this.authMode = AUTH_MODES.API_KEY;
83
- this.apiKey = key;
84
- }
85
-
86
- // token must be created and managed (refreshed) elsewhere
87
- async useRawAccessToken(token) {
88
- this.authMode = AUTH_MODES.RAW_ACCESS_TOKEN;
89
- this.accessToken = token;
90
- }
91
-
92
- async useOAuth2Client(oAuth2Client) {
93
- this.authMode = AUTH_MODES.OAUTH;
94
- this.oAuth2Client = oAuth2Client;
95
- }
96
-
97
- // creds should be an object obtained by loading the json file google gives you
98
- // impersonateAs is an email of any user in the G Suite domain
99
- // (only works if service account has domain-wide delegation enabled)
100
- async useServiceAccountAuth(creds, impersonateAs = null) {
101
- this.jwtClient = new JWT({
102
- email: creds.client_email,
103
- key: creds.private_key,
104
- scopes: GOOGLE_AUTH_SCOPES,
105
- subject: impersonateAs,
106
- });
107
- await this.renewJwtAuth();
108
- }
109
-
110
- async renewJwtAuth() {
111
- this.authMode = AUTH_MODES.JWT;
112
- await this.jwtClient.authorize();
113
- /*
114
- returned token looks like
115
- {
116
- access_token: 'secret-token...',
117
- token_type: 'Bearer',
118
- expiry_date: 1576005020000,
119
- id_token: undefined,
120
- refresh_token: 'jwt-placeholder'
121
- }
122
- */
123
- }
124
-
125
- // TODO: provide mechanism to share single JWT auth between docs?
126
-
127
- // INTERNAL UTILITY FUNCTIONS ////////////////////////////////////////////////////////////////////
128
- async _setAxiosRequestAuth(config) {
129
- // TODO: check auth mode, if valid, renew if expired, etc
130
- if (this.authMode === AUTH_MODES.JWT) {
131
- if (!this.jwtClient) throw new Error('JWT auth is not set up properly');
132
- // this seems to do the right thing and only renew the token if expired
133
- await this.jwtClient.authorize();
134
- config.headers.Authorization = `Bearer ${this.jwtClient.credentials.access_token}`;
135
- } else if (this.authMode === AUTH_MODES.RAW_ACCESS_TOKEN) {
136
- if (!this.accessToken) throw new Error('Invalid access token');
137
- config.headers.Authorization = `Bearer ${this.accessToken}`;
138
- } else if (this.authMode === AUTH_MODES.API_KEY) {
139
- if (!this.apiKey) throw new Error('Please set API key');
140
- config.params = config.params || {};
141
- config.params.key = this.apiKey;
142
- } else if (this.authMode === AUTH_MODES.OAUTH) {
143
- const credentials = await this.oAuth2Client.getAccessToken();
144
- config.headers.Authorization = `Bearer ${credentials.token}`;
145
- } else {
146
- throw new Error('You must initialize some kind of auth before making any requests');
147
- }
148
- return config;
149
- }
150
-
151
- async _handleAxiosResponse(response) { return response; }
152
- async _handleAxiosErrors(error) {
153
- // console.log(error);
154
- if (error.response && error.response.data) {
155
- // usually the error has a code and message, but occasionally not
156
- if (!error.response.data.error) throw error;
157
-
158
- const { code, message } = error.response.data.error;
159
- error.message = `Google API error - [${code}] ${message}`;
160
- throw error;
161
- }
162
-
163
- if (_.get(error, 'response.status') === 403) {
164
- if (this.authMode === AUTH_MODES.API_KEY) {
165
- throw new Error('Sheet is private. Use authentication or make public. (see https://github.com/theoephraim/node-google-spreadsheet#a-note-on-authentication for details)');
166
- }
167
- }
168
- throw error;
169
- }
170
-
171
- async _makeSingleUpdateRequest(requestType, requestParams) {
172
- const response = await this.axios.post(':batchUpdate', {
173
- requests: [{ [requestType]: requestParams }],
174
- includeSpreadsheetInResponse: true,
175
- // responseRanges: [string]
176
- // responseIncludeGridData: true
177
- });
178
-
179
- this._updateRawProperties(response.data.updatedSpreadsheet.properties);
180
- _.each(response.data.updatedSpreadsheet.sheets, (s) => this._updateOrCreateSheet(s));
181
- // console.log('API RESPONSE', response.data.replies[0][requestType]);
182
- return response.data.replies[0][requestType];
183
- }
184
-
185
- async _makeBatchUpdateRequest(requests, responseRanges) {
186
- // this is used for updating batches of cells
187
- const response = await this.axios.post(':batchUpdate', {
188
- requests,
189
- includeSpreadsheetInResponse: true,
190
- ...responseRanges && {
191
- responseIncludeGridData: true,
192
- ...responseRanges !== '*' && { responseRanges },
193
- },
194
- });
195
-
196
- this._updateRawProperties(response.data.updatedSpreadsheet.properties);
197
- _.each(response.data.updatedSpreadsheet.sheets, (s) => this._updateOrCreateSheet(s));
198
- }
199
-
200
- _ensureInfoLoaded() {
201
- if (!this._rawProperties) throw new Error('You must call `doc.loadInfo()` before accessing this property');
202
- }
203
-
204
- _updateRawProperties(newProperties) { this._rawProperties = newProperties; }
205
-
206
- _updateOrCreateSheet({ properties, data }) {
207
- const { sheetId } = properties;
208
- if (!this._rawSheets[sheetId]) {
209
- this._rawSheets[sheetId] = new GoogleSpreadsheetWorksheet(this, { properties, data });
210
- } else {
211
- this._rawSheets[sheetId]._rawProperties = properties;
212
- this._rawSheets[sheetId]._fillCellData(data);
213
- }
214
- }
215
-
216
- // BASIC PROPS //////////////////////////////////////////////////////////////////////////////
217
- _getProp(param) {
218
- this._ensureInfoLoaded();
219
- return this._rawProperties[param];
220
- }
221
- _setProp(param, newVal) { // eslint-disable-line no-unused-vars
222
- throw new Error('Do not update directly - use `updateProperties()`');
223
- }
224
-
225
- get title() { return this._getProp('title'); }
226
- get locale() { return this._getProp('locale'); }
227
- get timeZone() { return this._getProp('timeZone'); }
228
- get autoRecalc() { return this._getProp('autoRecalc'); }
229
- get defaultFormat() { return this._getProp('defaultFormat'); }
230
- get spreadsheetTheme() { return this._getProp('spreadsheetTheme'); }
231
- get iterativeCalculationSettings() { return this._getProp('iterativeCalculationSettings'); }
232
-
233
- set title(newVal) { this._setProp('title', newVal); }
234
- set locale(newVal) { this._setProp('locale', newVal); }
235
- set timeZone(newVal) { this._setProp('timeZone', newVal); }
236
- set autoRecalc(newVal) { this._setProp('autoRecalc', newVal); }
237
- set defaultFormat(newVal) { this._setProp('defaultFormat', newVal); }
238
- set spreadsheetTheme(newVal) { this._setProp('spreadsheetTheme', newVal); }
239
- set iterativeCalculationSettings(newVal) { this._setProp('iterativeCalculationSettings', newVal); }
240
-
241
- async updateProperties(properties) {
242
- // updateSpreadsheetProperties
243
- // https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets#SpreadsheetProperties
244
-
245
- /*
246
- title (string) - title of the spreadsheet
247
- locale (string) - ISO code
248
- autoRecalc (enum) - ON_CHANGE|MINUTE|HOUR
249
- timeZone (string) - timezone code
250
- iterativeCalculationSettings (object) - see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets#IterativeCalculationSettings
251
- */
252
-
253
- await this._makeSingleUpdateRequest('updateSpreadsheetProperties', {
254
- properties,
255
- fields: getFieldMask(properties),
256
- });
257
- }
258
-
259
- // BASIC INFO ////////////////////////////////////////////////////////////////////////////////////
260
- async loadInfo(includeCells) {
261
- const response = await this.axios.get('/', {
262
- params: {
263
- ...includeCells && { includeGridData: true },
264
- },
265
- });
266
- this._rawProperties = response.data.properties;
267
- _.each(response.data.sheets, (s) => this._updateOrCreateSheet(s));
268
- }
269
- async getInfo() { return this.loadInfo(); } // alias to mimic old version
270
-
271
- resetLocalCache() {
272
- this._rawProperties = null;
273
- this._rawSheets = {};
274
- }
275
-
276
- // WORKSHEETS ////////////////////////////////////////////////////////////////////////////////////
277
- get sheetCount() {
278
- this._ensureInfoLoaded();
279
- return _.values(this._rawSheets).length;
280
- }
281
-
282
- get sheetsById() {
283
- this._ensureInfoLoaded();
284
- return this._rawSheets;
285
- }
286
-
287
- get sheetsByIndex() {
288
- this._ensureInfoLoaded();
289
- return _.sortBy(this._rawSheets, 'index');
290
- }
291
-
292
- get sheetsByTitle() {
293
- this._ensureInfoLoaded();
294
- return _.keyBy(this._rawSheets, 'title');
295
- }
296
-
297
- async addSheet(properties = {}) {
298
- // Request type = `addSheet`
299
- // https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#AddSheetRequest
300
-
301
- const response = await this._makeSingleUpdateRequest('addSheet', {
302
- properties: _.omit(properties, 'headers', 'headerValues', 'headerRowIndex'),
303
- });
304
- // _makeSingleUpdateRequest already adds the sheet
305
- const newSheetId = response.properties.sheetId;
306
- const newSheet = this.sheetsById[newSheetId];
307
-
308
- // allow it to work with `.headers` but `.headerValues` is the real prop
309
- const headers = properties.headerValues || properties.headers;
310
- if (headers) {
311
- await newSheet.setHeaderRow(headers, properties.headerRowIndex);
312
- }
313
-
314
- return newSheet;
315
- }
316
- async addWorksheet(properties) { return this.addSheet(properties); } // alias to mimic old version
317
-
318
- async deleteSheet(sheetId) {
319
- // Request type = `deleteSheet`
320
- // https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#DeleteSheetRequest
321
- await this._makeSingleUpdateRequest('deleteSheet', { sheetId });
322
- delete this._rawSheets[sheetId];
323
- }
324
-
325
- // NAMED RANGES //////////////////////////////////////////////////////////////////////////////////
326
- async addNamedRange(name, range, namedRangeId) {
327
- // namedRangeId is optional
328
- return this._makeSingleUpdateRequest('addNamedRange', {
329
- name,
330
- range,
331
- namedRangeId,
332
- });
333
- }
334
-
335
- async deleteNamedRange(namedRangeId) {
336
- return this._makeSingleUpdateRequest('deleteNamedRange', { namedRangeId });
337
- }
338
-
339
- // LOADING CELLS /////////////////////////////////////////////////////////////////////////////////
340
- async loadCells(filters) {
341
- // you can pass in a single filter or an array of filters
342
- // strings are treated as a1 ranges
343
- // objects are treated as GridRange objects
344
- // TODO: make it support DeveloperMetadataLookup objects
345
-
346
- // TODO: switch to this mode if using a read-only auth token?
347
- const readOnlyMode = this.authMode === AUTH_MODES.API_KEY;
348
-
349
- const filtersArray = _.isArray(filters) ? filters : [filters];
350
- const dataFilters = _.map(filtersArray, (filter) => {
351
- if (_.isString(filter)) {
352
- return readOnlyMode ? filter : { a1Range: filter };
353
- }
354
- if (_.isObject(filter)) {
355
- if (readOnlyMode) {
356
- throw new Error('Only A1 ranges are supported when fetching cells with read-only access (using only an API key)');
357
- }
358
- // TODO: make this support Developer Metadata filters
359
- return { gridRange: filter };
360
- }
361
- throw new Error('Each filter must be an A1 range string or a gridrange object');
362
- });
363
-
364
- let result;
365
- // when using an API key only, we must use the regular get endpoint
366
- // because :getByDataFilter requires higher access
367
- if (this.authMode === AUTH_MODES.API_KEY) {
368
- result = await this.axios.get('/', {
369
- params: {
370
- includeGridData: true,
371
- ranges: dataFilters,
372
- },
373
- });
374
- // otherwise we use the getByDataFilter endpoint because it is more flexible
375
- } else {
376
- result = await this.axios.post(':getByDataFilter', {
377
- includeGridData: true,
378
- dataFilters,
379
- });
380
- }
381
-
382
- const { sheets } = result.data;
383
- _.each(sheets, (sheet) => { this._updateOrCreateSheet(sheet); });
384
- }
385
- }
386
-
387
- module.exports = GoogleSpreadsheet;