feat: initial commit for Alexa Malayalam Calendar skill

This commit is contained in:
2026-07-20 13:20:15 +05:30
commit 04f1d7d9d1
8 changed files with 355 additions and 0 deletions
+34
View File
@@ -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/
+9
View File
@@ -0,0 +1,9 @@
# Alexa Malayalam Calendar
## Testing
```
node test/test-local.mjs
or
npm test
```
+52
View File
@@ -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"
}
}
}
}
+16
View File
@@ -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"
}
}
+15
View File
@@ -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
+27
View File
@@ -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
View File
@@ -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);
};
+45
View File
@@ -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();