-
Notifications
You must be signed in to change notification settings - Fork 16
/
bot.js
241 lines (206 loc) · 8.38 KB
/
bot.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
// convenience methods and shared behaviour between LocalBot and CloudBot
import puppeteer from 'puppeteer-extra';
import * as aicombinator from './recipes.js';
import totp from 'totp-generator';
import inquirer from 'inquirer';
export class Bot {
constructor(){
}
async init_browser(){
let dirname = this.bot_id || this.token;
this.browser = await puppeteer.launch({
headless: this.headless,
defaultViewport: null,
userDataDir: `/tmp/aicombinator_chromedir/${dirname}`,
args: [
'--start-maximized'
]
});
}
async finish(){
// closes the browser and performs other cleanup
if(this.browser) await this.browser.close();
}
async get_param(ctx, name, params){
let name_with_ctx = `${ctx}_${name}`;
return params[name] || process.env[name_with_ctx.toUpperCase()] || params[name_with_ctx] || await this.get(name_with_ctx);
}
async get_latest_in_array(key){
// eg: latest email in email_inbox
// when value is an array of objects with timestamp and new entries are appended.
let value = await this.get(key);
return value[value.length - 1];
}
async get_2fa_totp(site, options, params) {
// standardize key name and totp library
let secret = await this.get_param(site, '2fa_secret', params);
if(!secret) return null; // 2FA not set-up for this site+account
return totp(secret, options);
}
async get_phone(){
return (await this.get('phone'));
}
async get_email(){
return (await this.get('email'));
}
// TODO: Other standard key names: payment_methods, address, username, image, bio, location
async wait(seconds){
return await new Promise(r => setTimeout(r, seconds*1000));
}
async execute_command(cmd){
let code = await this.parse_command(cmd);
let eval_code = `(async () => {\n${code}\n})()`;
console.log("\n\nWill run the following code:\n");
console.log(eval_code);
console.log("\n\n")
const answer = await inquirer.prompt({
type: 'confirm',
name: 'confirmed',
message: 'Proceed?'
});
if(!answer.confirmed) return;
let bot = this;
if(eval_code.includes("aicombinator.twitter") || eval_code.includes("aicombinator.google_sms") || eval_code.includes("aicombinator.github")){
// task requires a browser. initialize puppeteer.
await bot.init_browser();
}
await bot.wait(2);
await eval(eval_code);
}
static list_tasks(){
return Object.keys(aicombinator);
}
async parse_command_ai(cmd, openai_api_key){
let prompt = `
I want you to act as a senior programmer.
I want you to answer only with the fenced code block.
Do not write explanations.
Use the below listed utility methods to write Javascript code for the following task:
${cmd}
// API reference for all available utility methods:
aicombinator.google_sms.pair_device(bot, {})
aicombinator.twitter.login(bot, {twitter_username: "user", twitter_password: "password"})
aicombinator.twitter.post(bot, {message: "test"})
aicombinator.github.login(bot, {})
aicombinator.airtable.create_record(bot, {fields: {fieldName: "fieldValue"}})
aicombinator.asana.create_task(bot, {name: "name of the task", notes: "free-form notes"})
aicombinator.bannerbear.create_image(bot, {templates: "template_id", modifications: [{name: "message", text: "text on image"},{name: "face", image_url: "https://example.com/image_to_be_inserted.png"}]})
aicombinator.binance.fetch_crypto_pair_price(bot, {first_coin: "BTC", second_coin: "USDT"})
aicombinator.blackbaud.create_contact_batch(bot, {email: {address: "[email protected]"}})
aicombinator.blackbaud.search_contacts_after_date(bot, {})
aicombinator.blackbaud.create_contact_if_not_exists(bot, {email: {address: "[email protected]"}})
aicombinator.blackbaud.update_contact(bot, {})
aicombinator.clickup.create_task(bot, {})
aicombinator.clickup.create_folderless_list(bot, {})
aicombinator.clickup.get_list(bot, {})
aicombinator.clickup.get_space(bot, {})
aicombinator.clickup.get_spaces(bot, {})
aicombinator.clickup.get_task_comments(bot, {})
aicombinator.clickup.create_task_comments(bot, {})
aicombinator.clickup.create_subtask(bot, {})
aicombinator.discord.send_message_webhook(bot, {webhook_url, content: "Long-form text of the message"})
aicombinator.drip.apply_tag_to_subscriber(bot, {})
aicombinator.drip.add_subscriber_to_campaign(bot, {})
aicombinator.drip.upsert_subscriber(bot, {})
aicombinator.dropbox.create_new_folder(bot, {})
aicombinator.dropbox.create_new_text_file(bot, {})
aicombinator.figma.get_file(bot, {})
aicombinator.figma.get_comments(bot, {})
aicombinator.figma.post_comment(bot, {})
aicombinator.gmail.send_email(bot, {receiver, subject, body_text})
aicombinator.gmail.get_mail(bot, {})
aicombinator.gmail.search_mail(bot, {subject, from, to, label, category})
aicombinator.gmail.get_thread(bot, {})
aicombinator.googleCalendar.create_quick_event(bot, {text: "The text describing the event to be created"})
aicombinator.googleContacts.add_contact(bot, {})
aicombinator.googleDrive.create_new_gdrive_folder(bot, {})
aicombinator.googleDrive.create_new_gdrive_file(bot, {})
aicombinator.googleSheets.insert_row(bot, {})
aicombinator.googleTasks.add_task(bot, {})
aicombinator.hackernews.fetch_top_stories(bot, {number_of_stories: 1})
aicombinator.hubspot.create_contact(bot, {})
aicombinator.hubspot.create_or_update_contact(bot, {})
aicombinator.hubspot.add_contact_to_list(bot, {})
aicombinator.intercom.get_or_create_contact(bot, {})
aicombinator.intercom.create_contact(bot, {})
aicombinator.intercom.send_message(bot, {})
aicombinator.mailchimp.add_member_to_list(bot, {})
aicombinator.openai.ask_chatgpt(bot, {})
aicombinator.pipedrive.add_person(bot, {})
aicombinator.posthog.create_event(bot, {})
aicombinator.posthog.create_project(bot, {})
aicombinator.sendgrid.send_email(bot, {})
aicombinator.slack.send_direct_message(bot, {})
aicombinator.slack.send_channel_message(bot, {})
aicombinator.telegram-bot.send_text_message(bot, {})
aicombinator.todoist.create_task(bot, {})
aicombinator.trello.create_card(bot, {})
aicombinator.trello.get_card(bot, {})
aicombinator.twilio.send_sms(bot, {})
aicombinator.wordpress.create_post(bot, {})
aicombinator.zoom.create_meeting(bot, {})
aicombinator.zoom.create_meeting_registrant(bot, {})
// Example: post a message to twitter:
// Don't need to call aicombinator.twitter.login() unless given explicit crendentials
await aicombinator.twitter.post(bot, {message: 'hello'});
// some tasks may return useful results which should be printed:
let result = await aicombinator.hackernews.fetch_top_stories(bot, {number_of_stories: 1});
console.log({result});
// To make the bot pause or wait for 5 seconds for the user to manually intervene:
await bot.wait(5);
// Now generate code for the following task:
${cmd}
`;
const response = await fetch('https://api.openai.com/v1/engines/text-davinci-003/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${openai_api_key}`,
},
body: JSON.stringify({
prompt: prompt,
max_tokens: 1000,
n: 1,
stop: '\n\n\n',
}),
});
const data = await response.json();
// console.log({data});
let code = data.choices[0].text.trim();
// console.log({code});
return code;
}
async parse_command(cmd){
// console.log({cmd});
let openai_api_key = await this.get_param('openai', 'api_key', {});
if(openai_api_key){
return await this.parse_command_ai(cmd, openai_api_key);
}
let sentences = cmd.split('. ');
let sentences_clean = sentences.map(s => s.trim().replace(/\.$/,'').replace(/^then /i,'').trim());
// console.log({sentences_clean});
// {task} (at {site})? (with {args})?/
let parts = sentences_clean.map(s => {
let args = s.split(' with ')[1];
let site = s.split(' with ')[0].split(/ on | to | at | from /);
site = site.length == 1 ? undefined : site[site.length - 1].replace(' ', '_');
let task = s.split(' with ')[0].split(/ on | to | at | from /)[0].replace(' ', '_');
// console.log({task, site, args});
if(args)
args = args.split(' and ').reduce((acc, item) => {
let key = item.split(' ')[0];
let value = item.replace(/\w+ /,''); // remove first word because that's the key
return {...acc, [key]: value};
}, {});
else
args = {};
return {task, site, args};
});
// console.log({parts});
parts.forEach((p, idx) => {
if(p.site == undefined) p.site = parts[idx-1].site;
});
let code = parts.map(p => `\tawait aicombinator.${p.site}.${p.task}(bot, ${JSON.stringify(p.args)});`).join("\n");
return new Promise((resolve, reject) => resolve(code));
}
}