chatgpt-to-markdown 1.5.3 → 1.5.5

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/README.md CHANGED
@@ -97,6 +97,8 @@ git push --follow-tags
97
97
 
98
98
  ## Release notes
99
99
 
100
+ - 1.5.5: 02 Nov 2024. Add conversation link. Use conversation ID as fallback title if title is empty.
101
+ - 1.5.4: 02 Nov 2024. Skip `user_editable_context` to avoid polluting Markdown with custom instructions
100
102
  - 1.5.3: 05 Aug 2024. Show text from multimodal prompts
101
103
  - 1.5.2: 05 Aug 2024. Show tether_browsing_display summary
102
104
  - 1.5.1: 22 Mar 2024. Handle unicode filenames
package/cli.js CHANGED
@@ -10,15 +10,10 @@ async function run() {
10
10
  process.exit(1);
11
11
  }
12
12
 
13
- try {
14
- const data = await fs.readFile(filePath, "utf8");
15
- const json = JSON.parse(data);
16
- const sourceDir = path.dirname(filePath);
17
- await chatgptToMarkdown(json, sourceDir);
18
- } catch (err) {
19
- console.error("Error:", err.message);
20
- process.exit(1);
21
- }
13
+ const data = await fs.readFile(filePath, "utf8");
14
+ const json = JSON.parse(data);
15
+ const sourceDir = path.dirname(filePath);
16
+ await chatgptToMarkdown(json, sourceDir);
22
17
  }
23
18
 
24
19
  run();
package/index.js CHANGED
@@ -34,7 +34,7 @@ function wrapHtmlTagsInBackticks(text) {
34
34
  function indent(str) {
35
35
  return str
36
36
  .split("\n")
37
- .map((v) => ` ${v}\n`)
37
+ .map((v) => (v.trim() ? ` ${v}\n` : ""))
38
38
  .join("");
39
39
  }
40
40
 
@@ -69,13 +69,14 @@ async function chatgptToMarkdown(json, sourceDir, { dateFormat } = { dateFormat:
69
69
  }
70
70
 
71
71
  for (const conversation of json) {
72
- const sanitizedTitle = sanitizeFileName(conversation.title);
72
+ const sanitizedTitle = sanitizeFileName(conversation.title) || conversation.conversation_id;
73
73
  const fileName = `${sanitizedTitle}.md`;
74
74
  const filePath = path.join(sourceDir, fileName);
75
75
  const title = `# ${wrapHtmlTagsInBackticks(conversation.title)}\n`;
76
76
  const metadata = [
77
77
  `- Created: ${dateFormat(new Date(conversation.create_time * 1000))}\n`,
78
78
  `- Updated: ${dateFormat(new Date(conversation.update_time * 1000))}\n`,
79
+ `- Link: https://chatgpt.com/c/${conversation.conversation_id}\n`,
79
80
  ].join("");
80
81
  const messages = Object.values(conversation.mapping)
81
82
  .map((node) => {
@@ -113,6 +114,11 @@ async function chatgptToMarkdown(json, sourceDir, { dateFormat } = { dateFormat:
113
114
  case "system_error":
114
115
  body = `${content.name}\n\n${content.text}\n\n`;
115
116
  break;
117
+ case "user_editable_context":
118
+ // We don't want to pollute all Markdown with custom instuctions
119
+ // in content.user_instructions. So skip it
120
+ body = "";
121
+ break;
116
122
  default:
117
123
  body = content;
118
124
  break;
package/index.test.js CHANGED
@@ -22,6 +22,7 @@ describe("chatgptToMarkdown", () => {
22
22
  title: "Test Conversation",
23
23
  create_time: 1630454400,
24
24
  update_time: 1630458000,
25
+ conversation_id: "abc123",
25
26
  mapping: {
26
27
  0: {
27
28
  message: {
@@ -42,6 +43,7 @@ describe("chatgptToMarkdown", () => {
42
43
 
43
44
  - Created: ${formatDate(1630454400 * 1000)}
44
45
  - Updated: ${formatDate(1630458000 * 1000)}
46
+ - Link: https://chatgpt.com/c/abc123
45
47
 
46
48
  ## user (John)
47
49
 
@@ -226,4 +228,153 @@ describe("chatgptToMarkdown", () => {
226
228
  const fileContent = await fs.readFile(path.join(tempDir, "Test Conversation.md"), "utf8");
227
229
  expect(fileContent).toContain('```javascript\nconsole.log("Hello, world!");\n```\n');
228
230
  });
231
+
232
+ it("should skip user_editable_context content", async () => {
233
+ const json = [
234
+ {
235
+ title: "Test Conversation",
236
+ create_time: 1630454400,
237
+ update_time: 1630458000,
238
+ mapping: {
239
+ 0: {
240
+ message: {
241
+ author: { role: "user" },
242
+ content: {
243
+ content_type: "user_editable_context",
244
+ user_profile: "test profile",
245
+ user_instructions: "test instructions",
246
+ },
247
+ },
248
+ },
249
+ },
250
+ },
251
+ ];
252
+ await chatgptToMarkdown(json, tempDir);
253
+ const fileContent = await fs.readFile(path.join(tempDir, "Test Conversation.md"), "utf8");
254
+ expect(fileContent).not.toContain("test profile");
255
+ expect(fileContent).not.toContain("test instructions");
256
+ });
257
+
258
+ it("should handle system_error content", async () => {
259
+ const json = [
260
+ {
261
+ title: "Test Conversation",
262
+ create_time: 1630454400,
263
+ update_time: 1630458000,
264
+ mapping: {
265
+ 0: {
266
+ message: {
267
+ author: { role: "system" },
268
+ content: {
269
+ content_type: "system_error",
270
+ name: "Error Name",
271
+ text: "Error details",
272
+ },
273
+ },
274
+ },
275
+ },
276
+ },
277
+ ];
278
+
279
+ await chatgptToMarkdown(json, tempDir);
280
+ const fileContent = await fs.readFile(path.join(tempDir, "Test Conversation.md"), "utf8");
281
+ expect(fileContent).toContain("Error Name\n\nError details\n\n");
282
+ });
283
+
284
+ it("should handle execution_output content", async () => {
285
+ const json = [
286
+ {
287
+ title: "Test Conversation",
288
+ create_time: 1630454400,
289
+ update_time: 1630458000,
290
+ mapping: {
291
+ 0: {
292
+ message: {
293
+ author: { role: "assistant" },
294
+ content: {
295
+ content_type: "execution_output",
296
+ text: "Program output",
297
+ },
298
+ },
299
+ },
300
+ },
301
+ },
302
+ ];
303
+
304
+ await chatgptToMarkdown(json, tempDir);
305
+ const fileContent = await fs.readFile(path.join(tempDir, "Test Conversation.md"), "utf8");
306
+ expect(fileContent).toContain("```\nProgram output\n```");
307
+ });
308
+
309
+ it("should handle code content with unknown language", async () => {
310
+ const json = [
311
+ {
312
+ title: "Test Conversation",
313
+ create_time: 1630454400,
314
+ update_time: 1630458000,
315
+ mapping: {
316
+ 0: {
317
+ message: {
318
+ author: { role: "assistant" },
319
+ content: {
320
+ content_type: "code",
321
+ language: "unknown",
322
+ text: "some code",
323
+ },
324
+ },
325
+ },
326
+ },
327
+ },
328
+ ];
329
+
330
+ await chatgptToMarkdown(json, tempDir);
331
+ const fileContent = await fs.readFile(path.join(tempDir, "Test Conversation.md"), "utf8");
332
+ expect(fileContent).toContain("```\nsome code\n```");
333
+ });
334
+
335
+ it("should handle tether_browsing_display with summary", async () => {
336
+ const json = [
337
+ {
338
+ title: "Test Conversation",
339
+ create_time: 1630454400,
340
+ update_time: 1630458000,
341
+ mapping: {
342
+ 0: {
343
+ message: {
344
+ author: { role: "tool" },
345
+ content: {
346
+ content_type: "tether_browsing_display",
347
+ summary: "Page Summary",
348
+ result: "Search Result",
349
+ },
350
+ },
351
+ },
352
+ },
353
+ },
354
+ ];
355
+
356
+ await chatgptToMarkdown(json, tempDir);
357
+ const fileContent = await fs.readFile(path.join(tempDir, "Test Conversation.md"), "utf8");
358
+ expect(fileContent).toContain("```\nPage Summary\nSearch Result\n```");
359
+ });
360
+
361
+ it("should throw TypeError for invalid arguments", async () => {
362
+ await expect(chatgptToMarkdown("not an array", tempDir)).rejects.toThrow(TypeError);
363
+ await expect(chatgptToMarkdown([], 123)).rejects.toThrow(TypeError);
364
+ });
365
+
366
+ it("should use conversation_id as filename when sanitized title is empty", async () => {
367
+ const json = [
368
+ {
369
+ title: "?????", // Will be sanitized to empty string
370
+ conversation_id: "abc123",
371
+ create_time: 1630454400,
372
+ update_time: 1630458000,
373
+ mapping: {},
374
+ },
375
+ ];
376
+
377
+ await chatgptToMarkdown(json, tempDir);
378
+ await expect(fs.access(path.join(tempDir, "abc123.md"))).resolves.not.toThrow();
379
+ });
229
380
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chatgpt-to-markdown",
3
- "version": "1.5.3",
3
+ "version": "1.5.5",
4
4
  "description": "Convert ChatGPT exported conversations.json to Markdown",
5
5
  "main": "index.js",
6
6
  "type": "module",