AuraGem Servers > Tree [main]

/gemini/texts/judaism/sefaria.go/

..
View Raw
package judaism

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"

	// "net/url"

	"runtime"
	"strings"
	"time"
	"unicode/utf8"
)

func EncodeTextReference(s string) string {
	result := strings.Replace(s, " ", "_", -1)
	return result
}

/*
	type SefariaIndexCategory struct {
		Contents []SefariaIndexCategoryOrText `json:"contents"`
		Order float64 `json:"order"`
		EnComplete bool `json:"enComplete"`
		HeComplete bool `json:"heComplete"`
		EnDesc string `json:"enDesc"`
		HeDesc string `json:"heDesc"`
		EnShortDesc string `json:"enShortDesc"`
		HeShortDesc string `json:"heShortDesc"`
		SearchRoot string `json:"searchRoot"`
		HeCategory string `json:"heCategory"`
		Category string `json:"category"` // Title of Category
	}
*/
type SefariaIndexCategoryOrText struct {
	// Category Info
	Contents    []SefariaIndexCategoryOrText `json:"contents"`
	Order       float64                      `json:"order"` // Also in Text Info
	EnComplete  bool                         `json:"enComplete"`
	HeComplete  bool                         `json:"heComplete"`
	EnDesc      string                       `json:"enDesc"` // Also in Text Info
	HeDesc      string                       `json:"heDesc"` // Also in Text Info
	EnShortDesc string                       `json:"enShortDesc"`
	HeShortDesc string                       `json:"heShortDesc"`
	SearchRoot  string                       `json:"searchRoot"`
	HeCategory  string                       `json:"heCategory"`
	Category    string                       `json:"category"` // Title of Category

	// Text Info
	Categories        []string `json:"categories"`
	Dependence        string   `json:"dependence"`
	PrimaryCategory   string   `json:"primary_category"`
	CollectiveTitle   string   `json:"collective_title"`
	HeCollectiveTitle string   `json:"heCollectiveTitle"`
	Commentator       string   `json:"commentator"`
	HeCommentator     string   `json:"heCommentator"`
	BaseTextOrder     float64  `json:"base_text_order"`
	Corpus            string   `json:"corpus"`
	HeTitle           string   `json:"heTitle"`
	Title             string   `json:"title"`
}

func GetFullIndex() []SefariaIndexCategoryOrText {
	url := "https://sefaria.org/api/index"

	spaceClient := http.Client{
		Timeout: time.Second * 10, // Timeout after 10 seconds
	}

	req, err := http.NewRequest(http.MethodGet, url, nil)
	if err != nil {
		fmt.Println(err)
		return []SefariaIndexCategoryOrText{}
	}

	req.Header.Set("User-Agent", "ScholasticDiversity")
	req.Header.Set("accept", "application/json")
	res, getErr := spaceClient.Do(req)
	if getErr != nil {
		fmt.Println(err)
		return []SefariaIndexCategoryOrText{}
	}
	if res.Body != nil {
		defer res.Body.Close()
	}
	/*if res.Status != 200 {

	}*/

	body, readErr := io.ReadAll(res.Body)
	if readErr != nil {
		fmt.Println(err)
		return []SefariaIndexCategoryOrText{}
	}

	var sefariaIndex []SefariaIndexCategoryOrText
	jsonErr := json.Unmarshal(body, &sefariaIndex)
	if jsonErr != nil {
		fmt.Println(jsonErr)
		return []SefariaIndexCategoryOrText{}
	}

	return sefariaIndex
}

// Give array of category titles to go down
// TODO: Should not continue trying to find something when the exact ordering given in titles is not followed
func findInIndex(index []SefariaIndexCategoryOrText, titles []string) SefariaIndexCategoryOrText {
	for _, i := range index {
		if titles[0] == i.Category {
			if len(titles) == 1 {
				// This was the last thing to find, return it
				return i
			} else {
				// There's still more to find
				newTitles := titles[1:]
				return findInIndex(i.Contents, newTitles)
			}
		} else if len(i.Categories) != 0 && i.Title == titles[0] {
			// It is a text
			return i
		}
	}

	return SefariaIndexCategoryOrText{}
}

type SefariaText struct {
	Ref                      string          `json:"ref"`
	HeRef                    string          `json:"heRef"`
	IsComplex                bool            `json:"isComplex"`
	JsonText                 json.RawMessage `json:"text"` // This is used by the GetText function to fill in Text [][]string (normalizing the json to something consistent)
	Text                     [][]string      // This is what users will use
	JsonHeText               json.RawMessage `json:"he"`
	HeText                   [][]string
	Versions                 []SefariaTextVersion `json:"versions"` // Array of Versions
	TextDepth                int                  `json:"textDepth"`
	SectionNames             []string             `json:"sectionNames"` // Names of all section levels (ex: ["Chapter", "Verse"])
	AddressTypes             []string             `json:"addressTypes"`
	Lengths                  []int                `json:"lengths"` // Lengths for all section levels (from SectionNames field)
	Length                   int                  `json:"length"`  // Length in highest level section
	HeTitle                  string               `json:"heTitle"`
	TitleVariants            []string             `json:"titleVariants"`
	HeTitleVariants          []string             `json:"heTitleVariants"`
	Type                     string               `json:"type"` // Same as Categories[0]
	PrimaryCategory          string               `json:"primary_category"`
	Book                     string               `json:"book"`
	Categories               []string             `json:"categories"`
	Order                    interface{}          `json:"order"`      // NOTE: Can be []int or string
	Sections                 []json.RawMessage    `json:"sections"`   // TODO: Can be array of ints or strings
	ToSections               []json.RawMessage    `json:"toSections"` // Parallel to Sections filed, specifies end range (where the section is ending *to*)
	IsDependant              bool                 `json:"isDependant"`
	IndexTitle               string               `json:"indexTitle"`
	HeIndexTitle             string               `json:"heIndexTitle"`
	SectionRef               string               `json:"sectionRef"`
	FirstAvailableSectionRef string               `json:"firstAvailableSectionRef"`
	HeSectionRef             string               `json:"heSectionRef"`
	IsSpanning               bool                 `json:"isSpanning"`
	SpanningRefs             []string             `json:"spanningRefs"`
	Sources                  []string             `json:"sources"`

	// Version Info of Text field
	VersionTitle              string `json:"versionTitle"`
	VersionTitleInHebrew      string `json:"versionTitleInHebrew"`
	ShortVersionTitle         string `json:"shortVersionTitle"`
	ShortVersionTitleInHebrew string `json:"shortVersionTitleInHebrew"`
	VersionSource             string `json:"versionSource"`
	VersionStatus             string `json:"versionStatus"`
	VersionNotes              string `json:"versionNotes"`
	ExtendedNotes             string `json:"extendedNotes"`
	ExtendedNotesHebrew       string `json:"extendedNotesHebrew"`
	VersionNotesInHebrew      string `json:"versionNotesInHebrew"`
	DigitizedBySefaria        bool   `json:"digitizedBySefaria"`
	License                   string `json:"license"`
	FormatEnAsPoetry          bool   `json:"formatEnAsPoetry"`

	// Version Info of HeText field
	HeVersionTitle              string `json:"heVersionTitle"`
	HeVersionTitleInHebrew      string `json:"heVersionTitleInHebrew"`
	HeShortVersionTitle         string `json:"heShortVersionTitle"`
	HeShortVersionTitleInHebrew string `json:"heShortVersionTitleInHebrew"`
	HeVersionSource             string `json:"heVersionSource"`
	HeVersionStatus             string `json:"heVersionStatus"`
	HeVersionNotes              string `json:"heVersionNotes"`
	HeExtendedNotes             string `json:"heExtendedNotes"`
	HeExtendedNotesHebrew       string `json:"heExtendedNotesHebrew"`
	HeVersionNotesInHebrew      string `json:"heVersionNotesInHebrew"`
	HeDigitizedBySefaria        bool   `json:"heDigitizedBySefaria"`
	HeLicense                   string `json:"heLicense"`
	HeFormatHeAsPoetry          bool   `json:"formatHeAsPoetry"`

	JsonAlts json.RawMessage `json:"alts"`
	Alts     [][]SefariaTextAlt
	Next     string `json:"next"` // Ref of next section
	Prev     string `json:"prev"` // Ref of previous section

	// TODO: Don't know the types of these yet
	Commentary []SefariaTextLink `json:"commentary"`
	Sheets     []string          `json:"sheets"`
	Layer      []string          `json:"layer"`
}

type SefariaTextVersion struct {
	Title                     string      `json:"title"` // Title of text, not version title
	VersionTitle              string      `json:"versionTitle"`
	VersionSource             string      `json:"versionSource"`
	Language                  string      `json:"language"`
	Status                    string      `json:"status"`
	License                   string      `json:"license"`
	VersionNotes              string      `json:"versionNotes"`
	DigitizedBySefaria        interface{} `json:"digitizedBySefaria"` // NOTE: can be bool or empty string
	Priority                  interface{} `json:"priority"`           // NOTE: can be float64 or empty string
	VersionTitleInHebrew      string      `json:"versionTitleInHebrew"`
	VersionNotesInHebrew      string      `json:"versionNotesInHebrew"`
	ExtendedNotes             string      `json:"extendedNotes"`
	ExtendedNotesHebrew       string      `json:"extendedNotesHebrew"`
	PurchaseInformationImage  string      `json:"purchaseInformationImage"`
	PurchaseInformationURL    string      `json:"purchaseInformationURL"`
	ShortVersionTitle         string      `json:"shortVersionTitle"`
	ShortVersionTitleInHebrew string      `json:"shortVersionTitleInHebrew"`
}

type SefariaTextAlt struct {
	En    []string `json:"en"`
	He    []string `json:"he"`
	Whole bool     `json:"whole"`
}

// TODO: pad parameter will return entire books instead of going directly to the first section (Genesis will return entire book, instead of Genesis 1)
// context - specifying a verse will return it with its context
// commentary - whether to include a list of commentaries
func GetText(ref string, lang string, version string) SefariaText {
	apiUrl := "https://sefaria.org/api/texts/" + EncodeTextReference(ref)
	if lang != "" && version != "" {
		apiUrl += "/" + lang + "/" + version
	}

	fmt.Printf("Getting text for %s\n", apiUrl)

	spaceClient := http.Client{
		Timeout: time.Second * 10, // Timeout after 10 seconds
	}

	req, err := http.NewRequest(http.MethodGet, apiUrl, nil)
	if err != nil {
		_, filename, line, _ := runtime.Caller(0)
		fmt.Printf("%s:%d %s\n", filename, line, err)
		return SefariaText{}
	}

	req.Header.Set("User-Agent", "ScholasticDiversity")
	req.Header.Set("accept", "application/json")
	res, getErr := spaceClient.Do(req)
	if getErr != nil {
		_, filename, line, _ := runtime.Caller(0)
		fmt.Printf("%s:%d %s\n", filename, line, getErr)
		return SefariaText{}
	}
	if res.Body != nil {
		defer res.Body.Close()
	}
	/*if res.Status != 200 {

	}*/

	body, readErr := io.ReadAll(res.Body)
	if readErr != nil {
		_, filename, line, _ := runtime.Caller(0)
		fmt.Printf("%s:%d %s\n", filename, line, readErr)
		return SefariaText{}
	}

	sefariaText := SefariaText{}
	jsonErr := json.Unmarshal(body, &sefariaText)
	if jsonErr != nil {
		_, filename, line, _ := runtime.Caller(0)
		fmt.Printf("%s:%d %s\n", filename, line, jsonErr)
		return SefariaText{}
	}

	if utf8.Valid(sefariaText.JsonText) {
		if sefariaText.IsSpanning {
			// sefariaText.Text is [][]string, rather than []string
			jsonErr2 := json.Unmarshal(sefariaText.JsonText, &sefariaText.Text)
			if jsonErr2 != nil {
				_, filename, line, _ := runtime.Caller(0)
				fmt.Printf("%s:%d %s (`%s`)\n", filename, line, jsonErr2, string(sefariaText.JsonText))
				return SefariaText{}
			}
		} else {
			var t []string
			jsonErr2 := json.Unmarshal(sefariaText.JsonText, &t)
			if jsonErr2 != nil {
				_, filename, line, _ := runtime.Caller(0)
				fmt.Printf("%s:%d %s `%s`\n", filename, line, jsonErr2, string(sefariaText.JsonText))
				fmt.Printf("%s\n", body)
				return SefariaText{}
			}
			sefariaText.Text = append(sefariaText.Text, t)
		}
	}

	if utf8.Valid(sefariaText.JsonHeText) {
		if sefariaText.IsSpanning {
			// sefariaText.HeText is [][]string, rather than []string
			jsonErr2 := json.Unmarshal(sefariaText.JsonHeText, &sefariaText.HeText)
			if jsonErr2 != nil {
				_, filename, line, _ := runtime.Caller(0)
				fmt.Printf("%s:%d %s\n", filename, line, jsonErr2)
				return SefariaText{}
			}
		} else {
			var t []string
			jsonErr2 := json.Unmarshal(sefariaText.JsonHeText, &t)
			if jsonErr2 != nil {
				_, filename, line, _ := runtime.Caller(0)
				fmt.Printf("%s:%d %s\n", filename, line, jsonErr2)
				return SefariaText{}
			}
			sefariaText.HeText = append(sefariaText.HeText, t)
		}
	}

	if utf8.Valid(sefariaText.JsonAlts) {
		if sefariaText.IsSpanning {
			// sefariaText.Alts is [][]SefariaTextAlt, rather than []SefariaTextAlt
			jsonErr2 := json.Unmarshal(sefariaText.JsonAlts, &sefariaText.Alts)
			if jsonErr2 != nil {
				_, filename, line, _ := runtime.Caller(0)
				fmt.Printf("%s:%d %s\n", filename, line, jsonErr2)
				return SefariaText{}
			}
		} else {
			var t []SefariaTextAlt
			jsonErr2 := json.Unmarshal(sefariaText.JsonAlts, &t)
			if jsonErr2 != nil {
				_, filename, line, _ := runtime.Caller(0)
				fmt.Printf("%s:%d %s\n", filename, line, jsonErr2)
				return SefariaText{}
			}
			sefariaText.Alts = append(sefariaText.Alts, t)
		}
	}

	return sefariaText
}

// /api/v2/index/:title
type SefariaTextIndexRecord struct {
	Title      string   `json:"title"`
	Categories []string `json:"categories"`
	// Schema []SefariaTextIndexRecordSchema `json:"schema"`
	Order           []int    `json:"order"`
	Authors         []string `json:"authors"`
	EnDesc          string   `json:"enDesc"`
	HeDesc          string   `json:"heDesc"`
	EnShortDesc     string   `json:"enShortDesc"`
	HeShortDesc     string   `json:"heShortDesc"`
	PubDate         string   `json:"pubDate"`
	CompDate        string   `json:"compDate"`
	CompPlace       string   `json:"comPlace"`
	PubPlace        string   `json:"PubPlace"`
	ErrorMargin     string   `json:"errorMargin"`
	IsCited         bool     `json:"is_cited"`
	Corpora         []string `json:"corpora"`
	HeTitle         string   `json:"heTitle"`
	TitleVariants   []string `json:"titleVariants"`
	HeTitleVariants []string `json:"heTitleVariants"`
	// Alts SomeObjectHere `json:"alts"`
	SectionNames []string `json:"sectionNames"`
	Depth        int      `json:"depth"`
	HeCategories []string `json:"heCategories"`
	// CompDateString SomeObjHere `json:"compDateString"`
	// CompPlaceString SomeObjHere `json:"compPlaceString"`
}

func GetTextIndexRecord() SefariaTextIndexRecord {

	return SefariaTextIndexRecord{}
}

type SefariaTextLink struct {
	Id                string   `json:"_id"`
	IndexTitle        string   `json:"index_title"`
	Category          string   `json:"category"`
	Type              string   `json:"type"`
	Ref               string   `json:"ref"` // NOTE: Looks like this is the same as SourceRef
	AnchorRef         string   `json:"anchorRef"`
	AnchorRefExpanded []string `json:"anchorRefExpanded"`
	SourceRef         string   `json:"sourceRef"`
	SourceHeRef       string   `json:"sourceHeRef"`
	AnchorVerse       int      `json:"anchorVerse"`
	SourceHasEn       bool     `json:"sourceHasEn"`
	CompDate          []int    `json:"compDate"`
	ErrorMargin       int      `json:"errorMargin"`
	// AnchorVersion
	//sourceVersion
	DisplayedText   SefariaHebrewEnglishText `json:"displayedText"`
	CommentaryNum   float64                  `json:"commentaryNum"` // Will have decimal if the commentary covers only a verse (a part of the given ref); will be whole number for number of commentaries on whole given ref
	CollectiveTitle SefariaHebrewEnglishText `json:"collectiveTitle"`
	HeTitle         string                   `json:"heTitle"`
}

func GetLinks(ref string, lang string, version string) []SefariaTextLink {
	apiUrl := "https://sefaria.org/api/links/" + EncodeTextReference(ref)
	apiUrl += "?with_text=0"

	fmt.Printf("Getting links for %s\n", apiUrl)

	spaceClient := http.Client{
		Timeout: time.Second * 10, // Timeout after 10 seconds
	}

	req, err := http.NewRequest(http.MethodGet, apiUrl, nil)
	if err != nil {
		_, filename, line, _ := runtime.Caller(0)
		fmt.Printf("%s:%d %s\n", filename, line, err)
		return []SefariaTextLink{}
	}

	req.Header.Set("User-Agent", "ScholasticDiversity")
	req.Header.Set("accept", "application/json")
	res, getErr := spaceClient.Do(req)
	if getErr != nil {
		_, filename, line, _ := runtime.Caller(0)
		fmt.Printf("%s:%d %s\n", filename, line, getErr)
		return []SefariaTextLink{}
	}
	if res.Body != nil {
		defer res.Body.Close()
	}
	/*if res.Status != 200 {

	}*/

	body, readErr := io.ReadAll(res.Body)
	if readErr != nil {
		_, filename, line, _ := runtime.Caller(0)
		fmt.Printf("%s:%d %s\n", filename, line, readErr)
		return []SefariaTextLink{}
	}

	sefariaLinks := []SefariaTextLink{}
	jsonErr := json.Unmarshal(body, &sefariaLinks)
	if jsonErr != nil {
		_, filename, line, _ := runtime.Caller(0)
		fmt.Printf("%s:%d %s\n", filename, line, jsonErr)
		return []SefariaTextLink{}
	}

	return sefariaLinks
}

type SefariaCalendarResponse struct {
	Date          string                `json:"date"`
	Timezone      string                `json:"timezone"`
	CalendarItems []SefariaCalendarItem `json:"calendar_items"`
}

type SefariaCalendarItem struct {
	Title        SefariaHebrewEnglishText `json:"title"`
	DisplayValue SefariaHebrewEnglishText `json:"displayValue"`
	Url          string                   `json:"url"`
	Ref          string                   `json:"ref"`
	HeRef        string                   `json:"heRef"`
	Order        int                      `json:"order"`
	Category     string                   `json:"category"`
	// ExtraDetails SomeObjectHere `json:"extraDetails"`
	Description SefariaHebrewEnglishText `json:"description"`
}

type SefariaHebrewEnglishText struct {
	English string `json:"en"`
	Hebrew  string `json:"he"`
}

// TODO: Add parameters for day, month, year, timezone, diaspora, and custom (ashkenazi vs. sephardi)
func GetCalendars() SefariaCalendarResponse {
	url := "https://sefaria.org/api/calendars"

	spaceClient := http.Client{
		Timeout: time.Second * 10, // Timeout after 10 seconds
	}

	req, err := http.NewRequest(http.MethodGet, url, nil)
	if err != nil {
		fmt.Println(err)
		return SefariaCalendarResponse{}
	}

	req.Header.Set("User-Agent", "ScholasticDiversity")
	res, getErr := spaceClient.Do(req)
	if getErr != nil {
		fmt.Println(err)
		return SefariaCalendarResponse{}
	}
	if res.Body != nil {
		defer res.Body.Close()
	}
	/*if res.Status != 200 {

	}*/

	body, readErr := io.ReadAll(res.Body)
	if readErr != nil {
		fmt.Println(err)
		return SefariaCalendarResponse{}
	}

	sefariaCalendarResponse := SefariaCalendarResponse{}
	jsonErr := json.Unmarshal(body, &sefariaCalendarResponse)
	if jsonErr != nil {
		fmt.Println(jsonErr)
		return SefariaCalendarResponse{}
	}

	return sefariaCalendarResponse
}