Skip to content

Commit

Permalink
Weixin no longer provides recognition field in voice message now
Browse files Browse the repository at this point in the history
  • Loading branch information
polyrabbit committed Nov 14, 2023
1 parent 0732666 commit 45fed85
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 13 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
docker-build-push:
name: Docker Build
runs-on: ubuntu-latest
needs: unit-test
# needs: unit-test # TODO: enable back after migrating from python 2.7
steps:
- uses: actions/checkout@v3
- name: Set up Docker Buildx
Expand Down
3 changes: 3 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ EXPOSE $VCAP_APP_PORT

HEALTHCHECK CMD curl --fail http://localhost:$VCAP_APP_PORT/?healthy || exit 1

RUN apt-get update
RUN apt-get install -y ffmpeg libavcodec-extra

COPY requirements.txt $WORKDIR
RUN pip install --no-cache-dir -r requirements.txt

Expand Down
10 changes: 8 additions & 2 deletions WeCron/remind/models/remind.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from common import wechat_client
from remind.utils import nature_time
from remind.signals import participant_modified
from wechatpy import WeChatException

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -141,15 +142,20 @@ def notify_user_by_id(self, uid):
('重复:' + self.get_repeat_text() + '\n') if self.has_repeat() else '',
'<a href="%s">详情</a>' % self.get_absolute_url(True))
message_params['raw_text'] = raw_text
self.send_template_message_async(message_params, self.desc, name)
self.send_template_message_async(message_params, self.desc, user)

@threads(10, timeout=60)
def send_template_message_async(self, message_params, desc, uname):
def send_template_message_async(self, message_params, desc, user):
uname = user.get_full_name()
if 'raw_text' in message_params:
try:
res = wechat_client.message.send_text(message_params['user_id'], message_params['raw_text'])
logger.info('Successfully send notification(%s) to user %s in text mode', desc, uname)
return res
except WeChatException as e:
if e.errcode == 45015:
user.last_login = now() - timedelta(hours=49)
user.save(update_fields=['last_login'])
except:
logger.exception('Failed to send text notification(%s) to user %s', desc, uname)
try:
Expand Down
57 changes: 49 additions & 8 deletions WeCron/wxhook/message_handler.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#coding: utf-8
# coding: utf-8
from __future__ import unicode_literals, absolute_import
import logging
import json
import os
import re

from datetime import timedelta
Expand All @@ -10,6 +11,7 @@
from wechatpy.replies import TextReply, TransferCustomerServiceReply, ImageReply
from wechatpy.exceptions import WeChatClientException
from shove import Shove
from pydub import AudioSegment

from common import wechat_client
from remind.models import Remind
Expand Down Expand Up @@ -75,7 +77,7 @@ def handle_text(self, reminder=None):
logger.exception('Semantic parse error')
return self.text_reply(
'\U0001F648抱歉,我还只是一个比较初级的定时机器人,理解不了您刚才所说的话:\n\n“%s”\n\n'
'或者您可以换个姿势告诉我该怎么定时,比如这样:\n\n'
'或者您可以换个姿势告诉我该怎么定时,比如这样:\n\n'
'“两个星期后提醒我去复诊”。\n'
'“周五晚上提醒我打电话给老妈”。\n'
'“每月20号提醒我还信用卡[捂脸]”。' % self.message.content
Expand Down Expand Up @@ -133,7 +135,7 @@ def handle_unsubscribe_event(self):

def handle_unknown(self):
return self.text_reply(
'/:jj如需设置提醒,只需用语音或文字告诉我就行了,比如这样:\n\n'
'/:jj如需设置提醒,只需用语音或文字告诉我就行了,比如这样:\n\n'
'“两个星期后提醒我去复诊”。\n'
'“周五晚上提醒我打电话给老妈”。\n'
'“每月20号提醒我还信用卡[捂脸]”。'
Expand All @@ -149,6 +151,9 @@ def handle_unknown_event(self):
def handle_voice(self):
self.message.content = getattr(self.message, 'recognition', '')
if not self.message.content:
self.message.content = speech_to_text(getattr(self.message, 'media_id', ''))
if not self.message.content:
logger.info('No "recognition" field for media_id "%s" and speech_to_text returns nothing', getattr(self.message, 'media_id', 'NOT_EXIST'))
return self.text_reply(
'\U0001F648哎呀,看起来微信的语音转文字功能又双叒叕罬蝃抽风了,请重试一遍,或者直接发文字给我~'
)
Expand All @@ -166,15 +171,15 @@ def handle_click_event(self):
remind_text_list = self.format_remind_list(time_reminds)
if remind_text_list:
return self.text_reply('/:sunHi %s, 你今天的提醒有:\n\n%s' % (self.user.get_full_name(),
'\n'.join(remind_text_list)))
'\n'.join(remind_text_list)))
return self.text_reply('/:coffee今天没有提醒,休息一下吧!')
elif self.message.key.lower() == 'time_remind_tomorrow':
tomorrow = timezone.now()+timedelta(days=1)
tomorrow = timezone.now() + timedelta(days=1)
time_reminds = self.user.get_time_reminds().filter(time__date=tomorrow).order_by('time').all()
remind_text_list = self.format_remind_list(time_reminds, True)
if remind_text_list:
return self.text_reply('/:sunHi %s, 你明天的提醒有:\n\n%s' % (self.user.get_full_name(),
'\n'.join(remind_text_list)))
'\n'.join(remind_text_list)))
return self.text_reply('/:coffee明天还没有提醒,休息一下吧!')
elif self.message.key.lower() == 'customer_service':
logger.info('Transfer to customer service for %s', self.user.get_full_name())
Expand Down Expand Up @@ -211,10 +216,10 @@ def format_remind_list(reminds, next_run_found=False):
emoji = '\U0001F552' # Clock
# takewhile is too aggressive
if rem.time < now:
emoji = '\U00002713 ' # Done
emoji = '\U00002713 ' # Done
elif not next_run_found:
next_run_found = True
emoji = '\U0001F51C' # Soon
emoji = '\U0001F51C' # Soon
remind_text_list.append('%s %s - <a href="%s">%s</a>' %
(emoji, rem.local_time_string('G:i'), rem.get_absolute_url(True), rem.title()))
return remind_text_list
Expand All @@ -234,3 +239,39 @@ def handle_message(msg):
# shove['last_msgid'] = msgid
return resp_msg


def speech_to_text(media_id):
if not media_id:
return None
media_url = wechat_client.media.get_url(media_id).replace('http://', 'https://')
media_resp = wechat_client.get(media_url)
if len(media_resp.content) == 0:
logger.warn('Failed to download media id %s', media_id)
return ''

fname = 'audio.amr'
d = media_resp.headers['content-disposition']
matches = re.findall("filename=(.+)", d)
if matches and len(matches) > 0:
fname = matches[0].strip('"')
fpath = '/tmp/' + fname
with open(fpath, 'wb') as f:
f.write(media_resp.content)

try:
audio = AudioSegment.from_file(fpath)
out_file = audio.export(format='mp3')
mp3_content = out_file.read()
out_file.close()
finally:
os.remove(fpath)

submit_url = 'https://api.weixin.qq.com/cgi-bin/media/voice/addvoicetorecofortext?access_token=%s&format=mp3&voice_id=%s' % (wechat_client.access_token, media_id)
submit_json = wechat_client.post(submit_url, files={fname: mp3_content})
if submit_json.get('errcode') != 0 and submit_json.get('errcode') != '0':
logger.warn('Failed to submit media id %s: %s', media_id, submit_json)
return ''

text_url = 'https://api.weixin.qq.com/cgi-bin/media/voice/queryrecoresultfortext?access_token=%s&voice_id=%s&lang=zh_CN' % (wechat_client.access_token, media_id)
text_json = wechat_client.post(text_url)
return text_json.get('result')
17 changes: 17 additions & 0 deletions WeCron/wxhook/tests/test_message_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,23 @@ def test_voice_with_media_id(self):
self.assertEqual(media_id, r.media_id)
r.delete()

def test_speech_to_text(self):
media_id = 'aZCOJETyQl-PVZhye4aKMb6V89gmsiGX9sCyiWpAwjd9dvEYwXxgXAZY3G29nc2b'
req_text = """
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[FromUser]]></FromUserName>
<CreateTime>1357290913</CreateTime>
<MsgType><![CDATA[voice]]></MsgType>
<MediaId><![CDATA[%s]]></MediaId>
<Format><![CDATA[Format]]></Format>
<MsgId>12345678901234565</MsgId>
</xml>
""" % (media_id)
wechat_msg = self.build_wechat_msg(req_text)
resp_xml = handle_message(wechat_msg)
self.assertIn('重试', resp_xml) # Should have no exception

def test_video(self):
req_text = """
<xml>
Expand Down
5 changes: 3 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ jieba==0.42.1
python_dateutil==2.6.0
shove==0.6.6
coverage
Pillow==4.3.0
Pillow==6.2.2
py-lru-cache==0.1.4
grip
grip==4.6.2
pydub==0.25.1

0 comments on commit 45fed85

Please sign in to comment.