-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.js
More file actions
142 lines (123 loc) · 5.35 KB
/
index.js
File metadata and controls
142 lines (123 loc) · 5.35 KB
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
#!/usr/bin/env node
const axios = require('axios');
const jsdom = require("jsdom");
const JSDOM = jsdom.JSDOM
const fs = require('fs').promises;
const dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
const monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
const argv = require('yargs')
.option('username', {
alias: 'u',
type: 'string',
description: 'Username or ID of the codewars user',
})
.option('token', {
alias: 't',
type: 'string',
description: '\'remember_user_token\' value from codewars cookie',
})
.option('filename', {
alias: 'f',
type: 'string',
default: 'README.md',
description: 'Filename to output codewars solutions as markdown',
})
.demandOption(['u', 't'])
.argv
const BASE_URL = "https://www.codewars.com"
const USERNAME = argv.username
const TOKEN = argv.token
const FILENAME = argv.filename
// Parse solution code block from HTML page (code is not available via API) to Map
// Returns:
// {
// solutionId: {
// solutions: [{
// code: *codeBlock*,
// language: *solutionLanguage*
// }]
// },
// solutionsId: {
// ...
// }
const getCompletedSolutions = async (page = 0, codeMap = new Map()) => {
const { data } = await axios.get(`${BASE_URL}/users/${USERNAME}/completed_solutions${!!page ? `?page=${page}` : ''}`, {headers: {cookie: `remember_user_token=${TOKEN}`}})
const dom = new JSDOM(data)
const solutionElements = dom.window.document.getElementsByClassName('solutions')
// Create a map of solution ID to code block and language
if (solutionElements.length) {
Array.prototype.map.call(solutionElements, solutionElement => {
// Parse challenge ID
const titleEl = solutionElement.getElementsByTagName('a')[0]
const id = titleEl.href.replace('/kata/', '') // solution id
// Parse solution language
const languageEl = solutionElement.getElementsByTagName('h6')[0]
const language = languageEl.innerHTML.slice(0, languageEl.innerHTML.length - 1)
// Parse solution code block
const code = solutionElement.getElementsByTagName('code')[0].textContent
// Update the solution array for the challenge if it was completed in more than one language
const value = codeMap.get(id)
value ?
codeMap.set(id, { solutions: value.solutions.push({code, language})}) :
codeMap.set(id, { solutions: [{ code, language }]})
})
return getCompletedSolutions(page+1, codeMap)
} else return codeMap
}
// Use API to get solutions
const getCompletedChallenges = async(page = 0, previousData = []) => {
try {
const { data: {
totalPages, data
} } = await axios.get(`${BASE_URL}/api/v1/users/${USERNAME}/code-challenges/completed?page=${page}`)
const results = [...previousData, ...data]
if (totalPages > (page + 1)) await getCompletedChallenges(page++, results)
return results
} catch(error) {
throw error
}
}
const mapToMarkdown = (solutionCodeMap) => {
let markdown = ""
solutionCodeMap.forEach((challenge) => {
// Solution Title
// TODO: add coloring to different ranks
markdown += `# [${challenge.name}](${challenge.url}) - ${challenge.rank.name}\n`
// Complete At
const date = new Date(challenge.completedAt)
const dayName = dayNames[date.getDay()]
const monthName = monthNames[date.getMonth()]
markdown += `#### Completed: ${dayName}, ${monthName} ${date.getDate()}, ${date.getFullYear()}\n`
// Solution CodeBlock
challenge.solutions.forEach(({code, language}) => {
markdown += `### ${language}\n`
markdown += `\`\`\`${language.toLowerCase()}\n${code}\n\`\`\``
})
markdown += '\n\n'
})
return markdown
}
// Main
(async () => {
try {
// Parse completed challenge solutions from HTML
const solutionCodeMap = await getCompletedSolutions()
// Get Code Challenge information for all completed code challenges https://dev.codewars.com/#get-code-challenge
const codeChallenges = await Promise.all(Array.from(solutionCodeMap.keys()).map(id => axios.get(`${BASE_URL}/api/v1/code-challenges/${id}`).then(res => res.data)))
solutionCodeMap.forEach((value, key) => {
const codeChallenge = codeChallenges.find((challenge => challenge.id === key))
if(codeChallenge) solutionCodeMap.set(key, {...value, ...codeChallenge})
})
// Get information on user's challenges https://dev.codewars.com/#get-user:-completed-challenges
const completedChallenges = await getCompletedChallenges()
// Add completedAt for user's challenge
completedChallenges.forEach((challenge) => {
solutionCodeMap.set(challenge.id, {...solutionCodeMap.get(challenge.id), completedAt: challenge.completedAt})
})
// Output challenge solutions to markdown file
const output = mapToMarkdown(solutionCodeMap)
await fs.writeFile(FILENAME, output)
} catch (err) {
console.log(err)
}
})()