ipynb_to_medium

  1import html
  2import os
  3import re
  4
  5import jupytext
  6import markdown
  7import requests
  8from bs4 import BeautifulSoup
  9from markdown.extensions import fenced_code
 10from nbformat import read
 11
 12
 13class NotebookToMedium:
 14    """
 15    Write your Medium articles in a Jupyter Notebook and push it directly to Medium using the Medium API.
 16    """
 17
 18    def __init__(self):
 19        self.md = markdown.Markdown(extensions=[fenced_code.FencedCodeExtension()])
 20
 21    def convert_notebook_to_markdown(self, input_notebook, output_markdown):
 22        """
 23         Convert a Jupyter notebook to Markdown and save it to a file.
 24
 25        Args:
 26            input_notebook (str): Path to the input Jupyter notebook.
 27            output_markdown (str): Path to the output Markdown file.
 28        """
 29        with open(input_notebook, "r", encoding="utf-8") as notebook_file:
 30            notebook = read(notebook_file, as_version=4)
 31
 32        markdown_text = jupytext.writes(notebook, fmt="markdown")
 33
 34        # Remove the Jupytext metadata from the Markdown
 35        markdown_text = re.sub(
 36            r"---\s+jupyter:\s+\S+.*?---", "", markdown_text, flags=re.DOTALL
 37        )
 38
 39        with open(output_markdown, "w", encoding="utf-8") as output_file:
 40            output_file.write(markdown_text)
 41
 42    def convert_markdown_to_html(
 43        self,
 44        input_markdown,
 45        output_html,
 46        nest_as_medium=True,
 47        transform_pre_code=True,
 48        add_title_to_pictures=True,
 49    ):
 50        """
 51        Convert a Markdown file to HTML and save it to a file.
 52
 53        Args:
 54            input_markdown (str): Path to the input Markdown file.
 55            output_html (str): Path to the output HTML file.
 56        """
 57
 58        with open(input_markdown, "r", encoding="utf-8") as markdown_file:
 59            markdown_text = markdown_file.read()
 60
 61        markdown_text = html.escape(markdown_text)
 62
 63        html_text = self.md.convert(markdown_text)
 64
 65        if transform_pre_code:
 66            html_text = self.transform_pre_code(html_text)
 67
 68        if nest_as_medium:
 69            html_text = self.transform_nested_ul_to_medium_nested_list(html_text)
 70
 71        if add_title_to_pictures:
 72            html_text = self.add_title_to_pictures(html_text)
 73
 74        html_text = html.unescape(html_text)
 75
 76        with open(output_html, "w", encoding="utf-8") as output_file:
 77            output_file.write(html_text)
 78
 79    def convert_notebook_to_html(self, input_notebook, output_html):
 80        """
 81        Convert a Jupyter notebook to HTML.
 82
 83        This method performs the conversion by first converting the notebook to Markdown,
 84            and then converting the Markdown to HTML.
 85            IMPORTANT: This is to avoid generating binaries, using intermediate Markdown instead
 86            of converting directly to html with jupytext is purposeful
 87
 88        Args:
 89            input_notebook (str): Path to the input Jupyter notebook.
 90            output_html (str): Path to the output HTML file.
 91        """
 92        temp_markdown = "temp_markdown.md"
 93
 94        # Convert the notebook to Markdown
 95        self.convert_notebook_to_markdown(input_notebook, temp_markdown)
 96
 97        # Convert the Markdown to HTML
 98        self.convert_markdown_to_html(temp_markdown, output_html)
 99
100        # Remove the temporary Markdown file
101        os.remove(temp_markdown)
102
103    def transform_nested_ul_to_medium_nested_list(self, input_string):
104        """
105        Transform nested ```<ul>``` and ```<li>``` tags to a medium.com-friendly format.
106
107        Replaces nested ```<ul>``` tags with ```<br>``` and ```<li>``` tags with '-' to format them as medium.com lists.
108
109        Args:
110            input_string (str): The input HTML string containing nested ```<ul>``` and ```<li>``` tags.
111
112        Returns:
113            str: The transformed HTML string.
114        """
115        soup = BeautifulSoup(input_string, "html.parser")
116
117        # Find and replace NESTED <ul> tags and its <li> tags for Medium
118        for ul1 in soup.find_all("ul"):
119            for ul2 in ul1.find_all("ul"):
120                replace_string = " ".join(str(item) for item in ul2.contents)
121                replace_string = replace_string.replace("<li>", "<br>\n- ").replace(
122                    "</li>", ""
123                )
124                ul2.replace_with(replace_string)
125
126        result = str(soup.prettify())
127        return result
128
129    def transform_pre_code(self, input_string):
130        """
131        Transform ```<pre>``` elements with ```<code>``` tags inside.
132
133        This function takes an HTML string as input, searches for `<pre>` elements that contain `<code>` tags,
134        extracts the programming language from the `<code>` tag's class attribute, and transforms the `<pre>` element
135        with new attributes for Medium.com-friendly code blocks.
136
137        Args:
138            input_string (str): The input HTML string.
139
140        Returns:
141            str: The transformed HTML string with updated attributes for `<pre>` elements.
142        """
143        soup = BeautifulSoup(input_string, "html.parser")
144        for pre in soup.find_all("pre"):
145            code = pre.find("code")
146            if code:
147                language_list = code.get("class")
148                language = ""
149                if language_list:
150                    for item in language_list:
151                        if "language-" in item:
152                            language = item.replace("language-", "")
153                pre["data-code-block-lang"] = language
154                pre["data-code-block-mode"] = "2"
155                pre["spellcheck"] = "false"
156                pre["class"] = "graf--preV2"
157                pre["data-testid"] = "editorCodeBlockParagraph"
158
159                span_tag = soup.new_tag("span")
160                span_tag["class"] = "pre--content"
161                content = "".join(map(str, code.contents))
162                span_tag.string = content
163                code.replace_with(span_tag)
164
165        return str(soup.prettify())
166
167    def add_title_to_pictures(self, input_string):
168        """
169        Add captions to images with titles in the input HTML.
170
171        This function searches for `<img>` elements with a "title" attribute or with a quoted " "
172        string in the URL (which comes from the Markdown) in the input HTML string and adds extra
173        tags to transform them into figures with captions. The title attribute is used
174        as the caption text.
175
176        Args:
177            input_string (str): The input HTML string containing `<img>` elements.
178
179        Returns:
180            str: The HTML string with captions added to images with titles.
181        """
182        soup = BeautifulSoup(input_string, "html.parser")
183        for img in soup.find_all("img"):
184            title = img.get("title")
185            if not title:
186                src = img.get("src")
187                if '"' in src:
188                    title = src.split('"')[1]
189                else:
190                    title = ""
191            if title != "":
192                replace_string = (
193                    '<figure tabindex="0" contenteditable="false" data-testid="editorImageParagraph" class="graf graf--figure graf-after--h4">'
194                    + '<div class="aspectRatioPlaceholder">'
195                    + str(img)
196                    .replace(title, "")
197                    .replace('"', "")  # we don't want the title in the URL
198                    + "</div>"
199                    + f'<figcaption class="imageCaption" contenteditable="true" data-default-value="Type caption for image (optional)">{title}<br></figcaption>'
200                    + " </figure>"
201                )
202                img.replace_with(replace_string)
203        result = str(soup.prettify())
204        return result
205
206    def push_to_medium(
207        self,
208        file_to_upload,
209        medium_id,
210        token,
211        title,
212        tag_list,
213        publish_status="draft",
214        content_format="html",
215    ):
216        """
217        Push an HTML file to Medium as a draft post.
218
219        Args:
220            input_file (str): Path to the .ipynb or .md file to be uploaded.
221            medium_id (str): User ID for Medium.
222            token (str): Medium API token.
223            title (str): Title for the Medium post.
224            tag_list (list): List of tags for the Medium post.
225            publish_status (str, optional): Publish status (default: 'draft').
226        """
227        if len(tag_list) > 5:
228            raise ValueError("Tag list should not contain more than 5 elements.")
229
230        with open(file_to_upload, "r", encoding="utf-8") as content_text:
231            content = content_text.read()
232
233        url = f"https://api.medium.com/v1/users/{medium_id}/posts"
234
235        post_data = {
236            "title": title,
237            "contentFormat": content_format,
238            "content": content,
239            "tags": tag_list,
240            "publishStatus": publish_status,
241        }
242
243        headers = {
244            "Authorization": f"Bearer {token}",
245            "Content-Type": "application/json",
246            "Accept": "application/json",
247            "Accept-Charset": "utf-8",
248        }
249
250        response = requests.post(url, headers=headers, json=post_data)
251
252        if response.status_code == 201:
253            post_details = response.json()
254            print("Draft Post Created Successfully:")
255            print("Post Details:")
256            print(post_details)
257        else:
258            print("Failed to create draft post. Status code:", response.status_code)
259            print("Response:", response.text)
260
261    def push_ipynb_or_md_to_medium(
262        self, input_file, medium_id, token, title, tag_list, publish_status="draft"
263    ):
264        """
265        Convert an `.ipynb` or `.md` file to HTML and push to Medium as a draft post.
266
267        Args:
268            input_file (str): Path to the .ipynb or .md file to be uploaded.
269            id (str): User ID for Medium.
270            token (str): Medium API token.
271            title (str): Title for the Medium post.
272            tag_list (list): List of tags for the Medium post.
273            publish_status (str, optional): Publish status (default: 'draft').
274        """
275        if input_file.lower().endswith(".ipynb"):
276            html_output_file = os.path.splitext(input_file)[0] + ".html"
277            self.convert_notebook_to_html(input_file, html_output_file)
278        elif input_file.lower().endswith(".md"):
279            html_output_file = os.path.splitext(input_file)[0] + ".html"
280            self.convert_markdown_to_html(input_file, html_output_file)
281        else:
282            raise ValueError("Input file must be either .ipynb or .md")
283
284        self.push_to_medium(
285            html_output_file, medium_id, token, title, tag_list, publish_status
286        )
class NotebookToMedium:
 14class NotebookToMedium:
 15    """
 16    Write your Medium articles in a Jupyter Notebook and push it directly to Medium using the Medium API.
 17    """
 18
 19    def __init__(self):
 20        self.md = markdown.Markdown(extensions=[fenced_code.FencedCodeExtension()])
 21
 22    def convert_notebook_to_markdown(self, input_notebook, output_markdown):
 23        """
 24         Convert a Jupyter notebook to Markdown and save it to a file.
 25
 26        Args:
 27            input_notebook (str): Path to the input Jupyter notebook.
 28            output_markdown (str): Path to the output Markdown file.
 29        """
 30        with open(input_notebook, "r", encoding="utf-8") as notebook_file:
 31            notebook = read(notebook_file, as_version=4)
 32
 33        markdown_text = jupytext.writes(notebook, fmt="markdown")
 34
 35        # Remove the Jupytext metadata from the Markdown
 36        markdown_text = re.sub(
 37            r"---\s+jupyter:\s+\S+.*?---", "", markdown_text, flags=re.DOTALL
 38        )
 39
 40        with open(output_markdown, "w", encoding="utf-8") as output_file:
 41            output_file.write(markdown_text)
 42
 43    def convert_markdown_to_html(
 44        self,
 45        input_markdown,
 46        output_html,
 47        nest_as_medium=True,
 48        transform_pre_code=True,
 49        add_title_to_pictures=True,
 50    ):
 51        """
 52        Convert a Markdown file to HTML and save it to a file.
 53
 54        Args:
 55            input_markdown (str): Path to the input Markdown file.
 56            output_html (str): Path to the output HTML file.
 57        """
 58
 59        with open(input_markdown, "r", encoding="utf-8") as markdown_file:
 60            markdown_text = markdown_file.read()
 61
 62        markdown_text = html.escape(markdown_text)
 63
 64        html_text = self.md.convert(markdown_text)
 65
 66        if transform_pre_code:
 67            html_text = self.transform_pre_code(html_text)
 68
 69        if nest_as_medium:
 70            html_text = self.transform_nested_ul_to_medium_nested_list(html_text)
 71
 72        if add_title_to_pictures:
 73            html_text = self.add_title_to_pictures(html_text)
 74
 75        html_text = html.unescape(html_text)
 76
 77        with open(output_html, "w", encoding="utf-8") as output_file:
 78            output_file.write(html_text)
 79
 80    def convert_notebook_to_html(self, input_notebook, output_html):
 81        """
 82        Convert a Jupyter notebook to HTML.
 83
 84        This method performs the conversion by first converting the notebook to Markdown,
 85            and then converting the Markdown to HTML.
 86            IMPORTANT: This is to avoid generating binaries, using intermediate Markdown instead
 87            of converting directly to html with jupytext is purposeful
 88
 89        Args:
 90            input_notebook (str): Path to the input Jupyter notebook.
 91            output_html (str): Path to the output HTML file.
 92        """
 93        temp_markdown = "temp_markdown.md"
 94
 95        # Convert the notebook to Markdown
 96        self.convert_notebook_to_markdown(input_notebook, temp_markdown)
 97
 98        # Convert the Markdown to HTML
 99        self.convert_markdown_to_html(temp_markdown, output_html)
100
101        # Remove the temporary Markdown file
102        os.remove(temp_markdown)
103
104    def transform_nested_ul_to_medium_nested_list(self, input_string):
105        """
106        Transform nested ```<ul>``` and ```<li>``` tags to a medium.com-friendly format.
107
108        Replaces nested ```<ul>``` tags with ```<br>``` and ```<li>``` tags with '-' to format them as medium.com lists.
109
110        Args:
111            input_string (str): The input HTML string containing nested ```<ul>``` and ```<li>``` tags.
112
113        Returns:
114            str: The transformed HTML string.
115        """
116        soup = BeautifulSoup(input_string, "html.parser")
117
118        # Find and replace NESTED <ul> tags and its <li> tags for Medium
119        for ul1 in soup.find_all("ul"):
120            for ul2 in ul1.find_all("ul"):
121                replace_string = " ".join(str(item) for item in ul2.contents)
122                replace_string = replace_string.replace("<li>", "<br>\n- ").replace(
123                    "</li>", ""
124                )
125                ul2.replace_with(replace_string)
126
127        result = str(soup.prettify())
128        return result
129
130    def transform_pre_code(self, input_string):
131        """
132        Transform ```<pre>``` elements with ```<code>``` tags inside.
133
134        This function takes an HTML string as input, searches for `<pre>` elements that contain `<code>` tags,
135        extracts the programming language from the `<code>` tag's class attribute, and transforms the `<pre>` element
136        with new attributes for Medium.com-friendly code blocks.
137
138        Args:
139            input_string (str): The input HTML string.
140
141        Returns:
142            str: The transformed HTML string with updated attributes for `<pre>` elements.
143        """
144        soup = BeautifulSoup(input_string, "html.parser")
145        for pre in soup.find_all("pre"):
146            code = pre.find("code")
147            if code:
148                language_list = code.get("class")
149                language = ""
150                if language_list:
151                    for item in language_list:
152                        if "language-" in item:
153                            language = item.replace("language-", "")
154                pre["data-code-block-lang"] = language
155                pre["data-code-block-mode"] = "2"
156                pre["spellcheck"] = "false"
157                pre["class"] = "graf--preV2"
158                pre["data-testid"] = "editorCodeBlockParagraph"
159
160                span_tag = soup.new_tag("span")
161                span_tag["class"] = "pre--content"
162                content = "".join(map(str, code.contents))
163                span_tag.string = content
164                code.replace_with(span_tag)
165
166        return str(soup.prettify())
167
168    def add_title_to_pictures(self, input_string):
169        """
170        Add captions to images with titles in the input HTML.
171
172        This function searches for `<img>` elements with a "title" attribute or with a quoted " "
173        string in the URL (which comes from the Markdown) in the input HTML string and adds extra
174        tags to transform them into figures with captions. The title attribute is used
175        as the caption text.
176
177        Args:
178            input_string (str): The input HTML string containing `<img>` elements.
179
180        Returns:
181            str: The HTML string with captions added to images with titles.
182        """
183        soup = BeautifulSoup(input_string, "html.parser")
184        for img in soup.find_all("img"):
185            title = img.get("title")
186            if not title:
187                src = img.get("src")
188                if '"' in src:
189                    title = src.split('"')[1]
190                else:
191                    title = ""
192            if title != "":
193                replace_string = (
194                    '<figure tabindex="0" contenteditable="false" data-testid="editorImageParagraph" class="graf graf--figure graf-after--h4">'
195                    + '<div class="aspectRatioPlaceholder">'
196                    + str(img)
197                    .replace(title, "")
198                    .replace('"', "")  # we don't want the title in the URL
199                    + "</div>"
200                    + f'<figcaption class="imageCaption" contenteditable="true" data-default-value="Type caption for image (optional)">{title}<br></figcaption>'
201                    + " </figure>"
202                )
203                img.replace_with(replace_string)
204        result = str(soup.prettify())
205        return result
206
207    def push_to_medium(
208        self,
209        file_to_upload,
210        medium_id,
211        token,
212        title,
213        tag_list,
214        publish_status="draft",
215        content_format="html",
216    ):
217        """
218        Push an HTML file to Medium as a draft post.
219
220        Args:
221            input_file (str): Path to the .ipynb or .md file to be uploaded.
222            medium_id (str): User ID for Medium.
223            token (str): Medium API token.
224            title (str): Title for the Medium post.
225            tag_list (list): List of tags for the Medium post.
226            publish_status (str, optional): Publish status (default: 'draft').
227        """
228        if len(tag_list) > 5:
229            raise ValueError("Tag list should not contain more than 5 elements.")
230
231        with open(file_to_upload, "r", encoding="utf-8") as content_text:
232            content = content_text.read()
233
234        url = f"https://api.medium.com/v1/users/{medium_id}/posts"
235
236        post_data = {
237            "title": title,
238            "contentFormat": content_format,
239            "content": content,
240            "tags": tag_list,
241            "publishStatus": publish_status,
242        }
243
244        headers = {
245            "Authorization": f"Bearer {token}",
246            "Content-Type": "application/json",
247            "Accept": "application/json",
248            "Accept-Charset": "utf-8",
249        }
250
251        response = requests.post(url, headers=headers, json=post_data)
252
253        if response.status_code == 201:
254            post_details = response.json()
255            print("Draft Post Created Successfully:")
256            print("Post Details:")
257            print(post_details)
258        else:
259            print("Failed to create draft post. Status code:", response.status_code)
260            print("Response:", response.text)
261
262    def push_ipynb_or_md_to_medium(
263        self, input_file, medium_id, token, title, tag_list, publish_status="draft"
264    ):
265        """
266        Convert an `.ipynb` or `.md` file to HTML and push to Medium as a draft post.
267
268        Args:
269            input_file (str): Path to the .ipynb or .md file to be uploaded.
270            id (str): User ID for Medium.
271            token (str): Medium API token.
272            title (str): Title for the Medium post.
273            tag_list (list): List of tags for the Medium post.
274            publish_status (str, optional): Publish status (default: 'draft').
275        """
276        if input_file.lower().endswith(".ipynb"):
277            html_output_file = os.path.splitext(input_file)[0] + ".html"
278            self.convert_notebook_to_html(input_file, html_output_file)
279        elif input_file.lower().endswith(".md"):
280            html_output_file = os.path.splitext(input_file)[0] + ".html"
281            self.convert_markdown_to_html(input_file, html_output_file)
282        else:
283            raise ValueError("Input file must be either .ipynb or .md")
284
285        self.push_to_medium(
286            html_output_file, medium_id, token, title, tag_list, publish_status
287        )

Write your Medium articles in a Jupyter Notebook and push it directly to Medium using the Medium API.

md
def convert_notebook_to_markdown(self, input_notebook, output_markdown):
22    def convert_notebook_to_markdown(self, input_notebook, output_markdown):
23        """
24         Convert a Jupyter notebook to Markdown and save it to a file.
25
26        Args:
27            input_notebook (str): Path to the input Jupyter notebook.
28            output_markdown (str): Path to the output Markdown file.
29        """
30        with open(input_notebook, "r", encoding="utf-8") as notebook_file:
31            notebook = read(notebook_file, as_version=4)
32
33        markdown_text = jupytext.writes(notebook, fmt="markdown")
34
35        # Remove the Jupytext metadata from the Markdown
36        markdown_text = re.sub(
37            r"---\s+jupyter:\s+\S+.*?---", "", markdown_text, flags=re.DOTALL
38        )
39
40        with open(output_markdown, "w", encoding="utf-8") as output_file:
41            output_file.write(markdown_text)

Convert a Jupyter notebook to Markdown and save it to a file.

Args: input_notebook (str): Path to the input Jupyter notebook. output_markdown (str): Path to the output Markdown file.

def convert_markdown_to_html( self, input_markdown, output_html, nest_as_medium=True, transform_pre_code=True, add_title_to_pictures=True):
43    def convert_markdown_to_html(
44        self,
45        input_markdown,
46        output_html,
47        nest_as_medium=True,
48        transform_pre_code=True,
49        add_title_to_pictures=True,
50    ):
51        """
52        Convert a Markdown file to HTML and save it to a file.
53
54        Args:
55            input_markdown (str): Path to the input Markdown file.
56            output_html (str): Path to the output HTML file.
57        """
58
59        with open(input_markdown, "r", encoding="utf-8") as markdown_file:
60            markdown_text = markdown_file.read()
61
62        markdown_text = html.escape(markdown_text)
63
64        html_text = self.md.convert(markdown_text)
65
66        if transform_pre_code:
67            html_text = self.transform_pre_code(html_text)
68
69        if nest_as_medium:
70            html_text = self.transform_nested_ul_to_medium_nested_list(html_text)
71
72        if add_title_to_pictures:
73            html_text = self.add_title_to_pictures(html_text)
74
75        html_text = html.unescape(html_text)
76
77        with open(output_html, "w", encoding="utf-8") as output_file:
78            output_file.write(html_text)

Convert a Markdown file to HTML and save it to a file.

Args: input_markdown (str): Path to the input Markdown file. output_html (str): Path to the output HTML file.

def convert_notebook_to_html(self, input_notebook, output_html):
 80    def convert_notebook_to_html(self, input_notebook, output_html):
 81        """
 82        Convert a Jupyter notebook to HTML.
 83
 84        This method performs the conversion by first converting the notebook to Markdown,
 85            and then converting the Markdown to HTML.
 86            IMPORTANT: This is to avoid generating binaries, using intermediate Markdown instead
 87            of converting directly to html with jupytext is purposeful
 88
 89        Args:
 90            input_notebook (str): Path to the input Jupyter notebook.
 91            output_html (str): Path to the output HTML file.
 92        """
 93        temp_markdown = "temp_markdown.md"
 94
 95        # Convert the notebook to Markdown
 96        self.convert_notebook_to_markdown(input_notebook, temp_markdown)
 97
 98        # Convert the Markdown to HTML
 99        self.convert_markdown_to_html(temp_markdown, output_html)
100
101        # Remove the temporary Markdown file
102        os.remove(temp_markdown)

Convert a Jupyter notebook to HTML.

This method performs the conversion by first converting the notebook to Markdown, and then converting the Markdown to HTML. IMPORTANT: This is to avoid generating binaries, using intermediate Markdown instead of converting directly to html with jupytext is purposeful

Args: input_notebook (str): Path to the input Jupyter notebook. output_html (str): Path to the output HTML file.

def transform_nested_ul_to_medium_nested_list(self, input_string):
104    def transform_nested_ul_to_medium_nested_list(self, input_string):
105        """
106        Transform nested ```<ul>``` and ```<li>``` tags to a medium.com-friendly format.
107
108        Replaces nested ```<ul>``` tags with ```<br>``` and ```<li>``` tags with '-' to format them as medium.com lists.
109
110        Args:
111            input_string (str): The input HTML string containing nested ```<ul>``` and ```<li>``` tags.
112
113        Returns:
114            str: The transformed HTML string.
115        """
116        soup = BeautifulSoup(input_string, "html.parser")
117
118        # Find and replace NESTED <ul> tags and its <li> tags for Medium
119        for ul1 in soup.find_all("ul"):
120            for ul2 in ul1.find_all("ul"):
121                replace_string = " ".join(str(item) for item in ul2.contents)
122                replace_string = replace_string.replace("<li>", "<br>\n- ").replace(
123                    "</li>", ""
124                )
125                ul2.replace_with(replace_string)
126
127        result = str(soup.prettify())
128        return result

Transform nested <ul> and <li> tags to a medium.com-friendly format.

Replaces nested <ul> tags with <br> and <li> tags with '-' to format them as medium.com lists.

Args: input_string (str): The input HTML string containing nested <ul> and <li> tags.

Returns: str: The transformed HTML string.

def transform_pre_code(self, input_string):
130    def transform_pre_code(self, input_string):
131        """
132        Transform ```<pre>``` elements with ```<code>``` tags inside.
133
134        This function takes an HTML string as input, searches for `<pre>` elements that contain `<code>` tags,
135        extracts the programming language from the `<code>` tag's class attribute, and transforms the `<pre>` element
136        with new attributes for Medium.com-friendly code blocks.
137
138        Args:
139            input_string (str): The input HTML string.
140
141        Returns:
142            str: The transformed HTML string with updated attributes for `<pre>` elements.
143        """
144        soup = BeautifulSoup(input_string, "html.parser")
145        for pre in soup.find_all("pre"):
146            code = pre.find("code")
147            if code:
148                language_list = code.get("class")
149                language = ""
150                if language_list:
151                    for item in language_list:
152                        if "language-" in item:
153                            language = item.replace("language-", "")
154                pre["data-code-block-lang"] = language
155                pre["data-code-block-mode"] = "2"
156                pre["spellcheck"] = "false"
157                pre["class"] = "graf--preV2"
158                pre["data-testid"] = "editorCodeBlockParagraph"
159
160                span_tag = soup.new_tag("span")
161                span_tag["class"] = "pre--content"
162                content = "".join(map(str, code.contents))
163                span_tag.string = content
164                code.replace_with(span_tag)
165
166        return str(soup.prettify())

Transform <pre> elements with <code> tags inside.

This function takes an HTML string as input, searches for <pre> elements that contain <code> tags, extracts the programming language from the <code> tag's class attribute, and transforms the <pre> element with new attributes for Medium.com-friendly code blocks.

Args: input_string (str): The input HTML string.

Returns: str: The transformed HTML string with updated attributes for <pre> elements.

def add_title_to_pictures(self, input_string):
168    def add_title_to_pictures(self, input_string):
169        """
170        Add captions to images with titles in the input HTML.
171
172        This function searches for `<img>` elements with a "title" attribute or with a quoted " "
173        string in the URL (which comes from the Markdown) in the input HTML string and adds extra
174        tags to transform them into figures with captions. The title attribute is used
175        as the caption text.
176
177        Args:
178            input_string (str): The input HTML string containing `<img>` elements.
179
180        Returns:
181            str: The HTML string with captions added to images with titles.
182        """
183        soup = BeautifulSoup(input_string, "html.parser")
184        for img in soup.find_all("img"):
185            title = img.get("title")
186            if not title:
187                src = img.get("src")
188                if '"' in src:
189                    title = src.split('"')[1]
190                else:
191                    title = ""
192            if title != "":
193                replace_string = (
194                    '<figure tabindex="0" contenteditable="false" data-testid="editorImageParagraph" class="graf graf--figure graf-after--h4">'
195                    + '<div class="aspectRatioPlaceholder">'
196                    + str(img)
197                    .replace(title, "")
198                    .replace('"', "")  # we don't want the title in the URL
199                    + "</div>"
200                    + f'<figcaption class="imageCaption" contenteditable="true" data-default-value="Type caption for image (optional)">{title}<br></figcaption>'
201                    + " </figure>"
202                )
203                img.replace_with(replace_string)
204        result = str(soup.prettify())
205        return result

Add captions to images with titles in the input HTML.

This function searches for <img> elements with a "title" attribute or with a quoted " " string in the URL (which comes from the Markdown) in the input HTML string and adds extra tags to transform them into figures with captions. The title attribute is used as the caption text.

Args: input_string (str): The input HTML string containing <img> elements.

Returns: str: The HTML string with captions added to images with titles.

def push_to_medium( self, file_to_upload, medium_id, token, title, tag_list, publish_status='draft', content_format='html'):
207    def push_to_medium(
208        self,
209        file_to_upload,
210        medium_id,
211        token,
212        title,
213        tag_list,
214        publish_status="draft",
215        content_format="html",
216    ):
217        """
218        Push an HTML file to Medium as a draft post.
219
220        Args:
221            input_file (str): Path to the .ipynb or .md file to be uploaded.
222            medium_id (str): User ID for Medium.
223            token (str): Medium API token.
224            title (str): Title for the Medium post.
225            tag_list (list): List of tags for the Medium post.
226            publish_status (str, optional): Publish status (default: 'draft').
227        """
228        if len(tag_list) > 5:
229            raise ValueError("Tag list should not contain more than 5 elements.")
230
231        with open(file_to_upload, "r", encoding="utf-8") as content_text:
232            content = content_text.read()
233
234        url = f"https://api.medium.com/v1/users/{medium_id}/posts"
235
236        post_data = {
237            "title": title,
238            "contentFormat": content_format,
239            "content": content,
240            "tags": tag_list,
241            "publishStatus": publish_status,
242        }
243
244        headers = {
245            "Authorization": f"Bearer {token}",
246            "Content-Type": "application/json",
247            "Accept": "application/json",
248            "Accept-Charset": "utf-8",
249        }
250
251        response = requests.post(url, headers=headers, json=post_data)
252
253        if response.status_code == 201:
254            post_details = response.json()
255            print("Draft Post Created Successfully:")
256            print("Post Details:")
257            print(post_details)
258        else:
259            print("Failed to create draft post. Status code:", response.status_code)
260            print("Response:", response.text)

Push an HTML file to Medium as a draft post.

Args: input_file (str): Path to the .ipynb or .md file to be uploaded. medium_id (str): User ID for Medium. token (str): Medium API token. title (str): Title for the Medium post. tag_list (list): List of tags for the Medium post. publish_status (str, optional): Publish status (default: 'draft').

def push_ipynb_or_md_to_medium( self, input_file, medium_id, token, title, tag_list, publish_status='draft'):
262    def push_ipynb_or_md_to_medium(
263        self, input_file, medium_id, token, title, tag_list, publish_status="draft"
264    ):
265        """
266        Convert an `.ipynb` or `.md` file to HTML and push to Medium as a draft post.
267
268        Args:
269            input_file (str): Path to the .ipynb or .md file to be uploaded.
270            id (str): User ID for Medium.
271            token (str): Medium API token.
272            title (str): Title for the Medium post.
273            tag_list (list): List of tags for the Medium post.
274            publish_status (str, optional): Publish status (default: 'draft').
275        """
276        if input_file.lower().endswith(".ipynb"):
277            html_output_file = os.path.splitext(input_file)[0] + ".html"
278            self.convert_notebook_to_html(input_file, html_output_file)
279        elif input_file.lower().endswith(".md"):
280            html_output_file = os.path.splitext(input_file)[0] + ".html"
281            self.convert_markdown_to_html(input_file, html_output_file)
282        else:
283            raise ValueError("Input file must be either .ipynb or .md")
284
285        self.push_to_medium(
286            html_output_file, medium_id, token, title, tag_list, publish_status
287        )

Convert an .ipynb or .md file to HTML and push to Medium as a draft post.

Args: input_file (str): Path to the .ipynb or .md file to be uploaded. id (str): User ID for Medium. token (str): Medium API token. title (str): Title for the Medium post. tag_list (list): List of tags for the Medium post. publish_status (str, optional): Publish status (default: 'draft').