feat: initial commit for Alexa Malayalam Calendar skill
This commit is contained in:
+34
@@ -0,0 +1,34 @@
|
|||||||
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# Build and Deployment Output
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
*.zip
|
||||||
|
*.tar.gz
|
||||||
|
|
||||||
|
# Environment & Local Secrets
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Editor & OS files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Local Testing / Coverage
|
||||||
|
coverage/
|
||||||
|
.nyc_output/
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Alexa Malayalam Calendar
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
```
|
||||||
|
node test/test-local.mjs
|
||||||
|
or
|
||||||
|
npm test
|
||||||
|
```
|
||||||
Generated
+52
@@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"name": "alexamalayalamcalendar",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "alexamalayalamcalendar",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"ask-sdk-core": "^2.14.0",
|
||||||
|
"kollavarsham": "^2.5.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ask-sdk-core": {
|
||||||
|
"version": "2.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ask-sdk-core/-/ask-sdk-core-2.14.0.tgz",
|
||||||
|
"integrity": "sha512-G2yEKbY+XYTFzJXGRpsg7xJHqqgBcgnKEgklXUMRWRFv10gwHgtWDLLlBV6h4IdysTx782rCVJK/UcHcaGbEpA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"ask-sdk-runtime": "^2.14.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"ask-sdk-model": "^1.29.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ask-sdk-model": {
|
||||||
|
"version": "1.86.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ask-sdk-model/-/ask-sdk-model-1.86.0.tgz",
|
||||||
|
"integrity": "sha512-JmC5mypPBz5Q1Yx1WyeAr2Q/z2Cjm98EjLjTlYAolXF4gokU7fDDjeOAyAD5dkyHjyGLeCEvlC0MJYWFwc84dw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
|
"node_modules/ask-sdk-runtime": {
|
||||||
|
"version": "2.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ask-sdk-runtime/-/ask-sdk-runtime-2.14.0.tgz",
|
||||||
|
"integrity": "sha512-a96pPs1RU3GgXBHplqAVqh2uxEuSYjTD5+XSbHsf6Fz4KhHpgaxJogOIIybjA6O/d1B9sG+6bjHJGzxBJEcccA==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
|
"node_modules/kollavarsham": {
|
||||||
|
"version": "2.5.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/kollavarsham/-/kollavarsham-2.5.4.tgz",
|
||||||
|
"integrity": "sha512-0awQqPiG/6YV8exi1oBsKeliNPwpXoOsJCBKRH608pCACsbMw52tpxPJR5IBTtsqAttCY5MgJYv8olew1doWGw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 16",
|
||||||
|
"npm": ">= 8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"name": "malayalam-calendar-alexa-skill",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"main": "src/index.mjs",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node src/index.mjs",
|
||||||
|
"test": "node tests/test-local.mjs",
|
||||||
|
"build": "powershell -ExecutionPolicy Bypass -File ./scripts/build.ps1",
|
||||||
|
"deploy": "powershell -ExecutionPolicy Bypass -File ./scripts/deploy.ps1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ask-sdk-core": "^2.14.0",
|
||||||
|
"kollavarsham": "^2.5.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
$ProjectRoot = (Resolve-Path "$PSScriptRoot\..").Path
|
||||||
|
$DistPath = Join-Path -Path $ProjectRoot -ChildPath "dist"
|
||||||
|
$ZipPath = Join-Path -Path $DistPath -ChildPath "bundle.zip"
|
||||||
|
|
||||||
|
if (Test-Path -Path $DistPath) {
|
||||||
|
Remove-Item -Path $DistPath -Recurse -Force
|
||||||
|
}
|
||||||
|
New-Item -ItemType Directory -Path $DistPath | Out-Null
|
||||||
|
|
||||||
|
Write-Host "Packaging skill into $ZipPath..." -ForegroundColor Cyan
|
||||||
|
|
||||||
|
Set-Location -Path $ProjectRoot
|
||||||
|
Compress-Archive -Path "src", "node_modules", "package.json" -DestinationPath $ZipPath -Force
|
||||||
|
|
||||||
|
Write-Host "Build complete. Bundle created at $ZipPath" -ForegroundColor Green
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
param (
|
||||||
|
[string]$FunctionName = "AlexaMalayalamCalendar"
|
||||||
|
)
|
||||||
|
|
||||||
|
$ProjectRoot = (Resolve-Path "$PSScriptRoot\..").Path
|
||||||
|
$DistPath = Join-Path -Path $ProjectRoot -ChildPath "dist"
|
||||||
|
$BuildScript = Join-Path -Path $PSScriptRoot -ChildPath "build.ps1"
|
||||||
|
$ZipPath = Join-Path -Path $DistPath -ChildPath "bundle.zip"
|
||||||
|
|
||||||
|
# 1. Run build script first to ensure dist/bundle.zip is fresh
|
||||||
|
Write-Host "Triggering build..." -ForegroundColor Cyan
|
||||||
|
powershell -ExecutionPolicy Bypass -File $BuildScript
|
||||||
|
|
||||||
|
if (-not (Test-Path -Path $ZipPath)) {
|
||||||
|
Write-Host "Build failed! $ZipPath not found." -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# 2. Upload to AWS Lambda
|
||||||
|
Write-Host "Deploying $ZipPath to AWS Lambda ($FunctionName)..." -ForegroundColor Cyan
|
||||||
|
aws lambda update-function-code `
|
||||||
|
--function-name $FunctionName `
|
||||||
|
--zip-file fileb://$ZipPath `
|
||||||
|
--no-cli-pager `
|
||||||
|
| Out-Null
|
||||||
|
|
||||||
|
Write-Host "Deployment finished successfully." -ForegroundColor Green
|
||||||
+157
@@ -0,0 +1,157 @@
|
|||||||
|
import Alexa from 'ask-sdk-core';
|
||||||
|
import { Kollavarsham } from 'kollavarsham';
|
||||||
|
|
||||||
|
// 1. Core Logic: Handles explicit date/star question requests
|
||||||
|
const GetMalayalamDateIntentHandler = {
|
||||||
|
canHandle(handlerInput) {
|
||||||
|
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
|
||||||
|
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'GetMalayalamDateIntent';
|
||||||
|
},
|
||||||
|
handle(handlerInput) {
|
||||||
|
return generateCalendarResponse(handlerInput);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 2. Fallback Entry: Handles "Alexa, open Malayalam Calendar"
|
||||||
|
const LaunchRequestHandler = {
|
||||||
|
canHandle(handlerInput) {
|
||||||
|
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'LaunchRequest';
|
||||||
|
},
|
||||||
|
handle(handlerInput) {
|
||||||
|
return generateCalendarResponse(handlerInput);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Built-in Alexa Handlers
|
||||||
|
const HelpIntentHandler = {
|
||||||
|
canHandle(handlerInput) {
|
||||||
|
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
|
||||||
|
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.HelpIntent';
|
||||||
|
},
|
||||||
|
handle(handlerInput) {
|
||||||
|
const speechText = "You can ask me for today's Malayalam date or star.";
|
||||||
|
return handlerInput.responseBuilder
|
||||||
|
.speak(speechText)
|
||||||
|
.reprompt(speechText)
|
||||||
|
.getResponse();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const CancelAndStopIntentHandler = {
|
||||||
|
canHandle(handlerInput) {
|
||||||
|
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
|
||||||
|
&& (Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.CancelIntent'
|
||||||
|
|| Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.StopIntent');
|
||||||
|
},
|
||||||
|
handle(handlerInput) {
|
||||||
|
const speechText = 'Goodbye!';
|
||||||
|
return handlerInput.responseBuilder
|
||||||
|
.speak(speechText)
|
||||||
|
.getResponse();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const SessionEndedRequestHandler = {
|
||||||
|
canHandle(handlerInput) {
|
||||||
|
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'SessionEndedRequest';
|
||||||
|
},
|
||||||
|
handle(handlerInput) {
|
||||||
|
return handlerInput.responseBuilder.getResponse();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ErrorHandler = {
|
||||||
|
canHandle() {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
handle(handlerInput, error) {
|
||||||
|
console.error(`Error handled: ${error.stack}`);
|
||||||
|
const speechText = 'Sorry, I had trouble understanding that. Please try again.';
|
||||||
|
return handlerInput.responseBuilder
|
||||||
|
.speak(speechText)
|
||||||
|
.reprompt(speechText)
|
||||||
|
.getResponse();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get current gregorian date according to Indian Standard Time (IST)
|
||||||
|
function getIstDate() {
|
||||||
|
// Construct date string formatted explicitly for India timezone
|
||||||
|
const now = new Date();
|
||||||
|
const formatter = new Intl.DateTimeFormat('en-US', {
|
||||||
|
timeZone: 'Asia/Kolkata',
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Formats into MM/DD/YYYY in IST
|
||||||
|
const [{ value: month }, , { value: day }, , { value: year }] = formatter.formatToParts(now);
|
||||||
|
|
||||||
|
// Create UTC date anchored at noon for that IST date
|
||||||
|
return new Date(Date.UTC(year, month - 1, day, 12, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kollavarsham calculation.
|
||||||
|
function getMalayalamDate(gregorianDate) {
|
||||||
|
// Initialize Kollavarsham with Surya Siddhanta system
|
||||||
|
// and default coordinates of Kollarvarsham project documentation.
|
||||||
|
const kollavarsham = new Kollavarsham({
|
||||||
|
system: 'SuryaSiddhanta',
|
||||||
|
latitude: 10,
|
||||||
|
longitude: 76.2
|
||||||
|
});
|
||||||
|
return kollavarsham.fromGregorianDate(gregorianDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a number to its ordinal string representation (1st, 2nd, 3rd, 4th, etc.)
|
||||||
|
* @param {number} day - The day number (1-31)
|
||||||
|
* @returns {string} - Ordinal string (e.g., "4th")
|
||||||
|
*/
|
||||||
|
function getOrdinalDay(day) {
|
||||||
|
const suffixes = ['th', 'st', 'nd', 'rd'];
|
||||||
|
const remainder = day % 100;
|
||||||
|
|
||||||
|
// Handles special cases 11th, 12th, 13th
|
||||||
|
const suffix = (remainder >= 11 && remainder <= 13)
|
||||||
|
? 'th'
|
||||||
|
: (suffixes[day % 10] || 'th');
|
||||||
|
|
||||||
|
return `${day}${suffix}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Central Response Builder
|
||||||
|
function generateCalendarResponse(handlerInput) {
|
||||||
|
const kollavarshamDate = getMalayalamDate(getIstDate());
|
||||||
|
|
||||||
|
// Property mappings for kollavarsham package:
|
||||||
|
const dayNum = kollavarshamDate.date; // Numeric date (e.g. 4)
|
||||||
|
const month = kollavarshamDate.masaName; // Transliterated English month (e.g. "Karkidakam")
|
||||||
|
const star = kollavarshamDate.naksatraName; // Transliterated English Nakshatra (e.g. "Uthram")
|
||||||
|
const year = kollavarshamDate.year; // Malayalam Year (e.g. 1201)
|
||||||
|
|
||||||
|
const speechText = `Today it is the ${getOrdinalDay(dayNum)} of ${month}. Today's nakshatra is ${star}.`;
|
||||||
|
|
||||||
|
return handlerInput.responseBuilder
|
||||||
|
.speak(speechText)
|
||||||
|
.withSimpleCard('Malayalam Calendar', speechText)
|
||||||
|
.getResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Construct Skill Instance
|
||||||
|
const skill = Alexa.SkillBuilders.custom()
|
||||||
|
.addRequestHandlers(
|
||||||
|
LaunchRequestHandler,
|
||||||
|
GetMalayalamDateIntentHandler,
|
||||||
|
HelpIntentHandler,
|
||||||
|
CancelAndStopIntentHandler,
|
||||||
|
SessionEndedRequestHandler
|
||||||
|
)
|
||||||
|
.addErrorHandlers(ErrorHandler)
|
||||||
|
.create();
|
||||||
|
|
||||||
|
// 5. Modern Async Lambda Handler for Node.js 24+
|
||||||
|
export const handler = async (event, context) => {
|
||||||
|
return await skill.invoke(event, context);
|
||||||
|
};
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { handler } from '../src/index.mjs';
|
||||||
|
|
||||||
|
// Mock Alexa LaunchRequest Payload
|
||||||
|
const mockAlexaLaunchRequest = {
|
||||||
|
version: '1.0',
|
||||||
|
session: {
|
||||||
|
new: true,
|
||||||
|
sessionId: 'amzn1.echo-sdk-s-session-mock-id',
|
||||||
|
application: {
|
||||||
|
applicationId: 'amzn1.ask.skill.53645cde-742f-499e-83d3-07ed537bce39'
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
userId: 'amzn1.ask.account.mockUser'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
context: {
|
||||||
|
System: {
|
||||||
|
application: {
|
||||||
|
applicationId: 'amzn1.ask.skill.53645cde-742f-499e-83d3-07ed537bce39'
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
userId: 'amzn1.ask.account.mockUser'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
request: {
|
||||||
|
type: 'LaunchRequest',
|
||||||
|
requestId: 'amzn1.echo-api.request.mockRequestId',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
locale: 'en-US'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async function runLocalTest() {
|
||||||
|
console.log('🚀 Running Alexa Handler Locally...\n');
|
||||||
|
try {
|
||||||
|
const response = await handler(mockAlexaLaunchRequest, {});
|
||||||
|
console.log('✅ Response Received:\n');
|
||||||
|
console.log(JSON.stringify(response, null, 2));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error executing handler locally:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runLocalTest();
|
||||||
Reference in New Issue
Block a user