├── .gitignore ├── LICENSE.md ├── README.md ├── android ├── .gitignore ├── .idea │ ├── compiler.xml │ ├── copyright │ │ └── profiles_settings.xml │ ├── gradle.xml │ ├── inspectionProfiles │ │ ├── Project_Default.xml │ │ └── profiles_settings.xml │ ├── misc.xml │ ├── modules.xml │ ├── runConfigurations.xml │ └── vcs.xml ├── app │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── me │ │ │ └── weyo │ │ │ └── magicmirror │ │ │ └── android │ │ │ └── ExampleInstrumentedTest.java │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── assets │ │ │ └── iflytek │ │ │ │ ├── recognize.xml │ │ │ │ ├── voice_bg.9.png │ │ │ │ ├── voice_empty.png │ │ │ │ ├── voice_full.png │ │ │ │ ├── waiting.png │ │ │ │ └── warning.png │ │ ├── java │ │ │ ├── com │ │ │ │ └── iflytek │ │ │ │ │ └── speech │ │ │ │ │ └── util │ │ │ │ │ ├── ApkInstaller.java │ │ │ │ │ ├── FucUtil.java │ │ │ │ │ ├── JsonParser.java │ │ │ │ │ ├── SettingTextWatcher.java │ │ │ │ │ └── XmlParser.java │ │ │ └── me │ │ │ │ └── weyo │ │ │ │ └── magicmirror │ │ │ │ └── android │ │ │ │ ├── AboutActivity.java │ │ │ │ ├── IatSettings.java │ │ │ │ ├── MainActivity.java │ │ │ │ └── service │ │ │ │ └── HttpRequest.java │ │ └── res │ │ │ ├── drawable-xxhdpi │ │ │ ├── logo.png │ │ │ ├── main_bg1.jpg │ │ │ ├── main_bg2.jpg │ │ │ ├── main_bg3.jpg │ │ │ ├── main_bg4.jpg │ │ │ ├── main_bg5.jpg │ │ │ ├── main_bg6.jpg │ │ │ ├── main_bg7.jpg │ │ │ └── main_bg8.jpg │ │ │ ├── drawable-xxxhdpi │ │ │ └── icon.png │ │ │ ├── drawable │ │ │ ├── bg_bmp.xml │ │ │ ├── help.png │ │ │ ├── help_bg.xml │ │ │ ├── help_p.png │ │ │ ├── list_bg_color.xml │ │ │ ├── main_help_btn_np.xml │ │ │ ├── main_saying_btn_np.xml │ │ │ ├── main_setting_btn_np.xml │ │ │ ├── setting.png │ │ │ ├── setting_p.png │ │ │ ├── speaker.png │ │ │ └── speaker_p.png │ │ │ ├── layout │ │ │ ├── activity_about.xml │ │ │ ├── activity_main.xml │ │ │ ├── help_window.xml │ │ │ └── settings_toolbar.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-w820dp │ │ │ └── dimens.xml │ │ │ ├── values │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ │ └── xml │ │ │ └── iat_setting.xml │ │ └── test │ │ └── java │ │ └── me │ │ └── weyo │ │ └── magicmirror │ │ └── android │ │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle ├── bin ├── magicmirror.apk └── server.war ├── pics ├── logo_git_b.png ├── logo_git_bw.png ├── logo_git_o.png ├── screenshot.jpg ├── v0.1.png ├── v0.2.png └── v0.3.png └── server ├── pom.xml └── src └── main ├── java └── me │ └── weyo │ └── magicmirror │ └── server │ ├── Parameters.java │ ├── controller │ ├── AiController.java │ ├── AqiController.java │ ├── BaseController.java │ ├── CalendarController.java │ ├── HttpCommandController.java │ ├── InitController.java │ └── WebSocket.java │ ├── listener │ └── ServerListener.java │ ├── service │ ├── AiService.java │ ├── CommandService.java │ ├── InitService.java │ └── WebSocketService.java │ └── speech │ └── Recorder.java ├── resources └── log4j.properties └── webapp ├── WEB-INF └── web.xml ├── index.html ├── resources ├── css │ ├── ai.css │ ├── main.css │ └── weather-icons.css ├── font │ ├── HelveticaNeue-Light.eot │ ├── HelveticaNeue-Light.svg │ ├── HelveticaNeue-Light.ttf │ ├── HelveticaNeue-Light.woff │ ├── HelveticaNeue-Medium.eot │ ├── HelveticaNeue-Medium.svg │ ├── HelveticaNeue-Medium.ttf │ ├── HelveticaNeue-Medium.woff │ ├── HelveticaNeue-UltraLight.eot │ ├── HelveticaNeue-UltraLight.svg │ ├── HelveticaNeue-UltraLight.ttf │ ├── HelveticaNeue-UltraLight.woff │ ├── weathericons-regular-webfont.eot │ ├── weathericons-regular-webfont.svg │ ├── weathericons-regular-webfont.ttf │ └── weathericons-regular-webfont.woff ├── images │ ├── avatar.png │ ├── favicon.ico │ └── lmm.jpg └── js │ ├── ai │ ├── ai.js │ └── command.js │ ├── aqi │ └── aqi.js │ ├── calendar │ └── calendar.js │ ├── compliments │ └── compliments.js │ ├── config-default.js │ ├── ical_parser.js │ ├── jquery.feedToJSON.js │ ├── jquery.js │ ├── main.js │ ├── moment-with-locales.min.js │ ├── news │ └── news.js │ ├── rrule.js │ ├── time │ └── time.js │ ├── weather │ └── weather.js │ └── websocket.js └── talk.html /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Mobile Tools for Java (J2ME) 9 | .mtj.tmp/ 10 | 11 | # Package Files # 12 | *.jar 13 | #*.war 14 | *.ear 15 | 16 | # Java Project 17 | *.class 18 | .classpath 19 | .project 20 | logs/ 21 | target/ 22 | .settings/ 23 | 24 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 25 | hs_err_pid* 26 | 27 | # Privacy protection 28 | config.js 29 | 30 | # built application files 31 | #*.apk 32 | *.ap_ 33 | 34 | # files for the dex VM 35 | *.dex 36 | 37 | # generated files 38 | gen/ 39 | out/ 40 | build/ 41 | 42 | # Local configuration file (sdk path, etc) 43 | local.properties 44 | 45 | # Windows thumbnail db 46 | .DS_Store 47 | 48 | # IDEA/Android Studio project files, because 49 | # the project can be imported from settings.gradle 50 | *.iml 51 | .idea/* 52 | # Forcibly including certain files we want propagated. 53 | !\.idea/codeStyleSettings.xml 54 | !\.idea/copyright 55 | 56 | # Gradle cache 57 | .gradle 58 | 59 | # Sandbox stuff 60 | _sandbox 61 | 62 | # xf 63 | *.so -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright © 2016 weyo 5 | 6 | Permission is hereby granted, free of charge, to any person 7 | obtaining a copy of this software and associated documentation 8 | files (the “Software”), to deal in the Software without 9 | restriction, including without limitation the rights to use, 10 | copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | **The software is provided “as is”, without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the software.** 19 | 20 | 21 | 22 | The MIT License (MIT) 23 | ===================== 24 | 25 | Copyright © 2016 Michael Teeuw 26 | 27 | Permission is hereby granted, free of charge, to any person 28 | obtaining a copy of this software and associated documentation 29 | files (the “Software”), to deal in the Software without 30 | restriction, including without limitation the rights to use, 31 | copy, modify, merge, publish, distribute, sublicense, and/or sell 32 | copies of the Software, and to permit persons to whom the 33 | Software is furnished to do so, subject to the following 34 | conditions: 35 | 36 | The above copyright notice and this permission notice shall be 37 | included in all copies or substantial portions of the Software. 38 | 39 | **The software is provided “as is”, without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the software.** -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MagicMirror 魔镜 2 | =========== 3 | 4 | ![logo](pics/logo_git_o.png) 5 | 6 | ![v0.2](pics/v0.2.png) 7 | 8 | 9 | ## 说明 10 | 11 | 本项目为私人定制版魔镜。与[原版](https://github.com/MichMich/MagicMirror)的主要区别有: 12 | 13 | - 基于 Java Servlet 重构,全面修改默认程序环境为中文,包括: 14 | - 语言:中文 15 | - 时间:24小时制 16 | - 单位:公制 17 | - RSS:新浪财经新闻 18 | 19 | - 增加人工智能助手功能beta 20 | 21 | - 增加语音识别功能(暂只支持 Android 平台) 22 | 23 | - 增加实时空气质量展示 24 | 25 | - 调整配置文件上传方式 26 | 27 | - 取消版本自动更新功能 28 | 29 | pic3 30 | 31 | ## 使用方法 32 | 33 | 1. 将本项目 clone 到本地,根据需要在 IDE 中导入其中的 `server` 目录; 34 | 35 | 2. 将配置文件 `server/src/main/webapp/resources/js/config-default.js` 的文件名修改为 `config.js`,或者复制该文件,将新文件重命名为 `config.js`; 36 | 37 | 3. 在 `config.js` 中根据个人需要修改以下配置信息: 38 | - 时间格式 39 | - 所在城市 40 | - APPID(在 OpenWeatherMap 网站上注册用户获取免费的 APPID) 41 | - AppKey(在 [PM25.in](http://pm25.in/api_doc) 网站上申请的 AppKey) 42 | - Key(在 [图灵机器人](http://www.tuling123.com) 网站上申请的 Key) 43 | - 欢迎词 44 | - 最大日程数量及个人日程表链接(iPhone 可以在 iCloud 中将某个日历设为公开日历得到) 45 | - 新闻 RSS (可自行搜索可用的新闻 RSS) 46 | - ... 47 | 48 | 4. 根据个人爱好修改 AI 及用户头像; 49 | 50 | 5. 使用 Maven(`mvn clean install`)编译后台程序(本项目 bin 目录中提供有预编译版本 `server.war`,该 war 包移除了 `config.js`,需要按照步骤2、3手动配置),将生成的 war 包放入 Tomcat 的 webapps 目录下,执行 Tomcat 的 bin 目录下的 startup.sh 启动 Tomcat 服务器。 51 | 52 | 6. 启动魔小镜 APP 进行语音输入。 53 | 54 | >完整的软硬件配置说明请参考本人博客说明: 55 | > 56 | >- [魔镜制作——硬件篇](http://weyo.me/pages/techs/magic-mirror-hardware/) 57 | >- [魔镜制作——软件篇](http://weyo.me/pages/techs/magic-mirror-software/) 58 | 59 | pic4 60 | 61 | ## 其他说明 62 | 63 | 1. APP 源码(`android` 目录)中移除了编译所需的讯飞 SDK,如果需要在 Android Studio 中导入项目,需要手动建立 `android/app/libs` 目录(并在其中放入讯飞 SDK 提供的 `Msc.jar` 和 `Sunflower.jar`)和 `android/app/src/main/jniLibs` 目录(并在其中放入讯飞 SDK 提供的 .so 文件),同时需要将 `android/app/src/main/AndroidManifest.xml` 和 `android/app/src/main/res/values/strings.xml` 中的 appkey 修改为在讯飞开放平台申请的 appkey; 64 | 65 | 2. 由于某些众所周知的原因,日历功能暂只支持 iPhone,而且功能不是很稳定; 66 | 67 | 3. 默认关闭空气质量功能(减少对 PM25.in 网站的无效请求),需要开启请向 [PM25.in](http://pm25.in/api_doc) 网站申请 AppKey 并谨慎使用; 68 | 69 | 4. 由于天气数据来自国外的 OpenWeatherMap 网站,部分地区数据可能不够准确,请以 CCTV 天气预报或手机上其他天气 APP 为准(要你何用 - =; 70 | 71 | 5. 语音识别功能暂只支持 Android 手机; 72 | 73 | 6. 除了 APP 提供的实时语音识别功能,另外提供了手工访问接口(访问 http://<服务器IP>:8080/server/talk.html 网页),可以手动输入和 AI 对话。 74 | 75 | ## 鸣谢 76 | 77 | 感谢以下单位为本项目提供的支持(排名不分先后): 78 | 79 | - [OpenWeatherMap](http://openweathermap.org/) 80 | - [PM25.in](http://pm25.in/api_doc) 81 | - [图灵机器人](http://www.tuling123.com) 82 | - [讯飞开放平台](http://www.xfyun.cn/) 83 | 84 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /android/.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /android/.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /android/.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /android/.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | -------------------------------------------------------------------------------- /android/.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /android/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | -------------------------------------------------------------------------------- /android/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /android/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /android/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 24 5 | buildToolsVersion "24.0.3" 6 | defaultConfig { 7 | applicationId "me.weyo.magicmirror.android" 8 | minSdkVersion 14 9 | targetSdkVersion 24 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | compile "com.android.support:appcompat-v7:24.2.1" 25 | compile 'com.android.support:support-v4:24.2.1' 26 | compile 'com.android.support:support-vector-drawable:24.2.1' 27 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 28 | exclude group: 'com.android.support', module: 'support-annotations' 29 | }) 30 | testCompile 'junit:junit:4.12' 31 | } 32 | -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in C:\Users\Yohn\AppData\Local\Android\Sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /android/app/src/androidTest/java/me/weyo/magicmirror/android/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package me.weyo.magicmirror.android; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("me.weyo.magicmirror.android", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 42 | 45 | 46 | 47 | 50 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /android/app/src/main/assets/iflytek/recognize.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/android/app/src/main/assets/iflytek/recognize.xml -------------------------------------------------------------------------------- /android/app/src/main/assets/iflytek/voice_bg.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/android/app/src/main/assets/iflytek/voice_bg.9.png -------------------------------------------------------------------------------- /android/app/src/main/assets/iflytek/voice_empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/android/app/src/main/assets/iflytek/voice_empty.png -------------------------------------------------------------------------------- /android/app/src/main/assets/iflytek/voice_full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/android/app/src/main/assets/iflytek/voice_full.png -------------------------------------------------------------------------------- /android/app/src/main/assets/iflytek/waiting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/android/app/src/main/assets/iflytek/waiting.png -------------------------------------------------------------------------------- /android/app/src/main/assets/iflytek/warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/android/app/src/main/assets/iflytek/warning.png -------------------------------------------------------------------------------- /android/app/src/main/java/com/iflytek/speech/util/ApkInstaller.java: -------------------------------------------------------------------------------- 1 | package com.iflytek.speech.util; 2 | 3 | import android.app.Activity; 4 | import android.app.AlertDialog; 5 | import android.app.AlertDialog.Builder; 6 | import android.content.Context; 7 | import android.content.DialogInterface; 8 | import android.content.DialogInterface.OnClickListener; 9 | import android.content.Intent; 10 | import android.net.Uri; 11 | 12 | import com.iflytek.cloud.SpeechUtility; 13 | 14 | 15 | /** 16 | * 弹出提示框,下载服务组件 17 | */ 18 | public class ApkInstaller { 19 | private Activity mActivity ; 20 | 21 | public ApkInstaller(Activity activity) { 22 | mActivity = activity; 23 | } 24 | 25 | public void install(){ 26 | AlertDialog.Builder builder = new Builder(mActivity); 27 | builder.setMessage("检测到您未安装语记!\n是否前往下载语记?"); 28 | builder.setTitle("下载提示"); 29 | builder.setPositiveButton("确认前往", new OnClickListener() { 30 | @Override 31 | public void onClick(DialogInterface dialog, int which) { 32 | dialog.dismiss(); 33 | String url = SpeechUtility.getUtility().getComponentUrl(); 34 | String assetsApk="SpeechService.apk"; 35 | processInstall(mActivity, url,assetsApk); 36 | } 37 | }); 38 | builder.setNegativeButton("残忍拒绝", new OnClickListener() { 39 | @Override 40 | public void onClick(DialogInterface dialog, int which) { 41 | dialog.dismiss(); 42 | } 43 | }); 44 | builder.create().show(); 45 | return; 46 | } 47 | /** 48 | * 如果服务组件没有安装打开语音服务组件下载页面,进行下载后安装。 49 | */ 50 | private boolean processInstall(Context context ,String url,String assetsApk){ 51 | //直接下载方式 52 | Uri uri = Uri.parse(url); 53 | Intent it = new Intent(Intent.ACTION_VIEW, uri); 54 | context.startActivity(it); 55 | return true; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/iflytek/speech/util/FucUtil.java: -------------------------------------------------------------------------------- 1 | package com.iflytek.speech.util; 2 | import java.io.IOException; 3 | import java.io.InputStream; 4 | import java.util.ArrayList; 5 | 6 | import org.json.JSONArray; 7 | import org.json.JSONObject; 8 | 9 | import com.iflytek.cloud.ErrorCode; 10 | import com.iflytek.cloud.SpeechConstant; 11 | import com.iflytek.cloud.SpeechUtility; 12 | 13 | import android.content.Context; 14 | 15 | /** 16 | * 功能性函数扩展类 17 | */ 18 | public class FucUtil { 19 | /** 20 | * 读取asset目录下文件。 21 | * @return content 22 | */ 23 | public static String readFile(Context mContext,String file,String code) 24 | { 25 | int len = 0; 26 | byte []buf = null; 27 | String result = ""; 28 | try { 29 | InputStream in = mContext.getAssets().open(file); 30 | len = in.available(); 31 | buf = new byte[len]; 32 | in.read(buf, 0, len); 33 | 34 | result = new String(buf,code); 35 | } catch (Exception e) { 36 | e.printStackTrace(); 37 | } 38 | return result; 39 | } 40 | /** 41 | * 将字节缓冲区按照固定大小进行分割成数组 42 | * @param buffer 缓冲区 43 | * @param length 缓冲区大小 44 | * @param spsize 切割块大小 45 | * @return 46 | */ 47 | public ArrayList splitBuffer(byte[] buffer,int length,int spsize) 48 | { 49 | ArrayList array = new ArrayList(); 50 | if(spsize <= 0 || length <= 0 || buffer == null || buffer.length < length) 51 | return array; 52 | int size = 0; 53 | while(size < length) 54 | { 55 | int left = length - size; 56 | if(spsize < left) 57 | { 58 | byte[] sdata = new byte[spsize]; 59 | System.arraycopy(buffer,size,sdata,0,spsize); 60 | array.add(sdata); 61 | size += spsize; 62 | }else 63 | { 64 | byte[] sdata = new byte[left]; 65 | System.arraycopy(buffer,size,sdata,0,left); 66 | array.add(sdata); 67 | size += left; 68 | } 69 | } 70 | return array; 71 | } 72 | /** 73 | * 获取语记是否包含离线听写资源,如未包含跳转至资源下载页面 74 | *1.PLUS_LOCAL_ALL: 本地所有资源 75 | 2.PLUS_LOCAL_ASR: 本地识别资源 76 | 3.PLUS_LOCAL_TTS: 本地合成资源 77 | */ 78 | public static String checkLocalResource(){ 79 | String resource = SpeechUtility.getUtility().getParameter(SpeechConstant.PLUS_LOCAL_ASR); 80 | try { 81 | JSONObject result = new JSONObject(resource); 82 | int ret = result.getInt(SpeechUtility.TAG_RESOURCE_RET); 83 | switch (ret) { 84 | case ErrorCode.SUCCESS: 85 | JSONArray asrArray = result.getJSONObject("result").optJSONArray("asr"); 86 | if (asrArray != null) { 87 | int i = 0; 88 | // 查询否包含离线听写资源 89 | for (; i < asrArray.length(); i++) { 90 | if("iat".equals(asrArray.getJSONObject(i).get(SpeechConstant.DOMAIN))){ 91 | //asrArray中包含语言、方言字段,后续会增加支持方言的本地听写。 92 | //如:"accent": "mandarin","language": "zh_cn" 93 | break; 94 | } 95 | } 96 | if (i >= asrArray.length()) { 97 | 98 | SpeechUtility.getUtility().openEngineSettings(SpeechConstant.ENG_ASR); 99 | return "没有听写资源,跳转至资源下载页面"; 100 | } 101 | }else { 102 | SpeechUtility.getUtility().openEngineSettings(SpeechConstant.ENG_ASR); 103 | return "没有听写资源,跳转至资源下载页面"; 104 | } 105 | break; 106 | case ErrorCode.ERROR_VERSION_LOWER: 107 | return "语记版本过低,请更新后使用本地功能"; 108 | case ErrorCode.ERROR_INVALID_RESULT: 109 | SpeechUtility.getUtility().openEngineSettings(SpeechConstant.ENG_ASR); 110 | return "获取结果出错,跳转至资源下载页面"; 111 | case ErrorCode.ERROR_SYSTEM_PREINSTALL: 112 | //语记为厂商预置版本。 113 | default: 114 | break; 115 | } 116 | } catch (Exception e) { 117 | SpeechUtility.getUtility().openEngineSettings(SpeechConstant.ENG_ASR); 118 | return "获取结果出错,跳转至资源下载页面"; 119 | } 120 | return ""; 121 | } 122 | 123 | /** 124 | * 读取asset目录下音频文件。 125 | * 126 | * @return 二进制文件数据 127 | */ 128 | public static byte[] readAudioFile(Context context, String filename) { 129 | try { 130 | InputStream ins = context.getAssets().open(filename); 131 | byte[] data = new byte[ins.available()]; 132 | 133 | ins.read(data); 134 | ins.close(); 135 | 136 | return data; 137 | } catch (IOException e) { 138 | // TODO Auto-generated catch block 139 | e.printStackTrace(); 140 | } 141 | 142 | return null; 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/iflytek/speech/util/JsonParser.java: -------------------------------------------------------------------------------- 1 | package com.iflytek.speech.util; 2 | 3 | import org.json.JSONArray; 4 | import org.json.JSONObject; 5 | import org.json.JSONTokener; 6 | 7 | import android.util.Log; 8 | 9 | /** 10 | * Json结果解析类 11 | */ 12 | public class JsonParser { 13 | 14 | public static String parseIatResult(String json) { 15 | StringBuffer ret = new StringBuffer(); 16 | try { 17 | JSONTokener tokener = new JSONTokener(json); 18 | JSONObject joResult = new JSONObject(tokener); 19 | 20 | JSONArray words = joResult.getJSONArray("ws"); 21 | for (int i = 0; i < words.length(); i++) { 22 | // 转写结果词,默认使用第一个结果 23 | JSONArray items = words.getJSONObject(i).getJSONArray("cw"); 24 | JSONObject obj = items.getJSONObject(0); 25 | ret.append(obj.getString("w")); 26 | // 如果需要多候选结果,解析数组其他字段 27 | // for(int j = 0; j < items.length(); j++) 28 | // { 29 | // JSONObject obj = items.getJSONObject(j); 30 | // ret.append(obj.getString("w")); 31 | // } 32 | } 33 | } catch (Exception e) { 34 | e.printStackTrace(); 35 | } 36 | return ret.toString(); 37 | } 38 | 39 | public static String parseGrammarResult(String json) { 40 | StringBuffer ret = new StringBuffer(); 41 | try { 42 | JSONTokener tokener = new JSONTokener(json); 43 | JSONObject joResult = new JSONObject(tokener); 44 | 45 | JSONArray words = joResult.getJSONArray("ws"); 46 | for (int i = 0; i < words.length(); i++) { 47 | JSONArray items = words.getJSONObject(i).getJSONArray("cw"); 48 | for(int j = 0; j < items.length(); j++) 49 | { 50 | JSONObject obj = items.getJSONObject(j); 51 | if(obj.getString("w").contains("nomatch")) 52 | { 53 | ret.append("没有匹配结果."); 54 | return ret.toString(); 55 | } 56 | ret.append("【结果】" + obj.getString("w")); 57 | ret.append("【置信度】" + obj.getInt("sc")); 58 | ret.append("\n"); 59 | } 60 | } 61 | } catch (Exception e) { 62 | e.printStackTrace(); 63 | ret.append("没有匹配结果."); 64 | } 65 | return ret.toString(); 66 | } 67 | 68 | public static String parseLocalGrammarResult(String json) { 69 | StringBuffer ret = new StringBuffer(); 70 | try { 71 | JSONTokener tokener = new JSONTokener(json); 72 | JSONObject joResult = new JSONObject(tokener); 73 | 74 | JSONArray words = joResult.getJSONArray("ws"); 75 | for (int i = 0; i < words.length(); i++) { 76 | JSONArray items = words.getJSONObject(i).getJSONArray("cw"); 77 | for(int j = 0; j < items.length(); j++) 78 | { 79 | JSONObject obj = items.getJSONObject(j); 80 | if(obj.getString("w").contains("nomatch")) 81 | { 82 | ret.append("没有匹配结果."); 83 | return ret.toString(); 84 | } 85 | ret.append("【结果】" + obj.getString("w")); 86 | ret.append("\n"); 87 | } 88 | } 89 | ret.append("【置信度】" + joResult.optInt("sc")); 90 | 91 | } catch (Exception e) { 92 | e.printStackTrace(); 93 | ret.append("没有匹配结果."); 94 | } 95 | return ret.toString(); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/iflytek/speech/util/SettingTextWatcher.java: -------------------------------------------------------------------------------- 1 | package com.iflytek.speech.util; 2 | 3 | import java.util.regex.Pattern; 4 | import android.content.Context; 5 | import android.preference.EditTextPreference; 6 | import android.text.Editable; 7 | import android.text.TextUtils; 8 | import android.text.TextWatcher; 9 | import android.widget.Toast; 10 | 11 | /** 12 | * 输入框输入范围控制 13 | */ 14 | public class SettingTextWatcher implements TextWatcher { 15 | private int editStart ; 16 | private int editCount ; 17 | private EditTextPreference mEditTextPreference; 18 | int minValue;//最小值 19 | int maxValue;//最大值 20 | private Context mContext; 21 | 22 | public SettingTextWatcher(Context context,EditTextPreference e,int min, int max) { 23 | mContext = context; 24 | mEditTextPreference = e; 25 | minValue = min; 26 | maxValue = max; 27 | } 28 | 29 | @Override 30 | public void onTextChanged(CharSequence s, int start, int before, int count) { 31 | // Log.e("demo", "onTextChanged start:"+start+" count:"+count+" before:"+before); 32 | editStart = start; 33 | editCount = count; 34 | } 35 | 36 | @Override 37 | public void beforeTextChanged(CharSequence s, int start, int count,int after) { 38 | // Log.e("demo", "beforeTextChanged start:"+start+" count:"+count+" after:"+after); 39 | } 40 | 41 | @Override 42 | public void afterTextChanged(Editable s) { 43 | if (TextUtils.isEmpty(s)) { 44 | return; 45 | } 46 | String content = s.toString(); 47 | // Log.e("demo", "content:"+content); 48 | if (isNumeric(content)) { 49 | int num = Integer.parseInt(content); 50 | if (num > maxValue || num < minValue) { 51 | s.delete(editStart, editStart+editCount); 52 | mEditTextPreference.getEditText().setText(s); 53 | Toast.makeText(mContext, "超出有效值范围", Toast.LENGTH_SHORT).show(); 54 | } 55 | }else { 56 | s.delete(editStart, editStart+editCount); 57 | mEditTextPreference.getEditText().setText(s); 58 | Toast.makeText(mContext, "只能输入数字哦", Toast.LENGTH_SHORT).show(); 59 | } 60 | } 61 | 62 | /** 63 | * 正则表达式-判断是否为数字 64 | */ 65 | public static boolean isNumeric(String str){ 66 | Pattern pattern = Pattern.compile("[0-9]*"); 67 | return pattern.matcher(str).matches(); 68 | } 69 | 70 | }; 71 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/iflytek/speech/util/XmlParser.java: -------------------------------------------------------------------------------- 1 | package com.iflytek.speech.util; 2 | 3 | import java.io.ByteArrayInputStream; 4 | import java.io.InputStream; 5 | 6 | import javax.xml.parsers.DocumentBuilder; 7 | import javax.xml.parsers.DocumentBuilderFactory; 8 | 9 | import org.w3c.dom.Document; 10 | import org.w3c.dom.Element; 11 | /** 12 | * Xml结果解析类 13 | */ 14 | public class XmlParser { 15 | 16 | public static String parseNluResult(String xml) 17 | { 18 | StringBuffer buffer = new StringBuffer(); 19 | try 20 | { 21 | // DOM builder 22 | DocumentBuilder domBuilder = null; 23 | // DOM doc 24 | Document domDoc = null; 25 | 26 | // init DOM 27 | DocumentBuilderFactory domFact = DocumentBuilderFactory.newInstance(); 28 | domBuilder = domFact.newDocumentBuilder(); 29 | InputStream is = new ByteArrayInputStream(xml.getBytes()); 30 | domDoc = domBuilder.parse(is); 31 | 32 | // 获取根节点 33 | Element root = (Element) domDoc.getDocumentElement(); 34 | 35 | Element raw = (Element)root.getElementsByTagName("rawtext").item(0); 36 | buffer.append("【识别结果】" + raw.getFirstChild().getNodeValue()); 37 | buffer.append("\n"); 38 | 39 | Element e = (Element)root.getElementsByTagName("result").item(0); 40 | 41 | Element focus = (Element)e.getElementsByTagName("focus").item(0); 42 | buffer.append("【FOCUS】" + focus.getFirstChild().getNodeValue()); 43 | buffer.append("\n"); 44 | 45 | Element action = (Element)e.getElementsByTagName("action").item(0); 46 | Element operation = (Element)action.getElementsByTagName("operation").item(0); 47 | buffer.append("【ACTION】" + operation.getFirstChild().getNodeValue()); 48 | buffer.append("\n"); 49 | 50 | 51 | }catch(Exception e){ 52 | e.printStackTrace(); 53 | }; 54 | buffer.append("【ALL】" + xml); 55 | return buffer.toString(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /android/app/src/main/java/me/weyo/magicmirror/android/AboutActivity.java: -------------------------------------------------------------------------------- 1 | package me.weyo.magicmirror.android; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | import android.os.Bundle; 5 | import android.view.WindowManager; 6 | 7 | import com.iflytek.sunflower.FlowerCollector; 8 | 9 | public class AboutActivity extends AppCompatActivity { 10 | // 开放统计页面名称 11 | private static String TAG = AboutActivity.class.getSimpleName(); 12 | 13 | @Override 14 | protected void onCreate(Bundle savedInstanceState) { 15 | super.onCreate(savedInstanceState); 16 | //全屏 17 | getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 18 | WindowManager.LayoutParams.FLAG_FULLSCREEN); 19 | 20 | setContentView(R.layout.activity_about); 21 | } 22 | 23 | @Override 24 | protected void onResume() { 25 | // 开放统计 移动数据统计分析 26 | FlowerCollector.onResume(AboutActivity.this); 27 | FlowerCollector.onPageStart(TAG); 28 | super.onResume(); 29 | } 30 | 31 | @Override 32 | protected void onPause() { 33 | // 开放统计 移动数据统计分析 34 | FlowerCollector.onPageEnd(TAG); 35 | FlowerCollector.onPause(AboutActivity.this); 36 | super.onPause(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /android/app/src/main/java/me/weyo/magicmirror/android/IatSettings.java: -------------------------------------------------------------------------------- 1 | package me.weyo.magicmirror.android; 2 | 3 | import android.content.SharedPreferences; 4 | import android.content.SharedPreferences.OnSharedPreferenceChangeListener; 5 | import android.os.Bundle; 6 | import android.preference.DialogPreference; 7 | import android.preference.EditTextPreference; 8 | import android.preference.ListPreference; 9 | import android.preference.Preference; 10 | import android.preference.Preference.OnPreferenceChangeListener; 11 | import android.preference.PreferenceActivity; 12 | import android.support.v7.widget.Toolbar; 13 | import android.view.LayoutInflater; 14 | import android.view.View; 15 | import android.view.ViewGroup; 16 | import android.view.WindowManager; 17 | import android.widget.LinearLayout; 18 | 19 | import com.iflytek.speech.util.SettingTextWatcher; 20 | import com.iflytek.sunflower.FlowerCollector; 21 | 22 | import java.util.HashMap; 23 | 24 | /** 25 | * 听写设置界面 26 | */ 27 | public class IatSettings extends PreferenceActivity implements OnPreferenceChangeListener, OnSharedPreferenceChangeListener { 28 | // 开放统计页面名称 29 | private static String TAG = IatSettings.class.getSimpleName(); 30 | 31 | public static final String PREFER_NAME = "com.iflytek.setting"; 32 | 33 | private Toolbar mActionBar; 34 | 35 | private HashMap prefKeyValue = new HashMap<>(); 36 | 37 | private HashMap keyPreference = new HashMap<>(); 38 | 39 | @SuppressWarnings("deprecation") 40 | public void onCreate(Bundle savedInstanceState) { 41 | super.onCreate(savedInstanceState); 42 | //全屏 43 | getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 44 | WindowManager.LayoutParams.FLAG_FULLSCREEN); 45 | 46 | getPreferenceManager().setSharedPreferencesName(PREFER_NAME); 47 | addPreferencesFromResource(R.xml.iat_setting); 48 | mActionBar.setTitle(getTitle()); 49 | 50 | initPrefMaps(); 51 | } 52 | 53 | /** 54 | * 初始化参数 Map 55 | */ 56 | @SuppressWarnings("deprecation") 57 | private void initPrefMaps() { 58 | String pref_key_url = getResources().getString(R.string.pref_key_url); 59 | String pref_default_url = getResources().getString(R.string.pref_default_url); 60 | String pref_key_vadbos = getResources().getString(R.string.pref_key_vadbos); 61 | String pref_default_vadbos = getResources().getString(R.string.pref_default_vadbos); 62 | String pref_key_vadeos = getResources().getString(R.string.pref_key_vadeos); 63 | String pref_default_vadeos = getResources().getString(R.string.pref_default_vadeos); 64 | String pref_key_lang = getResources().getString(R.string.pref_key_language); 65 | String pref_default_lang = getResources().getString(R.string.pref_default_language); 66 | String pref_key_punc = getResources().getString(R.string.pref_key_punc); 67 | String pref_default_punc = getResources().getString(R.string.pref_default_punc); 68 | 69 | prefKeyValue.put(pref_key_url, pref_default_url); 70 | prefKeyValue.put(pref_key_vadbos, pref_default_vadbos); 71 | prefKeyValue.put(pref_key_vadeos, pref_default_vadeos); 72 | prefKeyValue.put(pref_key_lang, pref_default_lang); 73 | prefKeyValue.put(pref_key_punc, pref_default_punc); 74 | 75 | EditTextPreference mUrlPreference = (EditTextPreference) findPreference(pref_key_url); 76 | 77 | EditTextPreference mVadbosPreference = (EditTextPreference) findPreference(pref_key_vadbos); 78 | mVadbosPreference.getEditText().addTextChangedListener( 79 | new SettingTextWatcher(IatSettings.this, mVadbosPreference,0,10000)); 80 | 81 | EditTextPreference mVadeosPreference = (EditTextPreference) findPreference(pref_key_vadeos); 82 | mVadeosPreference.getEditText().addTextChangedListener( 83 | new SettingTextWatcher(IatSettings.this, mVadeosPreference,0,10000)); 84 | 85 | ListPreference mLangPreference = (ListPreference) findPreference(pref_key_lang); 86 | 87 | ListPreference mPuncPreference = (ListPreference) findPreference(pref_key_punc); 88 | 89 | keyPreference.put(pref_key_url, mUrlPreference); 90 | keyPreference.put(pref_key_vadbos, mVadbosPreference); 91 | keyPreference.put(pref_key_vadeos, mVadeosPreference); 92 | keyPreference.put(pref_key_lang, mLangPreference); 93 | keyPreference.put(pref_key_punc, mPuncPreference); 94 | } 95 | 96 | @Override 97 | public void setContentView(int layoutResID) { 98 | ViewGroup contentView = (ViewGroup) LayoutInflater.from(this).inflate( 99 | R.layout.settings_toolbar, new LinearLayout(this), false); 100 | 101 | mActionBar = (Toolbar) contentView.findViewById(R.id.action_bar); 102 | mActionBar.setNavigationOnClickListener(new View.OnClickListener() { 103 | @Override 104 | public void onClick(View v) { 105 | finish(); 106 | } 107 | }); 108 | 109 | ViewGroup contentWrapper = (ViewGroup) contentView.findViewById(R.id.content_wrapper); 110 | LayoutInflater.from(this).inflate(layoutResID, contentWrapper, true); 111 | 112 | getWindow().setContentView(contentView); 113 | } 114 | 115 | @Override 116 | public boolean onPreferenceChange(Preference preference, Object newValue) { 117 | return true; 118 | } 119 | 120 | @SuppressWarnings("deprecation") 121 | @Override 122 | protected void onResume() { 123 | super.onResume(); 124 | // 开放统计 移动数据统计分析 125 | FlowerCollector.onResume(this); 126 | FlowerCollector.onPageStart(TAG); 127 | 128 | initSummary(); 129 | getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(this); 130 | } 131 | 132 | @SuppressWarnings("deprecation") 133 | @Override 134 | protected void onPause() { 135 | super.onPause(); 136 | // 开放统计 移动数据统计分析 137 | FlowerCollector.onPageEnd(TAG); 138 | FlowerCollector.onPause(this); 139 | 140 | getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this); 141 | } 142 | 143 | @Override 144 | public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { 145 | registSummary(key); 146 | } 147 | 148 | /** 149 | * 初始化 Summary 150 | */ 151 | private void initSummary() { 152 | for (String key : prefKeyValue.keySet()) { 153 | registSummary(key); 154 | } 155 | } 156 | 157 | /** 158 | * 注册指定参数的 Summary 159 | * @param key 参数名称 160 | */ 161 | private void registSummary(String key) { 162 | DialogPreference preference = keyPreference.get(key); 163 | 164 | String value = null; 165 | if(preference instanceof EditTextPreference) { 166 | value = ((EditTextPreference) preference).getText(); 167 | } else { 168 | CharSequence entry = ((ListPreference) preference).getEntry(); 169 | if(entry != null) { 170 | value = entry.toString(); 171 | } 172 | } 173 | if (value == null || value.equals("")) { 174 | preference.setSummary(prefKeyValue.get(key)); 175 | } else { 176 | preference.setSummary(value); 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /android/app/src/main/java/me/weyo/magicmirror/android/MainActivity.java: -------------------------------------------------------------------------------- 1 | package me.weyo.magicmirror.android; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.content.SharedPreferences; 6 | import android.graphics.Color; 7 | import android.graphics.drawable.ColorDrawable; 8 | import android.os.Bundle; 9 | import android.support.v7.app.AppCompatActivity; 10 | import android.util.Log; 11 | import android.view.Gravity; 12 | import android.view.LayoutInflater; 13 | import android.view.View; 14 | import android.view.View.OnClickListener; 15 | import android.view.ViewGroup; 16 | import android.view.WindowManager; 17 | import android.widget.PopupWindow; 18 | import android.widget.RelativeLayout; 19 | import android.widget.Toast; 20 | 21 | import com.iflytek.cloud.ErrorCode; 22 | import com.iflytek.cloud.InitListener; 23 | import com.iflytek.cloud.RecognizerResult; 24 | import com.iflytek.cloud.SpeechConstant; 25 | import com.iflytek.cloud.SpeechError; 26 | import com.iflytek.cloud.SpeechRecognizer; 27 | import com.iflytek.cloud.SpeechUtility; 28 | import com.iflytek.cloud.ui.RecognizerDialog; 29 | import com.iflytek.cloud.ui.RecognizerDialogListener; 30 | import com.iflytek.speech.util.JsonParser; 31 | import com.iflytek.sunflower.FlowerCollector; 32 | 33 | import org.json.JSONException; 34 | import org.json.JSONObject; 35 | 36 | import java.io.IOException; 37 | import java.net.URLEncoder; 38 | import java.util.HashMap; 39 | import java.util.LinkedHashMap; 40 | import java.util.concurrent.ExecutorService; 41 | import java.util.concurrent.Executors; 42 | 43 | import me.weyo.magicmirror.android.service.HttpRequest; 44 | 45 | public class MainActivity extends AppCompatActivity implements OnClickListener { 46 | // 开放统计页面名称 47 | private static String TAG = MainActivity.class.getSimpleName(); 48 | // 语音听写对象 49 | private SpeechRecognizer mIat; 50 | // 语音听写UI 51 | private RecognizerDialog mIatDialog; 52 | // 用HashMap存储听写结果 53 | private HashMap mIatResults = new LinkedHashMap<>(); 54 | 55 | private Toast mToast; 56 | private SharedPreferences mSharedPreferences; 57 | 58 | // 魔镜后台服务器地址 59 | private String prefUrl; 60 | 61 | @Override 62 | protected void onCreate(Bundle savedInstanceState) { 63 | super.onCreate(savedInstanceState); 64 | //全屏 65 | getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 66 | WindowManager.LayoutParams.FLAG_FULLSCREEN); 67 | setContentView(R.layout.activity_main); 68 | 69 | findViewById(R.id.setting).setOnClickListener(this); 70 | findViewById(R.id.saying).setOnClickListener(this); 71 | findViewById(R.id.help).setOnClickListener(this); 72 | 73 | // 初始化语音输入控件 74 | SpeechUtility.createUtility(getApplicationContext(), 75 | SpeechConstant.APPID +"=" + getResources().getString(R.string.app_id)); 76 | 77 | // 初始化识别无UI识别对象 78 | // 使用SpeechRecognizer对象,可根据回调消息自定义界面; 79 | mIat = SpeechRecognizer.createRecognizer(MainActivity.this, mInitListener); 80 | 81 | // 初始化听写Dialog,如果只使用有UI听写功能,无需创建SpeechRecognizer 82 | // 使用UI听写功能,请根据sdk文件目录下的notice.txt,放置布局文件和图片资源 83 | mIatDialog = new RecognizerDialog(MainActivity.this, mInitListener); 84 | 85 | mSharedPreferences = getSharedPreferences(IatSettings.PREFER_NAME, 86 | Activity.MODE_PRIVATE); 87 | } 88 | 89 | @Override 90 | protected void onStart() { 91 | super.onStart(); 92 | 93 | // 随机设置背景图片 94 | int bgId = (int)(Math.random() * 8) + 1; 95 | String bgStr = "main_bg" + bgId; 96 | this.getWindow().setBackgroundDrawableResource( 97 | getResources().getIdentifier(bgStr, "drawable", getPackageName())); 98 | } 99 | 100 | @Override 101 | public void onClick(View view) { 102 | switch (view.getId()) { 103 | // 进入参数设置页面 104 | case R.id.setting: 105 | Intent intent = new Intent(MainActivity.this, IatSettings.class); 106 | startActivity(intent); 107 | break; 108 | // 开始听写 109 | // 如何判断一次听写结束:OnResult isLast=true 或者 onError 110 | case R.id.saying: 111 | // 移动数据分析,收集开始听写事件 112 | FlowerCollector.onEvent(MainActivity.this, "iat_recognize"); 113 | // 清理上次听写结果 114 | mIatResults.clear(); 115 | // 设置参数 116 | setParam(); 117 | // 显示听写对话框 118 | mIatDialog.setListener(mRecognizerDialogListener); 119 | mIatDialog.show(); 120 | showTip(getString(R.string.text_begin)); 121 | break; 122 | // 帮助 123 | case R.id.help: 124 | //showTip("点击话筒图标开始说话"); 125 | //showPopupWindow(); 126 | showHelpDialog(); 127 | break; 128 | default: 129 | break; 130 | } 131 | } 132 | 133 | /** 134 | * 初始化监听器。 135 | */ 136 | private InitListener mInitListener = new InitListener() { 137 | 138 | @Override 139 | public void onInit(int code) { 140 | Log.d(TAG, "SpeechRecognizer init() code = " + code); 141 | if (code != ErrorCode.SUCCESS) { 142 | showTip("初始化失败,错误码:" + code); 143 | } 144 | } 145 | }; 146 | 147 | /** 148 | * 听写UI监听器 149 | */ 150 | private RecognizerDialogListener mRecognizerDialogListener = new RecognizerDialogListener() { 151 | public void onResult(RecognizerResult results, boolean isLast) { 152 | // 必须在检查 isLast 前调用 printResult,确保所有听写结果记入 mIatResults 153 | String result = printResult(results); 154 | if (isLast) { 155 | sendMessage(result); 156 | } 157 | } 158 | 159 | /** 160 | * 识别回调错误. 161 | */ 162 | public void onError(SpeechError error) { 163 | showTip(error.getPlainDescription(true)); 164 | } 165 | }; 166 | 167 | /** 168 | * 显示语音输入结果 169 | * @param results 语音识别对象 170 | * @return 语音输入结果 171 | */ 172 | private String printResult(RecognizerResult results) { 173 | String result; 174 | String text = JsonParser.parseIatResult(results.getResultString()); 175 | 176 | String sn = null; 177 | // 读取json结果中的sn字段 178 | try { 179 | JSONObject resultJson = new JSONObject(results.getResultString()); 180 | sn = resultJson.optString("sn"); 181 | } catch (JSONException e) { 182 | Log.e(TAG, "语音消息JSON解析异常", e); 183 | } 184 | 185 | mIatResults.put(sn, text); 186 | 187 | StringBuilder resultBuffer = new StringBuilder(); 188 | for (String key : mIatResults.keySet()) { 189 | resultBuffer.append(mIatResults.get(key)); 190 | } 191 | result = resultBuffer.toString(); 192 | showTip(result); 193 | 194 | return result; 195 | } 196 | 197 | /** 198 | * 参数设置 199 | */ 200 | public void setParam() { 201 | // 清空参数 202 | mIat.setParameter(SpeechConstant.PARAMS, null); 203 | 204 | // 设置听写引擎 205 | String mEngineType = SpeechConstant.TYPE_CLOUD; 206 | mIat.setParameter(SpeechConstant.ENGINE_TYPE, mEngineType); 207 | // 设置返回结果格式 208 | mIat.setParameter(SpeechConstant.RESULT_TYPE, "json"); 209 | 210 | // 设置魔镜后台服务器地址 211 | prefUrl = mSharedPreferences.getString( 212 | getResources().getString(R.string.pref_key_url), ""); 213 | 214 | // 设置语言 215 | String lag = mSharedPreferences.getString( 216 | getResources().getString(R.string.pref_key_language), "mandarin"); 217 | if (lag.equals("en_us")) { 218 | mIat.setParameter(SpeechConstant.LANGUAGE, "en_us"); 219 | } else { 220 | mIat.setParameter(SpeechConstant.LANGUAGE, "zh_cn"); 221 | // 中文需要设置语言区域 222 | mIat.setParameter(SpeechConstant.ACCENT, lag); 223 | } 224 | 225 | // 设置语音前端点:静音超时时间,即用户多长时间不说话则当做超时处理 226 | mIat.setParameter(SpeechConstant.VAD_BOS, mSharedPreferences.getString( 227 | getResources().getString(R.string.pref_key_vadbos), "3000")); 228 | 229 | // 设置语音后端点:后端点静音检测时间,即用户停止说话多长时间内即认为不再输入, 自动停止录音 230 | mIat.setParameter(SpeechConstant.VAD_EOS, mSharedPreferences.getString( 231 | getResources().getString(R.string.pref_key_vadeos), "2000")); 232 | 233 | // 设置标点符号,设置为"0"返回结果无标点,设置为"1"返回结果有标点 234 | mIat.setParameter(SpeechConstant.ASR_PTT, mSharedPreferences.getString( 235 | getResources().getString(R.string.pref_key_punc), "1")); 236 | } 237 | 238 | /** 239 | * 向魔镜后台发送消息 240 | * @param msg 待发送的消息 241 | */ 242 | private void sendMessage(final String msg) { 243 | final String url = "http://" + prefUrl + "/server/command"; 244 | 245 | // 为网络访问操作建立子线程 246 | ExecutorService exec = Executors.newSingleThreadExecutor(); 247 | exec.submit(new Runnable() { 248 | @Override 249 | public void run() { 250 | try { 251 | String encodedMsg = URLEncoder.encode(msg,"UTF-8"); 252 | HttpRequest.sendGet(url, "message=" + encodedMsg); 253 | } catch (IOException e) { 254 | Log.d(TAG, "向魔镜发送语音消息出现异常," + 255 | "请检查魔镜服务器地址配置是否正确、魔镜程序是否正确开启!"); 256 | Log.e(TAG, "魔镜服务器连接异常", e); 257 | } 258 | } 259 | }); 260 | exec.shutdown(); 261 | } 262 | 263 | /** 264 | * 使用 PopupWindow 弹出帮助提示 265 | */ 266 | @SuppressWarnings("unused") 267 | private void showPopupWindow() { 268 | View popupView = LayoutInflater.from(this) 269 | .inflate(R.layout.help_window, new RelativeLayout(this), true); 270 | PopupWindow window = new PopupWindow(popupView, 271 | ViewGroup.LayoutParams.WRAP_CONTENT, 272 | ViewGroup.LayoutParams.WRAP_CONTENT, true); 273 | window.setBackgroundDrawable(new ColorDrawable(Color.parseColor("#00000000"))); 274 | window.setTouchable(true); 275 | window.setFocusable(true); 276 | window.setOutsideTouchable(true); 277 | window.update(); 278 | window.showAtLocation(findViewById(R.id.activity_main), Gravity.CENTER, 0, 380); 279 | } 280 | 281 | /** 282 | * 显示帮助对话框 283 | */ 284 | private void showHelpDialog() { 285 | android.support.v7.app.AlertDialog.Builder builder = 286 | new android.support.v7.app.AlertDialog.Builder(this); 287 | builder.setIcon(R.mipmap.ic_launcher); 288 | builder.setTitle("帮助"); 289 | builder.setMessage("这是魔镜的语音输入小助手,您可以点击下方的话筒图标开始说话。"); 290 | builder.setPositiveButton("确定", null); 291 | builder.show(); 292 | } 293 | 294 | /** 295 | * 显示 Toast 296 | * @param str 待显示的字符串信息 297 | */ 298 | private void showTip(String str) { 299 | // 防止弹出空提示 300 | if (str == null || str.equals("")) { 301 | return; 302 | } 303 | 304 | if(mToast == null) { 305 | mToast = Toast.makeText(this, "", Toast.LENGTH_LONG); 306 | mToast.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM, 0, 380); 307 | } 308 | mToast.setText(str); 309 | mToast.show(); 310 | } 311 | 312 | @Override 313 | protected void onDestroy() { 314 | super.onDestroy(); 315 | // 退出时释放连接 316 | mIat.cancel(); 317 | mIat.destroy(); 318 | } 319 | 320 | @Override 321 | protected void onResume() { 322 | // 开放统计 移动数据统计分析 323 | FlowerCollector.onResume(MainActivity.this); 324 | FlowerCollector.onPageStart(TAG); 325 | super.onResume(); 326 | } 327 | 328 | @Override 329 | protected void onPause() { 330 | // 开放统计 移动数据统计分析 331 | FlowerCollector.onPageEnd(TAG); 332 | FlowerCollector.onPause(MainActivity.this); 333 | super.onPause(); 334 | } 335 | } 336 | -------------------------------------------------------------------------------- /android/app/src/main/java/me/weyo/magicmirror/android/service/HttpRequest.java: -------------------------------------------------------------------------------- 1 | package me.weyo.magicmirror.android.service; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStreamReader; 6 | import java.net.URL; 7 | import java.net.URLConnection; 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | public class HttpRequest { 12 | 13 | /** 14 | * 向指定URL发送GET方法的请求 15 | * 16 | * @param url 17 | * 发送请求的URL 18 | * @param param 19 | * 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 20 | * @return URL 所代表远程资源的响应结果 21 | */ 22 | public static String sendGet(String url, String param) throws IOException { 23 | StringBuilder result = new StringBuilder(); 24 | BufferedReader in = null; 25 | try { 26 | String urlNameString = url + "?" + param; 27 | URL realUrl = new URL(urlNameString); 28 | // 打开和URL之间的连接 29 | URLConnection connection = realUrl.openConnection(); 30 | // 设置通用的请求属性 31 | connection.setRequestProperty("accept", "*/*"); 32 | connection.setRequestProperty("connection", "Keep-Alive"); 33 | connection.setRequestProperty("user-agent", 34 | "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); 35 | // 建立实际的连接 36 | connection.connect(); 37 | // 获取所有响应头字段 38 | Map> map = connection.getHeaderFields(); 39 | 40 | // 定义 BufferedReader输入流来读取URL的响应 41 | in = new BufferedReader(new InputStreamReader( 42 | connection.getInputStream())); 43 | String line; 44 | while ((line = in.readLine()) != null) { 45 | result.append(line); 46 | } 47 | } 48 | // 使用finally块来关闭输入流 49 | finally { 50 | try { 51 | if (in != null) { 52 | in.close(); 53 | } 54 | } catch (Exception e2) { 55 | e2.printStackTrace(); 56 | } 57 | } 58 | return result.toString(); 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/android/app/src/main/res/drawable-xxhdpi/logo.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/main_bg1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/android/app/src/main/res/drawable-xxhdpi/main_bg1.jpg -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/main_bg2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/android/app/src/main/res/drawable-xxhdpi/main_bg2.jpg -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/main_bg3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/android/app/src/main/res/drawable-xxhdpi/main_bg3.jpg -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/main_bg4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/android/app/src/main/res/drawable-xxhdpi/main_bg4.jpg -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/main_bg5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/android/app/src/main/res/drawable-xxhdpi/main_bg5.jpg -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/main_bg6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/android/app/src/main/res/drawable-xxhdpi/main_bg6.jpg -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/main_bg7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/android/app/src/main/res/drawable-xxhdpi/main_bg7.jpg -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/main_bg8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/android/app/src/main/res/drawable-xxhdpi/main_bg8.jpg -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/android/app/src/main/res/drawable-xxxhdpi/icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/bg_bmp.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/android/app/src/main/res/drawable/help.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/help_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/help_p.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/android/app/src/main/res/drawable/help_p.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/list_bg_color.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/main_help_btn_np.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/main_saying_btn_np.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/main_setting_btn_np.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/android/app/src/main/res/drawable/setting.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/setting_p.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/android/app/src/main/res/drawable/setting_p.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/speaker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/android/app/src/main/res/drawable/speaker.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/speaker_p.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/android/app/src/main/res/drawable/speaker_p.png -------------------------------------------------------------------------------- /android/app/src/main/res/layout/activity_about.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 22 | 23 | 24 | 31 | 32 | 36 | 37 | 43 | 44 | 52 | 53 | 54 | 62 | 63 | 70 | 77 | 85 | 94 | 95 | 100 | 101 | 102 | 111 | 112 | 119 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /android/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 19 | 20 | 21 | 30 | 31 | 35 | 36 | 46 | 52 | 59 | 60 | 65 | 72 | 73 | 78 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /android/app/src/main/res/layout/help_window.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /android/app/src/main/res/layout/settings_toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 10 | 11 | 16 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | 8 | #000000 9 | 10 | #161823 11 | #FFFFFF 12 | 13 | #2fb3cb 14 | #0f8ec5 15 | #737373 16 | 17 | #70000000 18 | 19 | #00000000 20 | #DCDCDC 21 | #000000 22 | 23 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 魔小镜 3 | Start 4 | 5 | 12345678 6 | 帮助 7 | logo 8 | speaker 9 | 点击话筒开始说话 10 | 设置 11 | 关于 12 | 魔小镜 v1.0 13 | 说明 14 | 项目地址 15 | 魔小镜是“魔镜”的配套语音输入小助手。\n使用前请配置好魔镜服务器地址。 16 | https://github.com/weyo/MagicMirror 17 | 【设置选项说明】\n 18 | 服务器地址:魔镜服务器后台地址(默认为 Demo 服务器地址);\n 19 | 语言:语音输入语言;\n 20 | 标点符号:语音识别结果是否带有标点符号;\n 21 | 语音输入前端点超时时间:静音时间,即开始录音之后用户多长时间不说话则当做超时处理;\n 22 | 语音输入后端点超时时间:后端点静音检测时间,即用户停止说话多长时间内即认为不再输入, 自动停止录音。 23 | 24 | Powered by \@weyo 25 | 本应用语音技术由科大讯飞提供 26 | 27 | 请开始说话… 28 | 开始音频流识别 29 | url_preference 30 | 设置魔镜后台服务器地址,例如:192.168.1.123:8080 31 | iat_vadbos_preference 32 | 请输入时间(0–10000)ms,建议值:3000 33 | iat_vadeos_preference 34 | 请输入时间(0–10000)ms,建议值:2000 35 | iat_lang_preference 36 | 语音识别语言,默认为普通话 37 | iat_punc_preference 38 | 语音识别结果是否带有标点符号,默认为有标点 39 | 40 | 41 | 42 | 有标点 43 | 无标点 44 | 45 | 46 | 1 47 | 0 48 | 49 | 50 | 51 | 普通话 52 | 粤语 53 | 英语 54 | 55 | 56 | mandarin 57 | cantonese 58 | en_us 59 | 60 | 61 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 14 | 15 | 19 | 20 | -------------------------------------------------------------------------------- /android/app/src/main/res/xml/iat_setting.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 14 | 15 | 20 | 21 | 24 | 25 | 28 | 29 | 32 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /android/app/src/test/java/me/weyo/magicmirror/android/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package me.weyo.magicmirror.android; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.2.2' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | } 20 | 21 | task clean(type: Delete) { 22 | delete rootProject.buildDir 23 | } 24 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Dec 28 10:00:20 PST 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip 7 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /bin/magicmirror.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/bin/magicmirror.apk -------------------------------------------------------------------------------- /bin/server.war: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/bin/server.war -------------------------------------------------------------------------------- /pics/logo_git_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/pics/logo_git_b.png -------------------------------------------------------------------------------- /pics/logo_git_bw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/pics/logo_git_bw.png -------------------------------------------------------------------------------- /pics/logo_git_o.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/pics/logo_git_o.png -------------------------------------------------------------------------------- /pics/screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/pics/screenshot.jpg -------------------------------------------------------------------------------- /pics/v0.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/pics/v0.1.png -------------------------------------------------------------------------------- /pics/v0.2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/pics/v0.2.png -------------------------------------------------------------------------------- /pics/v0.3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/pics/v0.3.png -------------------------------------------------------------------------------- /server/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | me.weyo.magicmirror 5 | server 6 | war 7 | 1.0 8 | server Maven Webapp 9 | http://maven.apache.org 10 | 11 | UTF-8 12 | 13 | 14 | 15 | 16 | snapshots-repo 17 | https://oss.sonatype.org/content/repositories/snapshots 18 | 19 | false 20 | 21 | 22 | true 23 | 24 | 25 | 26 | 27 | 28 | 29 | junit 30 | junit 31 | 4.11 32 | test 33 | 34 | 35 | org.slf4j 36 | slf4j-log4j12 37 | 1.7.12 38 | 39 | 40 | com.hp.hpl.jena 41 | json-jena 42 | 1.0 43 | 44 | 45 | javax.servlet 46 | servlet-api 47 | 2.5 48 | provided 49 | 50 | 51 | javax.websocket 52 | javax.websocket-api 53 | 1.0 54 | provided 55 | 56 | 57 | 58 | server 59 | 60 | 61 | 62 | org.apache.maven.plugins 63 | maven-compiler-plugin 64 | 3.1 65 | 66 | 1.7 67 | 1.7 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /server/src/main/java/me/weyo/magicmirror/server/Parameters.java: -------------------------------------------------------------------------------- 1 | package me.weyo.magicmirror.server; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * 参数配置 8 | * 9 | * @author WeYo 10 | */ 11 | public class Parameters { 12 | 13 | private static Map weatherParams = new HashMap(); 14 | private static Map aqiParams = new HashMap(); 15 | private static Map aiParams = new HashMap(); 16 | 17 | public static Map getWeatherParams() { 18 | return weatherParams; 19 | } 20 | 21 | public static Map getAqiParams() { 22 | return aqiParams; 23 | } 24 | 25 | public static Map getAiParams() { 26 | return aiParams; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /server/src/main/java/me/weyo/magicmirror/server/controller/AiController.java: -------------------------------------------------------------------------------- 1 | package me.weyo.magicmirror.server.controller; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.InputStreamReader; 5 | import java.net.URL; 6 | import java.net.URLConnection; 7 | import java.util.Map; 8 | import java.util.concurrent.Callable; 9 | 10 | import org.json.JSONObject; 11 | 12 | import me.weyo.magicmirror.server.Parameters; 13 | 14 | /** 15 | * @author WeYo 16 | */ 17 | public class AiController implements Callable { 18 | 19 | private String message; 20 | 21 | private Map aiParams = Parameters.getAiParams(); 22 | 23 | public AiController(String message) { 24 | this.message = message; 25 | } 26 | 27 | @Override 28 | public String call() throws Exception { 29 | String rurl = "http://www.tuling123.com/openapi/api?key=" 30 | + aiParams.get("key") 31 | + "&info=" + message 32 | + "&loc=" + aiParams.get("loc") 33 | + "&userid=" + aiParams.get("id"); 34 | 35 | URL url = new URL(rurl); 36 | URLConnection conn = url.openConnection(); 37 | BufferedReader br = new BufferedReader( 38 | new InputStreamReader(conn.getInputStream())); 39 | 40 | String inputLine; 41 | StringBuilder sb = new StringBuilder(); 42 | while ((inputLine = br.readLine()) != null) { 43 | sb.append(inputLine + "\n"); 44 | } 45 | br.close(); 46 | 47 | JSONObject jsonObj = new JSONObject(sb.toString()); 48 | 49 | String ret = null; 50 | switch (jsonObj.getInt("code")) { 51 | case 100000: 52 | ret = "0|" + jsonObj.getString("text"); 53 | break; 54 | case 200000: 55 | ret = "2|" + jsonObj.getString("text") + "|" + jsonObj.getString("url"); 56 | break; 57 | default: 58 | ret = "0|对不起,亲,我现在还听不懂这句话"; 59 | break; 60 | } 61 | 62 | return ret; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /server/src/main/java/me/weyo/magicmirror/server/controller/AqiController.java: -------------------------------------------------------------------------------- 1 | package me.weyo.magicmirror.server.controller; 2 | 3 | import javax.servlet.ServletException; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | /** 8 | * @author WeYo 9 | */ 10 | public class AqiController extends BaseController { 11 | 12 | /** Serial ID */ 13 | private static final long serialVersionUID = -2640350579706749958L; 14 | private static final Logger LOG = LoggerFactory.getLogger(AqiController.class); 15 | 16 | public void init() throws ServletException { 17 | this.contentType = "application/json"; 18 | } 19 | 20 | @Override 21 | public void doLog(String msg, Object object) { 22 | LOG.debug("AQI handled: " + msg); 23 | } 24 | } -------------------------------------------------------------------------------- /server/src/main/java/me/weyo/magicmirror/server/controller/BaseController.java: -------------------------------------------------------------------------------- 1 | package me.weyo.magicmirror.server.controller; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStreamReader; 6 | import java.io.PrintWriter; 7 | import java.net.URL; 8 | import java.net.URLConnection; 9 | 10 | import javax.servlet.ServletException; 11 | import javax.servlet.http.HttpServlet; 12 | import javax.servlet.http.HttpServletRequest; 13 | import javax.servlet.http.HttpServletResponse; 14 | 15 | /** 16 | * @author WeYo 17 | */ 18 | public abstract class BaseController extends HttpServlet { 19 | 20 | /** Serial ID */ 21 | private static final long serialVersionUID = 4773003481384602251L; 22 | 23 | public String contentType = "text/html"; 24 | 25 | public final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 26 | response.setContentType(this.contentType + ";charset=UTF-8"); 27 | PrintWriter out = response.getWriter(); 28 | URL url = new URL(request.getParameter("url")); 29 | URLConnection conn = url.openConnection(); 30 | BufferedReader br = new BufferedReader( 31 | new InputStreamReader(conn.getInputStream())); 32 | 33 | String inputLine; 34 | StringBuilder sb = new StringBuilder(); 35 | while ((inputLine = br.readLine()) != null) { 36 | out.write(inputLine + "\n"); 37 | sb.append(inputLine + "\n"); 38 | } 39 | 40 | out.flush(); 41 | out.close(); 42 | br.close(); 43 | 44 | doLog(sb.toString(), null); 45 | } 46 | 47 | public abstract void doLog(String msg, Object object); 48 | 49 | } -------------------------------------------------------------------------------- /server/src/main/java/me/weyo/magicmirror/server/controller/CalendarController.java: -------------------------------------------------------------------------------- 1 | package me.weyo.magicmirror.server.controller; 2 | 3 | import javax.servlet.ServletException; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | /** 9 | * @author WeYo 10 | */ 11 | public class CalendarController extends BaseController { 12 | 13 | /** Serial ID */ 14 | private static final long serialVersionUID = 6683891537044452693L; 15 | private static final Logger LOG = LoggerFactory.getLogger(CalendarController.class); 16 | 17 | public void init() throws ServletException { 18 | this.contentType = "text/calendar"; 19 | } 20 | 21 | @Override 22 | public void doLog(String msg, Object object) { 23 | LOG.debug("Calendar handled: " + msg); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /server/src/main/java/me/weyo/magicmirror/server/controller/HttpCommandController.java: -------------------------------------------------------------------------------- 1 | package me.weyo.magicmirror.server.controller; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.ServletException; 6 | import javax.servlet.http.HttpServlet; 7 | import javax.servlet.http.HttpServletRequest; 8 | import javax.servlet.http.HttpServletResponse; 9 | 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import me.weyo.magicmirror.server.service.CommandService; 14 | 15 | /** 16 | * 处理通过 Web 端访问的 AI 命令请求 17 | * 18 | * @author WeYo 19 | */ 20 | public class HttpCommandController extends HttpServlet { 21 | 22 | /** Serial ID */ 23 | private static final long serialVersionUID = 7116249330326069458L; 24 | private static final Logger LOG = LoggerFactory.getLogger(HttpCommandController.class); 25 | private CommandService cmdService; 26 | 27 | public void init() throws ServletException { 28 | this.cmdService = CommandService.INSTANCE; 29 | } 30 | 31 | public final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 32 | String message = request.getParameter("message"); 33 | String command = new String(message.getBytes("ISO-8859-1"), "utf-8"); 34 | LOG.debug("Web 端收到命令: " + command); 35 | 36 | cmdService.execute(command); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /server/src/main/java/me/weyo/magicmirror/server/controller/InitController.java: -------------------------------------------------------------------------------- 1 | package me.weyo.magicmirror.server.controller; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.ServletException; 6 | import javax.servlet.http.HttpServlet; 7 | import javax.servlet.http.HttpServletRequest; 8 | import javax.servlet.http.HttpServletResponse; 9 | 10 | import org.json.JSONException; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | import me.weyo.magicmirror.server.service.InitService; 15 | 16 | /** 17 | * @author WeYo 18 | */ 19 | public class InitController extends HttpServlet { 20 | 21 | /** Serial ID */ 22 | private static final long serialVersionUID = 3827530190760653764L; 23 | 24 | private static final Logger LOG = LoggerFactory.getLogger(InitController.class); 25 | 26 | private static InitService initService = new InitService(); 27 | 28 | public void init() throws ServletException { 29 | LOG.info("魔镜初始化完成。"); 30 | } 31 | 32 | public final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 33 | // config.js 中参数传入 34 | String ai = request.getParameter("ai"); 35 | String decodedAi = new String(ai.getBytes("ISO-8859-1"), "utf-8"); 36 | 37 | try { 38 | initService.initAi(decodedAi); 39 | } catch (JSONException e) { 40 | LOG.error("InitService JSON 解析异常|ai:" + decodedAi, e); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /server/src/main/java/me/weyo/magicmirror/server/controller/WebSocket.java: -------------------------------------------------------------------------------- 1 | package me.weyo.magicmirror.server.controller; 2 | 3 | import java.io.IOException; 4 | import java.util.concurrent.CopyOnWriteArraySet; 5 | import java.util.concurrent.atomic.AtomicInteger; 6 | 7 | import javax.websocket.OnClose; 8 | import javax.websocket.OnError; 9 | import javax.websocket.OnMessage; 10 | import javax.websocket.OnOpen; 11 | import javax.websocket.Session; 12 | import javax.websocket.server.ServerEndpoint; 13 | 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | 17 | @ServerEndpoint("/websocket") 18 | public class WebSocket { 19 | /** 在线客户端计数 */ 20 | private static AtomicInteger onlineCount = new AtomicInteger(0); 21 | private static CopyOnWriteArraySet webSocketSet = new CopyOnWriteArraySet(); 22 | private static final Logger LOG = LoggerFactory.getLogger(WebSocket.class); 23 | 24 | // 与某个客户端的连接会话,需要通过它来给客户端发送数据 25 | private Session session; 26 | 27 | public static int getOnlineCount() { 28 | return onlineCount.get(); 29 | } 30 | 31 | public static CopyOnWriteArraySet getWebSocketSet() { 32 | return webSocketSet; 33 | } 34 | 35 | /** 36 | * 连接建立成功调用的方法 37 | * 38 | * @param session 39 | * 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据 40 | */ 41 | @OnOpen 42 | public void onOpen(Session session) { 43 | this.session = session; 44 | webSocketSet.add(this); // 加入set中 45 | onlineCount.incrementAndGet(); // 在线数加1 46 | LOG.debug("有新连接加入!当前在线人数为" + onlineCount.get()); 47 | } 48 | 49 | /** 50 | * 连接关闭调用的方法 51 | */ 52 | @OnClose 53 | public void onClose() { 54 | webSocketSet.remove(this); // 从set中删除 55 | onlineCount.decrementAndGet(); // 在线数减1 56 | LOG.debug("有一连接关闭!当前在线人数为" + onlineCount.get()); 57 | } 58 | 59 | /** 60 | * 收到客户端消息后调用的方法 61 | * 62 | * @param message 63 | * 客户端发送过来的消息 64 | * @param session 65 | * 可选的参数 66 | */ 67 | @OnMessage 68 | public void onMessage(String message, Session session) { 69 | LOG.info("来自客户端的消息:" + message); 70 | } 71 | 72 | /** 73 | * 发生错误时调用 74 | * 75 | * @param session 76 | * @param error 77 | */ 78 | @OnError 79 | public void onError(Session session, Throwable error) { 80 | LOG.error("发生错误", error); 81 | } 82 | 83 | /** 84 | * 这个方法与上面几个方法不一样。没有用注解,是根据自己需要添加的方法。 85 | * 86 | * @param message 87 | * @throws IOException 88 | */ 89 | public void sendMessage(String message) throws IOException { 90 | this.session.getBasicRemote().sendText(message); 91 | } 92 | } -------------------------------------------------------------------------------- /server/src/main/java/me/weyo/magicmirror/server/listener/ServerListener.java: -------------------------------------------------------------------------------- 1 | package me.weyo.magicmirror.server.listener; 2 | 3 | import java.util.concurrent.ExecutorService; 4 | import java.util.concurrent.Executors; 5 | import java.util.concurrent.TimeUnit; 6 | 7 | import javax.servlet.ServletContextEvent; 8 | import javax.servlet.ServletContextListener; 9 | 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import me.weyo.magicmirror.server.service.AiService; 14 | import me.weyo.magicmirror.server.service.CommandService; 15 | import me.weyo.magicmirror.server.service.WebSocketService; 16 | import me.weyo.magicmirror.server.speech.Recorder; 17 | 18 | /** 19 | * @author WeYo 20 | */ 21 | public class ServerListener implements ServletContextListener { 22 | 23 | private static final Logger LOG = LoggerFactory.getLogger(ServerListener.class); 24 | private static Recorder recorder; 25 | private ExecutorService exec; 26 | 27 | @Override 28 | public void contextDestroyed(ServletContextEvent arg0) { 29 | recorder.shutdown(); 30 | 31 | exec.shutdown(); 32 | try { 33 | exec.awaitTermination(3, TimeUnit.SECONDS); 34 | LOG.info("魔镜已关闭。"); 35 | } catch (InterruptedException e) { 36 | LOG.error("魔镜主线程退出异常", e); 37 | } 38 | } 39 | 40 | @Override 41 | public void contextInitialized(ServletContextEvent arg0) { 42 | exec = Executors.newSingleThreadExecutor(); 43 | recorder = new Recorder(); 44 | recorder.registerWebSocketService(new WebSocketService()) 45 | .registerAiService(new AiService()) 46 | .registerCommandService(CommandService.INSTANCE); 47 | exec.execute(recorder); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /server/src/main/java/me/weyo/magicmirror/server/service/AiService.java: -------------------------------------------------------------------------------- 1 | package me.weyo.magicmirror.server.service; 2 | 3 | import java.util.concurrent.ExecutionException; 4 | import java.util.concurrent.ExecutorService; 5 | import java.util.concurrent.Executors; 6 | import java.util.concurrent.Future; 7 | 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import me.weyo.magicmirror.server.controller.AiController; 12 | 13 | /** 14 | * @author WeYo 15 | */ 16 | public class AiService { 17 | 18 | private static final Logger LOG = LoggerFactory.getLogger(AiService.class); 19 | private Future ai; 20 | 21 | public void request(String info) { 22 | ExecutorService exec = Executors.newSingleThreadExecutor(); 23 | this.ai = exec.submit(new AiController(info)); 24 | exec.shutdown(); 25 | } 26 | 27 | public String getResponse() { 28 | try { 29 | return ai.get(); 30 | } catch (InterruptedException e) { 31 | LOG.error("AiService 中断异常", e); 32 | } catch (ExecutionException e) { 33 | LOG.error("AiService 线程异常", e); 34 | } 35 | return null; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /server/src/main/java/me/weyo/magicmirror/server/service/CommandService.java: -------------------------------------------------------------------------------- 1 | package me.weyo.magicmirror.server.service; 2 | 3 | import java.util.concurrent.PriorityBlockingQueue; 4 | import java.util.concurrent.TimeUnit; 5 | 6 | /** 7 | * 基于枚举的单例模式 8 | * 9 | * @author WeYo 10 | */ 11 | public enum CommandService { 12 | 13 | INSTANCE; 14 | 15 | private static PriorityBlockingQueue cmds = new PriorityBlockingQueue(); 16 | 17 | public void execute(String command) { 18 | cmds.put(command); 19 | } 20 | 21 | public String takeCommand() throws InterruptedException { 22 | return cmds.poll(2, TimeUnit.SECONDS); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /server/src/main/java/me/weyo/magicmirror/server/service/InitService.java: -------------------------------------------------------------------------------- 1 | package me.weyo.magicmirror.server.service; 2 | 3 | import java.util.Map; 4 | 5 | import org.json.JSONException; 6 | import org.json.JSONObject; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import me.weyo.magicmirror.server.Parameters; 11 | 12 | /** 13 | * 将 js 中配置参数传入后台 14 | * 15 | * @author WeYo 16 | */ 17 | public class InitService { 18 | 19 | private static final Logger LOG = LoggerFactory.getLogger(InitService.class); 20 | private Map aiParams = Parameters.getAiParams(); 21 | 22 | public void initAi(String decodedAi) throws JSONException { 23 | JSONObject jsonObj = new JSONObject(decodedAi); 24 | aiParams.put("key", jsonObj.getString("key")); 25 | aiParams.put("loc", jsonObj.getString("loc")); 26 | aiParams.put("id", jsonObj.getString("id")); 27 | 28 | LOG.debug("ai 参数配置完成。"); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /server/src/main/java/me/weyo/magicmirror/server/service/WebSocketService.java: -------------------------------------------------------------------------------- 1 | package me.weyo.magicmirror.server.service; 2 | 3 | import java.io.IOException; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import me.weyo.magicmirror.server.controller.WebSocket; 9 | 10 | /** 11 | * @author WeYo 12 | */ 13 | public class WebSocketService { 14 | 15 | private static final Logger LOG = LoggerFactory.getLogger(WebSocketService.class); 16 | 17 | public void sendMessage(String message) { 18 | if (WebSocket.getOnlineCount() > 0) { 19 | for (WebSocket item : WebSocket.getWebSocketSet()) { 20 | try { 21 | if (message != null) { 22 | item.sendMessage(message); 23 | } 24 | } catch (IOException e) { 25 | LOG.error("WebSocketService 发送消息异常", e); 26 | } 27 | } 28 | } else { 29 | LOG.info("Nobody here..."); 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /server/src/main/java/me/weyo/magicmirror/server/speech/Recorder.java: -------------------------------------------------------------------------------- 1 | package me.weyo.magicmirror.server.speech; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import me.weyo.magicmirror.server.service.AiService; 7 | import me.weyo.magicmirror.server.service.CommandService; 8 | import me.weyo.magicmirror.server.service.WebSocketService; 9 | 10 | /** 11 | * @author WeYo 12 | */ 13 | public class Recorder implements Runnable { 14 | private static final Logger LOG = LoggerFactory.getLogger(Recorder.class); 15 | 16 | private WebSocketService webSocketService; 17 | private AiService aiService; 18 | private CommandService cmdService; 19 | private volatile boolean isRunning = true; 20 | 21 | @Override 22 | public void run() { 23 | while (isRunning) { 24 | String cmd = null; 25 | try { 26 | cmd = cmdService.takeCommand(); 27 | if (cmd == null) { 28 | continue; 29 | } 30 | if (cmd.equals("")) { 31 | webSocketService.sendMessage("1|" + cmd); 32 | webSocketService.sendMessage("0|不说话的孩子不乖哦"); 33 | continue; 34 | } 35 | aiService.request(cmd); 36 | webSocketService.sendMessage("1|" + cmd); 37 | webSocketService.sendMessage(aiService.getResponse()); 38 | } catch (InterruptedException e) { 39 | LOG.error("WebSocket 消息线程异常|cmd:" + cmd, e); 40 | } 41 | LOG.debug("已处理一次请求:" + cmd); 42 | } 43 | } 44 | 45 | public Recorder registerWebSocketService(WebSocketService wsService) { 46 | this.webSocketService = wsService; 47 | return this; 48 | } 49 | 50 | public Recorder registerAiService(AiService aiService) { 51 | this.aiService = aiService; 52 | return this; 53 | } 54 | 55 | public Recorder registerCommandService(CommandService commandService) { 56 | this.cmdService = commandService; 57 | return this; 58 | } 59 | 60 | public void shutdown() { 61 | this.isRunning = false; 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /server/src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger = INFO , Console, RoolingFile 2 | 3 | # Console 4 | log4j.appender.Console = org.apache.log4j.ConsoleAppender 5 | log4j.appender.Console.layout = org.apache.log4j.PatternLayout 6 | log4j.appender.Console.layout.ConversionPattern = [MagicMirror] %d %-5p %m %C - %t%n 7 | 8 | # File 9 | log4j.appender.RoolingFile = org.apache.log4j.DailyRollingFileAppender 10 | log4j.appender.RoolingFile.File = ${catalina.home}/logs/magicmirror.log 11 | log4j.appender.RoolingFile.datePattern = '.'yyyy-MM-dd'.txt' 12 | log4j.appender.RoolingFile.layout = org.apache.log4j.PatternLayout 13 | log4j.appender.RoolingFile.layout.ConversionPattern = [MagicMirror] %d | %-5p | %m | %C | %t%n 14 | -------------------------------------------------------------------------------- /server/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | Magic Mirror Server 7 | 8 | 9 | 10 | me.weyo.magicmirror.server.listener.ServerListener 11 | 12 | 13 | 14 | 15 | Calendar 16 | me.weyo.magicmirror.server.controller.CalendarController 17 | 18 | 19 | AQI 20 | me.weyo.magicmirror.server.controller.AqiController 21 | 22 | 23 | Command 24 | me.weyo.magicmirror.server.controller.HttpCommandController 25 | 26 | 27 | InitServlet 28 | me.weyo.magicmirror.server.controller.InitController 29 | 1 30 | 31 | 32 | 33 | Calendar 34 | /calendar 35 | 36 | 37 | AQI 38 | /aqi 39 | 40 | 41 | Command 42 | /command 43 | 44 | 45 | InitServlet 46 | /init 47 | 48 | 49 | -------------------------------------------------------------------------------- /server/src/main/webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Magic Mirror 5 | 6 | 7 | 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 | -------------------------------------------------------------------------------- /server/src/main/webapp/resources/css/ai.css: -------------------------------------------------------------------------------- 1 | .ai { 2 | width: 373px; 3 | height: 800px; 4 | display: block; 5 | overflow: hidden; 6 | text-align: justify; 7 | text-justify: auto; 8 | } 9 | 10 | .ai dl { 11 | position: relative; 12 | margin: 0; 13 | } 14 | 15 | .ai dt { 16 | display: block; 17 | margin-bottom: 10px; 18 | } 19 | 20 | .avatar { 21 | height : 48px; 22 | width : 48px; 23 | margin: 2px; 24 | } 25 | 26 | .fleft { 27 | float: left; 28 | } 29 | 30 | .fright { 31 | float: right; 32 | background-color: #696969; 33 | } 34 | 35 | .words { 36 | border: 1px solid; 37 | border-radius: 8px; 38 | padding: 10px; 39 | margin: 4px; 40 | width: 250px; 41 | } 42 | 43 | .clear { 44 | clear: both; 45 | } 46 | 47 | .talk input { 48 | margin: 2px; 49 | height:50px; 50 | max-width: 80%; 51 | font-size:42px; 52 | } 53 | 54 | .talk button { 55 | margin: 2px; 56 | height:57px; 57 | font-size:42px; 58 | } -------------------------------------------------------------------------------- /server/src/main/webapp/resources/css/main.css: -------------------------------------------------------------------------------- 1 | body, html { 2 | background: #000; 3 | padding: 0px; 4 | margin: 0px; 5 | width: 100%; 6 | height: 100%; 7 | font-family: "HelveticaNeue-Light"; 8 | letter-spacing: -2px; 9 | color: #fff; 10 | font-size: 75px; 11 | -webkit-font-smoothing: antialiased; 12 | } 13 | 14 | .wi { 15 | line-height: 75px; 16 | } 17 | 18 | .top { 19 | position: absolute; 20 | top: 50px; 21 | } 22 | 23 | .left { 24 | position: absolute; 25 | left: 50px; 26 | } 27 | 28 | .right { 29 | position: absolute; 30 | right: 50px; 31 | text-align: right; 32 | } 33 | 34 | .center-ver { 35 | position: absolute; 36 | top: 50%; 37 | height: 200px; 38 | margin-top: -100px; 39 | line-height: 100px; 40 | } 41 | 42 | .center-ver-new { 43 | position: absolute; 44 | top: 30%; 45 | } 46 | 47 | .lower-third { 48 | position: absolute; 49 | top: 66.666%; 50 | height: 200px; 51 | margin-top: -100px; 52 | /*line-height: 100px; */ 53 | } 54 | 55 | .center-hor { 56 | position: absolute; 57 | right: 50px; 58 | left: 50px; 59 | text-align: center; 60 | } 61 | 62 | .bottom { 63 | position: absolute; 64 | bottom: 50px; 65 | } 66 | 67 | .xxsmall { 68 | font-size: 15px; 69 | letter-spacing: 0px; 70 | font-family: "HelveticaNeue-Medium"; 71 | } 72 | 73 | .xxsmall .wi { 74 | line-height: 15px; 75 | } 76 | 77 | .xsmall { 78 | font-size: 20px; 79 | letter-spacing: 0px; 80 | font-family: "HelveticaNeue-Medium"; 81 | } 82 | 83 | .xsmall .wi { 84 | line-height: 20px; 85 | } 86 | 87 | .small { 88 | font-size: 25px; 89 | letter-spacing: 0px; 90 | font-family: "HelveticaNeue-Medium"; 91 | } 92 | 93 | .small .wi { 94 | line-height: 25px; 95 | } 96 | 97 | .medium { 98 | font-size: 35px; 99 | letter-spacing: -1px; 100 | font-family: "HelveticaNeue-Light"; 101 | } 102 | 103 | .medium .wi { 104 | line-height: 35px; 105 | } 106 | 107 | .xdimmed { 108 | color: #666; 109 | } 110 | 111 | .dimmed { 112 | color: #aaa; 113 | } 114 | 115 | .light { 116 | font-family: "HelveticaNeue-UltraLight"; 117 | } 118 | 119 | .icon { 120 | position: relative; 121 | top: -10px; 122 | display: inline-block; 123 | font-size: 45px; 124 | padding-right: 5px; 125 | font-weight: 100; 126 | margin-right: 10px; 127 | } 128 | 129 | .icon-small { 130 | position: relative; 131 | display: inline-block; 132 | font-size: 20px; 133 | padding-left: 10px; 134 | padding-right: -10px; 135 | font-weight: 100; 136 | } 137 | 138 | .time .sec { 139 | font-size: 25px; 140 | color: #666; 141 | padding-left: 5px; 142 | position: relative; 143 | top: -35px; 144 | } 145 | 146 | .forecast-table { 147 | float: right; 148 | text-align: right; 149 | font-size: 20px; 150 | line-height: 20px; 151 | } 152 | 153 | .forecast-table .day, .forecast-table .temp-min, .forecast-table .temp-max 154 | { 155 | width: 50px; 156 | text-align: right; 157 | } 158 | 159 | .forecast-table .temp-max { 160 | width: 60px; 161 | } 162 | 163 | .forecast-table .day { 164 | color: #999; 165 | } 166 | 167 | .calendar-table { 168 | font-size: 14px; 169 | line-height: 20px; 170 | margin-top: 10px; 171 | } 172 | 173 | .calendar-table .days { 174 | padding-left: 20px; 175 | text-align: right; 176 | } 177 | 178 | .dishwasher { 179 | background-color: white; 180 | color: black; 181 | margin: 0 200px; 182 | font-size: 60px; 183 | border-radius: 1000px; 184 | border-radius: 1200px; 185 | display: none; 186 | } 187 | 188 | @font-face { 189 | font-family: 'HelveticaNeue-UltraLight'; 190 | src: url('../font/HelveticaNeue-UltraLight.eot'); 191 | /* IE9 Compat Modes */ 192 | src: url('../font/HelveticaNeue-UltraLight.eot?#iefix') 193 | format('embedded-opentype'), /* IE6-IE8 */ 194 | url('../font/HelveticaNeue-UltraLight.woff') format('woff'), 195 | /* Modern Browsers */ 196 | url('../font/HelveticaNeue-UltraLight.ttf') format('truetype'), 197 | /* Safari, Android, iOS */ 198 | 199 | 200 | url('../font/HelveticaNeue-UltraLight.svg#9453ea8da727d260bcdbfa605bdbb5d2') 201 | format('svg'); /* Legacy iOS */ 202 | font-style: normal; 203 | font-weight: 100; 204 | } 205 | 206 | @font-face { 207 | font-family: 'HelveticaNeue-Medium'; 208 | src: url('../font/HelveticaNeue-Medium.eot'); /* IE9 Compat Modes */ 209 | src: url('../font/HelveticaNeue-Medium.eot?#iefix') 210 | format('embedded-opentype'), /* IE6-IE8 */ 211 | url('../font/HelveticaNeue-Medium.woff') format('woff'), 212 | /* Modern Browsers */ 213 | url('../font/HelveticaNeue-Medium.ttf') format('truetype'), 214 | /* Safari, Android, iOS */ 215 | 216 | 217 | url('../font/HelveticaNeue-Medium.svg#d7af0fd9278f330eed98b60dddea7bd6') 218 | format('svg'); /* Legacy iOS */ 219 | font-style: normal; 220 | font-weight: 400; 221 | } 222 | 223 | @font-face { 224 | font-family: 'HelveticaNeue-Light'; 225 | src: url('../font/HelveticaNeue-Light.eot'); /* IE9 Compat Modes */ 226 | src: url('../font/HelveticaNeue-Light.eot?#iefix') 227 | format('embedded-opentype'), /* IE6-IE8 */ 228 | url('../font/HelveticaNeue-Light.woff') format('woff'), 229 | /* Modern Browsers */ 230 | url('../font/HelveticaNeue-Light.ttf') format('truetype'), 231 | /* Safari, Android, iOS */ 232 | 233 | 234 | url('../font/HelveticaNeue-Light.svg#7384ecabcada72f0e077cd45d8e1c705') 235 | format('svg'); /* Legacy iOS */ 236 | font-style: normal; 237 | font-weight: 200; 238 | } -------------------------------------------------------------------------------- /server/src/main/webapp/resources/css/weather-icons.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Weather Icons Beta 1 3 | * Weather themed icons for Bootstrap 4 | * ------------------------------------------------------------------------------ 5 | * Maintained at http://erikflowers.github.io/weather-icons 6 | * http://twitter.com/Erik_UX 7 | * 8 | * License 9 | * ------------------------------------------------------------------------------ 10 | * - Fpmt licensed under SIL OFL 1.1 - 11 | * http://scripts.sil.org/OFL 12 | * - CSS and LESS are licensed under MIT License - 13 | * http://opensource.org/licenses/mit-license.html 14 | * - Documentation licensed under CC BY 3.0 - 15 | * http://creativecommons.org/licenses/by/3.0/ 16 | * - Inspired by and works great as a companion with Font Aweosme 17 | * "Font Awesome by Dave Gandy - http://fontawesome.io" 18 | * 19 | * Weather Icons Bootstrap Package Author - Erik Flowers - erik@helloerik.com 20 | * Weather Icons gives full credit for inspiration to Font Awesome and makes no 21 | * claim to invention, intellectual property, or ownership of methodology. 22 | * 23 | * Support Open Source! 24 | * 25 | * ------------------------------------------------------------------------------ 26 | * Email: erik@helloerik.com 27 | * Twitter: http://twitter.com/Erik_UX 28 | */ 29 | @font-face { 30 | font-family: 'weather'; 31 | src: url('../font/weathericons-regular-webfont.eot'); 32 | src: url('../font/weathericons-regular-webfont.eot?#iefix') 33 | format('embedded-opentype'), 34 | url('../font/weathericons-regular-webfont.woff') format('woff'), 35 | url('../font/weathericons-regular-webfont.ttf') 36 | format('truetype'), 37 | url('../font/weathericons-regular-webfont.svg#weathericons-regular-webfontRg') 38 | format('svg'); 39 | font-weight: normal; 40 | font-style: normal; 41 | } 42 | 43 | [class^="wi-"], [class*=" wi-"] { 44 | font-family: weather; 45 | font-weight: normal; 46 | font-style: normal; 47 | text-decoration: inherit; 48 | text-transform: none; 49 | -webkit-font-smoothing: antialiased; 50 | *margin-right: .3em; 51 | } 52 | 53 | [class^="wi-"]:before, [class*=" wi-"]:before { 54 | text-decoration: inherit; 55 | display: inline-block; 56 | speak: none; 57 | } 58 | 59 | .wi-day-cloudy-gusts:before { 60 | content: "\f000"; 61 | } 62 | 63 | .wi-day-cloudy-windy:before { 64 | content: "\f001"; 65 | } 66 | 67 | .wi-day-cloudy:before { 68 | content: "\f002"; 69 | } 70 | 71 | .wi-day-fog:before { 72 | content: "\f003"; 73 | } 74 | 75 | .wi-day-hail:before { 76 | content: "\f004"; 77 | } 78 | 79 | .wi-day-lightning:before { 80 | content: "\f005"; 81 | } 82 | 83 | .wi-day-rain-mix:before { 84 | content: "\f006"; 85 | } 86 | 87 | .wi-day-rain-wind:before { 88 | content: "\f007"; 89 | } 90 | 91 | .wi-day-rain:before { 92 | content: "\f008"; 93 | } 94 | 95 | .wi-day-showers:before { 96 | content: "\f009"; 97 | } 98 | 99 | .wi-day-snow:before { 100 | content: "\f00a"; 101 | } 102 | 103 | .wi-day-sprinkle:before { 104 | content: "\f00b"; 105 | } 106 | 107 | .wi-day-sunny-overcast:before { 108 | content: "\f00c"; 109 | } 110 | 111 | .wi-day-sunny:before { 112 | content: "\f00d"; 113 | } 114 | 115 | .wi-day-storm-showers:before { 116 | content: "\f00e"; 117 | } 118 | 119 | .wi-day-thunderstorm:before { 120 | content: "\f010"; 121 | } 122 | 123 | .wi-cloudy-gusts:before { 124 | content: "\f011"; 125 | } 126 | 127 | .wi-cloudy-windy:before { 128 | content: "\f012"; 129 | } 130 | 131 | .wi-cloudy:before { 132 | content: "\f013"; 133 | } 134 | 135 | .wi-fog:before { 136 | content: "\f014"; 137 | } 138 | 139 | .wi-hail:before { 140 | content: "\f015"; 141 | } 142 | 143 | .wi-lightning:before { 144 | content: "\f016"; 145 | } 146 | 147 | .wi-rain-mix:before { 148 | content: "\f017"; 149 | } 150 | 151 | .wi-rain-wind:before { 152 | content: "\f018"; 153 | } 154 | 155 | .wi-rain:before { 156 | content: "\f019"; 157 | } 158 | 159 | .wi-showers:before { 160 | content: "\f01a"; 161 | } 162 | 163 | .wi-snow:before { 164 | content: "\f01b"; 165 | } 166 | 167 | .wi-sprinkle:before { 168 | content: "\f01c"; 169 | } 170 | 171 | .wi-storm-showers:before { 172 | content: "\f01d"; 173 | } 174 | 175 | .wi-thunderstorm:before { 176 | content: "\f01e"; 177 | } 178 | 179 | .wi-windy:before { 180 | content: "\f021"; 181 | } 182 | 183 | .wi-night-alt-cloudy-gusts:before { 184 | content: "\f022"; 185 | } 186 | 187 | .wi-night-alt-cloudy-windy:before { 188 | content: "\f023"; 189 | } 190 | 191 | .wi-night-alt-hail:before { 192 | content: "\f024"; 193 | } 194 | 195 | .wi-night-alt-lightning:before { 196 | content: "\f025"; 197 | } 198 | 199 | .wi-night-alt-rain-mix:before { 200 | content: "\f026"; 201 | } 202 | 203 | .wi-night-alt-rain-wind:before { 204 | content: "\f027"; 205 | } 206 | 207 | .wi-night-alt-rain:before { 208 | content: "\f028"; 209 | } 210 | 211 | .wi-night-alt-showers:before { 212 | content: "\f029"; 213 | } 214 | 215 | .wi-night-alt-snow:before { 216 | content: "\f02a"; 217 | } 218 | 219 | .wi-night-alt-sprinkle:before { 220 | content: "\f02b"; 221 | } 222 | 223 | .wi-night-alt-storm-showers:before { 224 | content: "\f02c"; 225 | } 226 | 227 | .wi-night-alt-thunderstorm:before { 228 | content: "\f02d"; 229 | } 230 | 231 | .wi-night-clear:before { 232 | content: "\f02e"; 233 | } 234 | 235 | .wi-night-cloudy-gusts:before { 236 | content: "\f02f"; 237 | } 238 | 239 | .wi-night-cloudy-windy:before { 240 | content: "\f030"; 241 | } 242 | 243 | .wi-night-cloudy:before { 244 | content: "\f031"; 245 | } 246 | 247 | .wi-night-hail:before { 248 | content: "\f032"; 249 | } 250 | 251 | .wi-night-lightning:before { 252 | content: "\f033"; 253 | } 254 | 255 | .wi-night-rain-mix:before { 256 | content: "\f034"; 257 | } 258 | 259 | .wi-night-rain-wind:before { 260 | content: "\f035"; 261 | } 262 | 263 | .wi-night-rain:before { 264 | content: "\f036"; 265 | } 266 | 267 | .wi-night-showers:before { 268 | content: "\f037"; 269 | } 270 | 271 | .wi-night-snow:before { 272 | content: "\f038"; 273 | } 274 | 275 | .wi-night-sprinkle:before { 276 | content: "\f039"; 277 | } 278 | 279 | .wi-night-storm-showers:before { 280 | content: "\f03a"; 281 | } 282 | 283 | .wi-night-thunderstorm:before { 284 | content: "\f03b"; 285 | } 286 | 287 | .wi-celcius:before { 288 | content: "\f03c"; 289 | } 290 | 291 | .wi-cloud-down:before { 292 | content: "\f03d"; 293 | } 294 | 295 | .wi-cloud-refresh:before { 296 | content: "\f03e"; 297 | } 298 | 299 | .wi-cloud-up:before { 300 | content: "\f040"; 301 | } 302 | 303 | .wi-cloud:before { 304 | content: "\f041"; 305 | } 306 | 307 | .wi-degrees:before { 308 | content: "\f042"; 309 | } 310 | 311 | .wi-down-left:before { 312 | content: "\f043"; 313 | } 314 | 315 | .wi-down:before { 316 | content: "\f044"; 317 | } 318 | 319 | .wi-fahrenheit:before { 320 | content: "\f045"; 321 | } 322 | 323 | .wi-horizon-alt:before { 324 | content: "\f046"; 325 | } 326 | 327 | .wi-horizon:before { 328 | content: "\f047"; 329 | } 330 | 331 | .wi-left:before { 332 | content: "\f048"; 333 | } 334 | 335 | .wi-lightning:before { 336 | content: "\f016"; 337 | } 338 | 339 | .wi-night-fog:before { 340 | content: "\f04a"; 341 | } 342 | 343 | .wi-refresh-alt:before { 344 | content: "\f04b"; 345 | } 346 | 347 | .wi-refresh:before { 348 | content: "\f04c"; 349 | } 350 | 351 | .wi-right:before { 352 | content: "\f04d"; 353 | } 354 | 355 | .wi-sprinkles:before { 356 | content: "\f04e"; 357 | } 358 | 359 | .wi-strong-wind:before { 360 | content: "\f050"; 361 | } 362 | 363 | .wi-sunrise:before { 364 | content: "\f051"; 365 | } 366 | 367 | .wi-sunset:before { 368 | content: "\f052"; 369 | } 370 | 371 | .wi-thermometer-exterior:before { 372 | content: "\f053"; 373 | } 374 | 375 | .wi-thermometer-internal:before { 376 | content: "\f054"; 377 | } 378 | 379 | .wi-thermometer:before { 380 | content: "\f055"; 381 | } 382 | 383 | .wi-tornado:before { 384 | content: "\f056"; 385 | } 386 | 387 | .wi-up-right:before { 388 | content: "\f057"; 389 | } 390 | 391 | .wi-up:before { 392 | content: "\f058"; 393 | } 394 | 395 | .wi-wind-east:before { 396 | content: "\f059"; 397 | } 398 | 399 | .wi-wind-north-east:before { 400 | content: "\f05a"; 401 | } 402 | 403 | .wi-wind-north-west:before { 404 | content: "\f05b"; 405 | } 406 | 407 | .wi-wind-north:before { 408 | content: "\f05c"; 409 | } 410 | 411 | .wi-wind-south-east:before { 412 | content: "\f05d"; 413 | } 414 | 415 | .wi-wind-south-west:before { 416 | content: "\f05e"; 417 | } 418 | 419 | .wi-wind-south:before { 420 | content: "\f060"; 421 | } 422 | 423 | .wi-wind-west:before { 424 | content: "\f061"; 425 | } -------------------------------------------------------------------------------- /server/src/main/webapp/resources/font/HelveticaNeue-Light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/server/src/main/webapp/resources/font/HelveticaNeue-Light.eot -------------------------------------------------------------------------------- /server/src/main/webapp/resources/font/HelveticaNeue-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/server/src/main/webapp/resources/font/HelveticaNeue-Light.ttf -------------------------------------------------------------------------------- /server/src/main/webapp/resources/font/HelveticaNeue-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/server/src/main/webapp/resources/font/HelveticaNeue-Light.woff -------------------------------------------------------------------------------- /server/src/main/webapp/resources/font/HelveticaNeue-Medium.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/server/src/main/webapp/resources/font/HelveticaNeue-Medium.eot -------------------------------------------------------------------------------- /server/src/main/webapp/resources/font/HelveticaNeue-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/server/src/main/webapp/resources/font/HelveticaNeue-Medium.ttf -------------------------------------------------------------------------------- /server/src/main/webapp/resources/font/HelveticaNeue-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/server/src/main/webapp/resources/font/HelveticaNeue-Medium.woff -------------------------------------------------------------------------------- /server/src/main/webapp/resources/font/HelveticaNeue-UltraLight.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/server/src/main/webapp/resources/font/HelveticaNeue-UltraLight.eot -------------------------------------------------------------------------------- /server/src/main/webapp/resources/font/HelveticaNeue-UltraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/server/src/main/webapp/resources/font/HelveticaNeue-UltraLight.ttf -------------------------------------------------------------------------------- /server/src/main/webapp/resources/font/HelveticaNeue-UltraLight.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/server/src/main/webapp/resources/font/HelveticaNeue-UltraLight.woff -------------------------------------------------------------------------------- /server/src/main/webapp/resources/font/weathericons-regular-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/server/src/main/webapp/resources/font/weathericons-regular-webfont.eot -------------------------------------------------------------------------------- /server/src/main/webapp/resources/font/weathericons-regular-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/server/src/main/webapp/resources/font/weathericons-regular-webfont.ttf -------------------------------------------------------------------------------- /server/src/main/webapp/resources/font/weathericons-regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/server/src/main/webapp/resources/font/weathericons-regular-webfont.woff -------------------------------------------------------------------------------- /server/src/main/webapp/resources/images/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/server/src/main/webapp/resources/images/avatar.png -------------------------------------------------------------------------------- /server/src/main/webapp/resources/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/server/src/main/webapp/resources/images/favicon.ico -------------------------------------------------------------------------------- /server/src/main/webapp/resources/images/lmm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weyo/MagicMirror/6eb849fdcef30663ab57f34fa3b7ee55f5282162/server/src/main/webapp/resources/images/lmm.jpg -------------------------------------------------------------------------------- /server/src/main/webapp/resources/js/ai/ai.js: -------------------------------------------------------------------------------- 1 | var ai = { 2 | aiLocation: '.ai', 3 | sayingLocation: '.saying', 4 | url: 'localhost:8080/server/websocket', 5 | fadeInterval: config.ai.fadeInterval, 6 | cleanInterval: config.ai.cleanInterval, 7 | appKey: config.ai.AppKey, 8 | loc: config.ai.loc, 9 | id: config.ai.id, 10 | mmAvatar: 'lmm.jpg', 11 | defAvatar: 'avatar.png', 12 | sayingTime: new Date(), 13 | intervalId : null 14 | } 15 | 16 | ai.saying = function (data) { 17 | 18 | var dt_head = '
'; 22 | var dt_tail = '

'; 23 | var avatar = ai.mmAvatar; 24 | var words = data.slice(2); 25 | var loc = 'fleft'; 26 | 27 | var line = ""; 28 | /* 29 | * 接收数据解析: 30 | * 0 —— AI 31 | * 1 —— 用户 32 | * 2 —— AI 返回超链接 33 | */ 34 | switch (data[0]) { 35 | case '1': 36 | avatar = ai.defAvatar; 37 | loc = 'fright'; 38 | case '0': 39 | line = dt_head + loc + dt_mid1 + avatar + dt_mid2 + loc + dt_mid3 + words + dt_tail; 40 | break; 41 | case '2': 42 | sn = words.indexOf('|'); 43 | line = dt_head + loc + dt_mid1 + avatar + dt_mid2 + loc + dt_mid3 + words.slice(0, sn) + dt_tail; 44 | $(ai.sayingLocation).append(line); 45 | ai.scrollWords(); 46 | realUrl = words.slice(sn + 1); 47 | // 临时修复返回的去哪儿网链接异常问题 48 | if (realUrl.indexOf("touch.qunar.com/h5/flight/flightlist?bd_source=chongdong&") > 0) { 49 | realUrl.replace("bd_source=chongdong&", ""); 50 | } 51 | line = '
'; 52 | break; 53 | default: 54 | console.log('不支持的数据:' + data); 55 | break; 56 | } 57 | 58 | $(ai.sayingLocation).append(line); 59 | ai.scrollWords(); 60 | ai.sayingTime = new Date(); 61 | } 62 | 63 | ai.scrollWords = function() { 64 | var height = $(ai.sayingLocation).outerHeight(); 65 | var offset = 795 - height; 66 | if (offset < 0) { 67 | $(ai.sayingLocation).animate({ 68 | marginTop: offset + 'px', 69 | }, ai.fadeInterval); 70 | } 71 | } 72 | 73 | ai.clearWords = function () { 74 | var nowTime = new Date(); 75 | if (nowTime - ai.sayingTime > ai.cleanInterval) { 76 | $(ai.sayingLocation).empty(); 77 | $(ai.sayingLocation).css({"marginTop": "0"}); 78 | } 79 | } 80 | 81 | ai.init = function () { 82 | 83 | new websocket(this.url, this.saying); 84 | 85 | this.intervalId = setInterval(function() { 86 | this.clearWords(); 87 | }.bind(this), this.cleanInterval); 88 | 89 | } -------------------------------------------------------------------------------- /server/src/main/webapp/resources/js/ai/command.js: -------------------------------------------------------------------------------- 1 | function send() { 2 | var message = encodeURIComponent(document.getElementById('text').value); 3 | url = "command?message=" + message; 4 | 5 | document.getElementById('text').value = ""; 6 | 7 | try { 8 | xmlhttp = window.XMLHttpRequest ? new XMLHttpRequest() 9 | : new ActiveXObject("Microsoft.XMLHTTP"); 10 | } catch (e) { 11 | } 12 | 13 | xmlhttp.onreadystatechange = function() { 14 | if ((xmlhttp.readyState == 4) && (xmlhttp.status == 200)) { 15 | console.log(xmlhttp.responseText); 16 | } 17 | }; 18 | xmlhttp.open("GET", url, true); 19 | xmlhttp.send(null); 20 | } -------------------------------------------------------------------------------- /server/src/main/webapp/resources/js/aqi/aqi.js: -------------------------------------------------------------------------------- 1 | var aqi = { 2 | city : config.aqi.city, 3 | apiBase : 'http://www.pm25.in/api/querys/only_aqi.json?city=', 4 | apiStation : '&stations=', 5 | apiToken : '&token=', 6 | appKey : config.aqi.AppKey, 7 | aqiInfoLocation : '.aqi-info', 8 | aqiLocation : '.aqi-value', 9 | aqiQualityLocation : '.aqi-quality', 10 | updateInterval : config.aqi.interval, 11 | fadeInterval : config.aqi.fadeInterval, 12 | intervalId : null 13 | } 14 | 15 | aqi.updateData = function(data) { 16 | var _aqis = eval(data); 17 | var _currentAqi = _aqis[0]; 18 | var _aqiValue = _currentAqi.aqi; 19 | var _aqiQuality = _currentAqi.quality; 20 | var _aqiPrimaryPollutant = _currentAqi.primary_pollutant; 21 | 22 | $(aqi.aqiInfoLocation).updateWithText('空气质量指数', aqi.fadeInterval); 23 | $(aqi.aqiLocation).updateWithText(_aqiValue, aqi.fadeInterval); 24 | $(aqi.aqiQualityLocation).updateWithText(_aqiQuality, 25 | aqi.fadeInterval); 26 | }; 27 | 28 | /** 29 | * Retrieves the current aqi from the PM25.in API 30 | */ 31 | aqi.updateCurrentAqi = function(callback) { 32 | url = "aqi?url=" + encodeURIComponent(aqi.apiBase + aqi.city + aqi.apiStation + 'no' 33 | + aqi.apiToken + aqi.appKey); 34 | 35 | try { 36 | xmlhttp = window.XMLHttpRequest ? new XMLHttpRequest() 37 | : new ActiveXObject("Microsoft.XMLHTTP"); 38 | } catch (e) { 39 | } 40 | 41 | xmlhttp.onreadystatechange = function() { 42 | if ((xmlhttp.readyState == 4) && (xmlhttp.status == 200)) { 43 | callback(xmlhttp.responseText); 44 | } 45 | }; 46 | xmlhttp.open("GET", url, true); 47 | xmlhttp.send(null); 48 | 49 | } 50 | 51 | aqi.init = function() { 52 | this.updateCurrentAqi(this.updateData); 53 | 54 | this.intervalId = setInterval(function() { 55 | this.updateCurrentAqi(this.updateData); 56 | }.bind(this), this.updateInterval); 57 | 58 | } -------------------------------------------------------------------------------- /server/src/main/webapp/resources/js/calendar/calendar.js: -------------------------------------------------------------------------------- 1 | var calendar = { 2 | eventList: [], 3 | calendarLocation: '.calendar', 4 | updateInterval: 30000, 5 | updateDataInterval: 60000, 6 | fadeInterval: 1000, 7 | intervalId: null, 8 | dataIntervalId: null, 9 | maximumEntries: config.calendar.maximumEntries || 10 10 | } 11 | 12 | calendar.updateData = function (callback) { 13 | 14 | new ical_parser("calendar?url="+encodeURIComponent(config.calendar.url), function(cal) { 15 | var events = cal.getEvents(); 16 | this.eventList = []; 17 | 18 | for (var i in events) { 19 | 20 | var e = events[i]; 21 | for (var key in e) { 22 | var value = e[key]; 23 | var seperator = key.search(';'); 24 | if (seperator >= 0) { 25 | var mainKey = key.substring(0,seperator); 26 | var subKey = key.substring(seperator+1); 27 | 28 | var dt; 29 | if (subKey == 'VALUE=DATE') { 30 | //date 31 | dt = new Date(value.substring(0,4), value.substring(4,6) - 1, value.substring(6,8)); 32 | } else { 33 | //time 34 | dt = new Date(value.substring(0,4), value.substring(4,6) - 1, value.substring(6,8), value.substring(9,11), value.substring(11,13), value.substring(13,15)); 35 | } 36 | 37 | if (mainKey == 'DTSTART') e.startDate = dt; 38 | if (mainKey == 'DTEND') e.endDate = dt; 39 | } 40 | } 41 | 42 | if (e.startDate == undefined){ 43 | //some old events in Gmail Calendar is "start_date" 44 | //FIXME: problems with Gmail's TimeZone 45 | var days = moment(e.DTSTART).diff(moment(), 'days'); 46 | var seconds = moment(e.DTSTART).diff(moment(), 'seconds'); 47 | var startDate = moment(e.DTSTART); 48 | } else { 49 | var days = moment(e.startDate).diff(moment(), 'days'); 50 | var seconds = moment(e.startDate).diff(moment(), 'seconds'); 51 | var startDate = moment(e.startDate); 52 | } 53 | 54 | //only add fututre events, days doesn't work, we need to check seconds 55 | if (seconds >= 0) { 56 | if (seconds <= 60*60*5 || seconds >= 60*60*24*2) { 57 | var time_string = moment(startDate).fromNow(); 58 | }else { 59 | var time_string = moment(startDate).calendar() 60 | } 61 | if (!e.RRULE) { 62 | this.eventList.push({'description':e.SUMMARY,'seconds':seconds,'days':time_string}); 63 | } 64 | e.seconds = seconds; 65 | } 66 | 67 | // Special handling for rrule events 68 | if (e.RRULE) { 69 | var options = new RRule.parseString(e.RRULE); 70 | options.dtstart = e.startDate; 71 | var rule = new RRule(options); 72 | 73 | // TODO: don't use fixed end date here, use something like now() + 1 year 74 | var dates = rule.between(new Date(), new Date(2016,11,31), true, function (date, i){return i < 10}); 75 | for (date in dates) { 76 | var dt = new Date(dates[date]); 77 | var days = moment(dt).diff(moment(), 'days'); 78 | var seconds = moment(dt).diff(moment(), 'seconds'); 79 | var startDate = moment(dt); 80 | if (seconds >= 0) { 81 | if (seconds <= 60*60*5 || seconds >= 60*60*24*2) { 82 | var time_string = moment(dt).fromNow(); 83 | } else { 84 | var time_string = moment(dt).calendar() 85 | } 86 | this.eventList.push({'description':e.SUMMARY,'seconds':seconds,'days':time_string}); 87 | } 88 | } 89 | } 90 | }; 91 | 92 | this.eventList = this.eventList.sort(function(a,b){return a.seconds-b.seconds}); 93 | 94 | // Limit the number of entries. 95 | this.eventList = this.eventList.slice(0, calendar.maximumEntries); 96 | 97 | if (callback !== undefined && Object.prototype.toString.call(callback) === '[object Function]') { 98 | callback(this.eventList); 99 | } 100 | 101 | }.bind(this)); 102 | 103 | } 104 | 105 | calendar.updateCalendar = function (eventList) { 106 | 107 | table = $('').addClass('xsmall').addClass('calendar-table'); 108 | opacity = 1; 109 | 110 | for (var i in eventList) { 111 | var e = eventList[i]; 112 | 113 | var row = $('').css('opacity',opacity); 114 | row.append($('
').html(e.description).addClass('description')); 115 | row.append($('').html(e.days).addClass('days dimmed')); 116 | table.append(row); 117 | 118 | opacity -= 1 / eventList.length; 119 | } 120 | 121 | $(this.calendarLocation).updateWithText(table, this.fadeInterval); 122 | 123 | } 124 | 125 | calendar.init = function () { 126 | 127 | this.updateData(this.updateCalendar.bind(this)); 128 | 129 | this.intervalId = setInterval(function () { 130 | this.updateCalendar(this.eventList) 131 | }.bind(this), this.updateInterval); 132 | 133 | this.dataIntervalId = setInterval(function () { 134 | this.updateData(this.updateCalendar.bind(this)); 135 | }.bind(this), this.updateDataInterval); 136 | 137 | } 138 | -------------------------------------------------------------------------------- /server/src/main/webapp/resources/js/compliments/compliments.js: -------------------------------------------------------------------------------- 1 | var compliments = { 2 | complimentLocation : '.compliment', 3 | currentCompliment : '', 4 | params : config.compliments.timeParams, 5 | complimentList : { 6 | 'morning' : config.compliments.morning, 7 | 'afternoon' : config.compliments.afternoon, 8 | 'evening' : config.compliments.evening 9 | }, 10 | updateInterval : config.compliments.interval || 30000, 11 | fadeInterval : config.compliments.fadeInterval || 4000, 12 | intervalId : null 13 | }; 14 | 15 | /** 16 | * Changes the compliment visible on the screen 17 | */ 18 | compliments.updateCompliment = function() { 19 | 20 | var _list = []; 21 | 22 | var hour = moment().hour(); 23 | 24 | // In the followign if statement we use .slice() on the 25 | // compliments array to make a copy by value. 26 | // This way the original array of compliments stays in tact. 27 | 28 | if (hour >= compliments.params.morning 29 | && hour < compliments.params.afternoon) { 30 | // Morning compliments 31 | _list = compliments.complimentList['morning'].slice(); 32 | } else if (compliments.params.afternoon 33 | && hour < compliments.params.evening) { 34 | // Afternoon compliments 35 | _list = compliments.complimentList['afternoon'].slice(); 36 | } else if (hour >= compliments.params.evening 37 | || hour < compliments.params.morning) { 38 | // Evening compliments 39 | _list = compliments.complimentList['evening'].slice(); 40 | } else { 41 | // Edge case in case something weird happens 42 | // This will select a compliment from all times of day 43 | Object.keys(compliments.complimentList).forEach(function(_curr) { 44 | _list = _list.concat(compliments.complimentList[_curr]).slice(); 45 | }); 46 | } 47 | 48 | // Search for the location of the current compliment in the list 49 | var _spliceIndex = _list.indexOf(compliments.currentCompliment); 50 | 51 | // If it exists, remove it so we don't see it again 52 | if (_spliceIndex !== -1) { 53 | _list.splice(_spliceIndex, 1); 54 | } 55 | 56 | // Randomly select a location 57 | var _randomIndex = Math.floor(Math.random() * _list.length); 58 | compliments.currentCompliment = _list[_randomIndex]; 59 | 60 | $('.compliment').updateWithText(compliments.currentCompliment, 61 | compliments.fadeInterval); 62 | 63 | } 64 | 65 | compliments.init = function() { 66 | 67 | this.updateCompliment(); 68 | 69 | this.intervalId = setInterval(function() { 70 | this.updateCompliment(); 71 | }.bind(this), this.updateInterval) 72 | 73 | } -------------------------------------------------------------------------------- /server/src/main/webapp/resources/js/config-default.js: -------------------------------------------------------------------------------- 1 | var config = { 2 | lang: 'zh-cn', 3 | time: { 4 | timeFormat: 24, // 时间格式 5 | }, 6 | weather: { 7 | params: { 8 | q: 'Shanghai', // 城市 9 | units: 'metric', 10 | //lang: 'zh-cn', 11 | APPID: '***' // 在 OpenWeatherMap 网站上注册的 APPID 12 | }, 13 | interval: 60000, 14 | fadeInterval: 1000 15 | }, 16 | aqi: { 17 | city: 'shanghai', // 城市 18 | AppKey: '***', // 在 PM25.in 网站上注册的 AppKey 19 | interval: 300000, 20 | fadeInterval: 1000 21 | }, 22 | ai: { 23 | AppKey: '***', // 在 tuling123.com 网站上注册的 Key 24 | loc: '上海市', // 城市名,中文 25 | id: 12345678, // 会话 ID,任意设置一个数字 26 | fadeInterval: 500, 27 | cleanInterval: 900000 // 最后一次接收到新的对话请求之后的等待清理对话列表时间 28 | }, 29 | compliments: { 30 | interval: 30000, 31 | fadeInterval: 4000, 32 | timeParams: { 33 | morning: 3, 34 | afternoon: 12, 35 | evening: 18 36 | }, 37 | morning: [ 38 | '喜欢清晨的阳光啊', 39 | '昨晚睡得好吗', 40 | '啊哈,又没吃早饭吧' 41 | ], 42 | afternoon: [ 43 | '休息,休息一下', 44 | '喝杯下午茶吧', 45 | '今天晚上吃什么' 46 | ], 47 | evening: [ 48 | '太阳下山啦', 49 | '要不要来点夜宵', 50 | '晚安' 51 | ] 52 | }, 53 | calendar: { 54 | maximumEntries: 10, // 可以显示的最大日程数量 55 | url: "https://p01-calendarws.icloud.com/ca/subscribe/1/n6x7Farxpt7m9S8bHg1TGArSj7J6kanm_2KEoJPL5YIAk3y70FpRo4GyWwO-6QfHSY5mXtHcRGVxYZUf7U3HPDOTG5x0qYnno1Zr_VuKH2M" 56 | }, 57 | news: { 58 | //feed: 'http://news.baidu.com/n?cmd=1&class=finannews&tn=rss' 59 | feed: 'http://rss.sina.com.cn/news/china/focus15.xml', 60 | } 61 | } 62 | 63 | config.doGet = function(url) { 64 | try { 65 | xmlhttp = window.XMLHttpRequest ? new XMLHttpRequest() 66 | : new ActiveXObject("Microsoft.XMLHTTP"); 67 | } catch (e) { 68 | } 69 | 70 | xmlhttp.onreadystatechange = function() { 71 | if ((xmlhttp.readyState == 4) && (xmlhttp.status == 200)) { 72 | console.log(xmlhttp.responseText); 73 | } 74 | }; 75 | xmlhttp.open("GET", url, true); 76 | xmlhttp.send(null); 77 | } 78 | 79 | config.getParams = function(callback) { 80 | var aiParams = new Object(); 81 | aiParams.key = ai.appKey; 82 | aiParams.loc = encodeURIComponent(ai.loc); 83 | aiParams.id = ai.id; 84 | url = "init?ai=" + JSON.stringify(aiParams); 85 | 86 | callback(url); 87 | } 88 | 89 | config.init = function() { 90 | this.getParams(this.doGet); 91 | } 92 | -------------------------------------------------------------------------------- /server/src/main/webapp/resources/js/ical_parser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Javascript ical Parser 3 | * Proof of concept method of reading icalendar (.ics) files with javascript. 4 | * 5 | * @author: Carl Saggs 6 | * @source: https://github.com/thybag/ 7 | * @version: 0.2 8 | */ 9 | function ical_parser(feed_url, callback){ 10 | //store of unproccesed data. 11 | this.raw_data = null; 12 | //Store of proccessed data. 13 | this.events = []; 14 | 15 | /** 16 | * loadFile 17 | * Using AJAX to load the requested .ics file, passing it to the callback when completed. 18 | * @param url URL of .ics file 19 | * @param callback Function to call on completion. 20 | */ 21 | this.loadFile = function(url, callback){ 22 | //Create request object 23 | try {xmlhttp = window.XMLHttpRequest?new XMLHttpRequest(): new ActiveXObject("Microsoft.XMLHTTP");} catch (e) { } 24 | //Grab file 25 | xmlhttp.onreadystatechange = function(){ 26 | if ((xmlhttp.readyState == 4) && (xmlhttp.status == 200)) { 27 | //On success, run callback. 28 | callback(xmlhttp.responseText); 29 | } 30 | } 31 | xmlhttp.open("GET", url, true); 32 | xmlhttp.send(null); 33 | } 34 | 35 | /** 36 | * makeDate 37 | * Convert the dateformat used by ICalendar in to one more suitable for javascript. 38 | * @param String ical_date 39 | * @return dt object, includes javascript Date + day name, hour/minutes/day/month/year etc. 40 | */ 41 | this.makeDate = function(ical_date){ 42 | //break date apart 43 | var dtutc = { 44 | year: ical_date.substr(0,4), 45 | month: ical_date.substr(4,2), 46 | day: ical_date.substr(6,2), 47 | hour: ical_date.substr(9,2), 48 | minute: ical_date.substr(11,2) 49 | } 50 | //Create JS date (months start at 0 in JS - don't ask) 51 | var utcdatems = Date.UTC(dtutc.year, (dtutc.month-1), dtutc.day, dtutc.hour, dtutc.minute); 52 | var dt = {}; 53 | dt.date = new Date(utcdatems); 54 | 55 | dt.year = dt.date.getFullYear(); 56 | dt.month = ('0' + (dt.date.getMonth()+1)).slice(-2); 57 | dt.day = ('0' + dt.date.getDate()).slice(-2); 58 | dt.hour = ('0' + dt.date.getHours()).slice(-2); 59 | dt.minute = ('0' + dt.date.getMinutes()).slice(-2); 60 | 61 | //Get the full name of the given day 62 | dt.dayname =["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"][dt.date.getDay()]; 63 | dt.monthname = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ][dt.date.getMonth()] ; 64 | 65 | return dt; 66 | } 67 | 68 | /** 69 | * parseICAL 70 | * Convert the ICAL format in to a number of javascript objects (Each representing a date) 71 | * 72 | * @param data Raw ICAL data 73 | */ 74 | this.parseICAL = function(data){ 75 | //Ensure cal is empty 76 | this.events = []; 77 | 78 | //Clean string and split the file so we can handle it (line by line) 79 | cal_array = data.replace(new RegExp( "\\r", "g" ), "").replace(/\n /g,"").split("\n"); 80 | 81 | //Keep track of when we are activly parsing an event 82 | var in_event = false; 83 | //Use as a holder for the current event being proccessed. 84 | var cur_event = null; 85 | for(var i=0;i') 137 | .replace(/\\n/g,'
') 138 | .replace(/\\,/g,','); 139 | } 140 | 141 | //Add the value to our event object. 142 | cur_event[type] = val; 143 | } 144 | } 145 | //Run this to finish proccessing our Events. 146 | this.complete(); 147 | } 148 | /** 149 | * complete 150 | * Sort all events in to a sensible order and run the original callback 151 | */ 152 | this.complete = function(){ 153 | //Sort the data so its in date order. 154 | this.events.sort(function(a,b){ 155 | return a.DTSTART-b.DTSTART; 156 | }); 157 | //Run callback method, if was defined. (return self) 158 | if(typeof callback == 'function') callback(this); 159 | } 160 | /** 161 | * getEvents 162 | * return all events found in the ical file. 163 | * 164 | * @return list of events objects 165 | */ 166 | this.getEvents = function(){ 167 | return this.events; 168 | } 169 | 170 | /** 171 | * getFutureEvents 172 | * return all events sheduled to take place after the current date. 173 | * 174 | * @return list of events objects 175 | */ 176 | this.getFutureEvents = function(){ 177 | var future_events = [], current_date = new Date(); 178 | 179 | this.events.forEach(function(itm){ 180 | //If the event ends after the current time, add it to the array to return. 181 | if(itm.DTEND > current_date) future_events.push(itm); 182 | }); 183 | return future_events; 184 | } 185 | 186 | /** 187 | * getPastEvents 188 | * return all events sheduled to take place before the current date. 189 | * 190 | * @return list of events objects 191 | */ 192 | this.getPastEvents = function(){ 193 | var past_events = [], current_date = new Date(); 194 | 195 | this.events.forEach(function(itm){ 196 | //If the event ended before the current time, add it to the array to return. 197 | if(itm.DTEND <= current_date) past_events.push(itm); 198 | }); 199 | return past_events.reverse(); 200 | } 201 | 202 | /** 203 | * load 204 | * load a new ICAL file. 205 | * 206 | * @param ical file url 207 | */ 208 | this.load = function(ical_file){ 209 | var tmp_this = this; 210 | this.raw_data = null; 211 | this.loadFile(ical_file, function(data){ 212 | //if the file loads, store the data and invoke the parser 213 | tmp_this.raw_data = data; 214 | tmp_this.parseICAL(data); 215 | }); 216 | } 217 | 218 | //Store this so we can use it in the callback from the load function. 219 | var tmp_this = this; 220 | //Store the feed url 221 | this.feed_url = feed_url; 222 | //Load the file 223 | this.load(this.feed_url); 224 | } 225 | -------------------------------------------------------------------------------- /server/src/main/webapp/resources/js/jquery.feedToJSON.js: -------------------------------------------------------------------------------- 1 | //jQuery extension to fetch an rss feed and return it as json via YQL 2 | //created by dboz@airshp.com 3 | (function($) { 4 | 5 | $.extend({ 6 | feedToJson: function(options, callback) { 7 | if ($.isFunction(options)) { 8 | callback = options; 9 | options = null; 10 | } 11 | options = $.extend($.feedToJson.defaults,options); 12 | var url = options.yqlURL + options.yqlQS + "'" + encodeURIComponent(options.feed) + "'" + "&_nocache=" + options.cacheBuster; 13 | return $.getJSON(url, function(data){ 14 | //console.log(data.query.results); 15 | data = data.query.results; 16 | $.isFunction(callback) && callback(data); //allows the callback function to be the only option 17 | $.isFunction(options.success) && options.success(data); 18 | }); 19 | } 20 | }); 21 | 22 | //defaults 23 | $.feedToJson.defaults = { 24 | yqlURL : 'http://query.yahooapis.com/v1/public/yql', //yql 25 | yqlQS : '?format=json&callback=?&q=select%20*%20from%20rss%20where%20url%3D', //yql query string 26 | feed:'http://instagr.am/tags/tacos/feed/recent.rss', //instagram recent posts tagged 'tacos' 27 | cachebuster: Math.floor((new Date().getTime()) / 1200 / 1000), //yql caches feeds, so we change the feed url every 20min 28 | success:null //success callback 29 | }; 30 | 31 | })(jQuery); 32 | // eo feedToJson 33 | 34 | 35 | -------------------------------------------------------------------------------- /server/src/main/webapp/resources/js/main.js: -------------------------------------------------------------------------------- 1 | jQuery.fn.updateWithText = function(text, speed) 2 | { 3 | var dummy = $('
').html(text); 4 | 5 | if ($(this).html() != dummy.html()) 6 | { 7 | $(this).fadeOut(speed/2, function() { 8 | $(this).html(text); 9 | $(this).fadeIn(speed/2, function() { 10 | //done 11 | }); 12 | }); 13 | } 14 | } 15 | 16 | jQuery.fn.outerHTML = function(s) { 17 | return s 18 | ? this.before(s).remove() 19 | : jQuery("

").append(this.eq(0).clone()).html(); 20 | }; 21 | 22 | function roundVal(temp) 23 | { 24 | return Math.round(temp * 10) / 10; 25 | } 26 | 27 | jQuery(document).ready(function($) { 28 | 29 | var eventList = []; 30 | var lastCompliment; 31 | var compliment; 32 | 33 | moment.locale(config.lang); 34 | 35 | config.init(); 36 | 37 | time.init(); 38 | 39 | calendar.init(); 40 | 41 | compliments.init(); 42 | 43 | weather.init(); 44 | 45 | // aqi.init(); 46 | 47 | ai.init(); 48 | 49 | news.init(); 50 | 51 | }); 52 | -------------------------------------------------------------------------------- /server/src/main/webapp/resources/js/news/news.js: -------------------------------------------------------------------------------- 1 | // A lot of this code is from the original feedToJson function that was included with this project 2 | // The new code allows for multiple feeds to be used but a bunch of variables and such have literally been copied and pasted into this code and some help from here: http://jsfiddle.net/BDK46/ 3 | // The original version can be found here: http://airshp.com/2011/jquery-plugin-feed-to-json/ 4 | var news = { 5 | feed: config.news.feed || null, 6 | newsLocation: '.news', 7 | newsItems: [], 8 | seenNewsItem: [], 9 | _yqURL: 'http://query.yahooapis.com/v1/public/yql', 10 | _yqlQS: '?format=json&q=select%20*%20from%20rss%20where%20url%3D', 11 | _cacheBuster: Math.floor((new Date().getTime()) / 1200 / 1000), 12 | _failedAttempts: 0, 13 | fetchInterval: config.news.fetchInterval || 60000, 14 | updateInterval: config.news.interval || 5500, 15 | fadeInterval: 2000, 16 | intervalId: null, 17 | fetchNewsIntervalId: null 18 | } 19 | 20 | /** 21 | * Creates the query string that will be used to grab a converted RSS feed into a JSON object via Yahoo 22 | * @param {string} feed The original location of the RSS feed 23 | * @return {string} The new location of the RSS feed provided by Yahoo 24 | */ 25 | news.buildQueryString = function (feed) { 26 | 27 | return this._yqURL + this._yqlQS + '\'' + encodeURIComponent(feed) + '\''; 28 | 29 | } 30 | 31 | /** 32 | * Fetches the news for each feed provided in the config file 33 | */ 34 | news.fetchNews = function () { 35 | 36 | // Reset the news feed 37 | this.newsItems = []; 38 | 39 | this.feed.forEach(function (_curr) { 40 | 41 | var _yqUrlString = this.buildQueryString(_curr); 42 | this.fetchFeed(_yqUrlString); 43 | 44 | }.bind(this)); 45 | 46 | } 47 | 48 | /** 49 | * Runs a GET request to Yahoo's service 50 | * @param {string} yqUrl The URL being used to grab the RSS feed (in JSON format) 51 | */ 52 | news.fetchFeed = function (yqUrl) { 53 | 54 | $.ajax({ 55 | type: 'GET', 56 | datatype:'jsonp', 57 | url: yqUrl, 58 | success: function (data) { 59 | 60 | if (data.query.count > 0) { 61 | this.parseFeed(data.query.results.item); 62 | } else { 63 | console.error('No feed results for: ' + yqUrl); 64 | } 65 | 66 | }.bind(this), 67 | error: function () { 68 | // non-specific error message that should be updated 69 | console.error('No feed results for: ' + yqUrl); 70 | } 71 | }); 72 | 73 | } 74 | 75 | /** 76 | * Parses each item in a single news feed 77 | * @param {Object} data The news feed that was returned by Yahoo 78 | * @return {boolean} Confirms that the feed was parsed correctly 79 | */ 80 | news.parseFeed = function (data) { 81 | 82 | var _rssItems = []; 83 | 84 | for (var i = 0, count = data.length; i < count; i++) { 85 | 86 | _rssItems.push(data[i].title); 87 | 88 | } 89 | 90 | this.newsItems = this.newsItems.concat(_rssItems); 91 | 92 | return true; 93 | 94 | } 95 | 96 | /** 97 | * Loops through each available and unseen news feed after it has been retrieved from Yahoo and shows it on the screen 98 | * When all news titles have been exhausted, the list resets and randomly chooses from the original set of items 99 | * @return {boolean} Confirms that there is a list of news items to loop through and that one has been shown on the screen 100 | */ 101 | news.showNews = function () { 102 | 103 | // If all items have been seen, swap seen to unseen 104 | if (this.newsItems.length === 0 && this.seenNewsItem.length !== 0) { 105 | 106 | if (this._failedAttempts === 20) { 107 | console.error('Failed to show a news story 20 times, stopping any attempts'); 108 | return false; 109 | } 110 | 111 | this._failedAttempts++; 112 | 113 | setTimeout(function () { 114 | this.showNews(); 115 | }.bind(this), 3000); 116 | 117 | } else if (this.newsItems.length === 0 && this.seenNewsItem.length !== 0) { 118 | this.newsItems = this.seenNewsItem.splice(0); 119 | } 120 | 121 | var _location = Math.floor(Math.random() * this.newsItems.length); 122 | 123 | var _item = news.newsItems.splice(_location, 1)[0]; 124 | 125 | this.seenNewsItem.push(_item); 126 | 127 | $(this.newsLocation).updateWithText(_item, this.fadeInterval); 128 | 129 | return true; 130 | 131 | } 132 | 133 | news.init = function () { 134 | 135 | if (this.feed === null || (this.feed instanceof Array === false && typeof this.feed !== 'string')) { 136 | return false; 137 | } else if (typeof this.feed === 'string') { 138 | this.feed = [this.feed]; 139 | } 140 | 141 | this.fetchNews(); 142 | this.showNews(); 143 | 144 | this.fetchNewsIntervalId = setInterval(function () { 145 | this.fetchNews() 146 | }.bind(this), this.fetchInterval) 147 | 148 | this.intervalId = setInterval(function () { 149 | this.showNews(); 150 | }.bind(this), this.updateInterval); 151 | 152 | } -------------------------------------------------------------------------------- /server/src/main/webapp/resources/js/time/time.js: -------------------------------------------------------------------------------- 1 | var time = { 2 | timeFormat: config.time.timeFormat || 24, 3 | dateLocation: '.date', 4 | timeLocation: '.time', 5 | updateInterval: 1000, 6 | intervalId: null 7 | }; 8 | 9 | /** 10 | * Updates the time that is shown on the screen 11 | */ 12 | time.updateTime = function () { 13 | 14 | var _now = moment(), 15 | _date = _now.format('LL, dddd'); 16 | 17 | $(this.dateLocation).html(_date); 18 | $(this.timeLocation).html(_now.format(this._timeFormat+':mm[]ss[]')); 19 | 20 | } 21 | 22 | time.init = function () { 23 | 24 | if (parseInt(time.timeFormat) === 12) { 25 | time._timeFormat = 'hh' 26 | } else { 27 | time._timeFormat = 'HH'; 28 | } 29 | 30 | this.intervalId = setInterval(function () { 31 | this.updateTime(); 32 | }.bind(this), 1000); 33 | 34 | } -------------------------------------------------------------------------------- /server/src/main/webapp/resources/js/weather/weather.js: -------------------------------------------------------------------------------- 1 | var weather = { 2 | // Default language is Dutch because that is what the original author used 3 | lang: config.lang || 'zh-cn', 4 | params: config.weather.params || null, 5 | iconTable: { 6 | '01d':'wi-day-sunny', 7 | '02d':'wi-day-cloudy', 8 | '03d':'wi-cloudy', 9 | '04d':'wi-cloudy-windy', 10 | '09d':'wi-showers', 11 | '10d':'wi-rain', 12 | '11d':'wi-thunderstorm', 13 | '13d':'wi-snow', 14 | '50d':'wi-fog', 15 | '01n':'wi-night-clear', 16 | '02n':'wi-night-cloudy', 17 | '03n':'wi-night-cloudy', 18 | '04n':'wi-night-cloudy', 19 | '09n':'wi-night-showers', 20 | '10n':'wi-night-rain', 21 | '11n':'wi-night-thunderstorm', 22 | '13n':'wi-night-snow', 23 | '50n':'wi-night-alt-cloudy-windy' 24 | }, 25 | temperatureLocation: '.temp', 26 | windSunLocation: '.windsun', 27 | forecastLocation: '.forecast', 28 | apiVersion: '2.5', 29 | apiBase: 'http://api.openweathermap.org/data/', 30 | weatherEndpoint: 'weather', 31 | forecastEndpoint: 'forecast/daily', 32 | updateInterval: config.weather.interval || 6000, 33 | fadeInterval: config.weather.fadeInterval || 1000, 34 | intervalId: null 35 | } 36 | 37 | /** 38 | * Rounds a float to one decimal place 39 | * @param {float} temperature The temperature to be rounded 40 | * @return {float} The new floating point value 41 | */ 42 | weather.roundValue = function (temperature) { 43 | return parseFloat(temperature).toFixed(1); 44 | } 45 | 46 | /** 47 | * Converts the wind speed (km/h) into the values given by the Beaufort Wind Scale 48 | * @see http://www.spc.noaa.gov/faq/tornado/beaufort.html 49 | * @param {int} kmh The wind speed in Kilometers Per Hour 50 | * @return {int} The wind speed converted into its corresponding Beaufort number 51 | */ 52 | weather.ms2Beaufort = function(ms) { 53 | var kmh = ms * 60 * 60 / 1000; 54 | var speeds = [1, 5, 11, 19, 28, 38, 49, 61, 74, 88, 102, 117, 1000]; 55 | for (var beaufort in speeds) { 56 | var speed = speeds[beaufort]; 57 | if (speed > kmh) { 58 | return beaufort; 59 | } 60 | } 61 | return 12; 62 | } 63 | 64 | /** 65 | * Retrieves the current temperature and weather patter from the OpenWeatherMap API 66 | */ 67 | weather.updateCurrentWeather = function () { 68 | 69 | $.ajax({ 70 | type: 'GET', 71 | url: weather.apiBase + '/' + weather.apiVersion + '/' + weather.weatherEndpoint, 72 | dataType: 'json', 73 | data: weather.params, 74 | success: function (data) { 75 | 76 | var _temperature = this.roundValue(data.main.temp), 77 | _temperatureMin = this.roundValue(data.main.temp_min), 78 | _temperatureMax = this.roundValue(data.main.temp_max), 79 | _wind = this.roundValue(data.wind.speed), 80 | _iconClass = this.iconTable[data.weather[0].icon]; 81 | 82 | var _icon = ''; 83 | 84 | var _newTempHtml = _icon + '' + _temperature + '°'; 85 | 86 | $(this.temperatureLocation).updateWithText(_newTempHtml, this.fadeInterval); 87 | 88 | var _now = moment().format('HH:mm'), 89 | _sunrise = moment(data.sys.sunrise*1000).format('HH:mm'), 90 | _sunset = moment(data.sys.sunset*1000).format('HH:mm'); 91 | 92 | var _newWindHtml = ' ' + this.ms2Beaufort(_wind), 93 | _newSunHtml = ' ' + _sunrise; 94 | 95 | if (_sunrise < _now && _sunset > _now) { 96 | _newSunHtml = ' ' + _sunset; 97 | } 98 | 99 | $(this.windSunLocation).updateWithText(_newWindHtml + ' ' + _newSunHtml, this.fadeInterval); 100 | 101 | }.bind(this), 102 | error: function () { 103 | 104 | } 105 | }); 106 | 107 | } 108 | 109 | /** 110 | * Updates the 5 Day Forecast from the OpenWeatherMap API 111 | */ 112 | weather.updateWeatherForecast = function () { 113 | 114 | $.ajax({ 115 | type: 'GET', 116 | url: weather.apiBase + '/' + weather.apiVersion + '/' + weather.forecastEndpoint, 117 | data: weather.params, 118 | success: function (data) { 119 | 120 | var _opacity = 1, 121 | _forecastHtml = ''; 122 | 123 | _forecastHtml += ''; 124 | 125 | for (var i = 0, count = data.list.length; i < count; i++) { 126 | 127 | var _forecast = data.list[i]; 128 | 129 | _forecastHtml += ''; 130 | 131 | _forecastHtml += ''; 132 | _forecastHtml += ''; 133 | _forecastHtml += ''; 134 | _forecastHtml += ''; 135 | 136 | _forecastHtml += ''; 137 | 138 | _opacity -= 0.155; 139 | 140 | } 141 | 142 | _forecastHtml += '
' + moment(_forecast.dt, 'X').format('ddd') + '' + this.roundValue(_forecast.temp.max) + '' + this.roundValue(_forecast.temp.min) + '
'; 143 | 144 | $(this.forecastLocation).updateWithText(_forecastHtml, this.fadeInterval); 145 | 146 | }.bind(this), 147 | error: function () { 148 | 149 | } 150 | }); 151 | 152 | } 153 | 154 | weather.init = function () { 155 | 156 | if (this.params.lang === undefined) { 157 | this.params.lang = this.lang; 158 | } 159 | 160 | if (this.params.cnt === undefined) { 161 | this.params.cnt = 5; 162 | } 163 | 164 | this.intervalId = setInterval(function () { 165 | this.updateCurrentWeather(); 166 | this.updateWeatherForecast(); 167 | }.bind(this), this.updateInterval); 168 | 169 | } 170 | -------------------------------------------------------------------------------- /server/src/main/webapp/resources/js/websocket.js: -------------------------------------------------------------------------------- 1 | function websocket(feed_url, callback) { 2 | var websocket = null; 3 | 4 | //判断当前浏览器是否支持WebSocket 5 | if ('WebSocket' in window) { 6 | var loc = window.location, new_uri; 7 | if (loc.protocol === "https:") { 8 | new_uri = "wss:"; 9 | } else { 10 | new_uri = "ws:"; 11 | } 12 | new_uri += "//" + feed_url; 13 | websocket = new WebSocket(new_uri); 14 | //websocket = new WebSocket(feed_url); 15 | } else { 16 | alert('Not support websocket') 17 | } 18 | 19 | //连接发生错误的回调方法 20 | websocket.onerror = function() { 21 | console.error("Websocket error!"); 22 | }; 23 | 24 | //连接成功建立的回调方法 25 | websocket.onopen = function(event) { 26 | console.log("WebSocket opened."); 27 | } 28 | 29 | //接收到消息的回调方法 30 | websocket.onmessage = function(event) { 31 | callback(event.data); 32 | } 33 | 34 | //连接关闭的回调方法 35 | websocket.onclose = function() { 36 | console.log("WebSocket closed."); 37 | } 38 | 39 | //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。 40 | window.onbeforeunload = function() { 41 | websocket.close(); 42 | } 43 | 44 | //关闭连接 45 | function closeWebSocket() { 46 | websocket.close(); 47 | } 48 | 49 | //发送消息 50 | function send(message) { 51 | websocket.send(message); 52 | } 53 | } -------------------------------------------------------------------------------- /server/src/main/webapp/talk.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Magic Mirror 4 | 5 | 6 | 7 | 9 | 11 | 13 | 14 | 15 | 16 |

17 |
Welcome
18 |
19 | 21 | 22 |
23 |
24 | 25 | --------------------------------------------------------------------------------