// Phelpsify is a tool to help assign prayer codes to prayers. // It requests prayers from bahaiprayers.net via the API url. // It reads already assigned prayer codes from rel/code.list. // It reads the conversion from number to language code from rel/lang.csv. // It writes the new prayer codes to rel/code.list. // rel/code.list is structured as prayer code, comma, prayer ids from bahaiprayers.net all separated by commas per line. // rel/lang.csv is a csv file with header id,iso,iso_type,name,english,flag_link,rtl. // The tool is a command line tool that first asks which languages you want to complete. // It then presents you a random prayer from those languages that doesn't have // a prayer code yet. It will then help you find the prayer among the prayers that already have a prayer code using keyword based search. // When a match is found, the id of the prayer will be added to the list after the prayer code. // The tool then asks you if you want to add another prayer and repeat the process. package main import ( "encoding/csv" "encoding/json" "fmt" "math/rand" "net/http" "os" "strconv" "strings" ) //BPNAPI is the API link of bahaiprayers.net //It is used to get the list of prayers per language by numerical id from lang.csv var BPNAPI = "https://bahaiprayers.net/api/prayer/prayersystembylanguage?languageid=" type BPNKind string type Language struct { Id int Iso string IsoType string Name string English string FlagLink string Rtl bool } //BPNAPIOutput is the JSON structure of the API output type BPNAPIOutput struct { ErrorMessage string IsInError bool Version int Prayers []Prayer Tags []struct { Id int LanguageId int Name string Kind BPNKind PrayerCount int } TagRelations []struct { Id int PrayerId int PrayerTagId int LanguageId int } Urls []interface{} Languages []struct { Id int Name string English string IsLeftToRight bool FlagLink string } } type Prayer struct { Id int AuthorId int LanguageId int Text string Tags []struct { Id int Name string Kind BPNKind } Tagkind struct { Kind BPNKind } Urls []interface{} } func (p Prayer) Author() string { if p.AuthorId > 0 && p.AuthorId < 4 { return []string{"Báb", "Bahá'u'lláh", "Abdu'l-Bahá"}[p.AuthorId-1] } return "Unknown Author" } type PrayerCode struct { Code string Language string } var Languages []Language var CodeList map[string][]int var PrayersWithCode map[PrayerCode]Prayer // ReadLangCSV reads rel/lang.csv and puts it in Languages // It does so by matching each CSV field to a struct field func ReadLangCSV() error { file, err := os.Open("rel/lang.csv") if err != nil { return err } defer file.Close() reader := csv.NewReader(file) langCSV, err := reader.ReadAll() if err != nil { return err } for _, lang := range langCSV { var language Language language.Id, _ = strconv.Atoi(lang[0]) language.Iso = lang[1] language.IsoType = lang[2] language.Name = lang[3] language.English = lang[4] language.FlagLink = lang[5] language.Rtl, _ = strconv.ParseBool(lang[6]) Languages = append(Languages, language) } return nil } func ReadCodeList() error { file, err := os.Open("rel/code.list") if err != nil { return err } defer file.Close() CodeList = make(map[string][]int) reader := csv.NewReader(file) for { line, err := reader.Read() if err != nil { break } CodeList[line[0]] = make([]int, 0) for _, prayerIDstr := range line[1:] { prayerID, err := strconv.Atoi(prayerIDstr) if err != nil { return err } CodeList[line[0]] = append(CodeList[line[0]], prayerID) } } return nil } func WriteCodeList(codeList map[string][]int) error { file, err := os.Create("rel/code.list") if err != nil { return err } defer file.Close() writer := csv.NewWriter(file) for code, prayerIDs := range codeList { line := make([]string, 0) line = append(line, code) for _, prayerID := range prayerIDs { line = append(line, strconv.Itoa(prayerID)) } writer.Write(line) } writer.Flush() return nil } func AskLanguages() []Language { // Ask "Which languages do you want to complete?" // The answer is a list of language codes that must be converted to numbers // using the conversion from lang.csv in Languages fmt.Print("Which languages do you want to complete? ") var languages []string fmt.Scanln(&languages) var outLangs []Language for _, language := range languages { for _, lang := range Languages { if lang.Iso == language || lang.English == language { outLangs = append(outLangs, lang) } } } return outLangs } func Code(p Prayer) string { // Get the prayer code for a prayer via CodeList // or return an empty string if it's not on the list for code, prayerIDs := range CodeList { for _, prayerID := range prayerIDs { if p.Id == prayerID { return code } } } return "" } func ReadPrayers(lang []Language) []Prayer { var prayers []Prayer for _, language := range lang { response, err := http.Get(BPNAPI + strconv.Itoa(language.Id)) if err != nil { fmt.Println(err) return nil } defer response.Body.Close() var output BPNAPIOutput err = json.NewDecoder(response.Body).Decode(&output) if err != nil { fmt.Println(err) return nil } for _, prayer := range output.Prayers { if Code(prayer) == "" { prayers = append(prayers, prayer) } } } return prayers } func main() { err := ReadLangCSV() if err != nil { panic(err) } err = ReadCodeList() if err != nil { panic(err) } // Iterate over all languages and read in all prayers // with a language code to PrayersWithCode for _, language := range Languages { prayers := ReadPrayers([]Language{language}) for _, prayer := range prayers { code := Code(prayer) if code != "" { prayerCode := PrayerCode{code, language.Iso} PrayersWithCode[prayerCode] = prayer } } } for { languages := AskLanguages() prayers := ReadPrayers(languages) // randomize the order of the prayers for i := len(prayers) - 1; i > 0; i-- { j := rand.Intn(i + 1) prayers[i], prayers[j] = prayers[j], prayers[i] } // pick the first prayer from the resulting list that // doesn't have a code in CodeList. var prayer Prayer var code string for _, p := range prayers { if Code(p) == "" { prayer = p break } } // Present the text, id and author of the prayer fmt.Println(prayer.Text) fmt.Println("ID:", prayer.Id) fmt.Println("Author:", prayer.Author()) for code == "" { // Ask for a keyword fmt.Print("Input a keyword for this prayer: ") var keyword string fmt.Scanln(&keyword) var Matches []Prayer // Check for the prayer text of each prayer in // PrayersWithCode if there is a match with the keyword // and add it to Matches for _, prayer := range PrayersWithCode { if strings.Contains(prayer.Text, keyword) { Matches = append(Matches, prayer) } } // If there are no matches, ask again if len(Matches) == 0 { fmt.Println("No matches found.") continue } // Ask which of the matches to use fmt.Println("Which of the following matches?") for i, match := range Matches { fmt.Println(i+1, ":", match.Text) fmt.Print("Does this match? (y/n) ") var answer string fmt.Scanln(&answer) if answer == "y" { prayer = match code = Code(match) break } } } // Add the code to CodeList CodeList[code] = append(CodeList[code], prayer.Id) // Ask if the user wants to identify another prayer // or if they want to quit // To identify another prayer, continue // To quit, save the CodeList and quit fmt.Print("Identify another prayer? (y/n) ") var answer string fmt.Scanln(&answer) if answer == "n" { err = WriteCodeList(CodeList) if err != nil { panic(err) } break } } }