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