10.0: Manage Twitter mentions with automations and custom records #8
jstanden
started this conversation in
Guides and Tutorials
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Create a new Twitter app
Sign in to Twitter using your corporate/team account.
Navigate to https://developer.twitter.com/en/portal/dashboard
Creating a project
If you don't have a project yet, you'll have to petition for one. If you already have a project you can skip this section.
Most likely you want: Professional >> Building customized solutions in-house.
Click the Get Started button.
Fill in the application.
Create a standalone app
Once you have an approved project you can create applications.
From Projects & Apps >> Overview click the Create App button in the Standalone Apps section at the bottom.
App Name: Twitter for Cerb
Make a note of your API key, secret key, and token.
Click the App settings button at the bottom.
Enable write permission
In the App permissions section, click the blue Edit button.
Select Read and Write and click the Save button.
Enable 3-legged OAuth
In the Authentication Settings section at the bottom, click the blue Edit button.
Enable 3-legged OAuth.
https://your-cerb-url/oauth/callback/cerb.service.provider.oauth1Click the blue Save button in the bottom right.
Import the package in Cerb
Navigate to Setup >> Packages >> Import.
Paste the following package:
{ "package": { "name": "Twitter for Cerb", "requires": { "cerb_version": "10.0.0" }, "configure": { "placeholders": [], "prompts": [ { "type": "text", "label": "Consumer Key", "key": "prompt_client_id", "params": { "default": "", "placeholder": "(paste your Consumer Key)" } }, { "type": "text", "label": "Consumer Secret", "key": "prompt_client_secret", "params": { "default": "", "placeholder": "(paste your Consumer Secret)" } }, { "type": "text", "label": "Twitter User Name", "key": "prompt_screen_name", "params": { "default": "", "placeholder": "(e.g. `cerb_ai`)" } } ] } }, "records": [ { "uid": "service_twitter", "_context": "connected_service", "name": "Twitter for Cerb", "uri": "twitter", "extension_id": "cerb.service.provider.oauth1", "params": { "grant_type": "authorization_code", "client_id": "{{{prompt_client_id}}}", "client_secret": "{{{prompt_client_secret}}}", "request_token_url": "https://api.twitter.com/oauth/request_token", "authentication_url": "https://api.twitter.com/oauth/authenticate", "access_token_url": "https://api.twitter.com/oauth/access_token", "signature_method_": "HMAC-SHA1" } }, { "uid": "account_twitter", "_context": "connected_account", "name": "Twitter (@username)", "service_id": "{{{uid.service_twitter}}}", "uri": "twitter_cerb", "owner__context": "app", "owner_id": 0 }, { "uid": "custom_record_tweet", "_context": "custom_record", "name": "Tweet", "name_plural": "Tweets", "uri": "tweet" }, { "uid": "cf_tweet_status_id", "_context": "custom_field", "name": "Status ID", "context": "tweet", "uri": "status_id", "type": "S", "params": { } }, { "uid": "cf_tweet_user_name", "_context": "custom_field", "name": "User Name", "context": "tweet", "uri": "user_name", "type": "S", "params": { } }, { "uid": "cf_tweet_user_screen_name", "_context": "custom_field", "name": "User Screen Name", "context": "tweet", "uri": "user_screen_name", "type": "S", "params": { } }, { "uid": "cf_tweet_user_profile_image", "_context": "custom_field", "name": "User Profile Image", "context": "tweet", "uri": "user_profile_image", "type": "U", "params": { } }, { "uid": "cf_tweet_message", "_context": "custom_field", "name": "Message", "context": "tweet", "uri": "message", "type": "T", "params": { } }, { "uid": "cf_tweet_account", "_context": "custom_field", "name": "Account", "context": "tweet", "uri": "account", "type": "L", "params": { "context": "cerberusweb.contexts.connected_account" } }, { "uid": "cf_tweet_parent_status_id", "_context": "custom_field", "name": "Parent Status ID", "context": "tweet", "uri": "parent_status_id", "type": "S", "params": { } }, { "uid": "card_widget_tweet_convo", "_context": "cerb.contexts.card.widget", "name": "Conversation", "record_type": "tweet", "extension_id": "cerb.card.widget.sheet", "pos": "1", "width_units": "4", "zone": "content", "extension_params": { "data_query": "type:worklist.records\r\nof:tweet\r\nexpand: [record_customfields,]\r\nquery:(\r\n (statusId:{{record_status_id}} OR statusId:{{record_parent_status_id}})\r\n sort:created\r\n)\r\nformat:dictionaries", "cache_secs": "", "placeholder_simulator_kata": "", "sheet_kata": "layout:\r\n style: table\r\n headings@bool: no\r\n paging@bool: yes\r\n #title_column: _label\r\n\r\ncolumns:\r\n card/tweet:\r\n label: Tweet\r\n params:\r\n context: tweet\r\n id_template@raw: {{id}}\r\n label_template@raw:\r\n <div style=\"float:left;margin-right:10px;\">\r\n <img class=\"cerb-rounded\" src=\"{{user_profile_image}}\" style=\"height:50px;width:50px;\">\r\n </div>\r\n <div style=\"overflow:hidden;\">\r\n <b>{{user_name}}</b> @{{user_screen_name}}<br>\r\n <div>\r\n {{message|nl2br}}\r\n </div>\r\n <small><i><abbr title=\"{{created_at|date('r')}}\">{{created_at|date_pretty}}</abbr></i></small>\r\n </div>\r\n underline@bool: no\r\n ", "toolbar_kata": "" } }, { "uid": "card_widget_tweet_reply", "_context": "cerb.contexts.card.widget", "name": "Actions", "record_type": "tweet", "extension_id": "cerb.card.widget.form_interaction", "pos": "1", "width_units": "4", "zone": "content", "extension_params": { "interactions_kata": "interaction/reply:\n label: Reply\n uri: cerb:automation:wgm.example.twitter.reply\n icon: send\n #hidden@bool:\n inputs:\n tweet: {{record_id}}\n after:\n refresh_widgets@list: Actions" } }, { "uid": "automation_twitter_mentions", "_context": "automation", "name": "wgm.example.twitter.syncMentions", "extension_id": "cerb.trigger.automation.timer", "description": "Sync Twitter mentions into Tweet records", "script": "inputs:\n record/connected_account:\n record_type: connected_account\n required@bool: yes\n\nstart:\n # Do we have a previous checkpoint?\n storage.get:\n inputs:\n key: wgm.example.twitter.syncMentions:{{inputs.connected_account.id}}:since_id\n output: since_id\n on_error:\n set:\n since_id: 0\n \n # Fetch new mentions from the Twitter API\n http.request/mentions:\n output: http_response\n inputs:\n method: GET\n url: https://api.twitter.com/1.1/statuses/mentions_timeline.json?count=200&tweet_mode=extended{{since_id ? '&since_id=' ~ since_id}}\n authentication: cerb:connected_account:{{inputs.connected_account.id}}\n on_error:\n log: {{http_response.error}}\n on_success:\n set:\n tweets@json: {{http_response.body}}\n repeat:\n each@json: {{tweets|keys|json_encode}}\n as: index\n do:\n set:\n tweet@json: {{tweets[index]|json_encode}}\n since_id: {{max(since_id, tweet.id_str)}}\n record.upsert:\n output: record_tweet\n inputs:\n record_type: tweet\n record_query: statusId:${status_id}\n record_query_params:\n status_id: {{tweet.id_str}}\n fields:\n name: {{tweet.user.screen_name}}: {{tweet.full_text}}\n created_at@date: {{tweet.created_at}}\n updated_at@date: {{tweet.created_at}}\n status_id: {{tweet.id_str}}\n user_name: {{tweet.user.name}}\n user_screen_name: {{tweet.user.screen_name}}\n user_profile_image: {{tweet.user.profile_image_url_https}}\n message: {{tweet.full_text}}\n account: {{inputs.connected_account.id}}\n parent_status_id: {{tweet.in_reply_to_status_id_str}}\n on_error:\n log: {{record_tweet.error}}\n \n # Save a new checkpoint\n storage.set:\n inputs:\n key: wgm.example.twitter.syncMentions:{{inputs.connected_account.id}}:since_id\n value@key: since_id\n \n # Resume now if more, otherwise in 5 mins", "policy_kata": "commands:\n http.request:\n deny/url@bool: {{inputs.url is not prefixed ('https://api.twitter.com/1.1/statuses/mentions_timeline.json')}}\n deny/account@bool: {{inputs.authentication is not prefixed ('cerb:connected_account:')}}\n allow@bool: yes\n storage.get:\n deny/key@bool: {{inputs.key is not prefixed ('wgm.example.twitter.syncMentions:')}}\n allow@bool: yes\n storage.set:\n deny/key@bool: {{inputs.key is not prefixed ('wgm.example.twitter.syncMentions:')}}\n allow@bool: yes\n record.create:\n deny/type@bool: {{inputs.record_type is not record type ('tweet')}}\n allow@bool: yes\n record.update:\n deny/type@bool: {{inputs.record_type is not record type ('tweet')}}\n allow@bool: yes\n record.upsert:\n deny/type@bool: {{inputs.record_type is not record type ('tweet')}}\n allow@bool: yes" }, { "uid": "automation_twitter_user_timeline", "_context": "automation", "name": "wgm.example.twitter.syncUserTimeline", "extension_id": "cerb.trigger.automation.timer", "description": "Sync Twitter posts by a specific user into Tweet records", "script": "inputs:\n record/connected_account:\n record_type: connected_account\n required@bool: yes\n text/screen_name:\n required@bool: yes\n\nstart:\n # Do we have a previous checkpoint?\n storage.get:\n inputs:\n key: wgm.example.twitter.syncUserTimeline:{{inputs.connected_account.id}}:{{inputs.screen_name}}:since_id\n output: since_id\n on_error:\n set:\n since_id: 0\n \n # Fetch new user tweets from the Twitter API\n http.request/tweets:\n output: http_response\n inputs:\n method: GET\n url: https://api.twitter.com/1.1/statuses/user_timeline.json?count=200&exclude_replies=false&include_rts=true&tweet_mode=extended{{since_id ? '&since_id=' ~ since_id}}\n authentication: cerb:connected_account:{{inputs.connected_account.id}}\n on_error:\n log: {{http_response.error}}\n on_success:\n set:\n tweets@json: {{http_response.body}}\n repeat:\n each@json: {{tweets|keys|json_encode}}\n as: index\n do:\n set:\n tweet@json: {{tweets[index]|json_encode}}\n since_id: {{max(since_id, tweet.id_str)}}\n record.upsert:\n output: record_tweet\n inputs:\n record_type: tweet\n record_query: statusId:${status_id}\n record_query_params:\n status_id: {{tweet.id_str}}\n fields:\n name: {{tweet.user.screen_name}}: {{tweet.full_text}}\n created_at@date: {{tweet.created_at}}\n updated_at@date: {{tweet.created_at}}\n status_id: {{tweet.id_str}}\n user_name: {{tweet.user.name}}\n user_screen_name: {{tweet.user.screen_name}}\n user_profile_image: {{tweet.user.profile_image_url_https}}\n message: {{tweet.full_text}}\n account: {{inputs.connected_account.id}}\n parent_status_id: {{tweet.in_reply_to_status_id_str}}\n on_error:\n log: {{record_tweet.error}}\n \n # Save a new checkpoint\n storage.set:\n inputs:\n key: wgm.example.twitter.syncUserTimeline:{{inputs.connected_account.id}}:{{inputs.screen_name}}:since_id\n value@key: since_id\n", "policy_kata": "commands:\n http.request:\n deny/url@bool: {{inputs.url is not prefixed ('https://api.twitter.com/1.1/statuses/user_timeline.json')}}\n deny/account@bool: {{inputs.authentication is not prefixed ('cerb:connected_account:')}}\n allow@bool: yes\n storage.get:\n deny/key@bool: {{inputs.key is not prefixed ('wgm.example.twitter.syncUserTimeline:')}}\n allow@bool: yes\n storage.set:\n deny/key@bool: {{inputs.key is not prefixed ('wgm.example.twitter.syncUserTimeline:')}}\n allow@bool: yes\n record.create:\n deny/type@bool: {{inputs.record_type is not record type ('tweet')}}\n allow@bool: yes\n record.update:\n deny/type@bool: {{inputs.record_type is not record type ('tweet')}}\n allow@bool: yes\n record.upsert:\n deny/type@bool: {{inputs.record_type is not record type ('tweet')}}\n allow@bool: yes" }, { "uid": "automation_twitter_reply", "_context": "automation", "name": "wgm.example.twitter.reply", "extension_id": "cerb.trigger.interaction.worker", "description": "Reply to a tweet using the same account", "script": "inputs:\n record/tweet:\n record_type: tweet\n required@bool: yes\n\nstart:\n # Get the reply message\n await:\n form:\n title: Reply\n elements:\n textarea/prompt_reply:\n label: Reply to {{inputs.tweet.user_screen_name}}:\n required@bool: yes\n default: @{{inputs.tweet.user_screen_name}} \n max_length: 280\n \n # Post the reply via Twitter API\n http.request/post:\n output: http_response\n inputs:\n method: POST\n url: https://api.twitter.com/1.1/statuses/update.json?in_reply_to_status_id={{inputs.tweet.status_id}}&status={{prompt_reply|url_encode}}\n authentication: cerb:connected_account:{{inputs.tweet.account_id}}\n on_success:\n return:\n on_error:\n await:\n form:\n title: Error\n elements:\n say:\n message@text: An unexpected error occurred.", "policy_kata": "commands:\n http.request:\n deny/url: {{inputs.url is not prefixed ('https://api.twitter.com/1.1/statuses/update.json')}}\n allow@bool: yes" }, { "uid": "automation_timer_mentions", "_context": "automation_timer", "name": "Sync Mentions", "automations_kata": "automation/syncMentions:\n uri: cerb:automation:wgm.example.twitter.syncMentions\n inputs:\n connected_account: cerb:connected_account:twitter_cerb", "next_run_at": "{{{'-5 mins'|date('U')}}}", "is_recurring": "1", "recurring_patterns": "# Every 15 minutes\n*/15 * * * *\n" }, { "uid": "automation_timer_tweets", "_context": "automation_timer", "name": "Sync @{{{prompt_screen_name}}} Tweets", "automations_kata": "automation/syncUser:\n uri: cerb:automation:wgm.example.twitter.syncUserTimeline\n inputs:\n connected_account: cerb:connected_account:twitter_cerb\n screen_name: {{{prompt_screen_name}}}", "next_run_at": "{{{'-5 mins'|date('U')}}}", "is_recurring": "1", "recurring_patterns": "# Every 15 minutes\n*/15 * * * *\n" } ] }Click the Import button.
Enter your Twitter API key and secret.
Click the Import button again.
Configure the Twitter connected account in Cerb
Click on the Twitter (@username) connected account in the package output.
Rename the connected account using your own Twitter username.
Click the Link to Twitter button and log into the account you want to automate.
Review the consent screen and click the Authorize app button.
Click the Save Changes button.
Synchronize tweets
Navigate to Setup >> Configure >> Scheduler.
In the Automations scheduler job, click run now.
It will take several seconds. When it is complete, you can close the extra tab in your browser.
Reading tweets
Navigate to Search >> Tweets.
You should your most recent @mentions, as well as the most recent posts and replies from your account.
You can open the card for a tweet to view its thread.
Replying to tweets
On tweet card popups there is a Reply interaction in the Actions section.
How it works
Custom record
Tweets
The package creates a new custom record type for Tweets. You'll find it in Search >> Custom Records.
The tweet record type includes several custom fields: message, status ID, parent status ID, account, user name, user screen name, user profile image, and account.
Automations
The package imports three automations. You'll find them in Search >> Automations.
wgm.example.twitter.syncMentions
wgm.example.twitter.syncMentionsis a timer-based automation that runs every 5 mins.It takes a Twitter-based connected account as input and fetches its @mentions timeline since the last update. This is tracked in a
since_idkey in automation storage.This automation can be used to sync any number of Twitter accounts.
After receiving a response from the Twitter API, results are converted to objects from JSON and iterated using a repeat: command.
For each tweet, the record.upsert: command is used to create or update its record based on Twitter's internal status ID for the message.
We also store the connected account with each message to simplify follow-up API actions like replying.
We keep track of the highest status ID as
since_idfor the next interval and store that at the end.Finally, we exit in the await: state. This creates a continuation so the timer resumes with the same state at the next interval (in 5 mins).
The automation policy needs the http.request: command with access to the specific API endpoint.
It needs the storage.get: and storage.set: commands to load and save data between executions.
It also needs the record.create:, record.update:, and record.upsert: commands to create or update the
tweetcustom record type.wgm.example.twitter.syncUserTimeline
wgm.example.twitter.syncUserTimelineis another timer-based automation that runs every 5 mins.It is functionally almost identical to the mentions automation above, except it syncs the tweets and replies of a specific account. In this case it's your own account. This is used to thread @mentions and your replies.
You can reuse this automation to sync the tweets of any number of accounts.
The automation policy is identical to the mentions automation, except for a different Twitter API endpoint.
wgm.example.twitter.reply
wgm.example.twitter.replyis a worker-based interaction automation.It takes a tweet record as input and prompts the current worker for a reply message to post to Twitter, and automatically @mentions the author of the tweet being replied to.
After the worker provides a message, the automation posts the tweet using the Twitter API.
The automation policy simply needs the http.request: command with access to the specific API endpoint.
Automation Timers
The package creates two automation timers. You can find them in Search >> Automation Timers.
Sync @username Tweets
This runs the
wgm.example.twitter.syncUserTimelinetimer automation using a specific connected account and target Twitter user. This syncs that user's recent tweets and replies intotweetrecords.You can create additional timers to sync other user timelines. You'd only need to change the
screen_name:.Sync Mentions
This runs the
wgm.example.twitter.syncMentionstimer automation using a specific connected account. This syncs the account's@mentiontimeline intotweetrecords.You can create additional timers to sync other account mentions. You'd only need to change the
connected_account:. You can create new connected accounts with your same Twitter service from Search >> Connected Accounts >> (+).Card widgets
The package creates two card widgets. You can find them in Search >> Card Widgets.
Conversation
This card widget displays the tweet in the familiar format; optionally including the tweet being replied to.
It uses a sheet widget and a
worklist.recordsdata query.The sheet schema has a single column with an HTML layout to display the user name, user screen name, user profile image, and message.
Action
This card widget provides a Reply interaction on tweet cards.
You can add additional interactions for other actions.
Beta Was this translation helpful? Give feedback.
All reactions