Save To Wallabag Plugin

I’m trying to get this to work:

and I’m not having any luck at all. Can anyone here confirm it works at all? I don’t know if it’s just too old and I’m wasting my time or if it legitimately works and I’m just not doing something right.

I get the following error when trying to save article to Wallabag:

ReferenceError: Can’t find variable: Notify

This is after I have enabled the plugin and setup my API key/secret combo.

looks like your tt-rss version is too old, you need to update

This plug-in broke for me a while ago , even running while the latest version of tt-rss.
Now I just use the wallabagger Firefox plug-in and right-click on the article title to save to wallabag.

This plugin works for me, in a fairly recent Wallabag and TT-RSS. There is a tt-rss error each time I collect an article, but the article is correctly parsed in Wallabag.

OK , Thanks @Glandos , perhaps I’ll give it another try.

Using the latest version of ttrss from git, and this plugin, I am currently experiencing no issues with the plugin. No errors are thrown and everything works as expected. Please open an issue if you encounter any problems.

Thanks @joshp , I reinstalled the plugin from scratch and it is working perfectly again.

edit: Solved! Did not include the /web shown on the end of my domain url. Now everything works! Carry on.

 Alert: No OAuth tokens in Database! Submit Username and Password to retrieve new tokens.

Clicking Save on account credentials returns no notification, as mentioned here[1]. I’ve tried refreshing the page or waiting several minutes, but same result with every attempt.

[1] https://github.com/joshp23/ttrss-to-wallabag-v2/issues/37

Attempting to use the plugin URL results in:
59%20AM

Question: Can I automatically trigger the Wallabag plugin for every item in a specified feed?

I am interested in this too

I’ll look into this when I have time. Pull requests are welcome.

Manually saving articles to Wallabag is nice.
But I think being able to send articles to wallabag with a filter would be great!

I’ve really tried to make this happen but I just don’t have the required skills. I’m only a normal ttrss user.

I’ve added the usual lines to the init block:

function init($host) {
	...
	$host->add_hook($host::HOOK_ARTICLE_FILTER_ACTION, $this);
	$host->add_filter_action($this, "send to Wallabag", "send to Wallabag");
}

and then there would be a filter action block:

function hook_article_filter_action($article, $action) {
    
    if ($action == "send to Wallabag") {
	$this->getwallabagInfo($article);
          }
 return $article;
}

and then inside the getwallabagInfo function you could extract the article_link and title with

	$article_link = $article["link"];
	$title = truncate_string(strip_tags($article["title"]), 100, '...');

Or you could just send the link and title to the function maybe?

Anyway it’s not coming together. As I said I don’t have the skills. Maybe it’s a mySQL problem (trying to save the article to the database while saving to wallabag?) because at some point I got some error like

PDOException: There is already an active transaction in /mydomain.tld/tt-rss/classes/pluginhost.php:364

I would be so glad if I could use your extension together with the great filter function.
Hope you’ll find the time and inspiration to make it happen.
Thank you very much.

OK, it think I got it working. So if you want to be able to send an article/url to wallabag with a filter (using the invoke plugin action) you just have to add some code to the init.php file.

First add these two lines to the function init($host):

$host->add_hook($host::HOOK_ARTICLE_FILTER_ACTION, $this);
$host->add_filter_action($this, "send to Wallabag", "send to Wallabag");

and then add the function hook_article_filter_action($article, $action) somewhere:

function hook_article_filter_action($article, $action) {

    if ($action == "send to Wallabag") {
        $article_link = $article["link"];

        $w_url = $this->host->get($this, "wallabag_url");
        $w_cid = $this->host->get($this, "wallabag_client_id");
        $w_cs = $this->host->get($this, "wallabag_client_secret");

        if (function_exists('curl_init')) {
            $w_access = $this->host->get($this, "wallabag_access_token");
            $old_timeout = $this->host->get($this, "wallabag_access_token_timeout");
            $now = time();
            if( $old_timeout < $now ) {
                $w_refresh = $this->host->get($this, "wallabag_refresh_token");
                $old_rTimeout = $this->host->get($this, "wallabag_access_token_timeout");
                if( $old_rTimeout < $now ) {
                    $w_user = $this->host->get($this, "wallabag_username");
                    $w_pass = $this->host->get($this, "wallabag_password");
                    $postfields = array(
                        "client_id"     => $w_cid,
                        "client_secret" => $w_cs,
                        "username"         => $w_user,
                        "password"         => $w_pass,
                        "grant_type"     => "password"
                    );
                    $OAcURL = curl_init();
                        curl_setopt($OAcURL, CURLOPT_URL, $w_url . '/oauth/v2/token');
                        curl_setopt($OAcURL, CURLOPT_HTTPHEADER, array('Content-type: application/x-www-form-urlencoded;charset=UTF-8'));
                        curl_setopt($OAcURL, CURLOPT_RETURNTRANSFER, true);
                        curl_setopt($OAcURL, CURLOPT_TIMEOUT, 30);
                        curl_setopt($OAcURL, CURLOPT_POST, true);
                        curl_setopt($OAcURL, CURLOPT_SSL_VERIFYPEER, false);
                        curl_setopt($OAcURL, CURLOPT_POSTFIELDS, http_build_query($postfields));
                    $OAresult = curl_exec($OAcURL);
                    $aTimeout =  time() + 3600;
                    $rTimeout =  time() + 1209600;
                    $OAstatus = curl_getinfo($OAcURL, CURLINFO_HTTP_CODE);
                        curl_close($OAcURL);
                    $OAresult = json_decode($OAresult,true);

                    $w_access = $OAresult["access_token"];
                    $w_refresh = $OAresult["refresh_token"];

                    if ($OAstatus == 200) {
                        $this->host->set($this, "wallabag_access_token", $w_access);
                        $this->host->set($this, "wallabag_access_token_timeout", $aTimeout);
                        $this->host->set($this, "wallabag_refresh_token", $w_refresh);
                        $this->host->set($this, "wallabag_refresh_token_timeout", $rTimeout);
                    }

                } else {

                    $postfields = array(
                        "client_id"     => $w_cid,
                        "client_secret" => $w_cs,
                        "refresh_token" => $w_refresh,
                        "grant_type"     => "refresh_token"
                    );
                    $OAcURL = curl_init();
                        curl_setopt($OAcURL, CURLOPT_URL, $w_url . '/oauth/v2/token');
                        curl_setopt($OAcURL, CURLOPT_HTTPHEADER, array('Content-type: application/x-www-form-urlencoded;charset=UTF-8'));
                        curl_setopt($OAcURL, CURLOPT_RETURNTRANSFER, true);
                        curl_setopt($OAcURL, CURLOPT_TIMEOUT, 30);
                        curl_setopt($OAcURL, CURLOPT_POST, true);
                        curl_setopt($OAcURL, CURLOPT_SSL_VERIFYPEER, false);
                        curl_setopt($OAcURL, CURLOPT_POSTFIELDS, http_build_query($postfields));
                    $OAresult = curl_exec($OAcURL);
                    $OAstatus = curl_getinfo($OAcURL, CURLINFO_HTTP_CODE);
                    $new_timeout =  time() + 3600;
                        curl_close($OAcURL);

                    $OAresult = json_decode($OAresult,true);

                    $w_access = $OAresult["access_token"];
                    $w_refresh = $OAresult["refresh_token"];

                    if ($OAstatus == 200) {
                        $this->host->set($this, "wallabag_access_token", $w_access);
                        $this->host->set($this, "wallabag_access_token_timeout", $new_timeout);
                        $this->host->set($this, "wallabag_refresh_token", $w_refresh);
                    }
                }
            }
        }

        $postfields = array(
            'access_token' => $w_access,
            'url'          => $article_link
        );
            $cURL = curl_init();
                curl_setopt($cURL, CURLOPT_URL, $w_url.'/api/entries.json');
                curl_setopt($cURL, CURLOPT_HEADER, 1);
                curl_setopt($cURL, CURLOPT_HTTPHEADER, array('Content-type: application/x-www-form-urlencoded;charset=UTF-8'));
                curl_setopt($cURL, CURLOPT_RETURNTRANSFER, true);
                curl_setopt($cURL, CURLOPT_TIMEOUT, 30);
                curl_setopt($cURL, CURLOPT_POST, 4);
                curl_setopt($cURL, CURLOPT_POSTFIELDS, http_build_query($postfields));
            $apicall = curl_exec($cURL);curl_close($cURL);

     return $article;
     }
 }

Of course you can still use the button to add articles to wallabag.
@dariottolo @sunjam : maybe this is what you were looking for?

@vnab @dariottolo @sunjam

Adding to Wallabag via Content Filtering has been added to the latest release, 1.9.0, with the following commit

@joshp
I’m afraid it seems that your code (1.9.0) runs into the same problem that I had. If you wrap the send function in the filter action function into try-catch like this

try {
          $this->_send( $title, $article_link, $source );
      } catch (exception $e) {
          echo $e;
      }

and set a filter to invoke your plugin and then update rss feeds on the command line you’ll get the error I mentioned earlier for every new feed

PDOException: There is already an active transaction 

New feeds to which the filter applies (for testing purposes I set it to all feeds and .* in the title) won’t show up in wallabag.
That’s why I packed the functions to get/refresh the auth token & the send function itself into the filter action part. It’s certainly not elegant and in most part duplicated code but it was the only way I could get it to work.

P.S. is there a command to clone your repo into plugins.local in a way that I have the wallabag_v2 folder without its enclosing ttrss-to-wallabag-v2 folder, i.e. the init.php and the git files on the same level so I can do a simple git pull master origin to update instead of having to move the wallabag_v2 folder on level up? I’m sorry but I’m not that familiar with git.

you should be able to do stuff like this with symlinks, checkout in a separate directory and symlink whatever you need inside into plugins.local.

thanks @fox for that input

@vnab, I do have articles posting to Wallabag via the filter with the latest code (1.9.0) so long as the Wallabag access token is still “fresh”. I have been running tests, and it appears as if the error occurs only when the token gets refreshed. Here’s how I got here:

  1. Save an article to Wallabag using the button method. This will refresh the token if necessary.

  2. Run a feed debug (fD) on a feed effected by a properly configured filter.

  3. Check Wallabag, error logs, and debug.txt, all look good.

  4. Wait until the token expires, run fD on the effected feed again. Experience the following error:

    2020/03/08 15:03:13 [error] 1753#1753: *313266 FastCGI sent in stderr: “PHP message: PHP Fatal error: Uncaught PDOException: There is already an active transaction in /var/www/ttrss/classes/pluginhost.php:335
    Stack trace:
    #0 /var/www/ttrss/classes/pluginhost.php(335): PDO->beginTransaction()
    #1 /var/www/ttrss/classes/pluginhost.php(370): PluginHost->save_data(‘Wallabag_v2’)
    #2 /var/www/ttrss/plugins.local/wallabag_v2/init.php(255): PluginHost->set(Object(Wallabag_v2), ‘wallabag_access…’, ‘REDACTED…’)
    #3 /var/www/ttrss/plugins.local/wallabag_v2/init.php(199): Wallabag_v2->_send(‘A thought i hav…’, ‘https://www.red…’, ‘filter’)
    #4 /var/www/ttrss/classes/rssutils.php(841): Wallabag_v2->hook_article_filter_action(Array, ‘wallabag_v2_sen…’)
    #5 /var/www/ttrss/classes/feeds.php(846): RSSUtils::update_rss_feed(10, true)
    #6 /var/www/ttrss/backend.php(123): Feeds->update_debugger()
    #7 {main}
    thrown in /var/www/ttrss/classes/pluginhost.php on line 335” while reading upstream, client: MY_IP_ADDRESS, server: ttrss.example.com, request: “GET /backend.php?op=feeds&method=update_debugger&xdebug=1&csrf_token=REDACTED&action=do_update&feed_id=10&force_refetch=1&force_rehash=1 HTTP/2.0”, upstream: “fastcgi://unix:/run/php/php7.3-fpm.sock:”, host: “ttrss.example.com”, referrer: “https://ttrss.example.com/backend.php?op=feeds&method=update_debugger&xdebug=1&csrf_token=REDACTED&action=do_update&feed_id=10&force_refetch=1&force_rehash=1

Item #2 of the Stack trace indicates that the last line of code in the plugin that is called before the failure is line 255,

which is attempting to save refresh token data. This error does not occure when saving and refreshing from the button. My guess here, and @fox might be able to help us out, is it is correct in assuming that the error occures when

Fox, would that check out?

I would rather solve that problem than bloat out the code with copypasta.

TT-RSS has already started a transaction for the database (for inserting new articles) and when you try to save new data for the plugin’s preferences that code also tries to start a new transaction.

How often does this token expire? If it lasts a reasonable amount of time you could try updating the token data during TT-RSS’s maintenance run (different than the updating). You could also store the token in a class property and update it at a later point before the updater exits.

you should be able to get another PDO handle, that’s how logger is working around concurrent transactions. (Db::pdo_connect)

@fox I’m confused about how to implement DB::pdo_connect here. I cannot cause PluginHandler, which is saving the data, to get another PDO handle, as the PDO property is private. I’ve tried several variations on
if (!$this->pdo) $this->pdo = Db::instance()->pdo_connect();
all which yield the same error as above.


It’s configurable at the Wallabag instance. What is the maintenance interval for TTRSS?

Been looking into this. I believe I have it. So I add this:

function init($host) {  
...
$host->add_hook($host::HOOK_HOUSE_KEEPING, $this);
---
}

function hook_house_keeping() {
     WALLABAG_AUTH_REFRESH
}

And the tokens stay fresh so long as they last a reasonable amount of time, ie, longer than the maintenance run intervals. Yes?