-
Notifications
You must be signed in to change notification settings - Fork 2
/
instagram.js
233 lines (196 loc) 路 7.64 KB
/
instagram.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
// instagram.js is responsible for managing messages to and from
// Instagram.
// Import Instagram API
const apiClient = require('instagram-private-api').IgApiClient;
const { IgCheckpointError } = require("instagram-private-api");
const config = require("./bot-config");
const lastUpdate = require("./lastupdate");
const tinyurl = require("tinyurl");
const signale = require("signale");
const Bluebird = require("bluebird");
const inquirer = require("inquirer");
const idConverter = require("instagram-id-to-url-segment");
// Create a new Instagram API instance
const api = new apiClient();
// List of user IDs to ignore messages from (the current account is added to this list)
const ignoreList = [];
// Setup function (runs once at start) with a function to run when
// a message is received.
module.exports.setup = async (msgReceived) => {
signale.info("Setting up Instagram...");
// Create a 'virtual' device for running Instagram.
api.state.generateDevice(config.instagram.username);
signale.debug("Logging into Instagram...");
// Login with the username and password from config file, wait until done.
Bluebird.try(async () => {
signale.debug("Trying normal login...");
const auth = await api.account.login(config.instagram.username, config.instagram.password);
signale.debug(auth);
}).catch(IgCheckpointError, async () => {
signale.debug("Login failed! Requesting code...");
signale.info(api.state.checkpoint);
await api.challenge.auto(true);
signale.info(api.state.checkpoint);
const { code } = await inquirer.prompt([
{
type: "input",
name: "code",
message: "Enter Code"
},
]);
signale.debug(`Sending security code '${code}'...`);
const auth = await api.challenge.sendSecurityCode(code);
signale.debug(auth);
}).finally(async () => {
const user = await api.account.currentUser();
signale.info(`Logged in as ${user.username} (ID: ${user.pk})`);
// Add the current user to the ignore list so our messages don't get resent
ignoreList.push(user.pk);
// Print chat thread IDs on startup for easy configuration
const unsetThreads = await this.threadNames();
signale.debug("Available chat threads", unsetThreads);
// Function to run every second to check for new messages.
return setInterval(async () => {
// Get *all* chat threads from Instagram
const threads = await getThreads();
// Filter through messages in the threads, checking for new ones.
handleMessages(threads, msgReceived);
}, 1000);
});
}
// Temporary function to get the thread ids for all the different
// chats available to the account.
module.exports.threadNames = async () => {
// Get *all* chat threads from Instagram
const threads = await getThreads();
const output = [];
// For each of the threads
await threads.forEach((thread) => {
// If the thread is setup in the config
if (config.mappings.find((m) => m.instagram === thread.thread_id)) {
// Skip this item
return;
}
// Add the thread's id and name to the output
output.push({
id: thread.thread_id,
name: thread.thread_title
});
});
return output;
}
// Function to send a message to specific thread
module.exports.send = (name="", content, targetThread) => {
signale.debug("Forwarding message to Instagram...");
signale.debug("Sending standard message...");
// Get the individual thread
const thread = api.entity.directThread(targetThread);
// If the thread doesn't exist, skip
if (!thread) { return; }
// Send the message to the thread
if (name !== "") {
thread.broadcastText(`[${name}]: ${content}`);
} else {
thread.broadcastText(content);
}
signale.debug("Sent!");
}
// Function to get all of the chat threads
async function getThreads() {
// Get direct message inbox.
const inbox = api.feed.directInbox();
// Get all of the threads in the inbox.
const threads = await inbox.items();
return threads;
}
// Function to filter through messages
async function handleMessages(threads, callback) {
// Get the last time we ran this function (to ensure we don't)
// send messages twice
const lastTimestamp = await lastUpdate.get();
// For each 'mapping' defined in the config
config.mappings.forEach((mapping) => {
// Find the thread in the list of threads
const thread = threads.find((t) => t.thread_id === mapping.instagram);
// If there isn't a thread, skip
if (!thread) { return; }
// Get the messages in the thread, reverse the order
// and then only get messages that are sent AFTER the last
// time we checked AND that aren't sent by us.
const messages = thread.items.reverse().filter((msg) => (parseInt(msg.timestamp) > lastTimestamp) && (ignoreList.indexOf(msg.user_id) < 0));
// If we don't have any messages to process, skip.
if (messages.length <= 0) { return; }
// For each of the messages that we need to process.
messages.forEach(async (msg) => {
// Get the user's profile information
const user = await api.user.info(msg.user_id);
// Get the user's name, if they don't have a full name set
// on their account, get their username.
const name = user.full_name || user.username;
// Get the user's avatar URL (used as profile picture in Discord)
const avatar = user.profile_pic_url;
// Get the type of message.
const type = msg.item_type;
// Print out the message type, details and user for debugging purposes.
// console.log(type, msg, user.username);
const converted = await convertMessage(type, msg);
// Now that we have a message, send them to discord.
callback(name, avatar, converted, mapping.discord, mapping.instagram);
});
// Now that we have processed all the messages, set the last
// time we checked for messages to be the time the LAST message
// was sent in the chat.
const newTimestamp = parseInt(messages[messages.length - 1].timestamp);
lastUpdate.set(newTimestamp);
});
}
async function convertMessage(type, msg) {
switch(type) {
case "media": {
const shortMedia = await tinyurl.shorten(msg.media.image_versions2.candidates[0].url);
return shortMedia;
}
case "like": {
return msg.like;
}
case "animated_media": {
const mediaObj = msg.animated_media.images
const shortGif = await tinyurl.shorten(mediaObj[Object.keys(mediaObj)[0]].url)
return shortGif;
}
case "text": {
return msg.text;
}
case "placeholder": {
if (msg.placeholder.title === "Post Unavailable") {
return "`[SHARED POST] This post is unavailable due to it's privacy settings.`";
}
return "`[SHARED POST] This post is unavailable.`";
}
case "media_share": {
const postObj = msg.media_share;
let short = "";
if (postObj.carousel_media) {
const objId = postObj.id.substring(0,postObj.id.indexOf("_"));
short = "https://www.instagram.com/p/" + idConverter.instagramIdToUrlSegment(objId) + "/";
} else if (postObj.image_versions2) {
const postUrl = postObj.image_versions2.candidates[0].url;
short = await tinyurl.shorten(postUrl);
} else {
signale.warn("UNSUPPORTED POST TYPE", type, msg);
return "`[SHARED POST] This post type is unsupported.`";
}
let text = `\`[SHARED POST] This post was posted by @${postObj.user.username}.`;
if (postObj.caption) { text += ` Caption: ${postObj.caption.text}`; }
text += `\` ${short}`;
return text;
}
case "link": {
return msg.link.text;
}
default: {
signale.warn("UNSUPPORTED MESSAGE TYPE", type, msg);
return "";
}
}
}