diff --git a/classes/api.php b/classes/api.php
index 952c971669844363cd343995eb68497f20df593d..0cac0bb90d8af473d63f90f274fbda344cb7fd5d 100755
--- a/classes/api.php
+++ b/classes/api.php
@@ -787,7 +787,8 @@ class API extends Handler {
 
 						list ($flavor_image, $flavor_stream, $flavor_kind) = Article::_get_image($enclosures,
 																												$line["content"], // unsanitized
-																												$line["site_url"]);
+																												$line["site_url"],
+																												$headline_row);
 
 						$headline_row["flavor_image"] = $flavor_image;
 						$headline_row["flavor_stream"] = $flavor_stream;
diff --git a/classes/article.php b/classes/article.php
index 648b1e2c1cb1cf41131389a9e571360f84684c2b..432354f783f8164cab1dee7fed5089ad517b22d0 100755
--- a/classes/article.php
+++ b/classes/article.php
@@ -543,7 +543,7 @@ class Article extends Handler_Protected {
 		return $rv;
 	}
 
-	static function _get_image($enclosures, $content, $site_url) {
+	static function _get_image(array $enclosures, string $content, string $site_url, array $headline) {
 
 		$article_image = "";
 		$article_stream = "";
@@ -553,7 +553,7 @@ class Article extends Handler_Protected {
 			function ($result) use (&$article_image, &$article_stream, &$content) {
 				list ($article_image, $article_stream, $content) = $result;
 			},
-			$enclosures, $content, $site_url);
+			$enclosures, $content, $site_url, $headline);
 
 		if (!$article_image && !$article_stream) {
 			$tmpdoc = new DOMDocument();
diff --git a/classes/feeds.php b/classes/feeds.php
index 5eb5c26d01a3ce939c4c7b493cb4aeb6b1fc9a63..68d535481773ba0c6634743c4da85138675b055a 100755
--- a/classes/feeds.php
+++ b/classes/feeds.php
@@ -251,21 +251,6 @@ class Feeds extends Handler_Protected {
 
 				$this->_mark_timestamp("   sanitize");
 
-				PluginHost::getInstance()->chain_hooks_callback(PluginHost::HOOK_RENDER_ARTICLE_CDM,
-					function ($result, $plugin) use (&$line) {
-						$line = $result;
-						$this->_mark_timestamp("       hook_render_cdm: " . get_class($plugin));
-					},
-					$line);
-
-				$this->_mark_timestamp("   hook_render_cdm");
-
-				$line['content'] = DiskCache::rewrite_urls($line['content']);
-
-				$this->_mark_timestamp("   disk_cache_rewrite");
-
-				$this->_mark_timestamp("   note");
-
 				if (!get_pref(Prefs::CDM_EXPANDED)) {
 					$line["cdm_excerpt"] = "<span class='collapse'>
 						<i class='material-icons' onclick='return Article.cdmUnsetActive(event)'
@@ -330,6 +315,20 @@ class Feeds extends Handler_Protected {
 				}
 
 				$this->_mark_timestamp("   color");
+				$this->_mark_timestamp("   pre-hook_render_cdm");
+
+				PluginHost::getInstance()->chain_hooks_callback(PluginHost::HOOK_RENDER_ARTICLE_CDM,
+					function ($result, $plugin) use (&$line) {
+						$line = $result;
+						$this->_mark_timestamp("       hook: " . get_class($plugin));
+					},
+					$line);
+
+				$this->_mark_timestamp("   hook_render_cdm");
+
+				$line['content'] = DiskCache::rewrite_urls($line['content']);
+
+				$this->_mark_timestamp("   disk_cache_rewrite");
 
 				/* we don't need those */
 
diff --git a/classes/handler/public.php b/classes/handler/public.php
index 15ea011032d13b8d2e2bb13324663f07537c6f29..2de073cc2550d028f6c95415ca44a344264bb0d7 100755
--- a/classes/handler/public.php
+++ b/classes/handler/public.php
@@ -152,7 +152,7 @@ class Handler_Public extends Handler {
 					$tpl->setVariable('ARTICLE_ENCLOSURE_LENGTH', "", true);
 				}
 
-				list ($og_image, $og_stream) = Article::_get_image($enclosures, $line['content'], $feed_site_url);
+				list ($og_image, $og_stream) = Article::_get_image($enclosures, $line['content'], $feed_site_url, $line);
 
 				$tpl->setVariable('ARTICLE_OG_IMAGE', $og_image, true);
 
diff --git a/plugins/nsfw/init.js b/plugins/nsfw/init.js
index 4bc2443e81b2798c0aca3176a7477051d3eae08d..71fe4747bdd18e480250f59c33293d90d8c38d05 100644
--- a/plugins/nsfw/init.js
+++ b/plugins/nsfw/init.js
@@ -2,11 +2,17 @@
 
 Plugins.NSFW = {
 	toggle: function(elem) {
-		const content = elem.domNode.parentNode.querySelector(".nswf.content");
+		elem = elem.domNode || elem;
 
-		if (content) {
-			Element.toggle(content);
-		}
+		const content = elem.closest(".nsfw-wrapper").querySelector('.nsfw-content');
+
+		// we can't use .toggle() here because this script could be invoked by the api client
+		// so it's back to vanilla js
+
+		if (content.style.display == 'none')
+			content.style.display = '';
+		else
+			content.style.display = 'none';
 	}
 }
 
diff --git a/plugins/nsfw/init.php b/plugins/nsfw/init.php
index 112c7d7c2461fe2bc4b3a8f4ec3f7bb93703ce3a..4279a397bf14a2b1894437747a7b240f3aa1f0c7 100644
--- a/plugins/nsfw/init.php
+++ b/plugins/nsfw/init.php
@@ -12,9 +12,11 @@ class NSFW extends Plugin {
 	function init($host) {
 		$this->host = $host;
 
-		$host->add_hook($host::HOOK_RENDER_ARTICLE, $this);
-		$host->add_hook($host::HOOK_RENDER_ARTICLE_CDM, $this);
-		$host->add_hook($host::HOOK_PREFS_TAB, $this);
+		$host->add_hook(PluginHost::HOOK_RENDER_ARTICLE, $this);
+		$host->add_hook(PluginHost::HOOK_RENDER_ARTICLE_CDM, $this);
+		$host->add_hook(PluginHost::HOOK_RENDER_ARTICLE_API, $this);
+		$host->add_hook(PluginHost::HOOK_ARTICLE_IMAGE, $this);
+		$host->add_hook(PluginHost::HOOK_PREFS_TAB, $this);
 
 	}
 
@@ -22,22 +24,46 @@ class NSFW extends Plugin {
 		return file_get_contents(__DIR__ . "/init.js");
 	}
 
-	function hook_render_article($article) {
-		$tags = array_map("trim", explode(",", $this->host->get($this, "tags")));
-		$a_tags = array_map("trim", explode(",", $article["tag_cache"]));
+	function hook_article_image($enclosures, $content, $site_url, $article) {
+		$tags = explode(",", $this->host->get($this, "tags"));
+		$article_tags = $article["tags"];
+
+		if (count(array_intersect($tags, $article_tags)) > 0) {
+			return [Config::get_self_url() . "/plugins/nsfw/nsfw.png", "", "nsfw", []];
+		} else {
+			return ["", "", $content];
+		}
+	}
+
+	private function rewrite_contents($article, bool $add_api_js = false) {
+		$tags = explode(",", $this->host->get($this, "tags"));
+		$article_tags = $article["tags"];
 
-		if (count(array_intersect($tags, $a_tags)) > 0) {
-			$article["content"] = "<div class='nswf wrapper'>".
+		if (count(array_intersect($tags, $article_tags)) > 0) {
+			$article["content"] = "<div class='nsfw-wrapper'>".
 					\Controls\button_tag(__("Not work safe (click to toggle)"), '', ['onclick' => 'Plugins.NSFW.toggle(this)']).
-					"<div class='nswf content' style='display : none'>".$article["content"]."</div>
+					"<div class='nsfw-content' style='display : none'>".$article["content"]."</div>
 				</div>";
+
+			if ($add_api_js) {
+				$article["content"] .= "<script type='text/javascript'>const Plugins = {}; " . $this->get_js() . "</script>";
+			}
 		}
 
 		return $article;
 	}
 
+	function hook_render_article_api($row) {
+		$article = isset($row['headline']) ? $row['headline'] : $row['article'];
+		return $this->rewrite_contents($article, true);
+	}
+
+	function hook_render_article($article) {
+		return $this->rewrite_contents($article);
+	}
+
 	function hook_render_article_cdm($article) {
-		return $this->hook_render_article($article);
+		return $this->rewrite_contents($article);
 	}
 
 	function hook_prefs_tab($args) {
diff --git a/plugins/share/init.php b/plugins/share/init.php
index 8145d51ee2e733e855028f5a89b0c0c2a8e1e1b4..64a9054ebfd3c9487fc6d60206f2e3f5d3f29a99 100644
--- a/plugins/share/init.php
+++ b/plugins/share/init.php
@@ -142,7 +142,7 @@ class Share extends Plugin {
 				$line);
 
 			$enclosures = Article::_get_enclosures($line["id"]);
-			list ($og_image, $og_stream) = Article::_get_image($enclosures, $line['content'], $line["site_url"]);
+			list ($og_image, $og_stream) = Article::_get_image($enclosures, $line['content'], $line["site_url"], $line);
 
 			$content_decoded = html_entity_decode($line["title"], ENT_NOQUOTES | ENT_HTML401);
 			$parsed_updated = TimeHelper::make_local_datetime($line["updated"], true, $owner_uid, true);