Skip to content

Commit

Permalink
feat: Add French parts of speech, add Dicolink. Add favicons.
Browse files Browse the repository at this point in the history
  • Loading branch information
vxern committed Aug 15, 2023
1 parent 050b60a commit ba68245
Show file tree
Hide file tree
Showing 15 changed files with 290 additions and 19 deletions.
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ DISCORD_SECRET=<Discord API secret>
FAUNA_SECRET=<Fauna API secret>
DEEPL_SECRET=<DeepL API secret>
SENTRY_SECRET=<Sentry API secret>
WORDS_SECRET=<Words API secret>
RAPID_API_SECRET=<RapidAPI API secret>
LAVALINK_HOST=<Lavalink host URI>
LAVALINK_PORT=<Lavalink port>
LAVALINK_PASSWORD=<Lavalink password>
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "logos",
"description": "A multi-purpose community bot built to cater to language-learning communities on Discord.",
"license": "ISC",
"version": "3.12.0",
"version": "3.13.0",
"type": "module",
"keywords": [
"javascript",
Expand Down
12 changes: 12 additions & 0 deletions src/constants/licences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ const licences = {
copyright: "Copyright © 2004-2023 dexonline (https://dexonline.ro)",
},
},
dicolink: {
name: "dicolink.com",
link: "https://www.dicolink.com/api/conditionsutilisations",
faviconLink: "https://www.dicolink.com/imgs/dicolink_128.png",
notices: {
licence:
"En contrepartie de vous permettre d'accéder à l'API Dicolink et aux données Dicolink, vous acceptez de respecter les exigences d'attribution énoncées à l'annexe A ci-après (exigences d'attribution) et ses modifications ultérieures. Vous acceptez que toutes les modifications apportées aux conditions d'attribution entreront en vigueur à la première date à laquelle Dicolink vous en informera par écrit par e-mail ou 30 jours après leur publication. Sous réserve des termes et conditions de cet accord, Dicolink vous accorde par la présente, une licence non exclusive, révocable, non sous-licenciable et non transférable pour utiliser les marques et logos désignés de Dicolink («Marques Dicolink»), uniquement dans la mesure nécessaire pour exécuter le les exigences d'attribution autorisées aux présentes pendant la durée du présent Contrat. Tous les droits non expressément concédés aux présentes sont réservés par Dicolink, et toute utilisation par Vous des Marques de Dicolink, (y compris toute la bonne volonté qui y est associée), sera au nom de Dicolink et en bénéficiera. À la résiliation de cet accord, Vous devez cesser immédiatement d'utiliser toutes les marques Dicolink.",
copyright: "© 2019-2022 Dicolink",
},
},
tatoeba: {
name: "tatoeba.org",
link: "https://tatoeba.org/en/terms_of_use#section-6",
Expand All @@ -26,6 +36,8 @@ const licences = {
wiktionary: {
name: "wiktionary.org",
link: "https://en.wiktionary.org/wiki/Wiktionary:Copyrights",
faviconLink:
"https://upload.wikimedia.org/wikipedia/commons/thumb/8/83/En.wiktionary_favicon.svg/1024px-En.wiktionary_favicon.svg.png",
notices: {
licence:
"The original texts of Wiktionary entries are dual-licensed to the public under both the [Creative Commons Attribution-ShareAlike 3.0 Unported License (CC-BY-SA)](https://en.wiktionary.org/wiki/Wiktionary:CC-BY-SA) and the [GNU Free Documentation License (GFDL)](https://en.wiktionary.org/wiki/Wiktionary:Text_of_the_GNU_Free_Documentation_License). The full text of both licenses can be found at [Wiktionary:Text of Creative Commons Attribution-ShareAlike 3.0 Unported License](https://en.wiktionary.org/wiki/Wiktionary:Text_of_Creative_Commons_Attribution-ShareAlike_3.0_Unported_License), as well as [Wiktionary:Text of the GNU Free Documentation License](https://en.wiktionary.org/wiki/Wiktionary:Text_of_the_GNU_Free_Documentation_License). Permission is granted to copy, distribute and/or modify the text of all Wiktionary entries under the terms of the Creative Commons Attribution-ShareAlike 3.0 Unported License, and the GNU Free Documentation License, Version 1.1 or any later version published by the [Free Software Foundation](https://en.wikipedia.org/wiki/Free_Software_Foundation); with no Invariant Sections, with no Front-Cover Texts, and with no Back-Cover Texts.",
Expand Down
1 change: 1 addition & 0 deletions src/constants/localisations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export default {
adverb: "words.adverb",
adposition: "words.adposition",
article: "words.article",
expression: "words.expression",
"proper-noun": "words.properNoun",
letter: "words.letter",
character: "words.character",
Expand Down
5 changes: 5 additions & 0 deletions src/constants/types/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ export default {
translate: "https://api-free.deepl.com/v2/translate",
},
words: {
host: "wordsapiv1.p.rapidapi.com",
word: (word: string) => `https://wordsapiv1.p.rapidapi.com/words/${word}`,
},
dicolink: {
host: "dicolink.p.rapidapi.com",
definitions: (word: string) => `https://dicolink.p.rapidapi.com/mot/${word}/definitions`,
},
};
4 changes: 2 additions & 2 deletions src/constants/types/links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ export default {
generateDexonlineDefinitionLink: (lemma: string) => `https://dexonline.ro/definitie/${lemma}`,
generateWiktionaryDefinitionLink: (lemma: string, language: string) =>
`https://en.wiktionary.org/wiki/${lemma}#${language}`,
wordsAPILink: "https://www.wordsapi.com/",
wordsApiHost: "wordsapiv1.p.rapidapi.com",
wordsAPILink: "https://wordsapi.com/",
generateDicolinkDefinitionLink: (lemma: string) => `https://dicolink.com/mots/${lemma}`,
};
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ async function setup(): Promise<void> {
faunaSecret: process.env.FAUNA_SECRET,
deeplSecret: process.env.DEEPL_SECRET,
sentrySecret: process.env.SENTRY_SECRET,
wordsSecret: process.env.WORDS_SECRET,
rapidApiSecret: process.env.RAPID_API_SECRET,
lavalinkHost: process.env.LAVALINK_HOST,
lavalinkPort: process.env.LAVALINK_PORT,
lavalinkPassword: process.env.LAVALINK_PASSWORD,
Expand Down
2 changes: 1 addition & 1 deletion src/lib/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ type Client = {
faunaSecret: string;
deeplSecret: string;
sentrySecret: string;
wordsSecret: string;
rapidApiSecret: string;
lavalinkHost: string;
lavalinkPort: string;
lavalinkPassword: string;
Expand Down
16 changes: 10 additions & 6 deletions src/lib/commands/language/commands/word.ts
Original file line number Diff line number Diff line change
Expand Up @@ -526,13 +526,17 @@ function entryToEmbeds(
} else {
const [detected, original] = entry.partOfSpeech;

const strings = {
partOfSpeech: localise(client, localisations.partsOfSpeech[detected], locale)(),
};
if (detected === "unknown") {
partOfSpeechDisplayed = original;
} else {
const strings = {
partOfSpeech: localise(client, localisations.partsOfSpeech[detected], locale)(),
};

partOfSpeechDisplayed = strings.partOfSpeech;
if (isUnknownPartOfSpeech(detected)) {
partOfSpeechDisplayed += ` — '${original}'`;
partOfSpeechDisplayed = strings.partOfSpeech;
if (isUnknownPartOfSpeech(detected)) {
partOfSpeechDisplayed += ` — '${original}'`;
}
}
}
const partOfSpeechFormatted = `***${partOfSpeechDisplayed}***`;
Expand Down
3 changes: 2 additions & 1 deletion src/lib/commands/language/dictionaries/adapters.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { LearningLanguage } from "../../../../constants/languages";
import { DictionaryAdapter } from "./adapter";
import dexonline from "./adapters/dexonline";
import dicolink from "./adapters/dicolink";
import wiktionary from "./adapters/wiktionary";
import wordsApi from "./adapters/words-api";

Expand All @@ -9,7 +10,7 @@ export default {
"English/American": [wiktionary, wordsApi],
"English/British": [wiktionary, wordsApi],
Finnish: [wiktionary],
French: [wiktionary],
French: [dicolink, wiktionary],
German: [wiktionary],
Greek: [wiktionary],
Hungarian: [wiktionary],
Expand Down
100 changes: 100 additions & 0 deletions src/lib/commands/language/dictionaries/adapters/dicolink.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import constants from "../../../../../constants/constants";
import { LearningLanguage, Locale } from "../../../../../constants/languages";
import licences from "../../../../../constants/licences";
import { Client } from "../../../../client";
import { getPartOfSpeech } from "../../module";
import { Definition, DictionaryAdapter, DictionaryEntry } from "../adapter";

type Result = {
id: string;
partOfSpeech: string;
source: string;
attributionText: string;
attributionUrl: string;
definition: string;
dicolinkUrl: string;
};

class WordsAPIAdapter extends DictionaryAdapter<Result[]> {
constructor() {
super({
name: "Dicolink",
provides: ["definitions"],
});
}

async fetch(client: Client, lemma: string, _: LearningLanguage): Promise<Result[] | undefined> {
const response = await fetch(constants.endpoints.dicolink.definitions(lemma), {
headers: {
"X-RapidAPI-Key": client.metadata.environment.rapidApiSecret,
"X-RapidAPI-Host": constants.endpoints.dicolink.host,
},
});
if (!response.ok) {
return undefined;
}

const data = await response.json();
const resultsAll = data.map((result: Record<string, unknown>) => ({
id: result.id,
partOfSpeech: result.nature ? result.nature : undefined,
source: result.source,
attributionText: result.attributionText,
attributionUrl: result.attributionUrl,
definition: result.definition,
dicolinkUrl: result.dicolinkUrl,
}));
const results = resultsAll.filter(
(result: Record<string, unknown>) => result.partOfSpeech !== undefined,
) as Result[];

return results;
}

parse(
_: Client,
lemma: string,
language: LearningLanguage,
resultsAll: Result[],
__: { locale: Locale },
): DictionaryEntry[] {
const entries: DictionaryEntry[] = [];

const sources = resultsAll.map((result) => result.source);
const resultsDistributed = resultsAll.reduce((distribution, result) => {
distribution[result.source]?.push(result);
return distribution;
}, Object.fromEntries(sources.map((source) => [source, []])) as Record<string, Result[]>);
const results = Object.values(resultsDistributed).reduce((a, b) => {
return a.length > b.length ? a : b;
});

for (const result of results) {
const partOfSpeechTopicWord = result.partOfSpeech.split(" ").at(0) ?? result.partOfSpeech;
const partOfSpeech = getPartOfSpeech(result.partOfSpeech, partOfSpeechTopicWord, language);

const definition: Definition = { value: result.definition };

const lastEntry = entries.at(-1);
if (
lastEntry !== undefined &&
(lastEntry.partOfSpeech[0] === partOfSpeech[0] || lastEntry.partOfSpeech[1] === partOfSpeech[1])
) {
lastEntry.nativeDefinitions?.push(definition);
continue;
}

entries.push({
lemma,
partOfSpeech,
nativeDefinitions: [definition],
sources: [[constants.links.generateDicolinkDefinitionLink(lemma), licences.dictionaries.dicolink]],
});
}
return entries;
}
}

const adapter = new WordsAPIAdapter();

export default adapter;
11 changes: 7 additions & 4 deletions src/lib/commands/language/dictionaries/adapters/words-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ class WordsAPIAdapter extends DictionaryAdapter<SearchResult> {
async fetch(client: Client, lemma: string, _: LearningLanguage): Promise<SearchResult | undefined> {
const response = await fetch(constants.endpoints.words.word(lemma), {
headers: {
"X-RapidAPI-Key": client.metadata.environment.wordsSecret,
"X-RapidAPI-Host": constants.links.wordsApiHost,
"X-RapidAPI-Key": client.metadata.environment.rapidApiSecret,
"X-RapidAPI-Host": constants.endpoints.words.host,
},
});
if (!response.ok) {
Expand All @@ -55,15 +55,18 @@ class WordsAPIAdapter extends DictionaryAdapter<SearchResult> {
): DictionaryEntry[] {
const entries: DictionaryEntry[] = [];
for (const result of searchResult.results) {
const lastEntry = entries.at(-1);
const partOfSpeech = getPartOfSpeech(result.partOfSpeech, result.partOfSpeech, language);

const definition: Definition = { value: result.definition };
if (result.synonyms !== undefined && result.synonyms.length !== 0) {
definition.relations = { synonyms: result.synonyms };
}

if (lastEntry !== undefined && lastEntry.partOfSpeech === partOfSpeech) {
const lastEntry = entries.at(-1);
if (
lastEntry !== undefined &&
(lastEntry.partOfSpeech[0] === partOfSpeech[0] || lastEntry.partOfSpeech[1] === partOfSpeech[1])
) {
lastEntry.nativeDefinitions?.push(definition);
continue;
}
Expand Down
3 changes: 2 additions & 1 deletion src/lib/commands/language/dictionaries/parts-of-speech.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { LearningLanguage } from "../../../../constants/languages";
import { PartOfSpeech } from "./part-of-speech";
import english from "./parts-of-speech/english";
import french from "./parts-of-speech/french";
import romanian from "./parts-of-speech/romanian";

export default {
Expand All @@ -10,7 +11,7 @@ export default {
"English/American": english,
"English/British": english,
Finnish: {},
French: {},
French: french,
German: {},
Greek: {},
Hungarian: {},
Expand Down
Loading

0 comments on commit ba68245

Please sign in to comment.