Initial commit
This commit is contained in:
commit
cdc58cd438
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
dist/*
|
||||
node_modules/*
|
||||
parser.ts
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2018
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
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.
|
22
README.md
Normal file
22
README.md
Normal file
|
@ -0,0 +1,22 @@
|
|||
# Twilio Time & Temperature in Typescript
|
||||
Make your own Time and Temperature phone number!
|
||||
|
||||
A rewrite of https://gitlab.com/zyphlar/twilio-time-and-temperature in Typescript.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A Twilio account
|
||||
- NodeJS 18.x
|
||||
- Ideally, AWS Lambda for hosting, but not required
|
||||
|
||||
## Configuration/Running/Building
|
||||
|
||||
- Copy `util/parser.orig.ts` to `util/parser.ts` and edit it to match your desired Twilio phone numbers and locales.
|
||||
- For easy dev, run `npm run dev-build-start`.
|
||||
- Access time-temp.php to ensure it's working (should generate Twilio-compatible XML aka TwiML.)
|
||||
|
||||
## Deployment
|
||||
|
||||
- To deploy to AWS Lambda, run `npm run build` and upload dist/index.zip to a new Lambda function.
|
||||
- Set your Twilio phone number(s) to send an HTTP POST WebHook to the Lambda function.
|
||||
- Call the number to test!
|
3213
package-lock.json
generated
Normal file
3213
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
38
package.json
Normal file
38
package.json
Normal file
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"name": "time-temp-typescript",
|
||||
"version": "0.0.1",
|
||||
"description": "Time and Temperature phone service for Typescript and AWS Lambda",
|
||||
"author": "zyphlar",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"prebuild": "rm -rf dist",
|
||||
"build": "esbuild src/index.ts --bundle --loader:.pug=file --sourcemap --platform=node --target=es2020 --outfile=dist/index.js",
|
||||
"postbuild": "cd dist && zip -r index.zip index.js* *.pug",
|
||||
"build-dev": "esbuild src/server.ts --bundle --loader:.pug=file --sourcemap --platform=node --target=es2020 --outfile=dist/server.js",
|
||||
"serve": "cd dist && node server.js",
|
||||
"start": "npm run serve",
|
||||
"dev-build-start": "npm run build-dev && npm run serve",
|
||||
"local-lambda": "cp util/test-lambda-event.json dist/event.json && cd dist && node -e \"console.log(require('./index').handler(require('./event.json')));\""
|
||||
},
|
||||
"dependencies": {
|
||||
"aws-lambda": "^1.0.7",
|
||||
"body-parser": "^1.20.1",
|
||||
"dwml-to-json": "^0.1.0",
|
||||
"errorhandler": "^1.5.1",
|
||||
"express": "^4.18.2",
|
||||
"moment": "^2.29.4",
|
||||
"moment-timezone": "^0.5.40",
|
||||
"pug": "^3.0.2",
|
||||
"xml-js": "^1.6.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/aws-lambda": "^8.10.110",
|
||||
"@types/errorhandler": "^1.5.0",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/moment": "^2.13.0",
|
||||
"@types/moment-timezone": "^0.5.30",
|
||||
"@types/pug": "^2.0.6",
|
||||
"@types/xml-js": "^1.0.0",
|
||||
"esbuild": "^0.17.7"
|
||||
}
|
||||
}
|
20
src/app.ts
Normal file
20
src/app.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import express from "express";
|
||||
import path from "path";
|
||||
import bodyParser from "body-parser";
|
||||
|
||||
import * as homeController from "./controllers/home";
|
||||
//import * as apiController from "./controllers/api";
|
||||
|
||||
const app = express();
|
||||
|
||||
app.set("port", process.env.PORT || 3000);
|
||||
// app.set("views", path.join(__dirname, "../views"));
|
||||
// app.set("view engine", "pug");
|
||||
app.use(bodyParser.json());
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
|
||||
app.get("/", homeController.index);
|
||||
app.post("/", homeController.index);
|
||||
//app.get("/api", apiController.getApi);
|
||||
|
||||
export default app;
|
158
src/controllers/home.ts
Normal file
158
src/controllers/home.ts
Normal file
|
@ -0,0 +1,158 @@
|
|||
import { Request, Response } from "express";
|
||||
import pug from 'pug';
|
||||
import { getCompassDirection, getApiConfigs } from "util/parser";
|
||||
import pugWeather from 'views/weather.pug'
|
||||
var moment = require('moment-timezone');
|
||||
const convert = require("xml-js");
|
||||
var dwmlParser = require('dwml-to-json');
|
||||
|
||||
// $postDigits = &$_POST['Digits'];
|
||||
// $from = &$_POST['From'];
|
||||
// $to = &$_POST['To'];
|
||||
// $city = &$_GET['city'];
|
||||
|
||||
// if (array_key_exists('Digits',$_GET) || array_key_exists('From',$_GET) || array_key_exists('To',$_GET)) {
|
||||
// $postDigits = &$_GET['Digits'];
|
||||
// $from = &$_GET['From'];
|
||||
// $to = &$_GET['To'];
|
||||
// }
|
||||
|
||||
/**
|
||||
* Home page.
|
||||
* @route GET /
|
||||
*/
|
||||
export const index = async (req: Request, res: Response) => {
|
||||
console.error("Request: ", req.params, req.query);
|
||||
|
||||
var params;
|
||||
|
||||
if (req.params.length > 0) {
|
||||
params = req.params;
|
||||
} else {
|
||||
params = req.query;
|
||||
}
|
||||
|
||||
res.set('Content-Type', 'application/xml');
|
||||
const template = pug.compileFile(pugWeather);
|
||||
res.send(template(await renderTimeTemp(params)));
|
||||
};
|
||||
|
||||
/**
|
||||
* Home page via lambda
|
||||
*/
|
||||
export const lambda = async (queryParams) => {
|
||||
console.error("Params: ", queryParams);
|
||||
const template = pug.compileFile(pugWeather);
|
||||
return template(await renderTimeTemp(queryParams));
|
||||
};
|
||||
|
||||
|
||||
|
||||
/******** Begin Weather Parse ***********/
|
||||
|
||||
interface Location {
|
||||
region: string;
|
||||
latitude: string;
|
||||
longitude: string;
|
||||
elevation: string;
|
||||
wfo: string;
|
||||
timezone: string;
|
||||
areaDescription: string;
|
||||
radar: string;
|
||||
zone: string;
|
||||
county: string;
|
||||
firezone: string;
|
||||
metar: string;
|
||||
}
|
||||
|
||||
interface Time {
|
||||
layoutKey: string;
|
||||
startPeriodName: string[];
|
||||
startValidTime: string[];
|
||||
tempLabel: string[];
|
||||
}
|
||||
|
||||
interface Data {
|
||||
temperature: string[];
|
||||
pop: (string | null)[];
|
||||
weather: string[];
|
||||
iconLink: string[];
|
||||
hazard: any[];
|
||||
hazardUrl: any[];
|
||||
text: string[];
|
||||
}
|
||||
|
||||
interface CurrentObservation {
|
||||
id: string;
|
||||
name: string;
|
||||
elev: string;
|
||||
latitude: string;
|
||||
longitude: string;
|
||||
Date: string;
|
||||
Temp: string;
|
||||
Dewp: string;
|
||||
Relh: string;
|
||||
Winds: string;
|
||||
Windd: string;
|
||||
Gust: string;
|
||||
Weather: string;
|
||||
Weatherimage: string;
|
||||
Visibility: string;
|
||||
Altimeter: string;
|
||||
SLP: string;
|
||||
timezone: string;
|
||||
state: string;
|
||||
WindChill: string;
|
||||
}
|
||||
|
||||
interface WeatherData {
|
||||
operationalMode: string;
|
||||
srsName: string;
|
||||
creationDate: string;
|
||||
creationDateLocal: string;
|
||||
productionCenter: string;
|
||||
credit: string;
|
||||
moreInformation: string;
|
||||
location: Location;
|
||||
time: Time;
|
||||
data: Data;
|
||||
currentobservation: CurrentObservationObj;
|
||||
}
|
||||
|
||||
async function renderTimeTemp(params:Array<string,string>) {
|
||||
|
||||
// console.error("Got: ",params);
|
||||
var apiConfigs:ApiConfigs = getApiConfigs(params);
|
||||
|
||||
// console.error("Now: ", apiConfigs);
|
||||
|
||||
var responseCopy:any;
|
||||
const weatherData:WeatherData = await fetch(
|
||||
apiConfigs.weather_url,
|
||||
{
|
||||
headers: new Headers({
|
||||
"Accept" : "application/json",
|
||||
"Content-Type" : "application/json",
|
||||
"User-Agent" : "time-temp-typescript"
|
||||
})
|
||||
}
|
||||
)
|
||||
.then(res => { responseCopy = res.clone(); return res.json()})
|
||||
.catch(error => {
|
||||
console.error("Fetch error", error, responseCopy);
|
||||
});
|
||||
|
||||
// console.log(weatherData);
|
||||
|
||||
return {
|
||||
data: weatherData,
|
||||
getCompassDirection: getCompassDirection,
|
||||
now: moment().tz(apiConfigs.tz).format('h:mm:ss A, MMMM D, YYYY'),
|
||||
voiceA: "Polly.Matthew-Neural",
|
||||
voiceB: "Polly.Joanna-Neural",
|
||||
apiConfigs: apiConfigs,
|
||||
formatObsDate: (d) => moment(d, "DD MMM hh:mm A Z").format('h:mm A'),
|
||||
convertFtoC: (f) => Math.round((f - 32) / 1.8),
|
||||
getLastPartOfLocation: (l) => { var match = l.match("^(.*)[-,](.*)$"); if (match && match.length > 1) { return match[2]; } else { return l; } }
|
||||
};
|
||||
}
|
23
src/index.ts
Normal file
23
src/index.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { Context, APIGatewayProxyResult, APIGatewayEvent } from 'aws-lambda';
|
||||
const querystring = require('node:querystring');
|
||||
|
||||
import * as homeController from "./controllers/home";
|
||||
|
||||
export const handler = async (event: APIGatewayEvent, context: Context): Promise<APIGatewayProxyResult> => {
|
||||
var params;
|
||||
console.error("Event", event);
|
||||
|
||||
if (event.isBase64Encoded && event.body != "") {
|
||||
const postVars = Buffer.from(event.body, 'base64').toString('utf8');
|
||||
params = querystring.parse(postVars);
|
||||
} else {
|
||||
params = event.queryStringParameters;
|
||||
}
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: {"content-type": "application/xml"},
|
||||
body: await homeController.lambda(params)
|
||||
};
|
||||
|
||||
};
|
16
src/server.ts
Normal file
16
src/server.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
// import errorHandler from "errorhandler";
|
||||
import app from "./app";
|
||||
|
||||
/**
|
||||
* Start Express server.
|
||||
*/
|
||||
const server = app.listen(app.get("port"), () => {
|
||||
console.log(
|
||||
" App is running at http://localhost:%d in %s mode",
|
||||
app.get("port"),
|
||||
app.get("env")
|
||||
);
|
||||
console.log(" Press CTRL-C to stop\n");
|
||||
});
|
||||
|
||||
export default server;
|
23
tsconfig.json
Normal file
23
tsconfig.json
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"target": "es6",
|
||||
"noImplicitAny": true,
|
||||
"moduleResolution": "node",
|
||||
"sourceMap": true,
|
||||
"outDir": "dist",
|
||||
"baseUrl": ".",
|
||||
"rootDir": ".",
|
||||
"paths": {
|
||||
"*": [
|
||||
"node_modules/*"
|
||||
],
|
||||
"util/*": [ "util/*" ],
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
]
|
||||
}
|
87
util/parser.orig.ts
Normal file
87
util/parser.orig.ts
Normal file
|
@ -0,0 +1,87 @@
|
|||
var moment = require('moment');
|
||||
|
||||
export interface ApiConfigs {
|
||||
weather_url: string;
|
||||
city_name: string;
|
||||
tz: string;
|
||||
}
|
||||
|
||||
export var getApiConfigs:ApiConfigs = function(params:Array<string,string>) {
|
||||
var first_number = "+12225551212";
|
||||
var second_number = "+13335554343";
|
||||
|
||||
var now = moment().format('Y-m-d');
|
||||
|
||||
var out:ApiConfigs = {};
|
||||
|
||||
if (params['To'] == first_number || params['city'] == "first") {
|
||||
out.weather_url = "https://forecast.weather.gov/MapClick.php?lat=34.052&lon=-118.243&unit=0&lg=english&FcstType=json";
|
||||
out.city_name = "Los Angeles";
|
||||
out.tz = "America/Los_Angeles";
|
||||
} else if (params['To'] == second_number || params['city'] == "second") {
|
||||
out.weather_url = "https://forecast.weather.gov/MapClick.php?lat=39.739&lon=-104.984&unit=0&lg=english&FcstType=json";
|
||||
out.city_name = "Denver";
|
||||
out.tz = "America/Denver";
|
||||
} else {
|
||||
out.weather_url = "https://forecast.weather.gov/MapClick.php?lat=40.7146&lon=-74.0071&unit=0&lg=english&FcstType=json";
|
||||
out.city_name = "New York";
|
||||
out.tz = "America/New_York";
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
export var getCompassDirection = function(bearing:number) {
|
||||
var direction:string;
|
||||
const tmp = Math.round(bearing / 22.5);
|
||||
switch(tmp) {
|
||||
case 1:
|
||||
direction = "North Northeast";
|
||||
break;
|
||||
case 2:
|
||||
direction = "North East";
|
||||
break;
|
||||
case 3:
|
||||
direction = "East Northeast";
|
||||
break;
|
||||
case 4:
|
||||
direction = "East";
|
||||
break;
|
||||
case 5:
|
||||
direction = "East Southeast";
|
||||
break;
|
||||
case 6:
|
||||
direction = "South East";
|
||||
break;
|
||||
case 7:
|
||||
direction = "South Southeast";
|
||||
break;
|
||||
case 8:
|
||||
direction = "South";
|
||||
break;
|
||||
case 9:
|
||||
direction = "South Southwest";
|
||||
break;
|
||||
case 10:
|
||||
direction = "South West";
|
||||
break;
|
||||
case 11:
|
||||
direction = "West Southwest";
|
||||
break;
|
||||
case 12:
|
||||
direction = "West";
|
||||
break;
|
||||
case 13:
|
||||
direction = "West Northwest";
|
||||
break;
|
||||
case 14:
|
||||
direction = "North West";
|
||||
break;
|
||||
case 15:
|
||||
direction = "North Northwest";
|
||||
break;
|
||||
default:
|
||||
direction = "North";
|
||||
}
|
||||
return direction;
|
||||
}
|
123
util/test-lambda-event.json
Normal file
123
util/test-lambda-event.json
Normal file
|
@ -0,0 +1,123 @@
|
|||
{
|
||||
"body": "dG89JTJCMTIyMjU1NTEyMTI",
|
||||
"resource": "/{proxy+}",
|
||||
"path": "/path/to/resource",
|
||||
"httpMethod": "POST",
|
||||
"isBase64Encoded": true,
|
||||
"queryStringParameters": {
|
||||
"foo": "bar"
|
||||
},
|
||||
"multiValueQueryStringParameters": {
|
||||
"foo": [
|
||||
"bar"
|
||||
]
|
||||
},
|
||||
"pathParameters": {
|
||||
"proxy": "/path/to/resource"
|
||||
},
|
||||
"stageVariables": {
|
||||
"baz": "qux"
|
||||
},
|
||||
"headers": {
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
|
||||
"Accept-Encoding": "gzip, deflate, sdch",
|
||||
"Accept-Language": "en-US,en;q=0.8",
|
||||
"Cache-Control": "max-age=0",
|
||||
"CloudFront-Forwarded-Proto": "https",
|
||||
"CloudFront-Is-Desktop-Viewer": "true",
|
||||
"CloudFront-Is-Mobile-Viewer": "false",
|
||||
"CloudFront-Is-SmartTV-Viewer": "false",
|
||||
"CloudFront-Is-Tablet-Viewer": "false",
|
||||
"CloudFront-Viewer-Country": "US",
|
||||
"Host": "1234567890.execute-api.us-east-1.amazonaws.com",
|
||||
"Upgrade-Insecure-Requests": "1",
|
||||
"User-Agent": "Custom User Agent String",
|
||||
"Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)",
|
||||
"X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==",
|
||||
"X-Forwarded-For": "127.0.0.1, 127.0.0.2",
|
||||
"X-Forwarded-Port": "443",
|
||||
"X-Forwarded-Proto": "https"
|
||||
},
|
||||
"multiValueHeaders": {
|
||||
"Accept": [
|
||||
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"
|
||||
],
|
||||
"Accept-Encoding": [
|
||||
"gzip, deflate, sdch"
|
||||
],
|
||||
"Accept-Language": [
|
||||
"en-US,en;q=0.8"
|
||||
],
|
||||
"Cache-Control": [
|
||||
"max-age=0"
|
||||
],
|
||||
"CloudFront-Forwarded-Proto": [
|
||||
"https"
|
||||
],
|
||||
"CloudFront-Is-Desktop-Viewer": [
|
||||
"true"
|
||||
],
|
||||
"CloudFront-Is-Mobile-Viewer": [
|
||||
"false"
|
||||
],
|
||||
"CloudFront-Is-SmartTV-Viewer": [
|
||||
"false"
|
||||
],
|
||||
"CloudFront-Is-Tablet-Viewer": [
|
||||
"false"
|
||||
],
|
||||
"CloudFront-Viewer-Country": [
|
||||
"US"
|
||||
],
|
||||
"Host": [
|
||||
"0123456789.execute-api.us-east-1.amazonaws.com"
|
||||
],
|
||||
"Upgrade-Insecure-Requests": [
|
||||
"1"
|
||||
],
|
||||
"User-Agent": [
|
||||
"Custom User Agent String"
|
||||
],
|
||||
"Via": [
|
||||
"1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)"
|
||||
],
|
||||
"X-Amz-Cf-Id": [
|
||||
"cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA=="
|
||||
],
|
||||
"X-Forwarded-For": [
|
||||
"127.0.0.1, 127.0.0.2"
|
||||
],
|
||||
"X-Forwarded-Port": [
|
||||
"443"
|
||||
],
|
||||
"X-Forwarded-Proto": [
|
||||
"https"
|
||||
]
|
||||
},
|
||||
"requestContext": {
|
||||
"accountId": "123456789012",
|
||||
"resourceId": "123456",
|
||||
"stage": "prod",
|
||||
"requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
|
||||
"requestTime": "09/Apr/2015:12:34:56 +0000",
|
||||
"requestTimeEpoch": 1428582896000,
|
||||
"identity": {
|
||||
"cognitoIdentityPoolId": null,
|
||||
"accountId": null,
|
||||
"cognitoIdentityId": null,
|
||||
"caller": null,
|
||||
"accessKey": null,
|
||||
"sourceIp": "127.0.0.1",
|
||||
"cognitoAuthenticationType": null,
|
||||
"cognitoAuthenticationProvider": null,
|
||||
"userArn": null,
|
||||
"userAgent": "Custom User Agent String",
|
||||
"user": null
|
||||
},
|
||||
"path": "/prod/path/to/resource",
|
||||
"resourcePath": "/{proxy+}",
|
||||
"httpMethod": "POST",
|
||||
"apiId": "1234567890",
|
||||
"protocol": "HTTP/1.1"
|
||||
}
|
||||
}
|
46
views/weather.pug
Normal file
46
views/weather.pug
Normal file
|
@ -0,0 +1,46 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
Response
|
||||
Gather(input="dtmf" timeout="5" numDigits="3")
|
||||
Say(voice=voiceA)
|
||||
| Hello. #{now}, was the current time when this call began.
|
||||
Say(voice=voiceB)
|
||||
| #{apiConfigs.city_name} Time and Temperature is a free hobby service, courtesy of your friendly local hackerspace, and the National Weather Service.
|
||||
Say(voice=voiceA)
|
||||
| It was #{data.currentobservation.Temp} degrees Fahrenheit or #{convertFtoC(data.currentobservation.Temp)} degrees Celsius at #{getLastPartOfLocation(data.currentobservation.name)} at #{formatObsDate(data.currentobservation.Date)}.
|
||||
| The relative humidity was #{data.currentobservation.Relh}%.
|
||||
| The dew point was #{data.currentobservation.Dewp} degrees.
|
||||
if data.currentobservation.WindChill != "NA"
|
||||
| The wind chill was #{data.currentobservation.WindChill} degrees.
|
||||
| The weather was #{data.currentobservation.Weather}.
|
||||
|
||||
| Winds were #{data.currentobservation.Winds} miles per hour coming from the #{getCompassDirection(data.currentobservation.Windd)}
|
||||
if data.currentobservation.Gust != "NA" && data.currentobservation.Gust != 0
|
||||
| and gusting to #{data.currentobservation.Gust} miles per hour.
|
||||
else
|
||||
| .
|
||||
|
||||
| The pressure was #{data.currentobservation.SLP} inches of mercury, which is
|
||||
if data.currentobservation.SLP > 30.4
|
||||
| very dry.
|
||||
else if data.currentobservation.SLP > 29.75
|
||||
| fair.
|
||||
else if data.currentobservation.SLP > 29.25
|
||||
| changing.
|
||||
else if data.currentobservation.SLP > 28.6
|
||||
| rainy.
|
||||
else if data.currentobservation.SLP > 0
|
||||
| stormy.
|
||||
|
||||
| The visibility was #{Math.round(data.currentobservation.Visibility)} miles.
|
||||
|
||||
Say(voice=voiceB)
|
||||
| Please press any key to hear this message again.
|
||||
// press two to hear the news, or press nine to hear a poem.
|
||||
|
||||
Say(voice=voiceB)
|
||||
| Thank you for calling.
|
||||
// Your fortune is: Your lover will never wish to leave you..
|
||||
|
||||
Pause(length=10)
|
||||
Say(voice=voiceB)
|
||||
| Goodbye!
|
Loading…
Reference in New Issue
Block a user