Related
I'd like to create a modal that instantly pops up when a new user joins the channel. My thought process was to use an #app.event to trigger when a new member joins, then somehow activate my slash command. But unfortunately app.event doesn't have a trigger_id so i can't just create a modal in an event method. I'm not married to using a slash command either, but it was all I found that could get users to use checkboxes and submit the checked responses for a modal. Any help connecting the two would be great, or other suggestions would be appreciated.
#app.event("member_joined_channel")
def modal_event(event, say, body):
usr_id = event["user"],
user_id = usr_id[0]
channel_id = event["channel"]
pprint.pprint(body)
say(text=f"Welcome to the channel, <#{user_id}>! 🎉 You can introduce yourself in this channel.")
app.client.chat_postMessage(
channel=channel_id,
)
#app.command("/cmd")
def modal(body):
pprint.pprint(body)
result = app.client.views_open(
trigger_id=body['trigger_id'],
view={
"title": {
"type": "plain_text",
"text": "My App",
"emoji": True
},
"submit": {
"type": "plain_text",
"text": "Submit",
"emoji": True
},
"type": "modal",
"close": {
"type": "plain_text",
"text": "Cancel",
"emoji": True
},
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Hello, Assistant to the Regional Manager Dwight! *Michael Scott* wants to know where you'd like to take the Paper Company investors to dinner tonight.\n\n"
}
},
{
"type": "input",
"element": {
"type": "checkboxes",
"options": [
{
"text": {
"type": "plain_text",
"text": "Gary Danko",
"emoji": True
},
"value": "value-0"
},
{
"text": {
"type": "plain_text",
"text": "Chipotle",
"emoji": True
},
"value": "value-1"
},
{
"text": {
"type": "plain_text",
"text": "Slack Cafe",
"emoji": True
},
"value": "value-2"
}
]
},
"label": {
"type": "plain_text",
"text": "Please select all restaurants you'd be willing to eat at:",
"emoji": True
}
}
]
}
)
Unfortunately, you need a trigger_id to open a modal. I don't know of a way you can open one without it. What you could do is listen for the [member_joined_channel][1]event and then have your app send the user an ephemeral message in the channel prompting them to click a button which would then open a modal.
I am currently making a scraper app, but before going full out with the app, using other frameworks like Discord.py, I had to first scrape the site first. It proved quite difficult to scrape the site. The site that I am trying to scrape from is Fiverr. Anyways, long story short, I had to get some cookies to login with Python Requests. The big issue now is that the data I need to scrape comes in the form of JSON, which I don't know much about. I managed to select the javascript in question, but once I load it it gives an error: "TypeError: the JSON object must be str, bytes or bytearray, not Tag". I specifically need the "rows" part which is part of the JSON data.
I'm not quite certain how to fix this and have read and tried some similar questions here. I will appreciate any help.
import requests
from bs4 import BeautifulSoup
import re
import json
# Irrelevant to the question
class JobClass:
def __init__(self, date=None, buyer=None, request=None, duration=None, budget=None, link="https://www.fiverr.com/users/myusername/requests", id=None):
self.date = date
self.buyer = buyer
self.request = request
self.duration = duration
self.budget = budget
self.link = link
self.id = id
# Irrelevant to the question
duplicateSet = set()
scrapedSet = set()
jobObjArr = []
headers = {
# Some private cookies. To get them you just need to use a site like https://curl.trillworks.com/ it is really a life saver
# This is used to tell the site who you are to be logged in (which is why I deleted this part out of the code)
}
# Please note that I used "myusername" in the URL. This is going to be different depending on user
# Using the requests module, we use the "get" function
# provided to access the webpage provided as an
# argument to this function:
result = requests.get(
'https://www.fiverr.com/users/myusername/requests', headers=headers)
# Now, let us store the page content of the website accessed
# from requests to a variable:
src = result.content
# Now that we have the page source stored, we will use the
# BeautifulSoup module to parse and process the source.
# To do so, we create a BeautifulSoup object based on the
# source variable we created above:
soup = BeautifulSoup(src, "lxml")
data = soup.select("[type='text/javascript']")[1]
print(data)
# TypeError: the JSON object must be str, bytes or bytearray, not Tag
jsonObject = json.loads(data)
# Here is the output of print(data):
<script type="text/javascript">
document.viewData = {
"dds": {
"subCats": {
"current": {
"text": "All Subcategories",
"val": "-1"
},
"options": [{
"text": "Web \u0026 Mobile Design",
"val": 151
}, {
"text": "Web Programming",
"val": 140
}]
}
},
"results": {
"rows": [{
"type": "none",
"identifier": "5cf132b55e08360011efe633",
"cells": [{
"text": "May 31, 2019",
"type": "date",
"withText": true
}, {
"userPict": "\u003cspan class=\"missing-image-user \"\u003ec\u003c/span\u003e",
"type": "profile-40",
"cssClass": "height95"
}, {
"hintBottom": false,
"text": "My website was hacked and deleted. Need to have it recreated ",
"type": "text-wide",
"tags": [],
"attachment": false
}, {
"text": 1,
"type": "applications",
"alignCenter": true
}, {
"text": "3 days",
"type": "hidden-action",
"actionVisible": false,
"alignCenter": true,
"withText": true,
"buttons": [{
"type": "span",
"text": "3 days",
"class": "duration"
}, {
"type": "button",
"text": "Remove Request",
"class": "remove-request js-remove-request",
"meta": {
"requestId": "5cf132b55e08360011efe633",
"isProfessional": false
}
}]
}, {
"text": "---",
"type": "hidden-action",
"actionVisible": false,
"alignCenter": true,
"withText": true,
"buttons": [{
"type": "span",
"text": "---",
"class": "budget"
}, {
"type": "button",
"text": "Send Offer",
"class": "btn-standard btn-green-grad js-send-offer",
"meta": {
"username": "conto217",
"category": 3,
"subCategory": 151,
"requestId": "5cf132b55e08360011efe633",
"requestText": "My website was hacked and deleted. Need to have it recreated ",
"userPict": "\u003cspan class=\"missing-image-user \"\u003ec\u003c/span\u003e",
"isProfessional": false,
"buyerId": 32969684
}
}]
}]
}, {
"type": "none",
"identifier": "5cf12f641b6e99000edf1b60",
"cells": [{
"text": "May 31, 2019",
"type": "date",
"withText": true
}, {
"userPict": "\u003cimg src=\"https://fiverr-res.cloudinary.com/t_profile_small,q_auto,f_auto/attachments/profile/photo/648ceb417a85844b25e8bf070a70d9a0-254781561534997516.9743/MyFileName\" alt=\"muazamkhokher\" width=\"40\" height=\"40\"\u003e",
"type": "profile-40",
"cssClass": "height95"
}, {
"hintBottom": false,
"text": "Need mobile ui/ux designer from marvel wireframes",
"type": "text-wide",
"tags": [],
"attachment": false
}, {
"text": 4,
"type": "applications",
"alignCenter": true
}, {
"text": "5 days",
"type": "hidden-action",
"actionVisible": false,
"alignCenter": true,
"withText": true,
"buttons": [{
"type": "span",
"text": "5 days",
"class": "duration"
}, {
"type": "button",
"text": "Remove Request",
"class": "remove-request js-remove-request",
"meta": {
"requestId": "5cf12f641b6e99000edf1b60",
"isProfessional": false
}
}]
}, {
"text": "$50",
"type": "hidden-action",
"actionVisible": false,
"alignCenter": true,
"withText": true,
"buttons": [{
"type": "span",
"text": "$50",
"class": "budget"
}, {
"type": "button",
"text": "Send Offer",
"class": "btn-standard btn-green-grad js-send-offer",
"meta": {
"username": "muazamkhokher",
"category": 3,
"subCategory": 151,
"requestId": "5cf12f641b6e99000edf1b60",
"requestText": "Need mobile ui/ux designer from marvel wireframes",
"userPict": "\u003cimg src=\"https://fiverr-res.cloudinary.com/t_profile_small,q_auto,f_auto/attachments/profile/photo/648ceb417a85844b25e8bf070a70d9a0-254781561534997516.9743/MyFileName\" alt=\"muazamkhokher\" width=\"100\" height=\"100\"\u003e",
"isProfessional": false,
"buyerId": 25478156
}
}]
}]
....
I expect the JSON to be loaded in jsonObject, but I get an error: "TypeError: the JSON object must be str, bytes or bytearray, not Tag"
Edit: Here is some code at the end of the print statement. It randomly cuts off for some reason with no ending script tag:
}, {
"type": "none",
"identifier": "5cf1236a959aa5000f1ce094",
"cells": [{
"text": "May 31, 2019",
"type": "date",
"withText": true
}, {
"userPict": "\u003cimg src=\"https://fiverr-res.cloudinary.com/t_profile_small,q_auto,f_auto/profile/photos/30069758/original/Universalco_2a_Cloud.png\" alt=\"clarky2000\" width=\"40\" height=\"40\"\u003e",
"type": "profile-40",
"cssClass": "height95"
}, {
"hintBottom": false,
"text": "Slider revolution slider. 3 slides for a music festival. I can supply a copy what each slide should look like (see attached) and all the individual objects. Anyone can create basic RS slides, but I want this to be dynamic as its for a music festival. We are using the free version of RS if were are required to use the paid version of SL for addons please let us know. Bottom line this must be 3 dynamic slides (using the same background) for a music festival audience. Unlimited revisions is a must.",
"type": "see-more",
"tags": [{
"text": "Graphic UI"
}, {
"text": "Landing Pages"
}],
"attachment": {
"url": "/download/file/1559260800%2Fgig_requests%2Fattachment_f2a5f51b9fb473e8fc7f498929f39e3f",
"name": "Outwith Rotator_1920x1080_1.jpg",
"size": "2.68 MB"
}
}, {
"text": 2,
"type": "applications",
"alignCenter": true
}, {
"text": "24 hours",
"type": "hidden-action",
"actionVisible": false,
"alignCenter": true,
"withText": true,
"buttons": [{
"type": "span",
"text": "24 hours",
"class": "duration"
}, {
"type": "button",
"text": "Remove Request",
"class": "remove-request js-remove-request",
"meta": {
"requestId": "5cf1236a959aa5000f1ce094",
"isProfessional": false
}
}]
}, {
"text": "$23",
"type": "hidden-action",
"actionVisible": false,
"alignCenter": true,
"withText": true,
"buttons": [{
"type": "span",
"text": "$23",
"class": "budget"
}, {
"type": "button",
"text": "Send Of
I've been working on an integration to create a dialog for a Slack bot that uses python 3, AWS API Gateway, and AWS Lambda. I'm currently working on the Slack side for integration versus the end purpose of my function.
I can get my bot running with some responses if I remove the dialog and just have it chat with the user from the tutorials here and here, however once I add a dialog into the mix the bot no longer responds. I've tried basing my code off the the example code from the Slack API's github but it doesn't help. I would appreciate any guidance on how to do this using python 3.
Code below:
import os
import logging
import urllib
import boto3
from slackclient import SlackClient
LOGGER = logging.getLogger()
LOGGER.setLevel(logging.DEBUG)
# Grab the Bot OAuth token from the environment + slack verification
BOT_TOKEN = os.environ["BOT_TOKEN"]
SLACK_VERIFICATION_TOKEN = os.environ["SLACK_VERIFICATION_TOKEN"]
slack_client = SlackClient(BOT_TOKEN)
SLACK_URL = "https://slack.com/api/dialog.open"
def lambda_handler(data, context):
''' Entry point for API Gateway '''
slack_event = data['event']
if "bot_id" in slack_event:
logging.warn("Ignore bot event")
else:
channel_id = slack_event["channel"]
support_form = slack_client.api_call(
"dialog.open",
trigger_id = slack_event["trigger_id"],
dialog = {
"title": "AWS Support Ticket",
"submit_label": "Submit",
"callback_id": "support_form",
"elements": [
{
"label": "Subject",
"type": "text",
"name": "subject",
"placeholder": "Support Case Subject"
},
{
"label": "Description",
"type": "textarea",
"name": "body",
"placeholder": "Describe the issue you would like to open a support case about"
},
{
"type": "select",
"label": "What is your issue type?",
"name": "issueType",
"options": [
{
"label": "Customer Service",
"value": "customerservice"
},
{
"label": "Technical",
"value": "technical"
}
]
},
{
"label": "What is your severity level?",
"type": "select",
"name": "serverity",
"options": [
{
"label": "5 - General Guidance",
"value": "5"
},
{
"label": "4 - System Impaired",
"value": "4"
},
{
"label": "3 - Production System Impaired",
"value": "3"
},
{
"label": "2 - Production System Down",
"value": "2"
},
{
"label": "1 - Business-critical System Down",
"value": "1"
}
]
},
{
"label": "Service Code",
"type": "text",
"name": "serviceCode"
},
{
"label": "Category Code",
"type": "text",
"name": "categoryCode"
},
{
"label": "Please choose your language",
"type": "select",
"name": "language",
"options": [
{
"label": "English",
"value": "english"
},
{
"label": "Japanese",
"value": "japanese"
}
]
},
{
"label": "What is your attachement set id?",
"type": "text",
"name": "attachementSetId"
},
{
"label": "Please enter the emails you want cc'd on this case:",
"type": "textarea",
"name": "ccEmailAddresses"
}
]
}
})
data = urllib.parse.urlencode(
(
("token", BOT_TOKEN),
("channel", channel_id),
("dialog", support_form)
)
)
# Construct the HTTP request that will be sent to the Slack API.
request = urllib.request.Request(
SLACK_URL,
data=data,
method="POST"
)
# Add a header mentioning that the text is URL-encoded.
request.add_header(
"Content-Type",
"application/x-www-form-urlencoded"
)
# Fire off the request!
urllib.request.urlopen(request).read()
# Everything went fine.
return "200 OK"
Python 3.6 and Flask-Ask
I can't seem to get Alexa to stream audio for my current hobby project (http://github.com/michaeljdietz/jukebox).
I can stream the audio manually through a web browser no problem with the https stream URL in my response object. Both Alexa and the AVS Service Simulator respond with speech and text correctly to the request. The directive appears to be formatted right to me, to the best of my knowledge anyway.
Here is the response to Alexa which should trigger the playback:
{
"version": "1.0",
"response": {
"outputSpeech": {
"text": "Playing the album The Bends by Radiohead on your jukebox",
"type": "PlainText"
},
"speechletResponse": {
"outputSpeech": {
"text": "Playing the album The Bends by Radiohead on your jukebox"
},
"directives": [
{
"playBehavior": "REPLACE_ALL",
"type": "AudioPlayer.Play",
"audioItem": {
"stream": {
"token": "334f1e7e-938d-4dea-a732-45884c6f6db9",
"url": "https://michaeljdietz.me/songs/1426",
"offsetInMilliseconds": 0
}
}
}
],
"shouldEndSession": true
}
},
"sessionAttributes": {}
}
UPDATE: It does seem to work about 1 out of 10 times for the same stream URL. Below is the response on from Alexa when it fails.
{
"version":"1.0",
"context": {
"AudioPlayer":{
"offsetInMilliseconds":0,
"token":"*****",
"playerActivity":"FINISHED"
},
"System": {
"application": {
"applicationId": "*****"
},
"user" {
"userId":"*****"
},
"device": {
"deviceId": "*****",
"supportedInterfaces": {
"AudioPlayer": {}
}
},
"apiEndpoint":"https://api.amazonalexa.com",
"apiAccessToken":"*****"
}
},
"request": {
"type":"AudioPlayer.PlaybackFailed",
"requestId":"*****",
"timestamp":"2018-01-09T00:02:48Z",
"locale":"en-US",
"currentPlaybackState": {
"offsetInMilliseconds":0,
"token":"*****",
"playerActivity":"FINISHED"
},
"error": {
"message":"Setting up renderers for an unknown media type: UNDEFINED",
"type":"MEDIA_ERROR_UNKNOWN"
},
"token":"*****"
}
}
Based on Amazons blog and documentation, it seems you're missing "type": "AudioPlayer.Play"in your directives.
I am using IBM Watson's Natural Language Understanding API to get back keywords and entities from a URL. I want to iterate through the JSON response to get all the keywords and entities and have them populate in my results.html file.
I'm trying to iterate through the results in the application.py file and the results.html file which uses jinja.
The helpers.py file is returning a json.dump and sending it to my application.py file so that I can iterate through the results.
However, I'm getting the following error:
TypeError: string indices must be integers
I've looked up json.dump vs json.load and strings and dictionaries to help solve this problem, but i can't get the code to work. If you need more info, please let me know. I need to get this figured out before the end of the year. Thank you in advance.
Here is the my applications.py file
#app.route("/URL", methods=["GET", "POST"])
def URL():
"""Analyze URL."""
# if user reached route via POST (as by submitting a form via POST)
if request.method == "POST":
# if nothing was entered return apology
if not request.form.get("URL"):
return apology("please enter a URL")
URL = request.form.get("URL")
# analyze URL using analyze function in helpers.py
results = analyze(request.form.get("URL"))
for item in results:
keywords = item["keywords"]["text"]
entities = item["entities"]["text"]
return render_template("results.html", results=results, URL=URL)
# check if URL is valid
if not results:
return apology("this is not a valid URL")
else:
return render_template("url.html")
Here is the helpers.py file.
def analyze(URL):
natural_language_understanding = NaturalLanguageUnderstandingV1(
version='2017-02-27',
username='MUSTGETYOURUSERNAME',
password='MUSTGETYOURPASSWORD')
response = natural_language_understanding.analyze(
url=URL,
features=Features(entities=EntitiesOptions(), keywords=KeywordsOptions()))
return (json.dumps(response, indent=2))
Here is the results.html file using jinja:
{% extends "layout.html" %}
{% block title %}
Results
{% endblock %}
{% block main %}
<h2>Powered by IBM Watson's AI to recommend your #'s and #'s
for tweeting</h2>
<p>{{URL}}</p>
{% for item in results %}
<tr>
<td>{{ item.keywords }}</td>
<td>{{ item.entities }}</td>
</tr>
{% endfor %}
<a class="twitter-share-button"
href="https://twitter.com/intent/tweet">Tweet</a>
{% endblock %}
Here is what the output looks like:
[
{
"text": "Android apps",
"relevance": 0.926516
},
{
"text": "Chrome OS",
"relevance": 0.878045
},
{
"text": "Sorry Android fanboys",
"relevance": 0.696885
},
{
"text": "Android tablet",
"relevance": 0.695471
},
{
"text": "absolutely wonderful Android",
"relevance": 0.672889
},
{
"text": "Chrome OS beta",
"relevance": 0.626619
},
{
"text": "Android Police",
"relevance": 0.592994
},
{
"text": "Chrome OS devices",
"relevance": 0.566831
},
{
"text": "count Android",
"relevance": 0.563911
},
{
"text": "dominant Google OS",
"relevance": 0.553724
},
{
"text": "Chrome Unboxed",
"relevance": 0.540076
},
{
"text": "overall tablet sales",
"relevance": 0.511826
},
{
"text": "inexpensive Google rival",
"relevance": 0.498259
},
{
"text": "half incremental improvements",
"relevance": 0.468663
},
{
"text": "standard operating procedure",
"relevance": 0.45946
},
{
"text": "uncommon Chromebook form",
"relevance": 0.456969
},
{
"text": "content consumption machines",
"relevance": 0.451775
},
{
"text": "absolute best pieces",
"relevance": 0.450763
},
{
"text": "content creation ones",
"relevance": 0.450345
},
{
"text": "rich new fusion",
"relevance": 0.446127
},
{
"text": "Amazon Fire tablet",
"relevance": 0.444685
},
{
"text": "selling tablet",
"relevance": 0.444241
},
{
"text": "tablet operating",
"relevance": 0.440434
},
{
"text": "Google Pixelbook",
"relevance": 0.440007
},
{
"text": "Google store",
"relevance": 0.439719
},
{
"text": "cheap tablets",
"relevance": 0.408395
},
{
"text": "immortal highlander",
"relevance": 0.404233
},
{
"text": "disparate OSes",
"relevance": 0.401626
},
{
"text": "laptop space",
"relevance": 0.40117
},
{
"text": "detachable two-in-one",
"relevance": 0.396257
},
{
"text": "pleasant surprises",
"relevance": 0.394027
},
{
"text": "additional oomph",
"relevance": 0.393127
},
{
"text": "Samsung",
"relevance": 0.391534
},
{
"text": "flashy Chromebook",
"relevance": 0.391359
},
{
"text": "sleek Chromebook",
"relevance": 0.390035
},
{
"text": "smaller devices",
"relevance": 0.389106
},
{
"text": "operating systems",
"relevance": 0.388958
},
{
"text": "new feature",
"relevance": 0.388395
},
{
"text": "true multitasking",
"relevance": 0.388097
},
{
"text": "tablet-like device",
"relevance": 0.387175
},
{
"text": "two-in-one Chromebook",
"relevance": 0.385518
},
{
"text": "nightmare fuel",
"relevance": 0.385284
},
{
"text": "mouse-first OS—not",
"relevance": 0.385193
},
{
"text": "parallel tasks",
"relevance": 0.381923
},
{
"text": "budget device",
"relevance": 0.380932
},
{
"text": "iPad",
"relevance": 0.35313
},
{
"text": "news",
"relevance": 0.333007
},
{
"text": "strides",
"relevance": 0.319667
},
{
"text": "iOS",
"relevance": 0.318235
},
{
"text": "thanks",
"relevance": 0.316534
}
]
[
{
"type": "Company",
"text": "Google",
"relevance": 0.385564,
"disambiguation": {
"subtype": [
"AcademicInstitution",
"AwardPresentingOrganization",
"OperatingSystemDeveloper",
"ProgrammingLanguageDeveloper",
"SoftwareDeveloper",
"VentureFundedCompany"
],
"name": "Google",
"dbpedia_resource": "http://dbpedia.org/resource/Google"
},
"count": 9
},
{
"type": "Company",
"text": "Samsung",
"relevance": 0.204475,
"disambiguation": {
"subtype": [],
"name": "Samsung",
"dbpedia_resource": "http://dbpedia.org/resource/Samsung"
},
"count": 4
},
{
"type": "Location",
"text": "Chromebooks",
"relevance": 0.129986,
"disambiguation": {
"subtype": [
"City"
]
},
"count": 2
},
{
"type": "Company",
"text": "Amazon",
"relevance": 0.119948,
"disambiguation": {
"subtype": [],
"name": "Amazon.com",
"dbpedia_resource": "http://dbpedia.org/resource/Amazon.com"
},
"count": 2
},
{
"type": "Location",
"text": "US",
"relevance": 0.109124,
"disambiguation": {
"subtype": [
"Region",
"AdministrativeDivision",
"GovernmentalJurisdiction",
"FilmEditor",
"Country"
],
"name": "United States",
"dbpedia_resource": "http://dbpedia.org/resource/United_States"
},
"count": 1
},
{
"type": "Company",
"text": "Apple",
"relevance": 0.108271,
"disambiguation": {
"subtype": [
"Brand",
"OperatingSystemDeveloper",
"ProcessorManufacturer",
"ProgrammingLanguageDesigner",
"ProgrammingLanguageDeveloper",
"ProtocolProvider",
"SoftwareDeveloper",
"VentureFundedCompany",
"VideoGameDeveloper",
"VideoGamePublisher"
],
"name": "Apple Inc.",
"dbpedia_resource": "http://dbpedia.org/resource/Apple_Inc."
},
"count": 1
},
{
"type": "Quantity",
"text": "$500",
"relevance": 0.0746897,
"count": 1
},
{
"type": "Quantity",
"text": "$50",
"relevance": 0.0746897,
"count": 1
}
]
There's some confusion going on here. You're not handling the Watson API response particularly well and it also seems you're misunderstanding the shape that this response comes back in.
Your analyze function handles the response from the Watson API call. The Watson API helpfully parses the JSON response it gets back from the server into Python objects such as lists and dicts. However, your code then calls json.dumps to convert that back to a string. In this way you're undoing some of the work the Watson API has done for you. Don't call json.dumps on response, just return response.
(I'm guessing you've taken this from the official IBM documentation here: it has the same call to json.dumps but the Python code sample only has that in for demonstration purposes.)
This explains the error you are getting: results is therefore a string, and so when you iterate over it using for item in results, each item is a 1-character string.
However, making this change won't be enough to get your code working. Next, we have to look at the loop where the error occurs, as there is still a problem with it:
for item in results:
keywords = item["keywords"]["text"]
entities = item["entities"]["text"]
This code treats results as if it were a list, with each item in the list being a dict containing a keywords property and an entities property. In other words, it assumes the data looks something like the following:
[
{
"keywords": { "text": "abc123", ... },
"entities": { "text": "def456", ... },
...
},
{
"keywords": { "text": "cba321", ... },
"entities": { "text": "fed654", ... },
...
},
...
]
However, if you take a closer look at the the IBM documentation (same page as linked to above), the response comes back in a different shape. It looks more like this:
{
"keywords": [
{ "text": "abc123", ... },
{ "text": "def456", ... }
],
"entities": [
{ "text": "ghi789", ... }
],
...
}
In particular, keywords and entities are separate lists under the top-level object/dict, and they might not have the same length.
Instead of your loop above, you probably want something more like the following:
for item in results["keywords"]:
keyword_text = item["text"]
for item in results["entities"]:
entity_text = item["text"]
However, I'm not sure what your original loop was supposed to do: it would fetch data out of your Watson response and then do nothing with the data it fetched out.
You'll also need to modify your Jinja template to include two separate loops. I'll leave this up to you.
Finally, you write the following code:
return render_template("results.html", results=results, URL=URL)
# check if URL is valid
if not results:
return apology("this is not a valid URL")
It's too late to check if the URL is valid after we've already returned! The check (if not results) is unreachable and will never run. Move this check to just after the line results = analyze(...).