Let me walk through creating a workflow in PipeDream to track watched TV time in Trakt through RescueTime. I use RescueTime to help manage my time and track billable hours for freelancing. Working with some international folks in other timezones, my work hours can be all over the place. I wanted to exclude known personal activities from “work” time so that the odd hours work time wouldn’t be marked as billable. Being a data nerd, I also track TV and movies I watch in Trakt. I should be able to connect the two, right?
Looking at workflow automation options:
- IFTTT
- You can track watched media in Trakt but it doesn’t include the duration of the show; getting extended info still doesn’t get the duration
- It does allow you to “Log offline time“
- Requires paid plan to do more than the basic stuff
- Zapier
- No built in Trakt support
- It does allow you to “Log offline time”
- Requires paid plan for more than 2 steps
- Activepieces
- Doesn’t have built in support for any of this
-
Huginn
- I don’t want to set up a deployment/server
- Doesn’t have built in support for either service
- Automatisch
- Needs selfhosting, no built in support
- PipeDream
I went with PipeDream. It might be more technical, but it gives me some flexibility. Also, I wanted to try out a new service other than the first two.
We create a new project. Since watching a show and watching a movie are different triggers, we’ll need two different workflows but both can be stored in this one project.
Let’s start with watching a show since this is more complicated metadata. Create a new workflow.
Add a trigger, search for Trakt, then click “New Show Or Movie Watched“.
Set up your Trakt account (it’s nice that even for the custom code, PipeDream can handle the authentication).
Then select “Shows” as the Type.
You’ll get some events from already watched shows – I selected a show I’ve watched multiple times in the past so we can see what more complex data looks like.
Under “Exports” you’ll see what data the trigger returns. We can see the structure and how it can contain multiple episodes if you were binging a series. Looking at the recent event list, this same event can fire multiple times, so there can be an overlap in the data. It appears we’re only interested in the episodes where the last_watched_at
matches the global/show last_watched_at
.
Now we’ll add a step. Since we only have the watch time and not the duration from the trigger, we’ll need to get the duration. This isn’t a new “watch” or “rating” so we’ll need to “Use any Trakt API in Node.js“. We get a boilerplate template to work with!
Looking at the Trakt API documentation, we can get extended media information with the Search API. This will give us our duration. The previous step gave us ids.trakt
for the show, so we can plug this in. We’ll alter the url to match the API documentation.
Next we’ll loop through the episode watches.
As mentioned above, we don’t want older viewings, only the viewings that match the global/show last_watched_at
are relevant for this event.
We don’t want to log old events (past a week, it’s not relevant for my RescueTime data) and especially at first Trakt will hand us some old data.
I’m storing the watch info in the format RescueTime will want it in. This requires some date formatting and adjusting for timezone.
import { axios } from "@pipedream/platform" export default defineComponent({ props: { trakt: { type: "app", app: "trakt", } }, async run({steps, $}) { // filter out old results before querying for show details const showWatchDate = Date.parse(steps.trigger.event.last_watched_at); const luDate = Date.parse(steps.trigger.event.last_updated_at); if( (Date.now() - showWatchDate) > (8*24*60*60*1000) || (Date.now() - luDate) > (8*24*60*60*1000) ) { return $.flow.exit("Watched more than 8 days ago"); } const data = await axios($, { url: `https://${this.trakt.$auth.environment}.trakt.tv/search/trakt/${steps.trigger.event.show.ids.trakt}?type=show&extended=full`, headers: { Authorization: `Bearer ${this.trakt.$auth.oauth_access_token}`, "Content-Type": `application/json`, "trakt-api-version": `2`, "trakt-api-key": `${this.trakt.$auth.oauth_client_id}`, }, }); // in case we couldn't get episode runtime for any reason const showDuration = data?.[0]?.show?.runtime; if(!showDuration) { return $.flow.exit("Couldn't get show runtime"); } let errorMsg = ""; const plays = []; steps.trigger.event.seasons.forEach((season)=>{ season.episodes.forEach((episode)=>{ // new viewings should match last_watched_at for show and episode let d = new Date(episode.last_watched_at); if(d.getTime() < showWatchDate) { errorMsg = "Entry is from a previous viewing"; return; } // filter out old viewings if((Date.now() - d.getTime()) > (8*24*60*60*1000)) { errorMsg = "Watched more than 8 days ago"; return; } // convert end time to start time, then change timezone d = new Date(d.getTime() - showDuration); d = new Date(d.toLocaleString("en-US", { timeZone: "America/New_York" })); // build datetime string in desired RescueTime format let mo = d.getMonth(); mo = mo < 10 ? `0${mo}` : mo; let day = d.getDate(); day = day < 10 ? `0${day}` : day; let hr = d.getHours(); hr = hr < 10 ? `0${hr}` : hr; let min = d.getMinutes(); min = min < 10 ? `0${min}` : min; let s = d.getSeconds(); s = s < 10 ? `0${s}` : s; // store in desired RescueTime structure plays.push({ "start_time": `${d.getFullYear()}-${mo}-${day} ${hr}:${min}:${s}`, "duration": showDuration, "activity_name": "Watch TV Show", "activity_details": `${data[0].show.title} S${season.number}E${episode.number}` }); }); }); // exit early if there were no good viewings to submit if(errorMsg && !plays.length) { return $.flow.exit(errorMsg); } return plays; }, })
Now we’ll add the RescueTime step. There’s not a built in step for offline logging, so we’ll “Use any RescueTime API in Node.js“. The boilerplate template is for a single HTTP request and the API doesn’t support multiple offline entries, so we’ll need to alter this significantly. The API documentation lists a specific format for a POST request; we’ll loop through the logs we have stored from the previous step and do serial calls to submit each.
import { axios } from "@pipedream/platform" export default defineComponent({ props: { rescuetime: { type: "app", app: "rescuetime", } }, async run({steps, $}) { const rdata = []; for(let v = 0; v < steps.trakt.$return_value.length; v++) { // do we have any data to submit? if(!steps.trakt.$return_value || !steps.trakt.$return_value.length) { return $.flow.exit(); } if(!steps.trakt.$return_value[v]) continue; // submit like the docs specify, make sure it's interpreting as JSON data const view = steps.trakt.$return_value[v]; // an error will block subsequent logs but that is okay const r = await axios($, { method: "POST", url: `https://www.rescuetime.com/anapi/offline_time_post`, data: view, params: { key: process.env.RT_API_KEY, }, headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', }, }); // store the return data, but not necessary since no more steps rdata.push(r); } // for debugging purposes return rdata; }, });
Now we can deploy! In the project view you can hit “Copy” to duplicate it and then you can alter it to support Movie views.