Generated feeds: include labels as tags?

Hi.

Articles in generated feeds maintain/inherit their tags but not their assigned labels.

Do you know of any plugin that converts assigned labels to tags (for generated feeds)?

BTW on a related note, is there any plugin that provides API calls to set article tags?

Thank you.

I’m trying to write a little plugin that converts labels to tags in generated feeds (so I can get them into other apps). Since I have little to no experience with ttrss plugins I’d really apppreciate your input.
And I have no experience with public functions.

If I understand this correctly I need to write a plugin that hooks into HOOK_ARTICLE_EXPORT_FEED. It will be called before the generated feed is created (is this triggered when the URL of the generated feed is called?).

So here’s what I have so far:

<?php
class Labels_To_Tags extends Plugin {
    private $host;

	public function about() {
        return array(1.0,
            'Add labels as tags in generated feeds',
            'C');
    }

	public function api_version() {
		return 2;
	}

	public function init( $host ) {
		$this->host = $host;
		$host->add_hook( $host::HOOK_ARTICLE_EXPORT_FEED, $this );
	}

	public function hook_article_export_feed( $line, $feed, $is_cat ) {

		// get all LABELS for this article / line
   		$labels = Article::get_article_labels($line['id']);

		if (count($labels) > 0) {

    		// get all TAGS for this article / line
			$tags = Article::get_article_tags($line['id']);

			// if article has tags add them to array first
			if (count($tags) > 0) {
				$article['tags'] = array();

				foreach ($tags as $tag) {
					array_push($article['tags'], $tag);
				}
			} else {
				$article['tags'] = array();
			}

			foreach ($labels as $label) {
				array_push($article['tags'], $label);
			}
		}
		return $line;
	}
}
?>

@JustAMacUser hope you don’t mind me pinging you but I saw in some other post that you have some experience with plugins for generated feeds.
Could you please take a quick look.
If you spot obvious errors or if the whole approach is wrong please let me know.

It would such a great help and save me a lot of time/work if I could make this work.
Thank you.

The code has a small error in that you’re receiving the article into the variable $line but only ever manipulating the variable $article. I think this is just a copy/paste error so you’d just need to change the references to $article.

To address your question, I don’t think this is going to work because on line 124 the tags are fetched and added to the published feed without using the tags key of the $line array. This means all the changes from the plugin would never be used.

In order to make this work you’d need to change the core, replacing:

$tags = Article::get_article_tags($line["id"], $owner_uid);

With:

$tags = $line['tags'];

But, as always, I recommend against changing the core because then you have to maintain that forever.

You might be better off hooking earlier, when the articles are coming into the database after fetching. I haven’t looked to see if plugin hooks would support that.

As an aside: I could see that it might be beneficial to move the tags portion earlier in the code so that plugins can work with that data. I’ll let fox weigh in on that. I don’t mind doing a PR he’s okay with the change but doesn’t want to do it himself.

yeah, this is a good idea. if you did a PR for this it would be great. :slight_smile:

Done (and fox has already merged it): https://git.tt-rss.org/fox/tt-rss/pulls/160

And your hook_article_export_feed method would be simplified to something like:

	public function hook_article_export_feed( $line, $feed, $is_cat ) {

		// get all LABELS for this article / line
   		$labels = Article::get_article_labels($line['id']);

		if (count($labels) > 0) {
			foreach ($labels as $label) {
				array_push($line['tags'], $label);
			}

			$line['tags'] = array_unique($line['tags']);
		}
		return $line;
	}

I threw in array_unique; it’s not strictly necessary but would avoid having two of the same feed categories if an article had both a label and tag with the same name.

Thank you so much @JustAMacUser and fox!
I’ve just updated but unfortunately the labels don’t show up as tags in my generated feed for starred articles. I’ve disabled and reenabled the plugin and tried this with several articles, no luck.

Here’s the plugin code incl. your improved part:

<?php
class Labels_To_Tags extends Plugin {
    private $host;

	public function about() {
        return array(1.0,
            'Add labels as tags in generated feeds',
            'nvab');
    }

	public function api_version() {
		return 2;
	}

	public function init( $host ) {
		$this->host = $host;
		$host->add_hook( $host::HOOK_ARTICLE_EXPORT_FEED, $this );
	}

	public function hook_article_export_feed( $line, $feed, $is_cat ) {

		// get all LABELS for this article / line
   		$labels = Article::get_article_labels($line['id']);

		if (count($labels) > 0) {
			foreach ($labels as $label) {
				array_push($line['tags'], $label[1]); // the name of the label is in [1]
			}

			$line['tags'] = array_unique($line['tags']);
		}
		return $line;
	}
}
?>

I did more testing and it seems that my plugin has no effect:

Even if I only include this line in the hook_article_export_feed function in my plugin:
array_push($line['tags'], 'testlabel');

testlabel is not part of the tags in the generated feed.

If I add this line in tt-rss/classes/handler/public.php (in the atom part below line 72) it appears in the generated feed.

I don’t know why my plugin has no effect, not even with this simple test line.

The generate feeds code runs without a user context so the plugin never gets loaded. You’ll need to add this as a system plugin.

Change the about method to:

	public function about() {
        return array(1.0,
            'Add labels as tags in generated feeds',
            'nvab',
            true);
    }

Then add the plugin to the PLUGINS constant in config.php.

btw since your plugin is running without valid user session, i suggest verifying that you pass a valid owner uid - if needed - to methods like get_article_labels and anything else from tt-rss core.

I include the $owner_uid like this:

$owner_uid = $this->host->get_owner_uid();
$labels = Article::get_article_labels($line['id'], $owner_uid);

It all works now on the tt-rss side, labels appear as categories in the generated feed.

Unfortunately, all the trouble may have been for nothing because DevonthinkPro (DTP) which I use to archive stuff just won’t see the extra tags that the plugin added.
It’s as if DTP received the articles from the generated feed before my plugin even had a chance to add the extra tags.

Anyway, I really wanted to thank you and JustAMacUser for all your help. I really appreciate it. Thank you so much.

It’s as if DTP received the articles from the generated feed before my plugin even had a chance to add the extra tags.

there should be a way to fool it, either changing the link slightly (tt-rss already creates a different GUID when exporting) or something like that.

I’ve done so more testing.

If I add the code to append the labels as tags just after $line['tags'] (line 183) has been defined in tt-rss/classes/handlers/public.php then DTP picks up the extra tags (labels). And that’s so far the only (bad) way I’ve managed to get these labels as tags into DTP. :frowning:

Is my plugin too slow/late to modify the generated feed and DTP so fast at grabbing it that by the time my plugin is done DTP has already finished grabbing the unmodified generated feed without the changes?

BTW when you said I could trying changing the link slightly which one did you refer to?

can you post the latest version of your plugin somewhere, like github, or here?

i meant article URL but you probably have a different kind of issue.

This is the version I’m using now:

<?php
class Labels_To_Tags extends Plugin {
    private $host;

	public function about() {
        return array(1.0,
            'Add labels as tags in generated feeds',
            'vnab',
            true);
    }

	public function api_version() {
		return 2;
	}

	public function init( $host ) {
		$this->host = $host;
		$host->add_hook( $host::HOOK_ARTICLE_EXPORT_FEED, $this );
	}

	public function hook_article_export_feed( $line, $feed, $is_cat ) {

   		$owner_uid = $this->host->get_owner_uid();
   		
		// get all LABELS for this article / line
   		$labels = Article::get_article_labels($line['id'], $owner_uid);
   		
		if (count($labels) > 0) {
			foreach ($labels as $label) {
				array_push($line['tags'], $label[1]); // the name of the label is in [1]
			}

			$line['tags'] = array_unique($line['tags']);
		}
		return $line;
	}
}
?>

well, you’re getting owner_uid the wrong way, $this->host->get_owner_uid(); is not going to return anything because there’s nobody logged on. it looks like its not actually easily accessible from this hook so i’ll add it to the function prototype (don’t forget to add it to the hook):

https://git.tt-rss.org/fox/tt-rss/commit/f67f0f864bbac9a60912b00b940848e45e96f046

with the above change and $owner_uid = $this->host->get_owner_uid(); commented out, your plugin seems to work for me, at least going by the XML.

Yes, that did it! Thank you so much. That’s incredible.

There’s just one more thing that’s been on my mind:
after many years my archive has now over 150 tags which I’d have to create/mirror in tt-rss.
Will tt-rss be ok with that many labels? Have you ever had any kind of user feedback that suggests otherwise?
And is there a way to batch import/create that many labels?

But really, you made my day. Thank you, fox. Good night.

you should be able to import labels via OPML (the syntax is tt-rss specific)

it should be okay to have many labels although the UI could get unwieldy with 150 of them in flat list, maybe using tags would be a better idea.

exactly my thoughts.

The background story: recently I’ve found a new iOS client app which supports ttrss without the fever plugin. The developer is very friendly and accommodating. We didn’t find a ttrss API method for assigning/editing/removing article tags but we found this plugin that justamacuser once mentioned and which seems to add API support for labels.
That’s how I got the idea of using labels as tags.
I briefly considered if it was possible to write a plugin that adds such api methods for tags but really, from a realistic pov, that’s just so beyond what I can achieve.

So hopefully the app will eventually support labels and then I’m quite content with that. It’s not tags but probably the next best thing. The important thing is that I can assign keywords to articles in the iOS client app and they end up in the archive eventually. Some time ago I wouldn’t even have dared to dream about this. Very nice.

well, you could ask that plugin guy to add methods for tags, those might be useful for his project and others who might be using that plugin.

like i said, tags would fit a lot better for what you’re trying to accomplish, labels UI is not really designed to deal with hundreds of them. it’s for a higher level categories of interest, i guess, of which you’d have less.

Yeah, seems that I was a little too eager and overlooked the (admittedly small :slight_smile: ) banner that announced that he set the repo to archived/read-only. His successor ‘app’ uses the fever plugin for tt-rss. It is what it is…
Before I give up on assigning article keywords altogether in iOS client apps and return to using the fever plugin again I might as well wait and see if the labels approach works out.
Thank you for your advice and help.