Dune Csv Export

Downloading the queries csv export for free subscription users

  1. // ==UserScript==
  2. // @name Dune Csv Export
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.2.6
  5. // @description Downloading the queries csv export for free subscription users
  6. // @author lulu
  7. // @match https://dune.com/queries*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=dune.com
  9. // @grant none
  10. // @require https://ajax.googleapis.com/ajax/libs/jquery/3.4.0/jquery.min.js
  11. // ==/UserScript==
  12.  
  13. function getElementByXpath(path){
  14. return document.evaluate(path, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
  15. }
  16.  
  17.  
  18. function getNextButton(){
  19. let path0 = '//*[@id="results"]/div/div[2]/div/div[2]/ul/li[5]/button';
  20. let path1 = '//*[@id="results"]/div/div[2]/div/div[2]/ul/li[6]/button';
  21.  
  22. let btn = getElementByXpath(path0);
  23. if (btn === null){
  24. btn = getElementByXpath(path1);
  25. }
  26. // console.log("BTN:", btn)
  27. return btn;
  28.  
  29. }
  30.  
  31. function getHeaders(table){
  32. const headers = [];
  33. $.each($(table).find("thead").find("tr").find("th"), function (key, val2) {
  34. headers[key] = $(val2).text(); //$(val2).find("div").text();
  35. });
  36. return headers;
  37. }
  38.  
  39. async function getCurrentTableValues(){
  40. const maxAttempts = 100; // Adjust as needed - determines maximum wait time. 50 attempts * 100ms = 5 seconds
  41. let attempt = 0;
  42. let table = null;
  43. const values = [];
  44.  
  45. while (attempt < maxAttempts) {
  46. table = getElementByXpath('//*[@id="results"]/div/div[2]/div/div[1]/table');
  47. if (table !== null && table !== undefined ) { // check for both null and undefined
  48. break;
  49. }
  50. // console.log("Table is Null, Waiting and Trying again.., attemp: ", attempt);
  51. // Object is null or undefined, wait and retry
  52. await new Promise(resolve => setTimeout(resolve, 100));
  53. attempt++;
  54. }
  55. if (table === null || table === undefined ) { // check for both null and undefined
  56. console.log("Table Did not collected after 100 attempts, Returning Empty!");
  57. return values;
  58. }
  59.  
  60. $.each($(table).find("tbody tr"), function(rowIndex, rowElement) {
  61. const row = [];
  62. $.each($(rowElement).find("td"), function(cellIndex, cellElement) {
  63. const cellValue = $(cellElement).find("div").first().text();
  64. row.push(cellValue.trim());
  65. });
  66.  
  67. values.push(row);
  68. });
  69.  
  70. // console.log("VALUES:", values);
  71. return values;
  72. }
  73.  
  74. async function collect_pages(){
  75. let table = getElementByXpath('//*[@id="results"]/div/div[2]/div/div[1]/table');
  76. let custom_limit = document.getElementById("customRowLimit").value;
  77. const limit = (str => str.trim() === '' ? null : parseInt(str))(custom_limit);
  78.  
  79. let hasNextPage = true;
  80. let collectedData = [];
  81. do {
  82. const currValues = await getCurrentTableValues();
  83. collectedData.push(...currValues);
  84.  
  85. const nextButton = getNextButton();
  86. hasNextPage = Boolean(
  87. nextButton &&
  88. !$(nextButton).is(":disabled") &&
  89. !nextButton.disabled
  90. );
  91.  
  92. if (!$(nextButton).is(":disabled") && nextButton != null){
  93. $(nextButton).trigger("click");
  94. await new Promise(resolve => setTimeout(resolve, 20));
  95. }
  96.  
  97. if (limit != null && collectedData.length >= limit) {
  98. break
  99. }
  100.  
  101. } while (hasNextPage)
  102.  
  103. const headers = getHeaders(table);
  104. download_csv(headers, collectedData);
  105. }
  106.  
  107. function escapeCsvValue(value) {
  108. if (typeof value !== 'string') value = String(value);
  109. value = value.replace(/"/g, '""');
  110. return `"${value}"`;
  111. }
  112.  
  113. function download_csv(headers, rows) {
  114. let csvHeaders = headers.join(",") + "\n";
  115. // let csvRows = rows.map(row => row.join(",")).join("\n");
  116. let csvRows = rows.map(row => row.map(escapeCsvValue).join(",")).join("\n");
  117. let csvButton = getElementByXpath('//*[@id="results"]/div/div[1]/div[1]/div/button');
  118. let custom_name = document.getElementById("customNameCsvFileFree").value;
  119.  
  120.  
  121. var downloadBtn = document.createElement("a");
  122. downloadBtn.href = "data:text/csv;charset=utf-8,"+encodeURI(csvHeaders+csvRows);
  123. downloadBtn.target = "_blank";
  124.  
  125. if (custom_name.length > 1) {
  126. downloadBtn.download = custom_name+".csv";
  127. } else {
  128. let url = window.location.href;
  129.  
  130. downloadBtn.download = "query_"+url.split("queries/")[1].replace("/", "_")+".csv"
  131. }
  132.  
  133. downloadBtn.click();
  134.  
  135. csvButton.disabled = false;
  136.  
  137.  
  138. }
  139.  
  140.  
  141. async function collectCsv() {
  142. let csvButton = getElementByXpath('//*[@id="results"]/div/div[1]/div[1]/div/button');
  143. csvButton.disabled = "disabled";
  144.  
  145. let nextButton = getNextButton(); //getElementByXpath('//*[@id="results"]/div/div[2]/div/div[2]/ul/li[5]/button');
  146. if (nextButton == null){
  147. // console.log("Butt not exists");
  148. await collect_pages();
  149. } else {
  150. // go to first
  151. let firstPageBtn = getElementByXpath('//*[@id="results"]/div/div[2]/div/div[2]/ul/li[3]/button');
  152. if (firstPageBtn != null && !$(firstPageBtn).is(":disabled")){
  153. $(firstPageBtn).trigger("click");
  154. await new Promise(resolve => setTimeout(resolve, 5));
  155. await collect_pages();
  156.  
  157. } else {
  158. await collect_pages();
  159. }
  160. }
  161.  
  162. }
  163.  
  164. function changeCsvButton(){
  165. // console.log("Running Dune Script");
  166. let csvButton = getElementByXpath('//*[@id="results"]/div/div[1]/div[1]/div/button');
  167. if (csvButton != null){
  168. csvButton.disabled = false;
  169. csvButton.onclick = collectCsv;
  170.  
  171. // adding a input for custom csv file name
  172. var inp_element = document.createElement('input');
  173. inp_element.className = "IconButton_iconButton___v3YQ buttonThemes_button__jfRFC buttonThemes_theme-tertiary__v7VoN IconButton_size-M__FIXfN";
  174. inp_element.id = "customNameCsvFileFree";
  175. inp_element.placeholder = "Custom filename";
  176.  
  177. var limit_inp_element = document.createElement('input');
  178. limit_inp_element.className = "IconButton_iconButton___v3YQ buttonThemes_button__jfRFC buttonThemes_theme-tertiary__v7VoN IconButton_size-M__FIXfN";
  179. limit_inp_element.id = "customRowLimit";
  180. limit_inp_element.placeholder = "Limit Csv Rows";
  181. limit_inp_element.type = 'number';
  182. limit_inp_element.min = '0';
  183.  
  184. getElementByXpath('//*[@id="results"]/div/div[1]/div[1]').appendChild(inp_element);
  185. getElementByXpath('//*[@id="results"]/div/div[1]/div[1]').appendChild(limit_inp_element);
  186.  
  187. // console.log("BTN CREATED");
  188.  
  189. } else {
  190. setTimeout(()=>{
  191. changeCsvButton();
  192. }, 1000)
  193. }
  194. }
  195.  
  196.  
  197. (function() {
  198. 'use strict';
  199. $(document).ready ( function(){
  200. changeCsvButton();
  201. // for (let i = 0; i < 10; i++) {}
  202. });
  203. })();