3
3
// @name:zh-CN ChatGPT Exporter
4
4
// @name:zh-TW ChatGPT Exporter
5
5
// @namespace pionxzh
6
- // @version 2.26 .0
6
+ // @version 2.27 .0
7
7
// @author pionxzh
8
8
// @description Easily export the whole ChatGPT conversation history for further analysis or sharing.
9
9
// @description:zh-CN 轻松导出 ChatGPT 聊天记录,以便进一步分析或分享。
@@ -1201,12 +1201,13 @@ html {
1201
1201
}
1202
1202
async function fetchAllConversations() {
1203
1203
const conversations = [];
1204
- const limit = 20 ;
1204
+ const limit = 100 ;
1205
1205
let offset = 0;
1206
1206
while (true) {
1207
1207
const result = await fetchConversations(offset, limit);
1208
1208
conversations.push(...result.items);
1209
1209
if (offset + limit >= result.total) break;
1210
+ if (offset + limit >= 1e3) break;
1210
1211
offset += limit;
1211
1212
}
1212
1213
return conversations;
@@ -1346,7 +1347,7 @@ html {
1346
1347
};
1347
1348
}
1348
1349
function extractConversationResult(conversationMapping, startNodeId) {
1349
- var _a, _b;
1350
+ var _a, _b, _c ;
1350
1351
const result = [];
1351
1352
let currentNodeId = startNodeId;
1352
1353
while (currentNodeId) {
@@ -1359,7 +1360,7 @@ html {
1359
1360
}
1360
1361
if (
1361
1362
// Skip system messages
1362
- ((_a = node2.message) == null ? void 0 : _a.author.role) !== "system" && ((_b = node2.message) == null ? void 0 : _b.content.content_type) !== "model_editable_context"
1363
+ ((_a = node2.message) == null ? void 0 : _a.author.role) !== "system" && ((_b = node2.message) == null ? void 0 : _b.content.content_type) !== "model_editable_context" && ((_c = node2.message) == null ? void 0 : _c.content.content_type) !== "user_editable_context"
1363
1364
) {
1364
1365
result.unshift(node2);
1365
1366
}
@@ -8732,6 +8733,55 @@ html {
8732
8733
<script>
8733
8734
hljs.highlightAll()
8734
8735
<\/script>
8736
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.3/katex.min.css">
8737
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.3/katex.min.js"><\/script>
8738
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.3/contrib/auto-render.min.js"><\/script>
8739
+ <script>
8740
+ document.addEventListener("DOMContentLoaded", function() {
8741
+ renderMathInElement(document.body, {
8742
+ delimiters: [
8743
+ { left: "$$", right: "$$", display: true },
8744
+ { left: "$", right: "$", display: false },
8745
+ { left: "\\\\[", right: "\\\\]", display: true },
8746
+ { left: "\\\\(", right: "\\\\)", display: false }
8747
+ ],
8748
+ throwOnError: false,
8749
+ ignoredClasses: ["no-katex"],
8750
+ preProcess: function(math) {
8751
+ return \`\\\\displaystyle \\\\Large \${math}\`;
8752
+ }
8753
+ });
8754
+ document.querySelectorAll('.katex').forEach(function(el) {
8755
+ const parent = el.parentNode;
8756
+ const grandparent = parent.parentNode;
8757
+ if (grandparent.tagName === 'P' && isOnlyContent(grandparent, parent)) {
8758
+ el.style.width = '100%';
8759
+ el.style.display = 'block';
8760
+ el.style.textAlign = 'center';
8761
+ parent.style.textAlign = 'center';
8762
+ } else {
8763
+ el.style.display = 'inline-block';
8764
+ el.style.width = 'fit-content';
8765
+ }
8766
+ });
8767
+ function isOnlyContent(parent, element) {
8768
+ let onlyKaTeX = true;
8769
+ parent.childNodes.forEach(function(child) {
8770
+ console.log(child.textContent);
8771
+ if (child !== element) {
8772
+ if (child.nodeType === Node.TEXT_NODE) {
8773
+ if (child.textContent.trim().length > 0) {
8774
+ onlyKaTeX = false;
8775
+ }
8776
+ } else if (child.nodeType === Node.ELEMENT_NODE) {
8777
+ onlyKaTeX = false;
8778
+ }
8779
+ }
8780
+ });
8781
+ return onlyKaTeX;
8782
+ }
8783
+ });
8784
+ <\/script>
8735
8785
8736
8786
<style>
8737
8787
:root {
@@ -8842,6 +8892,31 @@ html {
8842
8892
border: 1px solid #e2e8f0;
8843
8893
}
8844
8894
8895
+ [data-width="narrow"] .width-toggle .expand {
8896
+ display: block;
8897
+ }
8898
+
8899
+ [data-width="wide"] .width-toggle .narrow {
8900
+ display: block;
8901
+ }
8902
+
8903
+ .width-toggle {
8904
+ display: inline-flex;
8905
+ justify-content: center;
8906
+ align-items: center;
8907
+ width: 32px;
8908
+ height: 32px;
8909
+ border-radius: 4px;
8910
+ background-color: #fff;
8911
+ border: 1px solid #e2e8f0;
8912
+ margin-left: 8px;
8913
+ cursor: pointer;
8914
+ }
8915
+
8916
+ .width-toggle svg {
8917
+ display: none;
8918
+ }
8919
+
8845
8920
.metadata_container {
8846
8921
display: flex;
8847
8922
flex-direction: column;
@@ -9065,6 +9140,15 @@ html {
9065
9140
.conversation {
9066
9141
margin: 0 auto;
9067
9142
padding: 1rem;
9143
+ max-width: 64rem;
9144
+ }
9145
+
9146
+ [data-width="narrow"] .conversation {
9147
+ max-width: 64rem;
9148
+ }
9149
+
9150
+ [data-width="wide"] .conversation {
9151
+ max-width: 90%;
9068
9152
}
9069
9153
9070
9154
@media (min-width: 1280px) {
@@ -9185,6 +9269,7 @@ html {
9185
9269
font-size: 0.8rem;
9186
9270
color: #acacbe
9187
9271
}
9272
+
9188
9273
</style>
9189
9274
</head>
9190
9275
@@ -9202,10 +9287,18 @@ html {
9202
9287
<svg class="sun" stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg>
9203
9288
<svg class="moon" stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>
9204
9289
</button>
9290
+ <button class="toggle width-toggle">
9291
+ <svg class="expand" stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg" style="display: block;">
9292
+ <path d="M3 12h18M6 8l-4 4 4 4M18 8l4 4-4 4"></path>
9293
+ </svg>
9294
+ <svg class="narrow" stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg" style="display: none;">
9295
+ <path d="M3 12h7M14 12h7M6 16l4-4-4-4M18 16l-4-4 4-4"></path>
9296
+ </svg>
9297
+ </button>
9205
9298
</h1>
9206
9299
<div class="conversation-export">
9207
9300
<p>Exported by
9208
- <a href="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/pionxzh/chatgpt-exporter">ChatGPT Exporter</a>
9301
+ <a href="https://github.com/pionxzh/chatgpt-exporter.git ">ChatGPT Exporter</a>
9209
9302
at {{time}}</p>
9210
9303
</div>
9211
9304
{{details}}
@@ -9217,23 +9310,50 @@ html {
9217
9310
9218
9311
<script>
9219
9312
function toggleDarkMode(mode) {
9220
- const html = document.querySelector('html')
9221
- const isDarkMode = html.getAttribute('data-theme') === 'dark'
9222
- const newMode = mode || (isDarkMode ? 'light' : 'dark')
9223
- if (newMode !== 'dark' && newMode !== 'light') return
9224
- html.setAttribute('data-theme', newMode)
9313
+ const html = document.querySelector('html');
9314
+ const isDarkMode = html.getAttribute('data-theme') === 'dark';
9315
+ const newMode = mode || (isDarkMode ? 'light' : 'dark');
9316
+ if (newMode !== 'dark' && newMode !== 'light') return;
9317
+ html.setAttribute('data-theme', newMode);
9225
9318
9226
- const url = new URL(window.location)
9227
- url.searchParams.set('theme', newMode)
9228
- window.history.replaceState({}, '', url)
9319
+ const url = new URL(window.location);
9320
+ url.searchParams.set('theme', newMode);
9321
+ window.history.replaceState({}, '', url);
9322
+ }
9323
+ function toggleWidthMode(mode) {
9324
+ const body = document.querySelector('body');
9325
+ const widthToggleButton = document.querySelector('.width-toggle');
9326
+ const isWide = body.getAttribute('data-width') === 'wide';
9327
+ const newWidthMode = mode || (isWide ? 'narrow' : 'wide');
9328
+ if (newWidthMode !== 'narrow' && newWidthMode !== 'wide') return;
9329
+ body.setAttribute('data-width', newWidthMode);
9330
+
9331
+ const url = new URL(window.location);
9332
+ url.searchParams.set('width', newWidthMode);
9333
+ window.history.replaceState({}, '', url);
9334
+
9335
+ // Update the icon based on the current mode
9336
+ const narrowIcon = widthToggleButton.querySelector('.narrow');
9337
+ const expandIcon = widthToggleButton.querySelector('.expand');
9338
+
9339
+ if (newWidthMode === 'wide') {
9340
+ expandIcon.style.display = "none";
9341
+ narrowIcon.style.display = "block";
9342
+ } else {
9343
+ expandIcon.style.display = "block";
9344
+ narrowIcon.style.display = "none";
9345
+ }
9229
9346
}
9230
9347
9231
- // Support for ?theme=dark
9232
- const urlParams = new URLSearchParams(window.location.search)
9233
- const theme = urlParams.get('theme')
9234
- if (theme) toggleDarkMode(theme)
9348
+ const urlParams = new URLSearchParams(window.location.search);
9349
+ const theme = urlParams.get('theme');
9350
+ const width = urlParams.get('width');
9351
+
9352
+ if (theme) toggleDarkMode(theme);
9353
+ if (width) toggleWidthMode(width);
9235
9354
9236
- document.querySelector('.toggle').addEventListener('click', () => toggleDarkMode())
9355
+ document.querySelector('.toggle').addEventListener('click', () => toggleDarkMode());
9356
+ document.querySelector('.width-toggle').addEventListener('click', () => toggleWidthMode());
9237
9357
<\/script>
9238
9358
</body>
9239
9359
@@ -20522,14 +20642,15 @@ html {
20522
20642
level: 9
20523
20643
}
20524
20644
});
20525
- downloadFile("chatgpt-export.zip", "application/zip", blob);
20645
+ downloadFile("chatgpt-export-html .zip", "application/zip", blob);
20526
20646
return true;
20527
20647
}
20528
20648
function conversationToHtml(conversation, avatar, metaList) {
20529
20649
const { id, title: title2, model, modelSlug, createTime, updateTime, conversationNodes } = conversation;
20530
20650
const enableTimestamp = ScriptStorage.get(KEY_TIMESTAMP_ENABLED) ?? false;
20531
20651
const timeStampHtml = ScriptStorage.get(KEY_TIMESTAMP_HTML) ?? false;
20532
20652
const timeStamp24H = ScriptStorage.get(KEY_TIMESTAMP_24H) ?? false;
20653
+ const LatexRegex2 = /(\s\$\$.+?\$\$\s|\s\$.+?\$\s|\\\[.+?\\\]|\\\(.+?\\\))|(^\$$[\S\s]+?^\$$)|(^\$\$[\S\s]+?^\$\$\$)/gm;
20533
20654
const conversationHtml = conversationNodes.map(({ message }) => {
20534
20655
var _a, _b, _c, _d;
20535
20656
if (!message || !message.content) return null;
@@ -20550,11 +20671,27 @@ html {
20550
20671
let postSteps = [];
20551
20672
if (message.author.role === "assistant") {
20552
20673
postSteps = [...postSteps, (input) => transformFootNotes$2(input, message.metadata)];
20674
+ postSteps.push((input) => {
20675
+ const matches = input.match(LatexRegex2);
20676
+ const isCodeBlock = /```/.test(input);
20677
+ if (!isCodeBlock && matches) {
20678
+ let index2 = 0;
20679
+ input = input.replace(LatexRegex2, () => {
20680
+ return `╬${index2++}╬`;
20681
+ });
20682
+ input = input.replace(/^\\\[(.+)\\\]$/gm, "$$$$$1$$$$").replace(/\\\[/g, "$$").replace(/\\\]/g, "$$").replace(/\\\(/g, "$").replace(/\\\)/g, "$");
20683
+ }
20684
+ let transformed = toHtml(fromMarkdown(input));
20685
+ if (!isCodeBlock && matches) {
20686
+ transformed = transformed.replace(/╬(\d+)╬/g, (_24, index2) => {
20687
+ return matches[+index2];
20688
+ });
20689
+ }
20690
+ return transformed;
20691
+ });
20553
20692
}
20554
20693
if (message.author.role === "user") {
20555
- postSteps = [...postSteps, (input) => `<p>${escapeHtml(input)}</p>`];
20556
- } else {
20557
- postSteps = [...postSteps, (input) => toHtml(fromMarkdown(input))];
20694
+ postSteps = [...postSteps, (input) => `<p class="no-katex">${escapeHtml(input)}</p>`];
20558
20695
}
20559
20696
const postProcess = (input) => postSteps.reduce((acc, fn2) => fn2(acc), input);
20560
20697
const content2 = transformContent$2(message.content, message.metadata, postProcess);
@@ -20661,7 +20798,7 @@ ${content2.text}
20661
20798
}).join("\n")) || "";
20662
20799
}
20663
20800
default:
20664
- return postProcess(" [Unsupported Content]" );
20801
+ return postProcess(` [Unsupported Content: ${content2.content_type} ]` );
20665
20802
}
20666
20803
}
20667
20804
function escapeHtml(html2) {
@@ -20978,7 +21115,7 @@ ${content2.text}
20978
21115
level: 9
20979
21116
}
20980
21117
});
20981
- downloadFile("chatgpt-export.zip", "application/zip", blob);
21118
+ downloadFile("chatgpt-export-json .zip", "application/zip", blob);
20982
21119
return true;
20983
21120
}
20984
21121
function conversationToJson(conversation) {
@@ -21025,7 +21162,7 @@ ${content2.text}
21025
21162
level: 9
21026
21163
}
21027
21164
});
21028
- downloadFile("chatgpt-export.zip", "application/zip", blob);
21165
+ downloadFile("chatgpt-export-markdown .zip", "application/zip", blob);
21029
21166
return true;
21030
21167
}
21031
21168
const LatexRegex$1 = /(\s\$\$.+\$\$\s|\s\$.+\$\s|\\\[.+\\\]|\\\(.+\\\))|(^\$$[\S\s]+^\$$)|(^\$\$[\S\s]+^\$\$$)/gm;
0 commit comments