{"id":45,"date":"2025-02-19T11:13:17","date_gmt":"2025-02-19T16:13:17","guid":{"rendered":"http:\/\/www.dev-notes.com\/blog\/?p=45"},"modified":"2025-02-19T15:23:38","modified_gmt":"2025-02-19T20:23:38","slug":"using_php_to_read_from_rss_and_post_to_mastodon","status":"publish","type":"post","link":"https:\/\/www.dev-notes.com\/blog\/2025\/02\/19\/using_php_to_read_from_rss_and_post_to_mastodon\/","title":{"rendered":"Using PHP to read from RSS and post to Mastodon"},"content":{"rendered":"<p>I am currently using a version of the following PHP code to automate the posting of content from <a href=\"https:\/\/ww2db.com\" target=\"_ww2db\">World War II Database<\/a> to the Mastodon social network.  First, please note that you will need the following <a href=\"https:\/\/mastodon.social\/\" target=\"_mastodon\">https:\/\/mastodon.social\/<\/a> information that you can set up after logging on to your Mastodon account.<\/p>\n<ul>\n<li>Client ID<\/li>\n<li>Client Secret<\/li>\n<li>Access Token<\/li>\n<\/ul>\n<p>There are two more things left to configure.<\/li>\n<ul>\n<li>URL to the RSS Feed<\/li>\n<li>File Path\/Name for a Log File &#8211; This is used to avoid posting duplicate content on Mastodon<\/li>\n<\/ul>\n<p>Below is the code.  Note that in Step 2 there is a hard-coded value of &#8220;100&#8221; to note that the log file (to keep track of which RSS content has already been posted) will keep track of the most recent 100 records; Step 4 posts in the format of title-space-link, which you may wish to adjust based on your RSS feed structure.<\/p>\n<pre class=\"code\">\r\n$clientId = 'client_id_here';\r\n$clientSecret = 'client_secret_here';\r\n$accessToken = 'access_token_here';\r\n$redirectUri = 'urn:ietf:wg:oauth:2.0:oob';\r\n$rssUrl = 'rss_feed_url_here';\r\n$postedIdsFile = 'rss_to_mastodon_posted_ids.txt';\r\n\r\n\/\/ Change the following to \"N\" to output some debugging information\r\n$silentMode = \"Y\";\r\n\r\n\/\/ Step 1: Establish a function to fetch RSS feed\r\nfunction fetchRss($rssUrl) {\r\n    $rss = simplexml_load_file($rssUrl);\r\n    return $rss;\r\n}\r\n\r\n\/\/ Step 2: Establish a function to check the log file to avoid duplicates\r\nfunction checkAndUpdatePostIds($postId, $file) {\r\n    $postedIds = file_exists($file) ? file($file, FILE_IGNORE_NEW_LINES) : [];\r\n    if (in_array($postId, $postedIds)) {\r\n        return false;\r\n    }\r\n    array_push($postedIds, $postId);\r\n    if (count($postedIds) > 100) {\r\n        array_shift($postedIds);\r\n    }\r\n    file_put_contents($file, implode(\"\\n\", $postedIds) . \"\\n\");\r\n    return true;\r\n}\r\n\r\n\/\/ Step 3: Establish a function to post to Mastodon\r\nfunction postToMastodon($accessToken, $content) {\r\n    $url = \"https:\/\/mastodon.social\/api\/v1\/statuses\";\r\n\r\n    $data = [\r\n        'status' => $content,\r\n    ];\r\n\r\n    $options = [\r\n        CURLOPT_URL => $url,\r\n        CURLOPT_RETURNTRANSFER => true,\r\n        CURLOPT_POST => true,\r\n        CURLOPT_POSTFIELDS => http_build_query($data),\r\n        CURLOPT_HTTPHEADER => [\r\n            \"Authorization: Bearer $accessToken\",\r\n            \"Content-Type: application\/x-www-form-urlencoded\",\r\n        ],\r\n    ];\r\n\r\n    $ch = curl_init();\r\n    curl_setopt_array($ch, $options);\r\n    $response = curl_exec($ch);\r\n    curl_close($ch);\r\n\r\n    return $response;\r\n}\r\n\r\n\/\/ Step 4: Put eveything together\r\nif (!$accessToken) {\r\n    die(\"Error: Unable to obtain access token. Exiting...\");\r\n}\r\n\r\n$rss = fetchRss($rssUrl);\r\n\r\nforeach ($rss->channel->item as $item) {\r\n    $postId = (string) $item->guid;\r\n    $content = (string) $item->title . \" \" . (string) $item->link;\r\n\r\n    if (checkAndUpdatePostIds($postId, $postedIdsFile)) {\r\n        if ($silentMode == \"N\") {\r\n            echo \"Posting: $content\\n\";\r\n        }\r\n        $response = postToMastodon($accessToken, $content);\r\n        if ($silentMode == \"N\") {\r\n            echo $response . \"\\n\";\r\n        }\r\n    } else {\r\n        if ($silentMode == \"N\") {\r\n            echo \"Duplicate detected, skipping: $content\\n\";\r\n        }\r\n    }\r\n}\r\n<\/pre>\n<p>As far as usage goes, you can refactor the various pieces to fit into your existing PHP-based management tool.  As a shortcut, you can also take the above code as-is and run it via cron or other similar job schedulers.<\/p>\n<p>My implementation of this code posts contents to the WW2DB Mastodon page at the URL <a href=\"https:\/\/mastodon.social\/@ww2db\" target=\"_mastodon\">https:\/\/mastodon.social\/@ww2db<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This PHP code reads from a RSS feed and posts new items to Mastodon.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4,61],"tags":[],"class_list":["post-45","post","type-post","status-publish","format-standard","hentry","category-php","category-social-media"],"_links":{"self":[{"href":"https:\/\/www.dev-notes.com\/blog\/wp-json\/wp\/v2\/posts\/45","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.dev-notes.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.dev-notes.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.dev-notes.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.dev-notes.com\/blog\/wp-json\/wp\/v2\/comments?post=45"}],"version-history":[{"count":2,"href":"https:\/\/www.dev-notes.com\/blog\/wp-json\/wp\/v2\/posts\/45\/revisions"}],"predecessor-version":[{"id":422,"href":"https:\/\/www.dev-notes.com\/blog\/wp-json\/wp\/v2\/posts\/45\/revisions\/422"}],"wp:attachment":[{"href":"https:\/\/www.dev-notes.com\/blog\/wp-json\/wp\/v2\/media?parent=45"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.dev-notes.com\/blog\/wp-json\/wp\/v2\/categories?post=45"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.dev-notes.com\/blog\/wp-json\/wp\/v2\/tags?post=45"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}