Register
Login
Resources
Docs Blog Datasets Glossary Case Studies Tutorials & Webinars
Product
Data Engine LLMs Platform Enterprise
Pricing Explore
Connect to our Discord channel

googleSheets.ts 4.6 KB

You have to be logged in to leave a comment. Sign In
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
  1. import { fetchWithProxy } from './fetch';
  2. import logger from './logger';
  3. import type { CsvRow } from './types';
  4. export async function checkGoogleSheetAccess(url: string) {
  5. try {
  6. const response = await fetch(url);
  7. if (response.ok) {
  8. return { public: true, status: response.status };
  9. } else {
  10. return { public: false, status: response.status };
  11. }
  12. } catch (error) {
  13. logger.error(`Error checking sheet access: ${error}`);
  14. return { public: false };
  15. }
  16. }
  17. export async function fetchCsvFromGoogleSheetUnauthenticated(url: string): Promise<CsvRow[]> {
  18. const { parse: parseCsv } = await import('csv-parse/sync');
  19. const gid = new URL(url).searchParams.get('gid');
  20. const csvUrl = `${url.replace(/\/edit.*$/, '/export')}?format=csv${gid ? `&gid=${gid}` : ''}`;
  21. const response = await fetchWithProxy(csvUrl);
  22. if (response.status !== 200) {
  23. throw new Error(`Failed to fetch CSV from Google Sheets URL: ${url}`);
  24. }
  25. const csvData = await response.text();
  26. return parseCsv(csvData, { columns: true });
  27. }
  28. export async function fetchCsvFromGoogleSheetAuthenticated(url: string): Promise<CsvRow[]> {
  29. const { sheets: googleSheets, auth: googleAuth } = await import('@googleapis/sheets');
  30. const auth = new googleAuth.GoogleAuth({
  31. scopes: ['https://www.googleapis.com/auth/spreadsheets.readonly'],
  32. });
  33. const sheets = googleSheets('v4');
  34. const match = url.match(/\/d\/([^/]+)/);
  35. if (!match) {
  36. throw new Error(`Invalid Google Sheets URL: ${url}`);
  37. }
  38. const spreadsheetId = match[1];
  39. let range = 'A1:ZZZ';
  40. const gid = Number(new URL(url).searchParams.get('gid'));
  41. if (gid) {
  42. const spreadsheet = await sheets.spreadsheets.get({ spreadsheetId, auth });
  43. const sheetName = spreadsheet.data.sheets?.find((sheet) => sheet.properties?.sheetId === gid)
  44. ?.properties?.title;
  45. if (!sheetName) {
  46. throw new Error(`Sheet not found for gid: ${gid}`);
  47. }
  48. range = `${sheetName}!${range}`;
  49. }
  50. const response = await sheets.spreadsheets.values.get({ spreadsheetId, range, auth });
  51. const rows = response.data.values;
  52. if (!rows?.length) {
  53. throw new Error(`No data found in Google Sheets URL: ${url}`);
  54. }
  55. // Assuming the first row contains headers
  56. const headers = rows[0];
  57. const dataRows = rows.slice(1);
  58. return dataRows.map((row) => {
  59. const csvRow: CsvRow = {};
  60. headers.forEach((header, index) => {
  61. csvRow[header] = row[index];
  62. });
  63. return csvRow;
  64. });
  65. }
  66. export async function fetchCsvFromGoogleSheet(url: string): Promise<CsvRow[]> {
  67. const { public: isPublic } = await checkGoogleSheetAccess(url);
  68. logger.debug(`Google Sheets URL: ${url}, isPublic: ${isPublic}`);
  69. if (isPublic) {
  70. return fetchCsvFromGoogleSheetUnauthenticated(url);
  71. }
  72. return fetchCsvFromGoogleSheetAuthenticated(url);
  73. }
  74. export async function writeCsvToGoogleSheet(rows: CsvRow[], url: string): Promise<void> {
  75. const { sheets: googleSheets, auth: googleAuth } = await import('@googleapis/sheets');
  76. const auth = new googleAuth.GoogleAuth({
  77. scopes: ['https://www.googleapis.com/auth/spreadsheets'],
  78. });
  79. const sheets = googleSheets('v4');
  80. const match = url.match(/\/d\/([^/]+)/);
  81. if (!match) {
  82. throw new Error(`Invalid Google Sheets URL: ${url}`);
  83. }
  84. const spreadsheetId = match[1];
  85. let range = 'A1:ZZZ';
  86. const gid = Number(new URL(url).searchParams.get('gid'));
  87. if (gid) {
  88. const spreadsheet = await sheets.spreadsheets.get({ spreadsheetId, auth });
  89. const sheetName = spreadsheet.data.sheets?.find((sheet) => sheet.properties?.sheetId === gid)
  90. ?.properties?.title;
  91. if (!sheetName) {
  92. throw new Error(`Sheet not found for gid: ${gid}`);
  93. }
  94. range = `${sheetName}!${range}`;
  95. } else {
  96. // Create a new sheet if no gid is provided
  97. const newSheetTitle = `Sheet${Date.now()}`;
  98. await sheets.spreadsheets.batchUpdate({
  99. spreadsheetId,
  100. auth,
  101. requestBody: {
  102. requests: [
  103. {
  104. addSheet: {
  105. properties: {
  106. title: newSheetTitle,
  107. },
  108. },
  109. },
  110. ],
  111. },
  112. });
  113. range = `${newSheetTitle}!${range}`;
  114. }
  115. // Extract headers from the first row
  116. const headers = Object.keys(rows[0]);
  117. // Convert rows to a 2D array
  118. const values = [headers, ...rows.map((row) => headers.map((header) => row[header]))];
  119. // Write data to the sheet
  120. logger.debug(`Writing CSV to Google Sheets URL: ${url} with ${values.length} rows`);
  121. await sheets.spreadsheets.values.update({
  122. spreadsheetId,
  123. range,
  124. valueInputOption: 'USER_ENTERED',
  125. auth,
  126. requestBody: {
  127. values,
  128. },
  129. });
  130. }
Tip!

Press p or to see the previous file or, n or to see the next file

Comments

Loading...