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 |
19 |
25 |
30 |
35 |
38 |
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 += '' + moment(_forecast.dt, 'X').format('ddd') + ' ';
132 | _forecastHtml += ' ';
133 | _forecastHtml += '' + this.roundValue(_forecast.temp.max) + ' ';
134 | _forecastHtml += '' + this.roundValue(_forecast.temp.min) + ' ';
135 |
136 | _forecastHtml += ' ';
137 |
138 | _opacity -= 0.155;
139 |
140 | }
141 |
142 | _forecastHtml += '
';
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 |