{"id":19115332,"url":"https://github.com/syeopite/npf-renderer","last_synced_at":"2025-04-30T22:52:33.920Z","repository":{"id":198358721,"uuid":"700646798","full_name":"syeopite/npf-renderer","owner":"syeopite","description":"A Python Based Renderer for Tumblr's Neue Post Format (NPF)","archived":false,"fork":false,"pushed_at":"2025-03-27T06:31:35.000Z","size":514,"stargazers_count":3,"open_issues_count":12,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-27T07:30:21.488Z","etag":null,"topics":["library","neue-post-format","python","tumblr","tumblr-api"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/syeopite.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-10-05T02:24:36.000Z","updated_at":"2025-03-27T06:31:37.000Z","dependencies_parsed_at":null,"dependency_job_id":"40e5e938-f6df-4ef3-9600-dd8080514926","html_url":"https://github.com/syeopite/npf-renderer","commit_stats":null,"previous_names":["syeopite/npf-renderer"],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/syeopite%2Fnpf-renderer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/syeopite%2Fnpf-renderer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/syeopite%2Fnpf-renderer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/syeopite%2Fnpf-renderer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/syeopite","download_url":"https://codeload.github.com/syeopite/npf-renderer/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251199849,"owners_count":21551376,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["library","neue-post-format","python","tumblr","tumblr-api"],"created_at":"2024-11-09T04:46:05.389Z","updated_at":"2025-04-30T22:52:33.900Z","avatar_url":"https://github.com/syeopite.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# npf-renderer\n\nA Python library for rendering Tumblr's Neue Post Format (NPF) into HTML\n\n\u003cdetails\u003e\n\u003csummary\u003e\nOutput\n\u003c/summary\u003e\n\n![Example output](https://raw.githubusercontent.com/syeopite/npf-renderer/master/screenshots/example.png)\n\n\u003c/details\u003e\n\n## Usage:\n\n```python \nimport pytumblr\nimport npf_renderer\n\nclient = pytumblr.TumblrRestClient(*TUMBLR_API_KEYS)\n\nblog = client.posts(BLOG_NAME, npf=True)\npost = blog[\"posts\"][0]\n\ncontent, layout = post[\"content\"], post[\"layout\"]\n\nhas_error, post_html = npf_renderer.format_npf(content, layout, pretty_html=True)\n\nprint(post_html)\n```\n\n\u003cdetails\u003e\n\n\u003csummary\u003e\nHTML\n\u003c/summary\u003e\n\n```html\n\u003cdiv class=\"post-body\"\u003e\n  \u003cdiv class=\"layout-row\"\u003e\n    \u003cp class=\"text-block\"\u003e\n      \u003cspan class=\"inline-formatted-content\"\u003eThis is a \u003ci class=\"inline-italics\"\u003etext block\u003c/i\u003e! I have a \n        \u003cb class=\"inline-bold\"\u003elot\u003c/b\u003e of inline formatting options! \n        \u003cspan class=\"inline-color\" style=\"color: #ff4930;\"\u003eHere's some text in \u003c/span\u003e\n        \u003cspan class=\"inline-color\" style=\"color: #00b8ff;\"\u003ecolor\u003c/span\u003e\n      \u003c/span\u003e\n    \u003c/p\u003e\n  \u003c/div\u003e\n  \u003cdiv class=\"layout-row\"\u003e\n    \u003cfigure class=\"image-block\"\u003e\n      \u003cdiv class=\"image-container\"\u003e\n        \u003cimg alt=\"image\" class=\"image\" loading=\"lazy\" sizes=\"(max-width: 540px) 33vh, 180px\" srcset=\"...\"\u003e\n      \u003c/div\u003e\n    \u003c/figure\u003e\n    \u003cfigure class=\"image-block\"\u003e\n      \u003cdiv class=\"image-container\"\u003e\n        \u003cimg alt=\"image\" class=\"image\" loading=\"lazy\" sizes=\"(max-width: 540px) 33vh, 180px\" srcset=\"...\"\u003e\n      \u003c/div\u003e\n    \u003c/figure\u003e\n    \u003cfigure class=\"image-block\"\u003e\n      \u003cdiv class=\"image-container\"\u003e\n        \u003cimg alt=\"image\" class=\"image\" loading=\"lazy\" sizes=\"(max-width: 540px) 33vh, 180px\" srcset=\"...\"\u003e\n      \u003c/div\u003e\n    \u003c/figure\u003e\n  \u003c/div\u003e\n  \u003cdiv class=\"layout-row\"\u003e\n    \u003cfigure class=\"image-block\"\u003e\n      \u003cdiv class=\"image-container\"\u003e\n        \u003cimg alt=\"image\" class=\"image\" loading=\"lazy\" sizes=\"(max-width: 540px) 50vh, 270px\" srcset=\"...\"\u003e\n      \u003c/div\u003e\n    \u003c/figure\u003e\n    \u003cfigure class=\"image-block\"\u003e\n      \u003cdiv class=\"image-container\"\u003e\n        \u003cimg alt=\"image\" class=\"image\" loading=\"lazy\" sizes=\"(max-width: 540px) 50vh, 270px\" srcset=\"...\"\u003e\n      \u003c/div\u003e\n    \u003c/figure\u003e\n  \u003c/div\u003e\n  \u003cdiv class=\"layout-row\"\u003e\n    \u003cdiv class=\"link-block\"\u003e\n      \u003ca class=\"link-block-link\" href=\"https://href.li/?https://example.com\"\u003e\n        \u003cdiv class=\"link-block-title\"\u003e\n          \u003cspan\u003eExample Domain\u003c/span\u003e\n        \u003c/div\u003e\n        \u003cdiv class=\"link-block-description-container\"\u003e\n          \u003cdiv class=\"link-block-subtitles\"\u003e\n            \u003cspan\u003e\n              \u003cspan\u003eexample.com\u003c/span\u003e\n            \u003c/span\u003e\n          \u003c/div\u003e\n        \u003c/div\u003e\n      \u003c/a\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cdiv class=\"layout-row\"\u003e\n    \u003cp class=\"text-block\"\u003eThis is a link\u003c/p\u003e\n  \u003c/div\u003e\n```\n\n\u003c/details\u003e\n\n\u003cbr/\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\nOutput\n\u003c/summary\u003e\n\n![Example output](https://raw.githubusercontent.com/syeopite/npf-renderer/master/screenshots/example.png)\n\n\u003c/details\u003e\n\n\u003cbr/\u003e\n\n**Make sure to import the CSS from `npf_renderer.utils.BASIC_LAYOUT_CSS`!**\n\n```python\nwith open(\"basic_layout.css\", \"w\") as file:\n    file.write(npf_renderer.utils.BASIC_LAYOUT_CSS)\n```\n\n```html\n\u003clink rel=\"stylesheet\" type=\"text/css\" href=\"basic_layout.css\"\u003e\n```\n\n`format_npf` will return placeholder HTML for any blocks it doesn't support.\n\n\n\u003cdetails\u003e\n\u003csummary\u003eUnsupported blocks\u003c/summary\u003e\n\n```html\n\u003cdiv class=\"post-body\"\u003e\n  \u003cp class=\"text-block\"\u003e This text block is supported but the next block is not! \u003c/p\u003e\n  \u003cdiv class=\"unsupported-content-block\"\u003e\n    \u003cdiv class=\"unsupported-content-block-message\"\u003e\n      \u003ch1\u003eUnsupported content placeholder\u003c/h1\u003e\n      \u003cp\u003eHello! I'm a placeholder for the unsupported \"unsupported\" type NPF content block. Please report me!\u003c/p\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n\u003c/div\u003e\n```\n\n\u003c/details\u003e\n\n\u003cbr/\u003e\n\nIn the event that it cannot format anything an empty div will be returned\n\n---\n\n## Installation\n\n```bash\npip install npf-renderer\n```\n\n## Advanced\n\n### URL Handling\n\nYou can pass in a custom URL handler function to `format_npf` to replace any links within the NPF tree.\n\n```python\ndef url_handler(url):\n    url = urllib.parse.urlparse(url)\n\n    if url.hostname.endswith(\"example.com\"):\n        return url._replace(netloc=\"other.example.com\").geturl()\n\nformat_npf(contents, layouts, url_handler=url_handler)\n```\n\n### Polls\n\nAs polls require an additional request in order to fetch results, `npf-renderer` by default can only render a very basic poll without any votes attached.\n\nYou can however provide the data necessary for `npf-renderer` to populate polls by passing in a callback function to `format_npf` that takes a `poll_id` argument.\n\nBe sure to have the blog name and the post ID at hand too, as Tumblr's API requires all three to fetch poll results.\n\nThe data returned should be in the form:\n\n```jsonc\n{\n  \"results\": {\n    // answer_id =\u003e vote_count\n    \"9a025e86-2f02-452e-99d3-b4c0fd9afd48\": 123,\n    \"fad65faf-06d3-4a24-85a9-47c096ab07e3\": 321\n  },\n  \"timestamp\": 1706642448\n}\n```\n\nExample:\n\n```python\ndef create_callback(blog_name, post_id)\n    def poll_callback(poll_id):\n        initial_results = request_poll_results(blog_name, post_id, poll_id)\n\n        return initial_results[\"response\"]\n\n    return poll_callback\n\nnpf_renderer.format_npf(content, layout, poll_result_callback=create_callback(blog_name, post_id))\n```\n\n\n## Features\n\n(True as of master branch)\n\n- [x] Text blocks\n- [x] Image blocks\n- [x] Link Blocks\n- [x] Audio Blocks\n- [x] Video Blocks\n- [x] Polls Blocks\n\n- [x] Layouts \n- [x] Attributions\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsyeopite%2Fnpf-renderer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsyeopite%2Fnpf-renderer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsyeopite%2Fnpf-renderer/lists"}