first commit
This commit is contained in:
commit
db0daf5963
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
/node_modules
|
||||||
|
npm-debug.log
|
||||||
|
.DS_Store
|
||||||
|
/*.env
|
55
README.md
Normal file
55
README.md
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
# Sarcastic Twitter Bot
|
||||||
|
|
||||||
|
|
||||||
|
### This bot reluctantly answer with sarcastic responses. Uses OpenAI's [GPT-3 API](https://beta.openai.com/playground/p/default-marv-sarcastic-chat) to generate the comments. Hosted on [Heroku](https://www.heroku.com/), and uses [offcial Twitter SDK](https://github.com/twitterdev/twitter-api-typescript-sdk) to interact with Twitter.
|
||||||
|
|
||||||
|
**Note:** Right now, the bot is not deployed because Heroku removed its free tier and I can't find any other alternative that supports running a bot in the background. If you find one, connect me on Twitter [`roh1tkumar`](https://www.twitter.com/roh1tkumar)
|
||||||
|
|
||||||
|
**Note:** mention `@bot_witty` in a tweet or under a tweet like below example.
|
||||||
|
|
||||||
|
<img src="https://i.imgur.com/kxUPBrm.png" width=45% height=45%>
|
||||||
|
|
||||||
|
<img src="https://i.imgur.com/l34nAn6.png" width=45% height=45%>
|
||||||
|
|
||||||
|
<img src="https://i.imgur.com/wZmrQY8.gif" width=50% height=50%>
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Getting Stated
|
||||||
|
|
||||||
|
### Clone the repository
|
||||||
|
$ git clone https://github.com/rohit1kumar/sarcastic-bot.git
|
||||||
|
|
||||||
|
### Install dependencies
|
||||||
|
$ cd sarcastic-bot
|
||||||
|
$ npm install
|
||||||
|
|
||||||
|
|
||||||
|
### Add environment variables
|
||||||
|
- Visit [OpenAI](https://beta.openai.com/) and get your API key
|
||||||
|
- Visit [Twitter](https://developer.twitter.com/en/portal/dashboard) and get get your API keys and tokens.
|
||||||
|
- Create a `.env` file in the root directory
|
||||||
|
```
|
||||||
|
$ cp .env.example .env
|
||||||
|
$ nano .env
|
||||||
|
- Now fill the corresponding values in the `.env` file
|
||||||
|
|
||||||
|
```
|
||||||
|
TWITTER_BEARER_TOKEN=
|
||||||
|
TWITTER_API_KEY=
|
||||||
|
TWITTER_API_SECRET_KEY=
|
||||||
|
TWITTER_ACCESS_TOKEN=
|
||||||
|
TWITTER_ACCESS_TOKEN_SECRET=
|
||||||
|
OPENAI_API_KEY=
|
||||||
|
BOT_USERNAME=
|
||||||
|
```
|
||||||
|
**Note:** Use the same bot username whose API key and token are being used.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Run the bot
|
||||||
|
$ npm start
|
||||||
|
|
||||||
|
|
||||||
|
*I have used another library to post the tweet because the official Twitter SDK was not working for me, got some error which I was not able to resolve, hence I used [twit](https://www.npmjs.com/package/twit). If you are able to resolve the issue, please feel free to open a PR.*
|
144
index.mjs
Normal file
144
index.mjs
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
import { Client } from "twitter-api-sdk";
|
||||||
|
import Twit from "twit";
|
||||||
|
import dotenv from "dotenv";
|
||||||
|
dotenv.config();
|
||||||
|
import { Configuration, OpenAIApi } from "openai";
|
||||||
|
import fs from "fs/promises";
|
||||||
|
|
||||||
|
/*################################## TWITTER API #########################################*/
|
||||||
|
const config = {
|
||||||
|
consumer_key: process.env.TWITTER_API_KEY,
|
||||||
|
consumer_secret: process.env.TWITTER_API_SECRET_KEY,
|
||||||
|
access_token: process.env.TWITTER_ACCESS_TOKEN,
|
||||||
|
access_token_secret: process.env.TWITTER_ACCESS_TOKEN_SECRET,
|
||||||
|
timeout_ms: 60 * 1000,
|
||||||
|
strictSSL: true,
|
||||||
|
};
|
||||||
|
const T = new Twit(config);
|
||||||
|
|
||||||
|
const client = new Client(process.env.TWITTER_BEARER_TOKEN); //twitter api client
|
||||||
|
|
||||||
|
const botName = process.env.BOT_USERNAME; //Use the same Twitter username whose API key and token are being used.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*################################## OPENAI API #########################################*/
|
||||||
|
const configuration = new Configuration({ apiKey: process.env.OPENAI_API_KEY, });
|
||||||
|
const openai = new OpenAIApi(configuration);
|
||||||
|
|
||||||
|
/*############################# GET THE RESP. FROM OPENAI API ##########################*/
|
||||||
|
async function getJoke(questions) {
|
||||||
|
try {
|
||||||
|
const response = await openai.createCompletion({
|
||||||
|
model: "text-davinci-002",
|
||||||
|
prompt: `Marv is a chatbot that reluctantly answers questions with sarcastic responses:\n\nYou: ${questions}\nMarv:`,
|
||||||
|
temperature: 0.5,
|
||||||
|
max_tokens: 60,
|
||||||
|
top_p: 0.3,
|
||||||
|
frequency_penalty: 0.5,
|
||||||
|
presence_penalty: 0,
|
||||||
|
});
|
||||||
|
return response.data.choices[0].text;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*############################# REPLY TO TWITTER API #################################*/
|
||||||
|
async function replyToTweet(joke, author_id, id) {
|
||||||
|
const data = {
|
||||||
|
status: `${joke}`, //the joke
|
||||||
|
in_reply_to_user_id: author_id, //the id of the user who tweeted
|
||||||
|
in_reply_to_status_id: id, //the id of the tweet
|
||||||
|
auto_populate_reply_metadata: true, //auto populate the reply metadata
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const resp = await T.post("statuses/update", data);
|
||||||
|
if (resp) {
|
||||||
|
console.log("replied");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*############################# GET THE TWEETS FROM TWITTER API ##########################*/
|
||||||
|
|
||||||
|
async function getExitingRule() { //get the existing rules
|
||||||
|
try {
|
||||||
|
const rules = await client.tweets.getRules();
|
||||||
|
return rules;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteAndSetNewRules() { //delete the existing rules and set new rules
|
||||||
|
|
||||||
|
try {
|
||||||
|
const rules = await getExitingRule();
|
||||||
|
// if rules includes id in data then delete the rules
|
||||||
|
if (rules.data) {
|
||||||
|
console.log("rule exists, now deleting");
|
||||||
|
const ids = rules.data.map((rule) => rule.id);
|
||||||
|
await client.tweets.addOrDeleteRules({
|
||||||
|
delete: {
|
||||||
|
ids: ids,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log("setting new rules");
|
||||||
|
await client.tweets.addOrDeleteRules({
|
||||||
|
add: [
|
||||||
|
{
|
||||||
|
value: `@${botName} has:mentions`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function getMentionedTweet() {
|
||||||
|
try {
|
||||||
|
console.log("running");
|
||||||
|
await deleteAndSetNewRules();
|
||||||
|
const stream = await client.tweets.searchStream({
|
||||||
|
"tweet.fields": [
|
||||||
|
"author_id", // The ID of the user who posted the tweet
|
||||||
|
"id", // The ID of the tweet
|
||||||
|
"in_reply_to_user_id" // The ID of the user the tweet is replying to
|
||||||
|
],
|
||||||
|
"expansions": [
|
||||||
|
"referenced_tweets.id.author_id" // The ID of the user who posted the referenced tweet
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
for await (const response of stream) {
|
||||||
|
if (response.data.text.includes(`@${botName}`)) { //check if the tweet contains the bot's username
|
||||||
|
|
||||||
|
/* IF BOT IS MENTIONED **IN** THE TWEET */
|
||||||
|
if (response.includes.tweets === undefined) { //check if the tweet is a reply to another tweet
|
||||||
|
const tweet = JSON.stringify(response.data.text, null, 2).replace(/(https?:\/\/[^\s]+)/g, '').replace(/"/g, '').trim();
|
||||||
|
const joke = "works"
|
||||||
|
await replyToTweet(joke, response.data.author_id, response.data.id);
|
||||||
|
} else {
|
||||||
|
/* IF BOT IS MENTIONED **UNDER** THE TWEET THEN IT WILL REPLY TO WHOEVER MENTIONED
|
||||||
|
THE BOT BUT WILL TAKE QUESTIONS FROM THE ORIGINAL AUTHORS TWEET */
|
||||||
|
|
||||||
|
const tweet = JSON.stringify(response.includes.tweets[0].text, null, 2).replace(/(https?:\/\/[^\s]+)/g, '').replace(/"/g, '').trim(); //remove the urls and double quotes from the tweet and trim the spaces
|
||||||
|
const joke = "WORKS" //get the joke from the openai api
|
||||||
|
await replyToTweet(joke, response.data.author_id, response.data.id); // reply to the
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
getMentionedTweet();
|
1042
package-lock.json
generated
Normal file
1042
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
17
package.json
Normal file
17
package.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "sarcasm-bot",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "replies with sarcastic tweet",
|
||||||
|
"main": "index.mjs",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node index.mjs"
|
||||||
|
},
|
||||||
|
"author": "Rohit Kumar",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"dotenv": "^16.0.2",
|
||||||
|
"openai": "^3.0.0",
|
||||||
|
"twit": "^2.2.11",
|
||||||
|
"twitter-api-sdk": "^1.1.0"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user