Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,17 @@ This is an Alexa Skill that can be used to control your personal Audiobookshelf
- performs an an API "search" that is built-in to ABS
- if this fails, pulls all books from library and then performs a fuzzy search (effective, but may be resource intensive on large libraries)

## Installation:
1) Fork this repo
2) Edit the config.js file to include your **Audiobookshelf API key** and **server URL** (you can do this later in the 'Code' tab of Developer Console if using Alexa-hosted)
## Installation (Alexa hosted):
1) Follow the instructions here: https://developer.amazon.com/en-US/docs/alexa/hosted-skills/alexa-hosted-skills-git-import.html#import
2) Set your skill invocation name
3) Use the "Code" tab to edit config.js to include your **Audiobookshelf API key** and **server URL**
4) (Optional) Change locale
5) Save and deploy the skill
6) If using Alexa-hosted, go to the 'Test' tab of Developer Console, and enable skill testing for 'Development'

## Installation (local hosted):
1) Fork this repo (and mark it private)
2) Edit the config.js file to include your **Audiobookshelf API key** and **server URL**
3) Follow the instructions here: https://developer.amazon.com/en-US/docs/alexa/hosted-skills/alexa-hosted-skills-git-import.html#import
4) Set your skill invocation name and build the skill
5) Save and deploy the skill
Expand Down Expand Up @@ -61,3 +69,10 @@ This is an Alexa Skill that can be used to control your personal Audiobookshelf
- [ ] Add other intents, such as:
- [ ] "Start the book over"
- [ ] "Go to chapter 12"

## Language Contribution:
- Copy /skill-package/interactionModels/custom/en-US.json and edit the samples to reflect the locale you want to contribute.
- Edit /skill-package/skill.json to include the new locale under manifest/publishingInformation/locales
- Copy /lambda/locale/en-US.js and edit every string to reflect the new locale.
- If unsure about the usage of strings, open /lambda/index.js and search for ".speak" to find every time alexa is told to speak something.
- Maybe the new locale needs restructuring of the string construction - if you edit those, be sure to reflect the changes in all files under /lambda/locales/
3 changes: 2 additions & 1 deletion lambda/config.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// config.js
module.exports = {
localeconf: 'en-US', // may also be any other locale as defined in the folder locale and the package.json - use the locale your alexa is located in
ABS_API_KEY: 'xxxxxx',
SERVER_URL: 'https://abs.domain.tld',
USER_AGENT: 'AlexaSkill'
// OTHER OPTIONAL HEADERS
// CFAccessClientId: 'xxxxxxxxx.access',
// CFAccessClientSecret: 'xxxxxxxx'
};
};
53 changes: 36 additions & 17 deletions lambda/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,27 @@ let localSessionAttributes = {
//const ddbAdapter = require('ask-sdk-dynamodb-persistence-adapter');
// const Util = require('./util.js');

const { ABS_API_KEY, SERVER_URL, CFAccessClientId, CFAccessClientSecret } = require('./config.js');
const { localeconf, ABS_API_KEY, SERVER_URL, CFAccessClientId, CFAccessClientSecret } = require('./config.js');
// const { off, title } = require('process');
const localefile = './locale/' + localeconf + '.js'
const {
SpeakWelcome,
SpeakPlaying,
SpeakBy,
SpeakTryAgain,
SpeakPlaybackBookHandlerNoBookTitle,
SpeakPlaybackBookHandlerNoBookFoundP1,
SpeakPlaybackBookHandlerNoBookFoundP2,
SpeakPlayBookIntentHandlerNoBookTitle,
SpeakPlayBookIntentHandlerNoBookFound,
SpeakUnsupportedAudioIntentHandler,
SpeakHelpIntentHandler,
SpeakCancelAndStopIntentHandlerGoodbye,
SpeakFallbackIntentHandler,
SpeakIntentReflectorHandlerP1,
SpeakIntentReflectorHandlerP2,
SpeakErrorHandler
} = require(localefile);

const baseheaders = {
"Content-Type": 'application/json',
Expand Down Expand Up @@ -413,7 +432,7 @@ const LaunchRequestHandler = {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'LaunchRequest';
},
handle(handlerInput) {
const speakOutput = 'Welcome to Audiobookshelf, you can say "play audiobook" to start listening.';
const speakOutput = SpeakWelcome;


console.log(" ~~~ LOGGED AT END OF LaunchRequestHandler ")
Expand Down Expand Up @@ -571,7 +590,7 @@ const PlayAudioIntentHandler = {
//speakOutput = 'Resuming...';
}
else {
speakOutput = 'Playing ' + bookTitle + " by " + author;
speakOutput = SpeakPlaying + bookTitle + " " + SpeakBy + " " + author;
}
console.log("Playing: " + playUrl)

Expand Down Expand Up @@ -747,7 +766,7 @@ const PlaybackBookHandler = { // this handler is not currently used (has limitat
console.log("Title: " + bookTitle)
console.log("Author: " + author)
if (!bookTitle) {
let speakOutput = 'I did not understand the request. For example, try saying "Play audiobook title by author".';
let speakOutput = SpeakPlaybackBookHandlerNoBookTitle;
console.log("Book and/or author slot undefined")
return handlerInput.responseBuilder
.speak(speakOutput)
Expand Down Expand Up @@ -803,7 +822,7 @@ const PlaybackBookHandler = { // this handler is not currently used (has limitat
});
if (results[0].book.length == 0) {
console.log("No book of title '" + bookTitle + "' found")
const speakOutput = "No book of title '" + bookTitle + "' found. Please try again.";
const speakOutput = SpeakPlaybackBookHandlerNoBookFoundP1 + bookTitle + SpeakPlaybackBookHandlerNoBookFoundP2;
return handlerInput.responseBuilder
.speak(sanitizeForSSML(speakOutput))
.reprompt(sanitizeForSSML(speakOutput))
Expand Down Expand Up @@ -870,7 +889,7 @@ const PlaybackBookHandler = { // this handler is not currently used (has limitat
// sync localSessionAttributes to sessionAttributes
updateLocalSessionAttributes(sessionAttributes)

let speakOutput = 'Playing ' + userPlaySession.displayTitle + ' by ' + userPlaySession.displayAuthor;
let speakOutput = userPlaySession.displayTitle + ' ' + SpeakBy + ' ' + userPlaySession.displayAuthor;
console.log("Playing: " + playUrl)

const chapterTitle = getCurrentChapterByBookTime(currentTime, userPlaySession).title
Expand Down Expand Up @@ -1142,7 +1161,7 @@ const PlayBookIntentHandler = {

// require a book title (could later implement playing by author I suppose, maybe in another intent)
if (!bookTitle) {
let speakOutput = 'I did not understand the request. For example, try saying "Play audiobook title by author".';
let speakOutput = SpeakPlayBookIntentHandlerNoBookTitle;
console.log("Book and/or author slot undefined")
return handlerInput.responseBuilder
.speak(speakOutput)
Expand Down Expand Up @@ -1359,13 +1378,13 @@ const PlayBookIntentHandler = {
// if an author was found, could offer to play one of their other books instead?
// Would probably need to forward it to another intent..
if (libraryItem) {
console.log("Found a book in the library!")
console.log("Found a book in the library!");
console.log("Title: " + libraryItem.media.metadata.title);
console.log("Author: " + libraryItem.media.metadata.authorName);
}
else {
console.log("Could not find a playable book (" + rawTitle + " by " + rawAuthor + ")")
let speakOutput = "Could not find a playable book matching: " + rawTitle + " by " + rawAuthor + ". Please try again."
let speakOutput = SpeakPlayBookIntentHandlerNoBookFound + ' ' + rawTitle + " " + SpeakBy + " " + rawAuthor + ". " + SpeakTryAgain;
return handlerInput.responseBuilder
.speak(sanitizeForSSML(speakOutput))
.reprompt(sanitizeForSSML(speakOutput))
Expand Down Expand Up @@ -1428,7 +1447,7 @@ const PlayBookIntentHandler = {
// sync localSessionAttributes to sessionAttributes
updateLocalSessionAttributes(sessionAttributes)

let speakOutput = 'Playing ' + userPlaySession.displayTitle + ' by ' + userPlaySession.displayAuthor;
let speakOutput = userPlaySession.displayTitle + ' ' + SpeakBy + ' ' + userPlaySession.displayAuthor;
console.log("Playing: " + playUrl)


Expand Down Expand Up @@ -2061,7 +2080,7 @@ const UnsupportedAudioIntentHandler = {
);
},
async handle(handlerInput) {
const speakOutput = 'Sorry, I can\'t support that yet.';
const speakOutput = SpeakUnsupportedAudioIntentHandler;

return handlerInput.responseBuilder
.speak(sanitizeForSSML(speakOutput))
Expand All @@ -2075,7 +2094,7 @@ const HelpIntentHandler = {
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.HelpIntent';
},
handle(handlerInput) {
const speakOutput = 'You can say "play audio" to start playing your book! How can I help?';
const speakOutput = SpeakHelpIntentHandler;

return handlerInput.responseBuilder
.speak(speakOutput)
Expand All @@ -2095,7 +2114,7 @@ const CancelAndStopIntentHandler = {
|| Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.StopIntent');
},
handle(handlerInput) {
const speakOutput = 'Goodbye!';
const speakOutput = SpeakCancelAndStopIntentHandlerGoodbye;

const sessionAttributes = handlerInput.attributesManager.getSessionAttributes()
const userPlaySession = sessionAttributes.userPlaySession
Expand Down Expand Up @@ -2663,7 +2682,7 @@ const FallbackIntentHandler = {
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.FallbackIntent';
},
handle(handlerInput) {
const speakOutput = 'Sorry, I don\'t know about that. Please try again.';
const speakOutput = SpeakFallbackIntentHandler;

return handlerInput.responseBuilder
.speak(speakOutput)
Expand Down Expand Up @@ -2749,7 +2768,7 @@ const IntentReflectorHandler = {
},
handle(handlerInput) {
const intentName = Alexa.getIntentName(handlerInput.requestEnvelope);
const speakOutput = `You just triggered ${intentName}`;
const speakOutput = SpeakIntentReflectorHandlerP1 + `${intentName}` + SpeakIntentReflectorHandlerP2;

return handlerInput.responseBuilder
.speak(speakOutput)
Expand All @@ -2767,7 +2786,7 @@ const ErrorHandler = {
return true;
},
handle(handlerInput, error) {
const speakOutput = 'Sorry, I had trouble doing what you asked. Please try again.';
const speakOutput = SpeakErrorHandler;
console.log(`~~~~ Error handled: ${JSON.stringify(error)}`);

return handlerInput.responseBuilder
Expand Down Expand Up @@ -2857,4 +2876,4 @@ exports.handler = Alexa.SkillBuilders.custom()
})
)
*/
.lambda();
.lambda();
19 changes: 19 additions & 0 deletions lambda/locale/de-DE.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// locale/de-de.js
module.exports = {
SpeakWelcome: 'Wilkommen bei Audiobookshelf.',
SpeakPlaying: 'Spiele',
SpeakBy: 'von',
SpeakTryAgain: 'Versuch es nochmal.',
SpeakPlaybackBookHandlerNoBookTitle: 'Das habe ich nicht verstanden. Versuch es mit "Ließ mir Titel von Autor vor".',
SpeakPlaybackBookHandlerNoBookFoundP1: 'Kein Buch mit dem Titel',
SpeakPlaybackBookHandlerNoBookFoundP2: 'gefunden. Versuch es nochmal.',
SpeakPlayBookIntentHandlerNoBookTitle: 'Das habe ich nicht verstanden. Versuch es mit "Ließ mir Titel von Autor vor".',
SpeakPlayBookIntentHandlerNoBookFound: "Kein entsprechendes Buch gefunden:",
SpeakUnsupportedAudioIntentHandler: 'Entschuldige, das kann ich noch nicht.',
SpeakHelpIntentHandler: 'Du kannst sagen "Ließ mir etwas vor" - wie kann ich helfen?',
SpeakCancelAndStopIntentHandlerGoodbye: 'Bis zum nächsten mal!',
SpeakFallbackIntentHandler: 'Entschuldige, das weiß ich nicht. Versuch es nochmal.',
SpeakIntentReflectorHandlerP1: 'Du hast gerade',
SpeakIntentReflectorHandlerP2: 'ausgelöst',
SpeakErrorHandler: 'Entschuldige, ich hatte ein Problem dabei. Versuch es nochmal.'
};
19 changes: 19 additions & 0 deletions lambda/locale/en-US.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// locale/en-US.js
module.exports = {
SpeakWelcome: 'Welcome to Audiobookshelf, you can say "play audiobook" to start listening.',
SpeakPlaying: 'Playing',
SpeakBy: 'by',
SpeakTryAgain: 'Please try again.',
SpeakPlaybackBookHandlerNoBookTitle: 'I did not understand the request. For example, try saying "Play audiobook title by author".',
SpeakPlaybackBookHandlerNoBookFoundP1: '"No book of title',
SpeakPlaybackBookHandlerNoBookFoundP2: 'found. Please try again.',
SpeakPlayBookIntentHandlerNoBookTitle: 'I did not understand the request. For example, try saying "Play audiobook title by author".',
SpeakPlayBookIntentHandlerNoBookFound: "Could not find a playable book matching:",
SpeakUnsupportedAudioIntentHandler: 'Sorry, I can\'t support that yet.',
SpeakHelpIntentHandler: 'You can say "play audio" to start playing your book! How can I help?',
SpeakCancelAndStopIntentHandlerGoodbye: 'Goodbye!',
SpeakFallbackIntentHandler: 'Sorry, I don\'t know about that. Please try again.',
SpeakIntentReflectorHandlerP1: 'You just triggered',
SpeakIntentReflectorHandlerP2: '',
SpeakErrorHandler: 'Sorry, I had trouble doing what you asked. Please try again.'
};
Loading