Jak stworzyć twitterowego bota, który rozpoznaje obrazy?

alt text

Serverless?

W ciągu ostatnich kilku lat architektura serverless zyskała na popularności. Programiści i firmy zmieniają swoje podejście do tworzenia, utrzymywania i wdrażania aplikacji internetowych. Ale czym dokładnie jest serverless? Jak zdefiniowało to https://serverless-stack.com/:

Serverless to model programowania, w którym dostawca chmury (AWS, Azure lub Google Cloud) jest odpowiedzialny za wykonanie fragmentu kodu poprzez dynamiczne przydzielanie zasobów. Opłaty za taka usługę pobierane są tylko za ilość zasobów używanych do uruchomienia kodu. Kod jest zwykle uruchamiany w kontenerach, które mogą być uruchamiane przez różne eventy, w tym żądania HTTP, zapytania do bazy danych, usługi kolejkowania, alerty monitorowania, przesyłanie plików, zaplanowane eventy (cron) itp. Kod wysyłany do dostawcy w chmurze ma zazwyczaj postać funkcji. Dlatego serverless jest czasami określane jako Funkcje jako Usługa lub FaaS.

TIP: Sprawdź ich samouczek - jest naprawdę niesamowity i pomoże ci zrozumieć lepiej, o co chodzi w podejściu serverless.

Co będziemy budować?

W tym samouczku pokażę, jak zbudować Twitter bota, który po otrzymaniu tweeta ze zdjęciem jakiegoś zwierzaka, rozpozna, co jest na tym zdjęciu (jeśli jest to zwierzę) i odeśle tweeta z prawidłową odpowiedzią. Na przykład, jeśli wyślesz zdjęcie żyrafy, bot użyje naszej architektury serverless i natychmiast odpowie ci czymś w stylu - „Hej, na twoim zdjęciu jest żyrafa!”.
Aby to osiągnąć, wykorzystamy Framework Serverless. To fantastyczne narzędzie, które pozwala łatwo skonfigurować wszystkie usługi potrzebne do projektu, w jednym pliku konfiguracyjnym. Poza tym jest niezależny od dostawcy, więc nie musisz wybierać między AWS, Azure lub Google Cloud, możesz korzystać z nich wszystkich.
W tym przykładzie użyjesz Amazon Web Services - AWS. Ma dziesiątki świetnych usług w chmurze, ale użyjesz tylko kilku - S3 Bucket, Lambda Functions, API Gateway i Image Recognition. Poniżej możesz zobaczyć na schemacie, jak będzie działać nasz program.

alt text

Zacznijmy od początku

Zanim zaczniesz używać Serverless Framework, musisz upewnić się, że masz poprawnie skonfigurowane API Twittera.
Utwórz konto programisty na Twitterze i dodaj nową aplikację na https://developer.twitter.com. Po zakończeniu przejdź do sekcji uprawnień i upewnij się, że zmieniłeś je na „Czytaj, pisz i kieruj wiadomościami”. W sekcji kluczy i tokenów dostępu upewnij się, że wygenerowałeś token dostępu i tajny token dostępu. Będziesz ich później potrzebować do komunikacji z API.
Aby umożliwić wysyłanie danych do webhooka, musisz uzyskać dostęp do Account Activity API. Zarejestruj się tutaj. Nie przejmuj się informacja mówiąca ze jest to usługa premium. Do naszych celów wystarczy darmowy tryb sandbox.
Przejdź do Środowiska programistów i utwórz środowisko dla Account Activity API. Zanotuj nazwę środowiska programisty, ponieważ będzie ona nam potrzebna później.

Zarejestruj webhook na Twitterze

Sposób, w jaki działa Account Activity API, może początkowo wydawać się nieco skomplikowany, ale w rzeczywistości jest dość prosty. Oto kroki wymagane do uruchomienia:

  1. Wyślij zapytanie do interfejsu API Twittera z informacją o endpoincie URL, który zajmie się Twitter Challenge Response Check
  2. Interfejs API Twittera wysyła żądanie GET w celu spełnienia Twitter Challenge Response Check
  3. Twój endpoint odpowiada poprawnie sformatowaną odpowiedzią JSON - Webhook jest zarejestrowany (tak!).
  4. Wyślij żądanie POST do interfejsu API Twittera, aby zasubskrybować aplikację do aplikacji Twitter.

Aby obsłużyć wszystkie te żądania, utworzymy klasę kontrolerów API Twittera.
Po pierwsze, stwórzmy wszystkie właściwości, które będziemy musieli wykorzystać w naszych metodach:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const request = require('request-promise');

module.exports = class TwitterController {
constructor(consumerKey, consumerSecret, token, tokenSecret, urlBase, environment, crcUrl) {
this.consumerKey = consumerKey;
this.consumerSecret = consumerSecret;
this.token = token;
this.tokenSecret = tokenSecret;
this.urlBase = urlBase;
this.environment = environment;
this.crcUrl = crcUrl;
this.credentials = {
consumer_key: this.consumerKey,
consumer_secret: this.consumerSecret,
token: this.token,
token_secret: this.tokenSecret,
};

this.registerWebhook = this.registerWebhook.bind(this);
}
};

twittercontroller.js

Wszystkie właściwości, które przekażemy konstruktorowi, zostaną zapisane w pliku serverless.env.yml w katalogu głównym projektu. Wrócimy do tego później.

Teraz przyjrzyjmy się metodom, które będą obsługiwały komunikację z API Twittera.

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
setRequestOptions(type, webhhokId) {
let url = null;
let content = {};
const { urlBase, environment, credentials, crcUrl } = this;

switch (type) {
case ('registerWebhook'):
url = `${urlBase}${environment}/webhooks.json`;
content = {
form: {
url: crcUrl,
},
};
break;
case ('getWebhook'):
url = `${urlBase}${environment}/webhooks.json`;
break;
case ('deleteWebhook'):
url = `${urlBase}${environment}/webhooks/${webhhokId}.json`;
break;
case ('registerSubscription'):
url = `${urlBase}${environment}/subscriptions.json`;
break;
case ('createTweet'):
url = `${urlBase}update.json`;
break;
default:
url = `${urlBase}${environment}/webhooks.json`;
}
return Object.assign({}, {
url,
oauth: credentials,
headers: {
'Content-type': 'application/x-www-form-urlencoded',
},
resolveWithFullResponse: true,
}, content);
}

async registerWebhook() {
const requestOptions = this.setRequestOptions('registerWebhook');

try {
const response = await request.post(requestOptions);
console.log(response);
console.log('Succesfully register webhook');
} catch (err) {
console.log(err);
console.log('Cannot register webhook');
}
}

async registerSubscription() {
const requestOptions = this.setRequestOptions('registerSubscription');

try {
const response = await request.post(requestOptions);
if (response.statusCode === 204) {
console.log('Subscription added. Yay!');
}
} catch (err) {
console.log(err);
console.log('Cannot register subscription');
}
}

async createTweet(status, tweetID) {
const requestOptions = Object.assign({}, this.setRequestOptions('createTweet'), {
form: {
status,
in_reply_to_status_id: tweetID,
auto_populate_reply_metadata: true,
},
});

try {
await request.post(requestOptions);
} catch (err) {
console.log(err);
console.log('Cannot post tweet.');
}
}

twittercontroller.js

Większość z tych metod to funkcje asynchroniczne, które mają na celu utworzenie komunikacji z API. Do wysłania zadań będziemy korzystać z biblioteki request-promise. Wyjaśnijmy pokrótce metody:

    • setRequestOptions * - tworzy obiekt z parametrami, potrzebnymi do komunikacji z API
    • registerWebhook * - wysyła żądanie POST do API, z adresem URL zawierającym Twitter Challenge Response Check
    • registerSubscription * - wysyła żądanie POST do API, aby zarejestrować subskrypcję naszego webhooka
    • createTweet * - wysyła żądanie POST do API i tworzy nowy Tweet

The Serverless

Aby rozpocząć pracę z Serverless, musimy go zainstalować (duh!). Otwórz terminal i zainstaluj framework globalnie.

1
$ npm install serverless -g

Następnie przejdź do folderu projektu i uruchom:

1
$ serverless create --template aws-nodejs

To polecenie utworzy domyślny plik konfiguracyjny node.js + AWS. Wygenerowany plik yaml zawiera wiele zakomentowanego kodu. Nie będziemy go tutaj potrzebować, więc możemy go usunąć. Jedyne, na czym nam teraz zależy, to:

1
2
3
4
5
6
7
service: aws-nodejs
provider:
name: aws
runtime: nodejs8.10
functions:
hello:
handler: handler.hello

Jest to minimalna, podstawowa konfiguracja. Zanim przejdziemy dalej, musisz utworzyć konto AWS (jeśli jeszcze go nie masz) i skonfigurować AWS dla Serverless. Nie będę wchodził w szczegóły tego procesu, możesz zobaczyć, jak to zrobić [tutaj] (https://www.youtube.com/watch?v=KngM5bfpttA).

Po skonfigurowaniu uprawnień możesz rozpocząć dodawanie szczegółów konfiguracji. Zwykle Serverless domyślnie używa nazwy profilu i regionu AWS, którego używasz, ale jeśli masz wiele profili na swoim komputerze (prywatny, służbowy itp.), dobrą praktyką jest zdefiniowanie go w pliku serverless.yaml w następujący sposób:

1
2
3
4
5
provider:
name: aws
runtime: nodejs8.10
profile: aws-private # your profile name
region: eu-west-1 # aws region

TIP: W wierszu poleceń możesz użyć skrótu - zamiast „serverles…” możesz po prostu wpisać „sls…”.

Plik ENV

Jak wspomniałem wcześniej, do przechowywania naszych kluczy, tokenów i innych zmiennych utworzymy plik serverless.env.yml w folderze głównym. Powinno to wyglądać tak:

1
2
3
4
5
6
7
8
TWITTER_CONSUMER_KEY: ########
TWITTER_CONSUMER_SECRET: ########
TWITTER_TOKEN: ########
TWITTER_TOKEN_SECRET: ########
ENVIRONMENT: ########
URL_BASE: 'https://api.twitter.com/1.1/account_activity/all/'
URL_CREATE: 'https://api.twitter.com/1.1/statuses/'
CRC_URL: ########

Pierwsze pięć z nich, to te o których wspominaliśmy wcześniej, podczas tworzenia aplikacji na koncie deweloperskim Twittera. Znajduje się tu również base URL API Twittera - dobrze jest trzymać tego typu globalne zmienne w jednym miejscu.
W dalszej części artykuły utworzymy adres URL obsługujący Twitter Challenge Response Check, z wykorzystaniem Serverless Framework i AWS.

Z gotowym plikiem env możesz wstawiać zmienne do swojego kodu, umieszczając je w pliku serverless.yml. Oto sposób:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
custom:
CRC_URL: ${file(./serverless.env.yml):CRC_URL}
ENVIRONMENT: ${file(./serverless.env.yml):ENVIRONMENT}
TWITTER_CONSUMER_KEY: ${file(./serverless.env.yml):TWITTER_CONSUMER_KEY}
TWITTER_CONSUMER_SECRET: ${file(./serverless.env.yml):TWITTER_CONSUMER_SECRET}
TWITTER_TOKEN: ${file(./serverless.env.yml):TWITTER_TOKEN}
TWITTER_TOKEN_SECRET: ${file(./serverless.env.yml):TWITTER_TOKEN_SECRET}
URL_BASE: ${file(./serverless.env.yml):URL_BASE}
provider:
name: aws
runtime: nodejs8.10
profile: aws-private
region: eu-west-1
environment:
TWITTER_CONSUMER_KEY: ${self:custom.TWITTER_CONSUMER_KEY}
TWITTER_CONSUMER_SECRET: ${self:custom.TWITTER_CONSUMER_SECRET}
TWITTER_TOKEN: ${self:custom.TWITTER_TOKEN}
TWITTER_TOKEN_SECRET: ${self:custom.TWITTER_TOKEN_SECRET}
ENVIRONMENT: ${self:custom.ENVIRONMENT}
CRC_URL: ${self:custom.CRC_URL}
URL_BASE: ${self:custom.URL_BASE}

Dodając zmienne w obiekcie provider.environment, możemy uzyskać do nich dostęp w dowolnej funkcji, którą zdefiniujemy w pliku konfiguracyjnym Serverless. Możemy również przekazać go osobno w każdej funkcji, ale pokażę ten przykład w dalszej części samouczka.

Funkcje

Przejdźmy teraz do głównej części naszego projektu - funkcji lambda. Zacznijmy od zdefiniowania pierwszego z nich w naszym pliku konfiguracyjnym.

1
2
3
4
5
6
7
functions:
handleCrc:
handler: src/lambda_functions/handleCrc.handler
events:
- http:
path: twitter/webhook/handleapi
method: get

Tworzymy pierwszą funkcję lambda o nazwie handleCrc. W sekcji events określasz, kiedy funkcja ma zostać wywołana.
Jak widać, po wysłaniu żądania GET do właśnie stworzonego przez nas endpointu - twitter/webhook/handleapi, uruchomi się funkcja handleCrc.
To podstawowy sposób tworzenia konfiguracji funkcji lambda w Serverless Framework. Istnieje wiele opcji definiowania zdarzeń, na przykład -
obraz został przesłany do S3 Bucket, nowe dane zostały dodane do bazy danych itp.

Spójrzmy teraz na właściwa funkcje:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const crypto = require('crypto');

const encodeCrc = (crcToken, consumerSecret) => crypto.createHmac('sha256', consumerSecret).update(crcToken).digest('base64');

module.exports.handler = async (event) => {
const responseToken = encodeCrc(
event.queryStringParameters.crc_token,
process.env.TWITTER_CONSUMER_SECRET,
);
return {
statusCode: 200,
body: JSON.stringify({ response_token: `sha256=${responseToken}` }),
};
};

handleCrc.js

Użyjemy biblioteki Crypto do odkodowania odpowiedzi z API Twittera. Jak widać, jest to dość proste. Musisz przekazać token Twitter Challenge Response Check
i swój tajny klucz z API Twittera, aby zakodować funkcję CRC i zwrócić wynik. Zauważ, że nasz sekret pobieramy z obiektu process.env.
Możemy uzyskać do niego dostęp dzięki wcześniejszemu zdefiniowaniu go w pliku serverless.yml.

Teraz możemy zrobić deploy naszej funkcji, aby uzyskać adres URL Twitter Challenge Response Check, którego będziemy potrzebować później.

Aby wdrożyć naszą funkcję, po prostu uruchom z terminala polecenie:

1
$ sls deploy

Spowoduje to utworzenie nowego szablonu AWS CloudFormation i przesłanie funkcji do S3 Bucket. Jeśli wszystko poszło dobrze, powinieneś zobaczyć coś podobnego:

alt text

Tutaj możesz znaleźć wszystkie informacje o swoim projekcie: etap, nazwę projektu, endpointy, funkcje itp. To co, interesuje nas w tej chwili to endpoint CRD.
Jak wspomniałem wcześniej, będziesz potrzebować tego adresu URL, aby przejść Twitter Challenge Response Check. Skopiuj i wklej do pliku serverless.env.yml.

TIP: Jeśli chcesz dowiedzieć się, co faktycznie dzieje się za kulisami, podczas uruchamiania $ sls, możesz przejść [tutaj] (https://serverless.com/framework/docs/providers/aws/guide/deploying/ #aws — wdrażanie) i przeczytaj o tym wszystko.

Register webhook and subscription

Teraz, dodajmy funkcje lambda, które będą odpowiedzialne za rejestrację webhooka i subskrypcji użytkownika.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
functions:
...
registerWebhook:
handler: src/lambda_functions/registerWebhook.handler
events:
- http:
path: twitter/webhook/register
method: get
registerSubscription:
handler: src/lambda_functions/registerSubscription.handler
events:
- http:
path: twitter/subscription/register
method: get

Funkcje same w sobie są naprawdę proste. Ogólnie rzecz biorąc każda z nich wywołuje odpowiednią metodę z klasy TwitterController, którą wcześniej stworzyliśmy.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const TwitterController = require('../TwitterController');

module.exports.handler = async () => {
const controller = new TwitterController(
process.env.TWITTER_CONSUMER_KEY,
process.env.TWITTER_CONSUMER_SECRET,
process.env.TWITTER_TOKEN,
process.env.TWITTER_TOKEN_SECRET,
process.env.URL_BASE,
process.env.ENVIRONMENT,
process.env.CRC_URL,
);

await controller.registerSubscription();
};

registerSubscription.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const TwitterController = require('../TwitterController');

module.exports.handler = async () => {
const controller = new TwitterController(
process.env.TWITTER_CONSUMER_KEY,
process.env.TWITTER_CONSUMER_SECRET,
process.env.TWITTER_TOKEN,
process.env.TWITTER_TOKEN_SECRET,
process.env.URL_BASE,
process.env.ENVIRONMENT,
process.env.CRC_URL,
);

await controller.registerWebhook();
};

registerWebhook.js

Jak widzisz, nie ma tu żadnej magii. Tworzysz nowa instancje klasy, przekazujesz wszystkie dane dostępu i uruchamiasz funkcje. Teraz powinniśmy zrobić redploy naszej aplikacji:

1
$ sls deploy

Powinien zostać wyświetlony „raport” (podobny do tego, który otrzymaliśmy po pierwszym deploy) z adresami URL endpointow. W tym momencie mamy wszystko, aby zarejestrować nasz webhook.

Możesz dosłownie wkleić adres do paska przeglądarki. Zanim to zrobimy z metodą registerWebhook, zobaczmy, jak możemy monitorować nasze funkcje.

1
$ sls logs -f registerWebhook

Jeśli uruchomisz to w swoim terminalu, otrzymasz raport logów z ostatniego wywołania funkcji. Opcjonalnie możesz nasłuchiwać wywołań funkcji wywołując komendę z flagą -t:

1
$ sls logs -f registerWebhook -t

UWAGA: Ta opcja działa tylko wtedy, gdy twoje funkcja zostały wcześniej wywołane przynajmniej raz.

Teraz możesz wkleić adres URL endpointa registerWebhook w przeglądarce. Następnie przejdź do terminala i uruchom logi. Jeśli wszystko jest w porządku, powinieneś zobaczyć komunikat:

1
Successfully register webhook

Powtórz te same kroki dla funkcji registerSubscription. Super! Właśnie zarejestrowaliśmy nasze webhooki.

Obsługa odpowiedzi z API Twittera

Od tego momentu jakakolwiek aktywność na Twoim koncie spowoduje wywołanie żądania POST zawierającego wszystkie dane dotyczące tej aktywności. Aby zobaczyć dane, musisz utworzyć funkcję lambda, która obsłuży to żądanie.

1
2
3
4
5
6
7
8
9
/* serverless.yml */
functions:
...
handleTweet:
handler: src/lambda_functions/handleTweet.handler
events:
- http:
path: twitter/webhook/handleapi
method: post
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module.exports = (username, labels = []) => {
let message = '';
const ANIMAL_LABELS = ['Animal', 'Mammal', 'Bird', 'Fish', 'Reptile', 'Amphibian'];
const isAnimal = labels.length && labels.some(label => ANIMAL_LABELS.includes(label.Name));

if (labels.length === 0) {
message = `Sorry @${username}, you need to upload an image.`;
} else if (isAnimal) {
const recongizedLabels = labels.map(label => label.Name);
message = `Hi @${username}. On your image, I can recognize: ${recongizedLabels.join(', ')}.`;
} else {
message = `Ooops @${username} looks like it's not an animal on your image.`;
}

return message;
};

createMessage.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
const uploadImage = require('../helpers/uploadImage');
const createMessage = require('../helpers/createMessage');
const TwitterController = require('../TwitterController');

module.exports.handler = async (event) => {
const tweet = JSON.parse(event.body);
const tweetData = await tweet.tweet_create_events;

if (typeof tweetData === 'undefined' || tweetData.length < 1) {
return console.log('Not a new tweet event');
}

if (tweet.for_user_id === tweetData[0].user.id_str) {
return console.log('Same user, not sending response.');
}

const { id_str, user, entities } = tweetData[0];
const key = `${id_str}___---${user.screen_name}`;

// If tweet containes image
if (entities.hasOwnProperty('media')) {
const imageUrl = tweetData[0].entities.media[0].media_url_https;
await uploadImage(imageUrl, {
bucket: process.env.BUCKET,
key,
});
} else {
const controller = new TwitterController(
process.env.TWITTER_CONSUMER_KEY,
process.env.TWITTER_CONSUMER_SECRET,
process.env.TWITTER_TOKEN,
process.env.TWITTER_TOKEN_SECRET,
process.env.URL_CREATE,
process.env.ENVIRONMENT,
process.env.CRC_URL,
);
const message = createMessage(user.screen_name);
await controller.createTweet(message, key);
}
};

handleTweet.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
const fetch = require('node-fetch');
const AWS = require('aws-sdk');

const s3 = new AWS.S3();

module.exports = async (image, meta) => {
console.log('Uploading image....');

const mediaResponse = await fetch(image);
const bufferedMedia = await mediaResponse.buffer();
const params = {
Bucket: meta.bucket,
Key: meta.key,
Body: bufferedMedia,
};

try {
const uploadedImage = await s3.putObject(params).promise();
console.log(uploadedImage, 'Image uploaded.');
} catch (err) {
console.log(err);
console.log('Cannot upload.');
}
};

uploadImage.js

W pliku handleTweet.js:

  1. sprawdzanie obiektu zdarzenia, czy rzeczywiście jest to tweet (może to być prywatna wiadomość lub coś innego) i czy tweet pochodzi od innego użytkownika (nie chcemy tworzyć nieskończonej pętli podczas wysyłania odpowiedzi)
  2. sprawdzenie czy tweet posiada dodane zdjęcie, jeśli tak-przeslemy to zdjęcie do S3, jeśli nie-odeślemy tweet z odpowiednim przypomnieniem o jego braku.

UWAGA: W wierszu 18 tworzymy nazwę pliku ze zmiennych - identyfikator tweeta i nazwę użytkownika oraz kilka myślników / podkreśleń. Robimy to w taki sposób, aby łatwo odczytać te zmienne w dalszej części.

W pliku uploadImage.js:

  1. zainstaluj node-fetch za pomocą npm i - uzyjemy tej paczki go do pobrania obrazu zapisanego na serwerach Twittera
  2. przekonwertuj obraz do danych binarnych i przekaż je, jako treść w parametrach
  3. zainstaluj pakiet aws-sdk, aby korzystać z metod AWS bezpośrednio w kodzie
  4. załaduj obraz do S3 za pomocą metody s3.putObject

TIP: Możesz zwrócić promise zamiast callback w większości metod aws-sdk, uruchamiając na nich metodę promise (). Zobacz więcej tutaj.

Upload zdjęć do S3

Teraz ustawimy funkcję lambda, która będzie się uruchamiać, za każdym razem, gdy nowe zdjęcie zostanie przesłane do naszego kontenera w S3. Aby to zrobić, musimy dodać konfigurację do servereless.yml

1
2
3
4
5
6
7
8
/* serverless.yml */
functions:
...
respondToTweetWithImage:
handler: src/lambda_functions/respondToTweetWithImage.handler
events:
- s3:
bucket: ${self:custom.BUCKET}

Funckaj respondToTweetWithImage:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const AWS = require('aws-sdk');

module.exports = async (meta) => {
const rekognition = new AWS.Rekognition();
const params = {
Image: {
S3Object: {
Bucket: meta.bucket.name,
Name: meta.object.key,
},
},
MaxLabels: 5,
MinConfidence: 85,
};

try {
const data = await rekognition.detectLabels(params).promise();
return data.Labels;
} catch (err) {
console.log(err);
console.log('Cannot recognize image');
}
};

recognizeImage.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const AWS = require('aws-sdk');

module.exports = (meta) => {
const s3 = new AWS.S3();
const params = {
Bucket: meta.bucket.name,
Key: meta.object.key,
};

try {
s3.deleteObject(params).promise();
} catch (err) {
console.log(err);
console.log('Cannot delete image.');
}
};

removeImage.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
const recognizeImage = require('../helpers/recognizeImage');
const removeImage = require('../helpers/removeImage');
const createMessage = require('../helpers/createMessage');
const TwitterController = require('../TwitterController');

module.exports.handler = async (event) => {
const { s3 } = event.Records[0];
const tweetId = s3.object.key.split('___---')[0];
const username = s3.object.key.split('___---')[1];

const labels = await recognizeImage(s3);
const message = createMessage(username, labels);
const controller = new TwitterController(
process.env.TWITTER_CONSUMER_KEY,
process.env.TWITTER_CONSUMER_SECRET,
process.env.TWITTER_TOKEN,
process.env.TWITTER_TOKEN_SECRET,
process.env.URL_CREATE,
process.env.ENVIRONMENT,
process.env.CRC_URL,
);
await controller.createTweet(message, tweetId);
removeImage(s3);
};

respondToTweetWithImage.js

Przeanalizujmy funckcje:

  1. po przesłaniu obrazu do S3 funkcja otrzyma obiekt ze wszystkimi danymi o zdarzeniu
  2. dzięki specyficznej konstrukcji nazwy pliku obrazu możemy uzyskać oryginalny identyfikator tweeta i nazwę użytkownika, który go opublikował
  3. następnie funkcja przekaże dane o zdarzeniu do AWS Rekognition Class
  4. następnie rozpoznaje zawartość obrazu i zwraca ją do funkcji createMessage
  5. utworzona wiadomość jest wysyłana jako Tweet
  6. obraz jest usuwany z S3, ponieważ nie jest już potrzebny

Podsumowanie

Udało Ci się utworzyć Twitter-bota, który automatycznie rozpozna obraz i odeśle odpowiedz. Zachęcam do eksperymentowania z tą funkcjonalnością - rozpoznawania różnych rodzajów obrazów, tworzenia bardziej szczegółowych komunikatów itp. Ten przykład był tylko krótkim omówieniem Serverless i tego, jak można budować ciekawe aplikacje bez znajomości back-endu.

Jeśli masz jakieś uwagi lub sądzisz, że coś w tym artykule może być nie tak, wyślij mi wiadomość lub zostaw komentarz.


Autor: Maciej Grzybek
Programista JavaScript z Białegostoku. Obecnie mieszka i pracuje w Leeds. Lubi przekształcać złożone problemy w proste, piękne i intuicyjne interfejsy. Gdy nie koduje, prawdopodobnie znajdziesz go w kuchni, robiącego swoje ulubione danie: sushi.



Cześć

Nazywam się Paweł Zubkiewicz i cieszę się, że tu jesteś!
Od ponad 13 lat profesjonalnie tworzę oprogramowanie, a od 2016 roku pasjonuje się Serverless.
Tą stronę stworzyłem z myślą o Tobie i o nas wszystkich, którzy uważają, że trend serverless trwale zmieni sposób tworzenia oprogramowania.
Więcej o tej stronie...

Kategorie

Pobierz bezpłatny PDF

Poradnik 12 Rzeczy o Serverless

Wybrane artykuły