├── .gitignore ├── UTTERANCES ├── dev.js ├── README.md ├── LICENSE ├── Makefile ├── intents.json └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | main 2 | main.zip -------------------------------------------------------------------------------- /UTTERANCES: -------------------------------------------------------------------------------- 1 | FriendFollowersCount How many followers does {Friend} has 2 | FriendFollowersCount {Friend} followers count 3 | 4 | MyFollowersCount How many followers do I have 5 | MyFollowersCount My followers count 6 | 7 | MyLikes My likes 8 | MyLikes How much likes do I have -------------------------------------------------------------------------------- /dev.js: -------------------------------------------------------------------------------- 1 | // Script to generate UTTERANCES file out of intents.json 2 | var fs = require('fs') 3 | var intents = JSON.parse(fs.readFileSync('intents.json')) 4 | var text = ''; 5 | intents['languageModel']['intents'].forEach((intent) => { 6 | if (!intent.name.match('AMAZON')) { 7 | text += intent.samples.map(sample => `${intent.name} ${sample}`).join("\n") + "\n\n"; 8 | } 9 | }); 10 | fs.writeFileSync('UTTERANCES', text.replace(/\n+$/, '')); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # InstaGate 2 | 3 | InstaGate is a skill for Amazon Alexa to fetch your followers and like numbers from instagram 4 | 5 | ## Setup 6 | 7 | This setup is to be deployed as native in AWS Lambda. Processing your request in the serverless fashion. 8 | 9 | Get AccessToken 10 | https://api.instagram.com/oauth/authorize/?client_id=CLIENT-ID&redirect_uri=http://localhost&response_type=token&scope=basic+public_content 11 | 12 | 13 | [![Proofpic](https://img.youtube.com/vi/ZDQgtcmIqx4/hqdefault.jpg)](https://www.youtube.com/watch?v=ZDQgtcmIqx4 "Custom Amazon Alexa skill for instagram") -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Penkin Vladimir 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Go parameters 2 | GOCMD=go 3 | GOBUILD=$(GOCMD) build 4 | GOCLEAN=$(GOCMD) clean 5 | GOTEST=$(GOCMD) test 6 | GOGET=$(GOCMD) get 7 | BINARY_NAME=main 8 | 9 | all: clean build 10 | aws: clean build_aws zip 11 | aws_release: aws publish_aws clean 12 | 13 | build: 14 | node dev.js 15 | $(GOBUILD) -o $(BINARY_NAME) -v 16 | 17 | build_aws: 18 | GOOS=linux GOARCH=amd64 $(GOBUILD) -o $(BINARY_NAME) -v 19 | 20 | zip: 21 | zip $(BINARY_NAME).zip $(BINARY_NAME) 22 | 23 | publish_aws: 24 | aws lambda update-function-code --function-name "InstaGateAlexaSkill" --zip-file fileb://$(BINARY_NAME).zip 25 | 26 | update_aws_config: 27 | @read -p "Instagram Login:" instaLogin; \ 28 | read -p "Instagram password:" instaPassword; \ 29 | read -p "Instagram ClientId:" instaClientId; \ 30 | read -p "Instagram ClientSecret:" instaClientSecret; \ 31 | read -p "Instagram AccessToken:" instaAccessToken; \ 32 | aws lambda update-function-configuration --function-name "InstaGateAlexaSkill" --environment "Variables={InstaLogin=$$instaLogin,InstaPassword=$$instaPassword,InstaClientId=$$instaClientId,InstaClientSecret=$$instaClientSecret,InstaAccessToken=$$instaAccessToken}" 33 | 34 | test: 35 | $(GOTEST) -v ./... 36 | 37 | clean: 38 | $(GOCLEAN) 39 | rm -f $(BINARY_NAME) 40 | rm -f $(BINARY_NAME).zip 41 | 42 | run: 43 | $(GOBUILD) -o $(BINARY_NAME) -v ./... 44 | ./$(BINARY_NAME) 45 | -------------------------------------------------------------------------------- /intents.json: -------------------------------------------------------------------------------- 1 | { 2 | "languageModel": { 3 | "types": [ 4 | { 5 | "name": "MyFriends", 6 | "values": [ 7 | { 8 | "id": "3", 9 | "name": { 10 | "value": "Dasha", 11 | "synonyms": [ 12 | "Daria" 13 | ] 14 | } 15 | }, 16 | { 17 | "id": "1", 18 | "name": { 19 | "value": "Marina", 20 | "synonyms": [ 21 | "Maya", 22 | "Marisha" 23 | ] 24 | } 25 | }, 26 | { 27 | "id": "2", 28 | "name": { 29 | "value": "Alexey", 30 | "synonyms": [ 31 | "Lyoha" 32 | ] 33 | } 34 | } 35 | ] 36 | } 37 | ], 38 | "intents": [ 39 | { 40 | "name": "AMAZON.CancelIntent", 41 | "samples": [] 42 | }, 43 | { 44 | "name": "AMAZON.HelpIntent", 45 | "samples": [] 46 | }, 47 | { 48 | "name": "AMAZON.StopIntent", 49 | "samples": [] 50 | }, 51 | { 52 | "name": "FriendFollowersCount", 53 | "samples": [ 54 | "How many followers does {Friend} has", 55 | "{Friend} followers count" 56 | ], 57 | "slots": [ 58 | { 59 | "name": "Friend", 60 | "type": "MyFriends", 61 | "samples": [ 62 | "His name is {Friend}", 63 | "Her name is {Friend}" 64 | ] 65 | } 66 | ] 67 | }, 68 | { 69 | "name": "MyFollowersCount", 70 | "samples": [ 71 | "How many followers do I have", 72 | "My followers count" 73 | ], 74 | "slots": [] 75 | }, 76 | { 77 | "name": "MyLikes", 78 | "samples": [ 79 | "My likes", 80 | "How much likes do I have" 81 | ], 82 | "slots": [] 83 | } 84 | ], 85 | "invocationName": "investigate" 86 | }, 87 | "prompts": [ 88 | { 89 | "id": "Elicit.Intent-FriendFollowersCount.IntentSlot-Friend", 90 | "variations": [ 91 | { 92 | "type": "PlainText", 93 | "value": "What's your friend name?" 94 | }, 95 | { 96 | "type": "PlainText", 97 | "value": "What is his name?" 98 | }, 99 | { 100 | "type": "PlainText", 101 | "value": "What is her name?" 102 | } 103 | ] 104 | } 105 | ], 106 | "dialog": { 107 | "intents": [ 108 | { 109 | "name": "FriendFollowersCount", 110 | "confirmationRequired": false, 111 | "prompts": {}, 112 | "slots": [ 113 | { 114 | "name": "Friend", 115 | "type": "MyFriends", 116 | "elicitationRequired": true, 117 | "confirmationRequired": false, 118 | "prompts": { 119 | "elicitation": "Elicit.Intent-FriendFollowersCount.IntentSlot-Friend" 120 | } 121 | } 122 | ] 123 | } 124 | ] 125 | } 126 | } -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "os" 7 | "strings" 8 | 9 | "github.com/ahmdrz/goinsta" 10 | "github.com/aws/aws-lambda-go/lambda" 11 | "github.com/yanatan16/golang-instagram/instagram" 12 | ) 13 | 14 | // AlexaSkillEvent is a struct for the Alexa Skill Event 15 | type AlexaSkillEvent struct { 16 | Session struct { 17 | New bool `json:"new"` 18 | SessionID string `json:"sessionId"` 19 | Application struct { 20 | ApplicationID string `json:"applicationId"` 21 | } `json:"application"` 22 | Attributes interface{} `json:"attributes"` 23 | User struct { 24 | Userid string `json:"userId"` 25 | } `json:"user"` 26 | } `json:"session"` 27 | Request struct { 28 | Type string `json:"type"` 29 | RequestID string `json:"requestId"` 30 | Intent struct { 31 | Name string `json:"name"` 32 | Slots struct { 33 | Friend struct { 34 | Name string `json:"name"` 35 | Value string `json:"value"` 36 | } `json:"Friend"` 37 | } `json:"slots"` 38 | } `json:"intent"` 39 | Locale string `json:"locale"` 40 | Timestamp string `json:"timestamp"` 41 | } `json:"request"` 42 | Version string `json:"version"` 43 | } 44 | 45 | // AlexaResponse is a struct for the resonse to Alexa Skill Kit 46 | type AlexaResponse struct { 47 | Version string `json:"version,omitempty"` 48 | Response struct { 49 | OutputSpeech struct { 50 | Type string `json:"type"` 51 | Text string `json:"text"` 52 | SSML string `json:"ssml"` 53 | } `json:"outputSpeech"` 54 | } `json:"response"` 55 | ShouldEndSession bool `json:"shouldEndSession"` 56 | SessionAttributes struct{} `json:"sessionAttributes"` 57 | } 58 | 59 | // FriendsAliases are aliases for a friend slots 60 | var FriendsAliases = map[string]string{ 61 | "marina": "Marina", 62 | "maya": "Marina", 63 | "marisha": "Marina", 64 | "alexey": "Alexey", 65 | "lyoha": "Alexey", 66 | "dasha": "Dasha", 67 | "daria": "Dasha", 68 | } 69 | 70 | // FriendsMap is a map of my friends names and their usernames in instagram 71 | var FriendsMap = map[string]string{ 72 | "Marina": "maia_mois", 73 | "Alexey": "bigxam", 74 | "Dasha": "sashchenko_", 75 | } 76 | 77 | // GetInstaHandler returns handler for a instagram fetching func 78 | func GetInstaHandler(instaClient *goinsta.Instagram) func(request *AlexaSkillEvent) (*AlexaResponse, error) { 79 | return func(request *AlexaSkillEvent) (*AlexaResponse, error) { 80 | // stdout and stderr are sent to AWS CloudWatch Logs 81 | fmt.Println("Processing Lambda request for ", request.Request.Intent.Name) 82 | 83 | return processRequest(request, instaClient), nil 84 | } 85 | } 86 | 87 | func processRequest(event *AlexaSkillEvent, instaClient *goinsta.Instagram) *AlexaResponse { 88 | text := "" 89 | respType := "PlainText" 90 | 91 | switch event.Request.Intent.Name { 92 | case "MyFollowersCount": 93 | resp, err := instaClient.GetUserByUsername("penkinv") 94 | if err != nil { 95 | text = fmt.Sprintf("Can't reach instagram servers, error: %s", err) 96 | } else { 97 | text = fmt.Sprintf("You have %d followers", resp.User.FollowerCount) 98 | } 99 | case "FriendFollowersCount": 100 | friend := strings.ToLower(event.Request.Intent.Slots.Friend.Value) 101 | 102 | resp, err := instaClient.GetUserByUsername(FriendsMap[FriendsAliases[friend]]) 103 | if err != nil { 104 | text = fmt.Sprintf("Can't reach instagram servers, error: %s", err) 105 | } else { 106 | text = fmt.Sprintf("%s has %d followers", friend, resp.User.FollowerCount) 107 | } 108 | case "MyLikes": 109 | // Special case: Using different instagram API to fetch my most recent media 110 | instaAPI := instagram.New(os.Getenv("InstaClientId"), os.Getenv("InstaClientSecret"), os.Getenv("InstaAccessToken"), false) 111 | if ok, err := instaAPI.VerifyCredentials(); !ok { 112 | text = fmt.Sprintf("Can't reach instagram servers, error: %s", err) 113 | } 114 | 115 | params := url.Values{} 116 | params.Set("count", "1") 117 | 118 | resp, err := instaAPI.GetUserRecentMedia("self", params) 119 | if err != nil { 120 | text = fmt.Sprintf("Can't reach instagram servers, error: %s", err) 121 | } else { 122 | text = fmt.Sprintf("You have %d likes on your most recent photo", resp.Medias[0].Likes.Count) 123 | } 124 | default: 125 | text = "I have no idea what you just said, could you speak more clearly?" 126 | } 127 | 128 | return generateAlexaResponse(text, respType) 129 | } 130 | 131 | func generateAlexaResponse(text string, respType string) *AlexaResponse { 132 | response := &AlexaResponse{ 133 | Version: "1.0", 134 | } 135 | response.Response.OutputSpeech.Type = respType 136 | switch respType { 137 | case "PlainText": 138 | response.Response.OutputSpeech.Text = text 139 | case "SSML": 140 | response.Response.OutputSpeech.SSML = text 141 | } 142 | 143 | response.ShouldEndSession = true 144 | return response 145 | } 146 | 147 | func main() { 148 | instaClient := goinsta.New(os.Getenv("InstaLogin"), os.Getenv("InstaPassword")) 149 | if err := instaClient.Login(); err != nil { 150 | panic(err) 151 | } 152 | defer instaClient.Logout() 153 | 154 | fmt.Println("Starting lambdas") 155 | lambda.Start(GetInstaHandler(instaClient)) 156 | } 157 | --------------------------------------------------------------------------------