├── .gitignore ├── index.js ├── lib ├── mdConverters.js └── convertFromUrl.js ├── LICENSE ├── package.json ├── README.md └── examples ├── exampleOutputWithCodeBlocks.md └── exampleOutput.md /.gitignore: -------------------------------------------------------------------------------- 1 | # git ignore 2 | 3 | node_modules/ 4 | .vscode 5 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const convertFromUrl = require('./lib/convertFromUrl') 4 | 5 | module.exports = { 6 | convertFromUrl 7 | } 8 | 9 | // if run as cmd utility 10 | if (typeof require !== 'undefined' && require.main === module) { 11 | if(process.argv.length < 3){ 12 | console.log('What url to convert?') 13 | return 14 | } 15 | convertFromUrl(process.argv[2]).then(function (markdown) { 16 | console.log(markdown); //=> Markdown content of medium post 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /lib/mdConverters.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const converters = [ 4 | { 5 | filter: 'section', 6 | replacement: function(content) { 7 | return content; 8 | } 9 | }, 10 | { 11 | filter: 'div', 12 | replacement: function(content) { 13 | return content; 14 | } 15 | }, 16 | { 17 | filter: 'figure', 18 | replacement: function(content) { 19 | return content; 20 | } 21 | }, 22 | { 23 | filter: 'figcaption', 24 | replacement: function(content) { 25 | return content; 26 | } 27 | } 28 | ]; 29 | 30 | module.exports = converters 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 SkillFlow, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "medium-to-markdown", 3 | "version": "0.0.3", 4 | "description": "import a medium URL and convert it to markdown", 5 | "main": "index.js", 6 | "bin": "./index.js", 7 | "scripts": { 8 | "convert": "node index.js", 9 | "test": "echo \"Error: no test specified\" && exit 1", 10 | "genExample": "npm run convert https://medium.com/@almenon214/keeping-yourself-motivated-as-a-coder-a16a6fcf49c7 > examples/exampleOutput.md" 11 | }, 12 | "author": "SkillFlow, Inc.", 13 | "license": "MIT", 14 | "keywords": [ 15 | "medium", 16 | "convert", 17 | "to", 18 | "markdown" 19 | ], 20 | "dependencies": { 21 | "cheerio": "^1.0.0-rc.3", 22 | "request": "^2.88.0", 23 | "turndown": "^5.0.3", 24 | "turndown-plugin-gfm": "^1.0.2" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/SkillFlowHQ/medium-to-markdown.git" 29 | }, 30 | "bugs": { 31 | "url": "https://github.com/SkillFlowHQ/medium-to-markdown/issues" 32 | }, 33 | "homepage": "https://github.com/SkillFlowHQ/medium-to-markdown#readme" 34 | } 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Medium to markdown 2 | [![npm](https://img.shields.io/npm/v/medium-to-markdown.svg)](https://npmjs.com/package/medium-to-markdown) [![npm](https://img.shields.io/npm/l/medium-to-markdown.svg)]() 3 | 4 | This module lets you take a medium post and convert it to markdown. 5 | 6 | ### Command Line Usage 7 | 8 | **Setup:** 9 | 10 | 1. Install npm if not already installed 11 | 2. Clone the repo 12 | 3. run `npm install` inside the repo 13 | 14 | **Outputting to command line:** 15 | 16 | `npm run convert https://medium.com/@almenon214/keeping-yourself-motivated-as-a-coder-a16a6fcf49c7` 17 | 18 | Replace the link with the article you want to convert. 19 | 20 | **Outputting to file:** 21 | 22 | `npm run convert https://medium.com/@almenon214/keeping-yourself-motivated-as-a-coder-a16a6fcf49c7 > exampleOutput.md` 23 | 24 | Replace the link with the article you want to convert. 25 | 26 | ### What does the output look like? 27 | 28 | See `examples/exampleOutput.md` for an example of what https://medium.com/@almenon214/keeping-yourself-motivated-as-a-coder-a16a6fcf49c7 looks like when converted to markdown. 29 | 30 | ### API Usage 31 | 32 | Currently, the module supports getting the markdown from a medium post by URL. 33 | 34 | ```javascript 35 | const mediumToMarkdown = require('medium-to-markdown'); 36 | 37 | mediumToMarkdown.convertFromUrl('') 38 | .then(function (markdown) { 39 | console.log(markdown); //=> Markdown content of medium post 40 | }); 41 | ``` 42 | -------------------------------------------------------------------------------- /lib/convertFromUrl.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const request = require('request'); 4 | const cheerio = require('cheerio'); 5 | const TurndownService = require('turndown') 6 | const gfm = require('turndown-plugin-gfm').gfm 7 | 8 | const converters = require('./mdConverters'); 9 | 10 | 11 | const turndownService = new TurndownService() 12 | turndownService.use(gfm) 13 | 14 | converters.forEach((converter)=>{ 15 | turndownService.addRule(converter.filter, converter) 16 | }) 17 | 18 | // following block adapted from https://github.com/domchristie/turndown/blob/61c2748c99fc53699896c1449f953ea492311c5b/src/commonmark-rules.js#L131 19 | turndownService.addRule('mediumInlineLink', { 20 | filter: function (node, options) { 21 | return ( 22 | options.linkStyle === 'inlined' && 23 | node.nodeName === 'A' && 24 | node.getAttribute('href') 25 | ) 26 | }, 27 | 28 | replacement: function (content, node) { 29 | var href = node.getAttribute('href') 30 | 31 | // following code added in to handle medium relative urls 32 | // otherwise the link to article "foo" in the new website would go to 33 | // https://newwebsite.com/@username/foo-a16a6fcf49c7 which doesn't exist 34 | if(href.startsWith('/')){ 35 | href = "https://medium.com" + href 36 | } 37 | 38 | var title = node.title ? ' "' + node.title + '"' : '' 39 | return '[' + content + '](' + href + title + ')' 40 | } 41 | }) 42 | 43 | // Medium has these weird hidden images that are in the html and get rendered 44 | // by turndown. We filter these out. 45 | turndownService.addRule('noHiddenImages', { 46 | filter: function (node, options) { 47 | return ( 48 | node.nodeName === 'IMG' && 49 | node.getAttribute('src') && 50 | node.getAttribute('src').endsWith('?q=20') 51 | ) 52 | }, 53 | 54 | replacement: function () { 55 | return '' 56 | } 57 | }) 58 | 59 | turndownService.addRule('code blocks', { 60 | filter: 'pre', 61 | replacement: function (content, node) { 62 | return "```\n" + content + "\n```" 63 | } 64 | }) 65 | 66 | // todo: filter out profile header 67 | // (right below title, the div with author profile pic and name and time to read article) 68 | // unfortunately Medium uses randomly generated CSS properties which makes it hard to 69 | // identify the header and strip it out. For example, I could strip the div with 70 | // the class "eq" but the next time medium updated their CSS the div would have 71 | // a different class name and the filter wouldn't work anymore 72 | 73 | function convertFromUrl(url) { 74 | return new Promise(function(resolve, reject) { 75 | request({ 76 | uri: url, 77 | method: 'GET' 78 | }, function (err, httpResponse, body) { 79 | 80 | if (err) 81 | return reject(err); 82 | 83 | let $ = cheerio.load(body); 84 | let html = $('article').html() || ''; 85 | let markdown = turndownService.turndown(html); 86 | 87 | resolve(markdown); 88 | 89 | }); 90 | }); 91 | } 92 | 93 | module.exports = convertFromUrl; 94 | -------------------------------------------------------------------------------- /examples/exampleOutputWithCodeBlocks.md: -------------------------------------------------------------------------------- 1 | 2 | > medium-to-markdown@0.0.3 convert C:\dev\misc\medium-to-markdown 3 | > node index.js "https://medium.com/@almenon214/adding-telemetry-to-your-vscode-extension-f3d52d2e573c" 4 | 5 | Adding telemetry to your vscode extension 6 | ========================================= 7 | 8 | [![Almenon](https://miro.medium.com/fit/c/96/96/1*4FAOBq9qVna6uHkxaolTtA.png)](https://medium.com/@almenon214?source=post_page-----f3d52d2e573c----------------------)[Almenon](https://medium.com/@almenon214?source=post_page-----f3d52d2e573c----------------------)Follow[May 6, 2018](https://medium.com/@almenon214/adding-telemetry-to-your-vscode-extension-f3d52d2e573c?source=post_page-----f3d52d2e573c----------------------) · 3 min read 9 | 10 | The [1% rule](https://en.wikipedia.org/wiki/1%25_rule_(Internet_culture)) of the internet states that the vast majority of users do not engage with the content — they use it and see it, but leave no feedback. In some cases they might give your product one try, don’t like it, and never come back. Or not even try it! This lack of feedback makes it very hard to improve your product or market it to new users. 11 | 12 | Enter telemetry — by automatically collecting statistics and/or errors, you can get feedback without having to conduct expensive market research. 13 | 14 | [Azure application insights](https://azure.microsoft.com/en-us/services/application-insights/) makes this collection very simple (and free): 15 | 16 | ``` 17 | npm install [vscode-extension-telemetry](https://github.com/Microsoft/vscode-extension-telemetry) --save 18 | ``` 19 | 20 | If you want a real-world example of its useage you can take a look at how I use it in [AREPL-vscode](https://github.com/Almenon/AREPL-vscode/blob/master/src/telemetry.ts). 21 | 22 | Once it is working you should see events appear in the metrics explorer in azure: 23 | 24 | 25 | 26 | But the beauty of application insights is not just in the logging — Microsoft offers a sophistacted query language, similar to SQL / Splunk. 27 | 28 | 29 | 30 | distinct users, ordered by last use date. 31 | 32 | Some other useful queries: 33 | 34 | ``` 35 | // heaviest users by avg time spent using extcustomEvents | 36 | where timestamp < now() and name=="almenon.arepl/closed" | 37 | summarize timeOpen=avg(todouble(customDimensions.timeSpent)) by cloud\_RoleInstance | order by timeOpen 38 | ``` 39 | 40 | * * * 41 | 42 | ``` 43 | // most frequent users by number of times openedcustomEvents | 44 | where timestamp < now() and name=="almenon.arepl/closed" | 45 | summarize numEvents=count(iKey) by cloud\_RoleInstance | order by numEvents 46 | ``` 47 | 48 | * * * 49 | 50 | You can even project your results into graphs 51 | 52 | ``` 53 | customEvents | where name == 'almenon.arepl/closed' | summarize count() by client\_CountryOrRegion | render piechart 54 | ``` 55 | 56 | 57 | 58 | The analysis leads to some interesting conclusions: 59 | 60 | 1. Despite having hundreds of downloads, the actual user count is much much lower. 5 people have used it so far with one person using it twice… not great statistics. Should pick up once I market AREPL at pycon. 61 | 2. The range of users is quite geographically diverse. You don’t just get people in California or America; there’s people from canada, italy, portugal, all sorts of places. I guess that is to be expected with internet marketing — people can see your extension from countries across the world. 62 | 63 | Once I get more people using the extension I should be able to draw more insights — like what settings they change, for example. Or how often errors occur. Or the most popular time the extension is used. Really, sky’s the limit! 64 | 65 | **Update:** further anlysis of my telemetry data can be found at: 66 | 67 | [ 68 | 69 | AREPL stats for June 70 | -------------------- 71 | 72 | ### So about a month ago I added telemetry to AREPL-vscode to get an idea of how my user base was doing. The information I… 73 | 74 | #### medium.com 75 | 76 | ](https://medium.com/@almenon214/arepl-stats-for-june-5e0c87636c3) 77 | -------------------------------------------------------------------------------- /examples/exampleOutput.md: -------------------------------------------------------------------------------- 1 | 2 | > medium-to-markdown@0.0.3 convert C:\dev\misc\medium-to-markdown 3 | > node index.js "https://medium.com/@almenon214/keeping-yourself-motivated-as-a-coder-a16a6fcf49c7" 4 | 5 | Keeping yourself motivated as a coder 6 | ===================================== 7 | 8 | [![Almenon](https://miro.medium.com/fit/c/96/96/1*4FAOBq9qVna6uHkxaolTtA.png)](https://medium.com/@almenon214?source=post_page-----a16a6fcf49c7----------------------)[Almenon](https://medium.com/@almenon214?source=post_page-----a16a6fcf49c7----------------------)Follow[Sep 23, 2018](https://medium.com/@almenon214/keeping-yourself-motivated-as-a-coder-a16a6fcf49c7?source=post_page-----a16a6fcf49c7----------------------) · 4 min read 9 | 10 | Programmers probably have one of the most distracting jobs in existence. Their entire workday is spent in front of a computer — a literally endless source of entertainment and pleasure, a limitless virtual heaven. With literally just a click of a button, you could be in another world. You could be slaying dragons. You could be learning about the [great molasses flood of 1919](https://www.damninteresting.com/the-great-molasses-flood-of-1919/). You could literally be doing _anything._ So why do programmers spend their time programming, a hobby that could only be described as exciting by masochistic workaholics? 11 | 12 | 13 | 14 | Pictured: sticky, sweet, syrupy disaster 15 | 16 | Well, the vast sums of money that a software engineer gets is a pretty good reason. Another reason is that they probably are workaholics. You don’t see doctors spending all-nighters at hackathons for a 20$ starbucks giftcard, or linguists designing [languages composed of Arnold Schwarzenegger quotes](http://lhartikk.github.io/ArnoldC/) as a joke. 17 | 18 | Or maybe they do it for the pride. Making open source projects gets you virtual street cred — the bigger the project, the better the cred. Guido, the creator of python, is practically a king. Before abdicating his throne he was called the BDFL, or _benevolent dictator for life_. Contributing to a project is a way of becoming something greater than youself and leaving a lasting mark on society. Make a commit, and git will forever stamp you into history, a hash 785b312 saying **_I was there_**_._ _xNinja47, 4/30/2005, changed 3 files for the better_. 19 | 20 | Or it could just be for practical reasons — you need to keep your skills sharp, after all. And open source work looks good on your resume. Not everyone loves programming — for some it’s just a high paying job to slog through untill they get home to their families and social life. 21 | 22 | But if you’re reading this article, maybe you _don’t_ have anything to motivate you. Maybe reddit sounds like a good idea. Stop! Don’t do it! No [funny cat pics](https://i.reddituploads.com/788c8a3ab05243d59259410aa0eb71f9?fit=max&h=1536&w=1536&s=47f691afce2df783c20f31d702427957)! No [cute bears](https://i.redd.it/vkyg59t2ddp01.jpg)! Keep on writing this medium article! (_I may be speaking to myself here)_. So without further ado, here is some strategies to employ for keeping yourself in shape: 23 | 24 | 1. **Deadlines**: set deadlines for yourself — github [milestones](https://github.com/Almenon/AREPL-vscode/milestones?state=closed) and [issues](https://github.com/Almenon/AREPL-vscode/issues) are great for this. If you look at my projects I have _hundreds_ of issues I create for myself to keep track of my work. Another benefit of writing issues is that you have written documentation to refer to when you go back to working on it. Or if you’re not on github, you can simply set up calendar events. Maybe even tell your friends that you will get something done by a certain time — that creates commitment. 25 | 2. **Releases.** Your project is a piece of crap. It barely functions, the architecture is horrible, and [you don’t even really know what you’re doing](https://en.wikipedia.org/wiki/Impostor_syndrome). The last thing you want to do is to expose your baby duckling to the public. But at a certain point you have to cut your losses — you can’t just keep on working on something forever, because then you’re going to have burnout. And you may be pleasantly surprised. Maybe people will like it. Worst case scenario, people will hate it, but in the process of shitting all over it they inadvertently give you helpful advice to fix it. In fact, in my experience the detractors are more helpful than the fans, because they will give you actual advice, not just “oh that looks cool”. 26 | 3. **Maximize Fun**. Look for more pleasurable and fulfilling fun, rather than lazy and cheap entertainment. For example, it’s far too easy to _control-n_ to open a new tab and type in the letter r to get the autocomplete for reddit. That’s just 3 keys and a enter. Reddit is fun, I love it, but its filled with low effort reposts. Taking the time to watch a show or even a movie might be a better idea — you have a clear chance to stop (at the end) rather than scrolling through reddit’s infinite amount of content. Important disclaimer: _never_ click on netflix’s next episode button. That’s a feature designed by the devil to get you to binge-watch shows and hate yourself afterwards. If you actually have time to watch the next episode then you can wait for the credits to go by. Or more likely, realize what you are doing and go do something productive. 27 | 4. **Take walks.** Rest your eyes. Think about how to approach the problem. If you’re me, try reading while walking. It’s not that dangerous — you’ve only bumped into a pole once or twice. 28 | 5. **Get the support of others**. Join a slack/discord community. Make a program that other people use. Ask for advice from your friends. Go to programming meetups. Go to [programming conventions](https://medium.com/@almenon214/pycon-2018-6b1c45889e3b) — it’s a great excuse for a vacation, and it’s a lot less dry than it sounds. Go past your normal boundaries — you might be surprised as to what you are capable of. 29 | 6. **Add** [**telemetry**](https://medium.com/@almenon214/adding-telemetry-to-your-vscode-extension-f3d52d2e573c) **or** [**alerting**](https://medium.com/@almenon214/setting-up-email-alerts-for-your-vscode-extension-using-azure-d755651b2e0d) **to your program**. It might not be practical for a small application, but it is _extremely_ useful as a motivating tool. Every day I get emails from around the world. People use [AREPL](https://github.com/Almenon/AREPL-vscode) in [New York, Canada, Brazil, Chile. Europe, Japan, Israel, and Pakistan](https://medium.com/@almenon214/arepl-stats-for-june-5e0c87636c3). Every day it’s a different country. It’s significant to know that people are using what I make and relying on me to continue developing it. In a similar vein, I also get emails whenever my reddit bot posts. I can see if people upvote it or if there are problems with the bot I need to fix. 30 | 31 | Hopefully these tips are helpful to you. If they are, I’d love to hear it. Or maybe you have a tip of your own — either way, leave a comment below! 32 | --------------------------------------------------------------------------------