Writing and editing on the Medium website is not only painful and also wasting of time on style wrangling. I am wondering if there is a way to write in the Markdown file and then publish to Medium directly using Python.
After some searches, no open source and free solution exist, not to mention image uploading capability. So I decided to build one and open source it. This article is a summary of my solution.
To publish a Markdown file to the Medium site, I have to solve the following four problems:
- Translate Markdown to HTML string.
- Extract meta info like
title
andtags
from Markdown file. - The hardest part, upload images/GIFs to Medium server and update HTML image links.
- Post the HTML string to Medium site.
Before moving on, Go to your medium home page to get the access token:
Click your avatar -> Settings -> Security and apps -> Integration tokens
The token will be used for Post uploading.
Check out the code I post in github azmdpub. Pull the code, compile it, and start publishing your markdown files to Medium with images.
The code works in Windows, Linux and MacOS.
Here are steps to use it:
Step 1. Pull the code to your local machine
Step 2. Navigate to the git folder, and install dependant packages
pip install -r requirments.txt
Step 3. Use pyinstaller
to compile the code
Install pyinstaller
:
pip install pyinstaller
Compile python code:
cd src/azmdpub
pyinstaller -F 'azmdpub.py' -n 'azmdpub' -c
Step 4. Now you shall see the executable file under dist
folder, you can execute it like this:
./dist/azmdpub 'markdown_file.md'
The program will ask for access token
in the first time use, then cache the token in file ~/medium.conf
.
The content in the markdown_file.md
is like this:
---
title: Test markdown file
tags: ["tag1","tag2","tag3"]
---
# Test markdown file
This is a test markdown file
## Title2
content content
...
Note that the title and tags in the yaml meta section will be used during the uploading session.
If you still want to know how it works, please read on.
With some researching and comparisons, I found package markdown-it-py is the best. The only problem is its confusing installation instruction from its readme file.
Here is the working way to install the package with pip
.
Install markdown-it-py
:
pip install markdown-it-py
Enable plugins:
pip install mdit_py_plugins
Translate Markdown file to HTML string using the following code:
from markdown_it import MarkdownIt
from mdit_py_plugins.front_matter import front_matter_plugin
from mdit_py_plugins.footnote import footnote_plugin
md = (
MarkdownIt()
.use(front_matter_plugin)
.use(footnote_plugin)
.enable('image')
.enable('table')
)
with open(md_path,'r') as f:
md_text = f.read()
I can use Regex to extract the title and tags, but there is another module can do the extraction job. Module markdown
can extract the meta information such as title
and tags
defined in the yaml header.
---
title: Test markdown file
tags: ["tag1","tag2","tag3"]
---
# Test markdown file
Here is the code do the meta extraction work:
import markdown
with open(md_path,'r') as f:
md_text = f.read()
md = markdown.Markdown(extensions = ['meta'], output_format='html5')
html = md.convert(md_text)
meta = md.Meta
return {
'title':meta['title'][0]
,'tags':meta['tags'][0]
}
I spent most of my time on implementing this feature, the post boundary
settings is confusing. With the help from this solution about MultipartEncoder
, I managed to implement the image uploading function. The code support four image types:
- image/jpeg
- image/png
- image/gif
- image/tiff
Yes, Animated gifs are also supported.
Use your power for good
Here is the full function code, to run it, please check out the whole code file using the below link.
def upload_img(self,img_path):
'''
Upload one image to medium server
'''
post_image_url = "https://api.medium.com/v1/images"
img_type = img_path.split('.')[-1]
files = {
'image': (
img_path
,open(img_path,'rb')
,f'image/{img_type}'
)
}
m = MultipartEncoder(files, boundary='FormBoundaryXYZ')
image_up_headers = {
'Authorization': "Bearer " + self.access_token
,'Content-Type': m.content_type
,'Accept': 'application/json'
,'Accept-Charset': 'utf-8'
}
r = requests.post(
post_image_url
,data = m.to_string()
,headers = image_up_headers
)
r_obj = json.loads(r.text)
return r_obj['data']['url']
See full code file here.
From the Medium's official API document on Posting. You will see an authorId
with double parentheses.
POST https://api.medium.com/v1/users/{{authorId}}/posts
The authorId
here is the user id or client id, you can fetch it with this code:
self.base_url = "https://api.medium.com/v1/"
self.headers = {
'Authorization': "Bearer " + self.access_token
,'Content-Type': 'application/json'
,'Accept': 'application/json'
,'Accept-Charset': 'utf-8'
}
try:
r = requests.get(self.base_url+'me',headers=self.headers)
self.client_obj = json.loads(r.text)
self.client_id = self.client_obj['data']['id']
...
See full code here. The {{}}
is unnecessary, you should remove it in the request call.
Here is my implementation of blog Posting:
def pub_post(self,md_path,status='draft'):
post_url = f"{self.base_url}/users/{self.client_id}/posts"
post_obj = self.md_to_html_meta(md_path)
html_content = self.md_to_html(md_path)
post_json = {
"title": post_obj['title'],
"contentFormat": "html",
"content": html_content,
"tags": json.loads(post_obj['tags']),
"publishStatus": status
}
r = requests.post(post_url,json=post_json,headers=self.headers)
print(r.text)
See full code here.
There are some other trivial processes I did not covered above, for example, the image URLs needed to be updated with the uploaded image links. These should be easy by using Python's string replacing functions.
This doc is written and edited with Markdown in VSCode. And post with azmdpub
without any editing in Medium editor. Let me know if you are stuck or have any questions. Hope this tool enrich your writing experience.