| #!/usr/bin/env python3 |
| # -*- coding: utf-8 -*- |
| # Licensed to the Apache Software Foundation (ASF) under one or more |
| # contributor license agreements. See the NOTICE file distributed with |
| # this work for additional information regarding copyright ownership. |
| # The ASF licenses this file to You under the Apache License, Version 2.0 |
| # (the "License"); you may not use this file except in compliance with |
| #the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| """ |
| This is an experimental tone analyzer plugin for using Watson/BlueMix for |
| analyzing the mood of email on a list. This requires a Watson account |
| and a watson section in config.yaml, as such: |
| |
| watson: |
| username: $user |
| password: $pass |
| api: https://$something.watsonplatform.net/tone-analyzer/api |
| |
| Currently only pony mail is supported. more to come. |
| """ |
| |
| import time |
| import datetime |
| import re |
| import json |
| import hashlib |
| import requests |
| import json |
| import uuid |
| |
| def watsonTone(KibbleBit, body): |
| """ Sentiment analysis using IBM Watson """ |
| if 'watson' in KibbleBit.config: |
| headers = { |
| 'Content-Type': 'application/json' |
| } |
| |
| # Crop out quotes |
| lines = body.split("\n") |
| body = "\n".join([x for x in lines if not x.startswith(">")]) |
| |
| js = { |
| 'text': body |
| } |
| try: |
| rv = requests.post( |
| "%s/v3/tone?version=2017-09-21&sentences=false" % KibbleBit.config['watson']['api'], |
| headers = headers, |
| data = json.dumps(js), |
| auth = (KibbleBit.config['watson']['username'], KibbleBit.config['watson']['password']) |
| ) |
| jsout = rv.json() |
| except: |
| jsout = {} # borked Watson? |
| mood = {} |
| if 'document_tone' in jsout: |
| for tone in jsout['document_tone']['tones']: |
| mood[tone['tone_id']] = tone['score'] |
| else: |
| KibbleBit.pprint("Failed to analyze email body.") |
| return mood |
| |
| def azureTone(KibbleBit, body): |
| """ Sentiment analysis using Azure Text Analysis API """ |
| if 'azure' in KibbleBit.config: |
| headers = { |
| 'Content-Type': 'application/json', |
| 'Ocp-Apim-Subscription-Key': KibbleBit.config['azure']['apikey'] |
| } |
| |
| # Crop out quotes |
| lines = body.split("\n") |
| body = "\n".join([x for x in lines if not x.startswith(">")]) |
| js = { |
| "documents": [ |
| { |
| "language": "en", |
| "id": str(uuid.uuid4()), |
| "text": body |
| } |
| ] |
| } |
| try: |
| rv = requests.post( |
| "https://%s.api.cognitive.microsoft.com/text/analytics/v2.0/sentiment" % KibbleBit.config['azure']['location'], |
| headers = headers, |
| data = json.dumps(js) |
| ) |
| jsout = rv.json() |
| except: |
| jsout = {} # borked sentiment analysis? |
| mood = {} |
| if 'documents' in jsout and len(jsout['documents']) > 0: |
| # This is more parred than Watson, so we'll split it into three groups: positive, neutral and negative. |
| # Divide into four segments, 0->40%, 25->75% and 60->100%. |
| # 0-40 promotes negative, 60-100 promotes positive, and 25-75% promotes neutral. |
| # As we don't want to over-represent negative/positive where the results are |
| # muddy, the neutral zone is larger than the positive/negative zones by 10%. |
| val = jsout['documents'][0]['score'] |
| mood['negative'] = max(0, ((0.4 - val) * 2.5)) # For 40% and below, use 2½ distance |
| mood['positive'] = max(0, ((val-0.6) * 2.5)) # For 60% and above, use 2½ distance |
| mood['neutral'] = max(0, 1 - (abs(val - 0.5) * 2)) # Between 25% and 75% use double the distance to middle. |
| else: |
| KibbleBit.pprint("Failed to analyze email body.") |
| # Depending on price tier, Azure will return a 429 if you go too fast. |
| # If we see a statusCode return, let's just stop for now. |
| # Later scans can pick up the slack. |
| if 'statusCode' in jsout: |
| KibbleBit.pprint("Possible rate limiting in place, stopping for now.") |
| return False |
| return mood |
| |